diff --git a/.gitignore b/.gitignore index 5149e1c69..b0c2f90ca 100644 --- a/.gitignore +++ b/.gitignore @@ -53,17 +53,20 @@ /third_party/android_testrunner /third_party/android_tools /third_party/asan +/third_party/binutils /third_party/clang_format +/third_party/colorama /third_party/cygwin /third_party/directxsdk /third_party/expat /third_party/gaeunit /third_party/gflags/src -/third_party/gold /third_party/google-visualization-python /third_party/icu /third_party/jsoncpp /third_party/junit +/third_party/libc++ +/third_party/libc++abi /third_party/libjingle /third_party/libjpeg /third_party/libjpeg_turbo @@ -74,6 +77,7 @@ /third_party/llvm-build /third_party/nss /third_party/oauth2 +/third_party/openmax_dl /third_party/openssl /third_party/opus /third_party/protobuf @@ -87,13 +91,16 @@ /tools/android-dummy-test /tools/clang /tools/find_depot_tools +/tools/generate_library_loader /tools/gn /tools/grit /tools/gritsettings /tools/gyp /tools/protoc_wrapper /tools/python +/tools/sanitizer_options /tools/swarming_client +/tools/tsan_suppressions /tools/valgrind /tools/win /webrtc/examples/android/media_demo/bin diff --git a/AUTHORS b/AUTHORS index 4d7ba0563..d9bb54d55 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,12 +3,14 @@ Anil Kumar Ben Strong +Bridger Maxwell Christophe Dumez Eric Rescorla, RTFM Inc. Jie Mao Luke Weber Martin Storsjo Pali Rohar +Paul Kapustin Rafael Lopez Diez Robert Nagy Silviu Caragea diff --git a/BUILD.gn b/BUILD.gn index 12ad5ff4c..b7f7a0619 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -8,16 +8,7 @@ # This file is copied and modified from Chromium (src/BUILD.gn). group("root") { - external = true - deps = [ + "//webrtc", ] } - -# A standard (non-group) target is required in the build to load any of the -# configurations, which is in turn required to load all of the build parameters -# (in build/config/BUILD.gn). -executable("dummy") { - external = true -} - diff --git a/DEPS b/DEPS index 664bd2b95..48fac47a4 100644 --- a/DEPS +++ b/DEPS @@ -11,7 +11,7 @@ vars = { "googlecode_url": "http://%s.googlecode.com/svn", "sourceforge_url": "http://svn.code.sf.net/p/%(repo)s/code", "chromium_trunk" : "http://src.chromium.org/svn/trunk", - "chromium_revision": "260462", + "chromium_revision": "277350", # A small subset of WebKit is needed for the Android Python test framework. "webkit_trunk": "http://src.chromium.org/blink/trunk", @@ -42,12 +42,18 @@ deps = { "testing/gtest": From("chromium_deps", "src/testing/gtest"), + "third_party/binutils": + Var("chromium_trunk") + "/src/third_party/binutils@" + Var("chromium_revision"), + "third_party/clang_format": Var("chromium_trunk") + "/src/third_party/clang_format@" + Var("chromium_revision"), "third_party/clang_format/script": From("chromium_deps", "src/third_party/clang_format/script"), + "third_party/colorama/src": + From("chromium_deps", "src/third_party/colorama/src"), + "third_party/expat": Var("chromium_trunk") + "/src/third_party/expat@" + Var("chromium_revision"), @@ -67,6 +73,21 @@ deps = { "third_party/junit/": (Var("googlecode_url") % "webrtc") + "/deps/third_party/junit@3367", + "third_party/libc++": + Var("chromium_trunk") + "/src/third_party/libc++@" + Var("chromium_revision"), + + "third_party/libc++/trunk": + From("chromium_deps", "src/third_party/libc++/trunk"), + + "third_party/libc++abi": + Var("chromium_trunk") + "/src/third_party/libc++abi@" + Var("chromium_revision"), + + "third_party/libc++abi/trunk": + From("chromium_deps", "src/third_party/libc++abi/trunk"), + + "third_party/openmax_dl/": + (Var("googlecode_url") % "webrtc") + "/deps/third_party/openmax@6096", + "third_party/libjpeg": Var("chromium_trunk") + "/src/third_party/libjpeg@" + Var("chromium_revision"), @@ -77,19 +98,19 @@ deps = { From("chromium_deps", "src/third_party/libsrtp"), "third_party/libvpx": - Var("chromium_trunk") + "/deps/third_party/libvpx@259973", + Var("chromium_trunk") + "/deps/third_party/libvpx@278497", "third_party/libyuv": - (Var("googlecode_url") % "libyuv") + "/trunk@994", + (Var("googlecode_url") % "libyuv") + "/trunk@1000", "third_party/opus": - Var("chromium_trunk") + "/src/third_party/opus@258909", + Var("chromium_trunk") + "/src/third_party/opus@277414", "third_party/opus/src": Var("chromium_trunk") + "/deps/third_party/opus@256783", "third_party/protobuf": - Var("chromium_trunk") + "/src/third_party/protobuf@251211", + Var("chromium_trunk") + "/src/third_party/protobuf@" + Var("chromium_revision"), "third_party/sqlite/": Var("chromium_trunk") + "/src/third_party/sqlite@" + Var("chromium_revision"), @@ -103,6 +124,9 @@ deps = { "tools/clang": Var("chromium_trunk") + "/src/tools/clang@" + Var("chromium_revision"), + "tools/generate_library_loader": + Var("chromium_trunk") + "/src/tools/generate_library_loader@" + Var("chromium_revision"), + "tools/gn": Var("chromium_trunk") + "/src/tools/gn@" + Var("chromium_revision"), @@ -115,9 +139,15 @@ deps = { "tools/python": Var("chromium_trunk") + "/src/tools/python@" + Var("chromium_revision"), + "tools/sanitizer_options": + File(Var("chromium_trunk") + "/src/base/debug/sanitizer_options.cc@" + Var("chromium_revision")), + "tools/swarming_client": From("chromium_deps", "src/tools/swarming_client"), + "tools/tsan_suppressions": + File(Var("chromium_trunk") + "/src/base/debug/tsan_suppressions.cc@" + Var("chromium_revision")), + "tools/valgrind": Var("chromium_trunk") + "/src/tools/valgrind@" + Var("chromium_revision"), @@ -132,11 +162,14 @@ deps = { Var("chromium_trunk") + "/src/third_party/usrsctp@" + Var("chromium_revision"), "third_party/usrsctp/usrsctplib": - (Var("googlecode_url") % "sctp-refimpl") + "/trunk/KERN/usrsctp/usrsctplib@8723", + (Var("googlecode_url") % "sctp-refimpl") + "/trunk/KERN/usrsctp/usrsctplib@8875", } deps_os = { "win": { + "third_party/drmemory": + Var("chromium_trunk") + "/src/third_party/drmemory@" + Var("chromium_revision"), + "third_party/winsdk_samples/src": (Var("googlecode_url") % "webrtc") + "/deps/third_party/winsdk_samples_v71@3145", @@ -148,10 +181,6 @@ deps_os = { "third_party/nss": From("chromium_deps", "src/third_party/nss"), - # SyzyASan to make it possible to run tests under ASan on Windows. - "third_party/syzygy/binaries": - From("chromium_deps", "src/third_party/syzygy/binaries"), - "tools/find_depot_tools": File(Var("chromium_trunk") + "/src/tools/find_depot_tools.py@" + Var("chromium_revision")), }, @@ -160,6 +189,12 @@ deps_os = { # NSS, for SSLClientSocketNSS. "third_party/nss": From("chromium_deps", "src/third_party/nss"), + + # TODO(kjellander): remove once bug 2152 is fixed. + # This needs to specify the path directly (instead of using the + # chromium_deps version) because chromium_deps only defines this for ios. + "testing/iossim/third_party/class-dump": + Var("chromium_trunk") + "/deps/third_party/class-dump@199203", }, "ios": { @@ -176,16 +211,11 @@ deps_os = { Var("chromium_trunk") + "/src/testing/iossim@" + Var("chromium_revision"), }, - "unix": { - "third_party/gold": - From("chromium_deps", "src/third_party/gold"), - }, - "android": { # Precompiled tools needed for Android test execution. Needed since we can't # compile them from source in WebRTC since they depend on Chromium's base. "tools/android": - (Var("googlecode_url") % "webrtc") + "/deps/tools/android@4258", + (Var("googlecode_url") % "webrtc") + "/deps/tools/android@6306", "tools/android-dummy-test": (Var("googlecode_url") % "webrtc") + "/deps/tools/android-dummy-test@4244", @@ -298,6 +328,42 @@ hooks = [ "action": ["python", Var("root_dir") + "/tools/clang/scripts/update.py", "--if-needed"], }, + { + # Update the Windows toolchain if necessary. + "name": "win_toolchain", + "pattern": ".", + "action": ["python", + Var("root_dir") + "/webrtc/build/download_vs_toolchain.py", + "update"], + }, + { + # Pull binutils for gold. + "name": "binutils", + "pattern": ".", + "action": ["python", Var("root_dir") + "/third_party/binutils/download.py"], + }, + { + "name": "drmemory", + "pattern": ".", + "action": [ "download_from_google_storage", + "--no_resume", + "--platform=win32", + "--no_auth", + "--bucket", "chromium-drmemory", + "-s", Var("root_dir") + "/third_party/drmemory/drmemory-windows-sfx.exe.sha1", + ], + }, + { + # Pull the Syzygy binaries, used for optimization and instrumentation. + "name": "syzygy-binaries", + "pattern": ".", + "action": ["python", + Var("root_dir") + "/build/get_syzygy_binaries.py", + "--output-dir=%s/third_party/syzygy/binaries" % Var("root_dir"), + "--revision=b08fb72610963d31cc3eae33f746a04e263bd860", + "--overwrite", + ], + }, { # Download test resources, i.e. video and audio files from Google Storage. "pattern": "\\.sha1", diff --git a/OWNERS b/OWNERS index 36d9928e7..ac9c857cd 100644 --- a/OWNERS +++ b/OWNERS @@ -4,9 +4,9 @@ mflodman@webrtc.org niklas.enbom@webrtc.org tina.legrand@webrtc.org tommi@webrtc.org -per-file *.isolate=kjellander@webrtc.org per-file .gitignore=* per-file AUTHORS=* +per-file BUILD.gn=kjellander@webrtc.org per-file DEPS=* per-file WATCHLISTS=* per-file webrtc_examples.gyp=* diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 5f913a950..45e92e738 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -93,23 +93,6 @@ def _CheckApprovedFilesLintClean(input_api, output_api, return result -def _CheckTalkOrWebrtcOnly(input_api, output_api): - base_folders = set(["webrtc", "talk"]) - base_folders_in_cl = set() - - for f in input_api.AffectedFiles(): - full_path = f.LocalPath() - base_folders_in_cl.add(full_path[:full_path.find('/')]) - - results = [] - if base_folders.issubset(base_folders_in_cl): - error_type = output_api.PresubmitError - results.append(error_type( - 'It is not allowed to check in files to ' + ', '.join(base_folders) + - ' in the same cl', - [])) - return results - def _CommonChecks(input_api, output_api): """Checks common to both upload and commit.""" # TODO(kjellander): Use presubmit_canned_checks.PanProjectChecks too. @@ -151,7 +134,6 @@ def _CommonChecks(input_api, output_api): results.extend(_CheckApprovedFilesLintClean(input_api, output_api)) results.extend(_CheckNoIOStreamInHeaders(input_api, output_api)) results.extend(_CheckNoFRIEND_TEST(input_api, output_api)) - results.extend(_CheckTalkOrWebrtcOnly(input_api, output_api)) return results def CheckChangeOnUpload(input_api, output_api): @@ -190,6 +172,7 @@ def GetPreferredTryMasters(project, change): android_bots = [ 'android', + 'android_arm64', 'android_apk', 'android_apk_rel', 'android_rel', @@ -205,7 +188,6 @@ def GetPreferredTryMasters(project, change): 'linux_baremetal', 'linux_memcheck', 'linux_rel', - 'linux_tsan', 'linux_tsan2', ] mac_bots = [ @@ -219,6 +201,7 @@ def GetPreferredTryMasters(project, change): 'win', 'win_asan', 'win_baremetal', + 'win_drmemory_light', 'win_rel', 'win_x64_rel', ] diff --git a/WATCHLISTS b/WATCHLISTS index d480c2e30..83704692b 100644 --- a/WATCHLISTS +++ b/WATCHLISTS @@ -34,7 +34,8 @@ 'filepath': '\.java$|\.xml$', }, 'video_engine': { - 'filepath': 'webrtc/video_engine/.*', + 'filepath': 'webrtc/video_engine/.*|'\ + 'webrtc/video/.*', }, 'voice_engine': { 'filepath': 'webrtc/voice_engine/.*', @@ -60,9 +61,6 @@ 'neteq': { 'filepath': 'webrtc/modules/audio_coding/neteq/.*', }, - 'neteq4': { - 'filepath': 'webrtc/modules/audio_coding/neteq4/.*', - }, 'audio_processing': { 'filepath': 'webrtc/modules/audio_processing/.*', }, @@ -81,6 +79,9 @@ 'remote_bitrate_estimator': { 'filepath': 'webrtc/modules/remote_bitrate_estimator/.*' }, + 'pacing': { + 'filepath': 'webrtc/modules/pacing/.*' + }, 'rtp_rtcp': { 'filepath': 'webrtc/modules/rtp_rtcp/.*' }, @@ -114,19 +115,26 @@ 'video_render': ['mflodman@webrtc.org', 'perkj@webrtc.org'], 'audio_device': ['henrika@webrtc.org'], - 'audio_coding': ['tina.legrand@webrtc.org'], + 'audio_coding': ['tina.legrand@webrtc.org', + 'henrik.lundin@webrtc.org', + 'kwiberg@webrtc.org'], 'neteq': ['henrik.lundin@webrtc.org'], - 'neteq4': ['henrik.lundin@webrtc.org'], 'audio_processing': ['andrew@webrtc.org', - 'bjornv@webrtc.org'], + 'bjornv@webrtc.org', + 'kwiberg@webrtc.org'], 'video_codecs': ['henrik.lundin@webrtc.org', 'pwestin@webrtc.org'], 'video_coding': ['stefan@webrtc.org', - 'mikhal@webrtc.org'], + 'mikhal@webrtc.org', + 'mflodman@webrtc.org'], 'video_processing': ['mikhal@webrtc.org', 'stefan@webrtc.org'], - 'bitrate_controller': ['stefan@webrtc.org'], - 'remote_bitrate_estimator': ['stefan@webrtc.org'], + 'bitrate_controller': ['stefan@webrtc.org', + 'mflodman@webrtc.org'], + 'remote_bitrate_estimator': ['stefan@webrtc.org', + 'mflodman@webrtc.org'], + 'pacing': ['stefan@webrtc.org', + 'mflodman@webrtc.org'], 'rtp_rtcp': ['mflodman@webrtc.org', 'pwestin@webrtc.org', 'stefan@webrtc.org'], diff --git a/all.gyp b/all.gyp index f7e397dca..4830c5425 100644 --- a/all.gyp +++ b/all.gyp @@ -9,16 +9,17 @@ { 'variables': { 'libjingle_root%': '<(DEPTH)', + 'include_tests%': 1, }, 'targets': [ { 'target_name': 'All', 'type': 'none', 'dependencies': [ + 'third_party/openmax_dl/dl/dl.gyp:*', 'webrtc/webrtc.gyp:*', '<(libjingle_root)/talk/libjingle.gyp:*', '<(libjingle_root)/talk/libjingle_examples.gyp:*', - '<(libjingle_root)/talk/libjingle_tests.gyp:*', ], 'conditions': [ ['OS=="android"', { @@ -26,6 +27,11 @@ 'webrtc/webrtc_examples.gyp:*', ], }], + ['include_tests==1', { + 'dependencies': [ + '<(libjingle_root)/talk/libjingle_tests.gyp:*', + ], + }], ], }, ], diff --git a/data/audio_processing/output_data_float.pb b/data/audio_processing/output_data_float.pb index b63ff9253..ffc12d6b0 100644 Binary files a/data/audio_processing/output_data_float.pb and b/data/audio_processing/output_data_float.pb differ diff --git a/resources/audio_coding/neteq4_universal_ref_win_32.pcm.sha1 b/resources/audio_coding/neteq4_universal_ref_win_32.pcm.sha1 index f899483aa..cc441d56f 100644 --- a/resources/audio_coding/neteq4_universal_ref_win_32.pcm.sha1 +++ b/resources/audio_coding/neteq4_universal_ref_win_32.pcm.sha1 @@ -1 +1 @@ -eb60f83b0db65826622c390e8d2e688345aff1dd \ No newline at end of file +49d12cf21b37f2da3f52cf2ab76ecb08adbb2a69 \ No newline at end of file diff --git a/resources/audio_coding/neteq4_universal_ref_win_64.pcm.sha1 b/resources/audio_coding/neteq4_universal_ref_win_64.pcm.sha1 new file mode 100644 index 000000000..3a97d9909 --- /dev/null +++ b/resources/audio_coding/neteq4_universal_ref_win_64.pcm.sha1 @@ -0,0 +1 @@ +84972ecea6a0bf85b873f109c88697eab44cedd3 \ No newline at end of file diff --git a/resources/audio_coding/neteq_universal.rtp.sha1 b/resources/audio_coding/neteq_universal.rtp.sha1 deleted file mode 100644 index 89cb315e7..000000000 --- a/resources/audio_coding/neteq_universal.rtp.sha1 +++ /dev/null @@ -1 +0,0 @@ -4cb8bb0b2a7504a87c44e1d9e008709c390ca51d \ No newline at end of file diff --git a/resources/far44_stereo.pcm.sha1 b/resources/far44_stereo.pcm.sha1 new file mode 100644 index 000000000..56f4adc1e --- /dev/null +++ b/resources/far44_stereo.pcm.sha1 @@ -0,0 +1 @@ +f812b1cfff98da276973e947b1ba177cd462f58a \ No newline at end of file diff --git a/resources/far48_stereo.pcm.sha1 b/resources/far48_stereo.pcm.sha1 new file mode 100644 index 000000000..9b74ce8f5 --- /dev/null +++ b/resources/far48_stereo.pcm.sha1 @@ -0,0 +1 @@ +69c679d06f275c1bb4a616dc626a9a893d9c11f9 \ No newline at end of file diff --git a/resources/near44_stereo.pcm.sha1 b/resources/near44_stereo.pcm.sha1 new file mode 100644 index 000000000..12dceb7b1 --- /dev/null +++ b/resources/near44_stereo.pcm.sha1 @@ -0,0 +1 @@ +88f6962dd4a7ed9dcbc58b106c162ea5cb8a3f5a \ No newline at end of file diff --git a/resources/near48_stereo.pcm.sha1 b/resources/near48_stereo.pcm.sha1 new file mode 100644 index 000000000..e464ed3be --- /dev/null +++ b/resources/near48_stereo.pcm.sha1 @@ -0,0 +1 @@ +6c63f53a6ea6b25b91616a7d03c56cd08362ca22 \ No newline at end of file diff --git a/resources/utility/encapsulated_pcm16b_8khz.wav.sha1 b/resources/utility/encapsulated_pcm16b_8khz.wav.sha1 new file mode 100644 index 000000000..480cfea43 --- /dev/null +++ b/resources/utility/encapsulated_pcm16b_8khz.wav.sha1 @@ -0,0 +1 @@ +43092df43f4093e474c41cd74e3085e7ca401c7d \ No newline at end of file diff --git a/resources/utility/encapsulated_pcmu_8khz.wav.sha1 b/resources/utility/encapsulated_pcmu_8khz.wav.sha1 new file mode 100644 index 000000000..e2269f7e0 --- /dev/null +++ b/resources/utility/encapsulated_pcmu_8khz.wav.sha1 @@ -0,0 +1 @@ +d96caa3ff9c48559ec37784791887994d28f3b5a \ No newline at end of file diff --git a/samples/js/OWNERS b/samples/js/OWNERS deleted file mode 100644 index 79b28349a..000000000 --- a/samples/js/OWNERS +++ /dev/null @@ -1,9 +0,0 @@ -braveyao@webrtc.org -dutton@google.com -henrika@webrtc.org -hta@webrtc.org -juberti@webrtc.org -kjellander@webrtc.org -phoglund@webrtc.org -vikasmarwaha@webrtc.org -wu@webrtc.org diff --git a/samples/js/apprtc/app.yaml b/samples/js/apprtc/app.yaml deleted file mode 100644 index 6ef5e7505..000000000 --- a/samples/js/apprtc/app.yaml +++ /dev/null @@ -1,29 +0,0 @@ -application: apprtc -version: 6 -runtime: python27 -threadsafe: true -api_version: 1 - -handlers: -- url: /html - static_dir: html - -- url: /images - static_dir: images - -- url: /js - static_dir: js - -- url: /css - static_dir: css - -- url: /.* - script: apprtc.app - secure: always - -inbound_services: -- channel_presence - -libraries: -- name: jinja2 - version: latest diff --git a/samples/js/apprtc/apprtc.py b/samples/js/apprtc/apprtc.py deleted file mode 100644 index 3652c8efd..000000000 --- a/samples/js/apprtc/apprtc.py +++ /dev/null @@ -1,482 +0,0 @@ -#!/usr/bin/python2.4 -# -# Copyright 2011 Google Inc. All Rights Reserved. - -"""WebRTC Demo - -This module demonstrates the WebRTC API by implementing a simple video chat app. -""" - -import cgi -import logging -import os -import random -import re -import json -import jinja2 -import webapp2 -import threading -from google.appengine.api import channel -from google.appengine.ext import db - -jinja_environment = jinja2.Environment( - loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) - -# Lock for syncing DB operation in concurrent requests handling. -# TODO(brave): keeping working on improving performance with thread syncing. -# One possible method for near future is to reduce the message caching. -LOCK = threading.RLock() - -def generate_random(length): - word = '' - for _ in range(length): - word += random.choice('0123456789') - return word - -def sanitize(key): - return re.sub('[^a-zA-Z0-9\-]', '-', key) - -def make_client_id(room, user): - return room.key().id_or_name() + '/' + user - -def get_default_stun_server(user_agent): - default_stun_server = 'stun.l.google.com:19302' - if 'Firefox' in user_agent: - default_stun_server = 'stun.services.mozilla.com' - return default_stun_server - -def get_preferred_audio_receive_codec(): - return 'opus/48000' - -def get_preferred_audio_send_codec(user_agent): - # Empty string means no preference. - preferred_audio_send_codec = '' - # Prefer to send ISAC on Chrome for Android. - if 'Android' in user_agent and 'Chrome' in user_agent: - preferred_audio_send_codec = 'ISAC/16000' - return preferred_audio_send_codec - -def make_pc_config(stun_server, turn_server, ts_pwd): - servers = [] - if turn_server: - turn_config = 'turn:{}'.format(turn_server) - servers.append({'urls':turn_config, 'credential':ts_pwd}) - if stun_server: - stun_config = 'stun:{}'.format(stun_server) - servers.append({'urls':stun_config}) - return {'iceServers':servers} - -def create_channel(room, user, duration_minutes): - client_id = make_client_id(room, user) - return channel.create_channel(client_id, duration_minutes) - -def make_loopback_answer(message): - message = message.replace("\"offer\"", "\"answer\"") - message = message.replace("a=ice-options:google-ice\\r\\n", "") - return message - -def handle_message(room, user, message): - message_obj = json.loads(message) - other_user = room.get_other_user(user) - room_key = room.key().id_or_name() - if message_obj['type'] == 'bye': - # This would remove the other_user in loopback test too. - # So check its availability before forwarding Bye message. - room.remove_user(user) - logging.info('User ' + user + ' quit from room ' + room_key) - logging.info('Room ' + room_key + ' has state ' + str(room)) - if other_user and room.has_user(other_user): - if message_obj['type'] == 'offer': - # Special case the loopback scenario. - if other_user == user: - message = make_loopback_answer(message) - on_message(room, other_user, message) - else: - # For unittest - on_message(room, user, message) - -def get_saved_messages(client_id): - return Message.gql("WHERE client_id = :id", id=client_id) - -def delete_saved_messages(client_id): - messages = get_saved_messages(client_id) - for message in messages: - message.delete() - logging.info('Deleted the saved message for ' + client_id) - -def send_saved_messages(client_id): - messages = get_saved_messages(client_id) - for message in messages: - channel.send_message(client_id, message.msg) - logging.info('Delivered saved message to ' + client_id) - message.delete() - -def on_message(room, user, message): - client_id = make_client_id(room, user) - if room.is_connected(user): - channel.send_message(client_id, message) - logging.info('Delivered message to user ' + user) - else: - new_message = Message(client_id = client_id, msg = message) - new_message.put() - logging.info('Saved message for user ' + user) - -def make_media_track_constraints(constraints_string): - if not constraints_string or constraints_string.lower() == 'true': - track_constraints = True - elif constraints_string.lower() == 'false': - track_constraints = False - else: - track_constraints = {'mandatory': {}, 'optional': []} - for constraint_string in constraints_string.split(','): - constraint = constraint_string.split('=') - if len(constraint) != 2: - logging.error('Ignoring malformed constraint: ' + constraint_string) - continue - if constraint[0].startswith('goog'): - track_constraints['optional'].append({constraint[0]: constraint[1]}) - else: - track_constraints['mandatory'][constraint[0]] = constraint[1] - - return track_constraints - -def make_media_stream_constraints(audio, video): - stream_constraints = ( - {'audio': make_media_track_constraints(audio), - 'video': make_media_track_constraints(video)}) - logging.info('Applying media constraints: ' + str(stream_constraints)) - return stream_constraints - -def maybe_add_constraint(constraints, param, constraint): - if (param.lower() == 'true'): - constraints['optional'].append({constraint: True}) - elif (param.lower() == 'false'): - constraints['optional'].append({constraint: False}) - - return constraints - -def make_pc_constraints(dtls, dscp, ipv6): - constraints = { 'optional': [] } - maybe_add_constraint(constraints, dtls, 'DtlsSrtpKeyAgreement') - maybe_add_constraint(constraints, dscp, 'googDscp') - maybe_add_constraint(constraints, ipv6, 'googIPv6') - - return constraints - -def make_offer_constraints(): - constraints = { 'mandatory': {}, 'optional': [] } - return constraints - -def append_url_arguments(request, link): - for argument in request.arguments(): - if argument != 'r': - link += ('&' + cgi.escape(argument, True) + '=' + - cgi.escape(request.get(argument), True)) - return link - -# This database is to store the messages from the sender client when the -# receiver client is not ready to receive the messages. -# Use TextProperty instead of StringProperty for msg because -# the session description can be more than 500 characters. -class Message(db.Model): - client_id = db.StringProperty() - msg = db.TextProperty() - -class Room(db.Model): - """All the data we store for a room""" - user1 = db.StringProperty() - user2 = db.StringProperty() - user1_connected = db.BooleanProperty(default=False) - user2_connected = db.BooleanProperty(default=False) - - def __str__(self): - result = '[' - if self.user1: - result += "%s-%r" % (self.user1, self.user1_connected) - if self.user2: - result += ", %s-%r" % (self.user2, self.user2_connected) - result += ']' - return result - - def get_occupancy(self): - occupancy = 0 - if self.user1: - occupancy += 1 - if self.user2: - occupancy += 1 - return occupancy - - def get_other_user(self, user): - if user == self.user1: - return self.user2 - elif user == self.user2: - return self.user1 - else: - return None - - def has_user(self, user): - return (user and (user == self.user1 or user == self.user2)) - - def add_user(self, user): - if not self.user1: - self.user1 = user - elif not self.user2: - self.user2 = user - else: - raise RuntimeError('room is full') - self.put() - - def remove_user(self, user): - delete_saved_messages(make_client_id(self, user)) - if user == self.user2: - self.user2 = None - self.user2_connected = False - if user == self.user1: - if self.user2: - self.user1 = self.user2 - self.user1_connected = self.user2_connected - self.user2 = None - self.user2_connected = False - else: - self.user1 = None - self.user1_connected = False - if self.get_occupancy() > 0: - self.put() - else: - self.delete() - - def set_connected(self, user): - if user == self.user1: - self.user1_connected = True - if user == self.user2: - self.user2_connected = True - self.put() - - def is_connected(self, user): - if user == self.user1: - return self.user1_connected - if user == self.user2: - return self.user2_connected - -@db.transactional -def connect_user_to_room(room_key, user): - room = Room.get_by_key_name(room_key) - # Check if room has user in case that disconnect message comes before - # connect message with unknown reason, observed with local AppEngine SDK. - if room and room.has_user(user): - room.set_connected(user) - logging.info('User ' + user + ' connected to room ' + room_key) - logging.info('Room ' + room_key + ' has state ' + str(room)) - else: - logging.warning('Unexpected Connect Message to room ' + room_key) - return room - -class ConnectPage(webapp2.RequestHandler): - def post(self): - key = self.request.get('from') - room_key, user = key.split('/') - with LOCK: - room = connect_user_to_room(room_key, user) - if room and room.has_user(user): - send_saved_messages(make_client_id(room, user)) - -class DisconnectPage(webapp2.RequestHandler): - def post(self): - key = self.request.get('from') - room_key, user = key.split('/') - with LOCK: - room = Room.get_by_key_name(room_key) - if room and room.has_user(user): - other_user = room.get_other_user(user) - room.remove_user(user) - logging.info('User ' + user + ' removed from room ' + room_key) - logging.info('Room ' + room_key + ' has state ' + str(room)) - if other_user and other_user != user: - channel.send_message(make_client_id(room, other_user), - '{"type":"bye"}') - logging.info('Sent BYE to ' + other_user) - logging.warning('User ' + user + ' disconnected from room ' + room_key) - - -class MessagePage(webapp2.RequestHandler): - def post(self): - message = self.request.body - room_key = self.request.get('r') - user = self.request.get('u') - with LOCK: - room = Room.get_by_key_name(room_key) - if room: - handle_message(room, user, message) - else: - logging.warning('Unknown room ' + room_key) - -class MainPage(webapp2.RequestHandler): - """The main UI page, renders the 'index.html' template.""" - def get(self): - """Renders the main page. When this page is shown, we create a new - channel to push asynchronous updates to the client.""" - - # Append strings to this list to have them thrown up in message boxes. This - # will also cause the app to fail. - error_messages = [] - # Get the base url without arguments. - base_url = self.request.path_url - user_agent = self.request.headers['User-Agent'] - room_key = sanitize(self.request.get('r')) - stun_server = self.request.get('ss') - if not stun_server: - stun_server = get_default_stun_server(user_agent) - turn_server = self.request.get('ts') - ts_pwd = self.request.get('tp') - - # Use "audio" and "video" to set the media stream constraints. Defined here: - # http://goo.gl/V7cZg - # - # "true" and "false" are recognized and interpreted as bools, for example: - # "?audio=true&video=false" (Start an audio-only call.) - # "?audio=false" (Start a video-only call.) - # If unspecified, the stream constraint defaults to True. - # - # To specify media track constraints, pass in a comma-separated list of - # key/value pairs, separated by a "=". Examples: - # "?audio=googEchoCancellation=false,googAutoGainControl=true" - # (Disable echo cancellation and enable gain control.) - # - # "?video=minWidth=1280,minHeight=720,googNoiseReduction=true" - # (Set the minimum resolution to 1280x720 and enable noise reduction.) - # - # Keys starting with "goog" will be added to the "optional" key; all others - # will be added to the "mandatory" key. - # - # The audio keys are defined here: talk/app/webrtc/localaudiosource.cc - # The video keys are defined here: talk/app/webrtc/videosource.cc - audio = self.request.get('audio') - video = self.request.get('video') - - if self.request.get('hd').lower() == 'true': - if video: - message = 'The "hd" parameter has overridden video=' + str(video) - logging.error(message) - error_messages.append(message) - video = 'minWidth=1280,minHeight=720' - - if self.request.get('minre') or self.request.get('maxre'): - message = ('The "minre" and "maxre" parameters are no longer supported. ' - 'Use "video" instead.') - logging.error(message) - error_messages.append(message) - - audio_send_codec = self.request.get('asc') - if not audio_send_codec: - audio_send_codec = get_preferred_audio_send_codec(user_agent) - - audio_receive_codec = self.request.get('arc') - if not audio_receive_codec: - audio_receive_codec = get_preferred_audio_receive_codec() - - # Set stereo to false by default. - stereo = 'false' - if self.request.get('stereo'): - stereo = self.request.get('stereo') - - # Options for making pcConstraints - dtls = self.request.get('dtls') - dscp = self.request.get('dscp') - ipv6 = self.request.get('ipv6') - - debug = self.request.get('debug') - if debug == 'loopback': - # Set dtls to false as DTLS does not work for loopback. - dtls = 'false' - - # token_timeout for channel creation, default 30min, max 1 days, min 3min. - token_timeout = self.request.get_range('tt', - min_value = 3, - max_value = 1440, - default = 30) - - unittest = self.request.get('unittest') - if unittest: - # Always create a new room for the unit tests. - room_key = generate_random(8) - - if not room_key: - room_key = generate_random(8) - redirect = '/?r=' + room_key - redirect = append_url_arguments(self.request, redirect) - self.redirect(redirect) - logging.info('Redirecting visitor to base URL to ' + redirect) - return - - user = None - initiator = 0 - with LOCK: - room = Room.get_by_key_name(room_key) - if not room and debug != "full": - # New room. - user = generate_random(8) - room = Room(key_name = room_key) - room.add_user(user) - if debug != 'loopback': - initiator = 0 - else: - room.add_user(user) - initiator = 1 - elif room and room.get_occupancy() == 1 and debug != 'full': - # 1 occupant. - user = generate_random(8) - room.add_user(user) - initiator = 1 - else: - # 2 occupants (full). - template = jinja_environment.get_template('full.html') - self.response.out.write(template.render({ 'room_key': room_key })) - logging.info('Room ' + room_key + ' is full') - return - - if turn_server == 'false': - turn_server = None - turn_url = '' - else: - turn_url = 'https://computeengineondemand.appspot.com/' - turn_url = turn_url + 'turn?' + 'username=' + user + '&key=4080218913' - - room_link = base_url + '?r=' + room_key - room_link = append_url_arguments(self.request, room_link) - token = create_channel(room, user, token_timeout) - pc_config = make_pc_config(stun_server, turn_server, ts_pwd) - pc_constraints = make_pc_constraints(dtls, dscp, ipv6) - offer_constraints = make_offer_constraints() - media_constraints = make_media_stream_constraints(audio, video) - template_values = {'error_messages': error_messages, - 'token': token, - 'me': user, - 'room_key': room_key, - 'room_link': room_link, - 'initiator': initiator, - 'pc_config': json.dumps(pc_config), - 'pc_constraints': json.dumps(pc_constraints), - 'offer_constraints': json.dumps(offer_constraints), - 'media_constraints': json.dumps(media_constraints), - 'turn_url': turn_url, - 'stereo': stereo, - 'audio_send_codec': audio_send_codec, - 'audio_receive_codec': audio_receive_codec - } - if unittest: - target_page = 'test/test_' + unittest + '.html' - else: - target_page = 'index.html' - - template = jinja_environment.get_template(target_page) - self.response.out.write(template.render(template_values)) - logging.info('User ' + user + ' added to room ' + room_key) - logging.info('Room ' + room_key + ' has state ' + str(room)) - - -app = webapp2.WSGIApplication([ - ('/', MainPage), - ('/message', MessagePage), - ('/_ah/channel/connected/', ConnectPage), - ('/_ah/channel/disconnected/', DisconnectPage) - ], debug=True) diff --git a/samples/js/apprtc/css/main.css b/samples/js/apprtc/css/main.css deleted file mode 100644 index 15d9eee2b..000000000 --- a/samples/js/apprtc/css/main.css +++ /dev/null @@ -1,95 +0,0 @@ -a:link { color: #FFFFFF; } -a:visited {color: #FFFFFF; } -html, body { - background-color: #000000; - height: 100%; - font-family: Verdana, Arial, Helvetica, sans-serif; -} -body { - margin: 0; - padding: 0; -} -footer { - position: absolute; - bottom: 0; - width: 100%; - height: 28px; - background-color: #3F3F3F; - color: #FFFFFF; - font-size: 13px; font-weight: bold; - line-height: 28px; - text-align: center; -} -#container { - background-color: #000000; - position: absolute; - height: 100%; - width: 100%; - margin: 0px auto; - -webkit-perspective: 1000; -} -#card { - -webkit-transition-duration: 2s; - -webkit-transform-style: preserve-3d; -} -#local { - position: absolute; - width: 100%; - transform: scale(-1, 1); - -webkit-transform: scale(-1, 1); - -webkit-backface-visibility: hidden; -} -#remote { - position: absolute; - width: 100%; - -webkit-transform: rotateY(180deg); - -webkit-backface-visibility: hidden; -} -#mini { - position: absolute; - height: 30%; - width: 30%; - bottom: 32px; - right: 4px; - opacity: 1.0; - transform: scale(-1, 1); - -webkit-transform: scale(-1, 1); -} -#localVideo { - width: 100%; - height: 100%; - opacity: 0; - -webkit-transition-property: opacity; - -webkit-transition-duration: 2s; -} -#remoteVideo { - width: 100%; - height: 100%; - opacity: 0; - -webkit-transition-property: opacity; - -webkit-transition-duration: 2s; -} -#miniVideo { - width: 100%; - height: 100%; - opacity: 0; - -webkit-transition-property: opacity; - -webkit-transition-duration: 2s; -} -#hangup { - font-size: 13px; font-weight: bold; - color: #FFFFFF; - width: 128px; - height: 24px; - background-color: #808080; - border-style: solid; - border-color: #FFFFFF; - margin: 2px; -} -#infoDiv { - position: absolute; - float: right; - background-color: grey; - margin: 2px; - display: none; -} diff --git a/samples/js/apprtc/full.html b/samples/js/apprtc/full.html deleted file mode 100644 index b14ac6009..000000000 --- a/samples/js/apprtc/full.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - -
- -
- - - diff --git a/samples/js/apprtc/html/help.html b/samples/js/apprtc/html/help.html deleted file mode 100644 index 7fd2bf624..000000000 --- a/samples/js/apprtc/html/help.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - WebRtc Demo App Help - - -TODO - - diff --git a/samples/js/apprtc/images/webrtc_black_20p.png b/samples/js/apprtc/images/webrtc_black_20p.png deleted file mode 100644 index a35c1dff2..000000000 Binary files a/samples/js/apprtc/images/webrtc_black_20p.png and /dev/null differ diff --git a/samples/js/apprtc/index.html b/samples/js/apprtc/index.html deleted file mode 100644 index a240f2980..000000000 --- a/samples/js/apprtc/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - -WebRTC Reference App - - - - - - - - - - - - - - -
-
-
-
-
- -
-
-
-
-
- -
-
-
- diff --git a/samples/js/apprtc/js/adapter.js b/samples/js/apprtc/js/adapter.js deleted file mode 120000 index c19e2ce99..000000000 --- a/samples/js/apprtc/js/adapter.js +++ /dev/null @@ -1 +0,0 @@ -../../base/adapter.js \ No newline at end of file diff --git a/samples/js/apprtc/js/main.js b/samples/js/apprtc/js/main.js deleted file mode 100644 index 79313dcb9..000000000 --- a/samples/js/apprtc/js/main.js +++ /dev/null @@ -1,763 +0,0 @@ -var localVideo; -var miniVideo; -var remoteVideo; -var hasLocalStream; -var localStream; -var remoteStream; -var channel; -var pc; -var socket; -var xmlhttp; -var started = false; -var turnDone = false; -var channelReady = false; -var signalingReady = false; -var msgQueue = []; -// Set up audio and video regardless of what devices are present. -var sdpConstraints = {'mandatory': { - 'OfferToReceiveAudio': true, - 'OfferToReceiveVideo': true }}; -var isVideoMuted = false; -var isAudioMuted = false; -// Types of gathered ICE Candidates. -var gatheredIceCandidateTypes = { Local: {}, Remote: {} }; -var infoDivErrors = []; - -function initialize() { - if (errorMessages.length > 0) { - for (i = 0; i < errorMessages.length; ++i) { - window.alert(errorMessages[i]); - } - return; - } - - console.log('Initializing; room=' + roomKey + '.'); - card = document.getElementById('card'); - localVideo = document.getElementById('localVideo'); - // Reset localVideo display to center. - localVideo.addEventListener('loadedmetadata', function(){ - window.onresize();}); - miniVideo = document.getElementById('miniVideo'); - remoteVideo = document.getElementById('remoteVideo'); - resetStatus(); - // NOTE: AppRTCClient.java searches & parses this line; update there when - // changing here. - openChannel(); - maybeRequestTurn(); - - // Caller is always ready to create peerConnection. - signalingReady = initiator; - - if (mediaConstraints.audio === false && - mediaConstraints.video === false) { - hasLocalStream = false; - maybeStart(); - } else { - hasLocalStream = true; - doGetUserMedia(); - } -} - -function openChannel() { - console.log('Opening channel.'); - var channel = new goog.appengine.Channel(channelToken); - var handler = { - 'onopen': onChannelOpened, - 'onmessage': onChannelMessage, - 'onerror': onChannelError, - 'onclose': onChannelClosed - }; - socket = channel.open(handler); -} - -function maybeRequestTurn() { - // Allow to skip turn by passing ts=false to apprtc. - if (turnUrl == '') { - turnDone = true; - return; - } - - for (var i = 0, len = pcConfig.iceServers.length; i < len; i++) { - if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') { - turnDone = true; - return; - } - } - - var currentDomain = document.domain; - if (currentDomain.search('localhost') === -1 && - currentDomain.search('apprtc') === -1) { - // Not authorized domain. Try with default STUN instead. - turnDone = true; - return; - } - - // No TURN server. Get one from computeengineondemand.appspot.com. - xmlhttp = new XMLHttpRequest(); - xmlhttp.onreadystatechange = onTurnResult; - xmlhttp.open('GET', turnUrl, true); - xmlhttp.send(); -} - -function onTurnResult() { - if (xmlhttp.readyState !== 4) - return; - - if (xmlhttp.status === 200) { - var turnServer = JSON.parse(xmlhttp.responseText); - // Create turnUris using the polyfill (adapter.js). - var iceServers = createIceServers(turnServer.uris, - turnServer.username, - turnServer.password); - if (iceServers !== null) { - pcConfig.iceServers = pcConfig.iceServers.concat(iceServers); - } - } else { - messageError('No TURN server; unlikely that media will traverse networks. ' - + 'If this persists please report it to ' - + 'discuss-webrtc@googlegroups.com.'); - } - // If TURN request failed, continue the call with default STUN. - turnDone = true; - maybeStart(); -} - -function resetStatus() { - if (!initiator) { - setStatus('Waiting for someone to join: \ - ' + roomLink + ''); - } else { - setStatus('Initializing...'); - } -} - -function doGetUserMedia() { - // Call into getUserMedia via the polyfill (adapter.js). - try { - getUserMedia(mediaConstraints, onUserMediaSuccess, - onUserMediaError); - console.log('Requested access to local media with mediaConstraints:\n' + - ' \'' + JSON.stringify(mediaConstraints) + '\''); - } catch (e) { - alert('getUserMedia() failed. Is this a WebRTC capable browser?'); - messageError('getUserMedia failed with exception: ' + e.message); - } -} - -function createPeerConnection() { - try { - // Create an RTCPeerConnection via the polyfill (adapter.js). - pc = new RTCPeerConnection(pcConfig, pcConstraints); - pc.onicecandidate = onIceCandidate; - console.log('Created RTCPeerConnnection with:\n' + - ' config: \'' + JSON.stringify(pcConfig) + '\';\n' + - ' constraints: \'' + JSON.stringify(pcConstraints) + '\'.'); - } catch (e) { - messageError('Failed to create PeerConnection, exception: ' + e.message); - alert('Cannot create RTCPeerConnection object; \ - WebRTC is not supported by this browser.'); - return; - } - pc.onaddstream = onRemoteStreamAdded; - pc.onremovestream = onRemoteStreamRemoved; - pc.onsignalingstatechange = onSignalingStateChanged; - pc.oniceconnectionstatechange = onIceConnectionStateChanged; -} - -function maybeStart() { - if (!started && signalingReady && channelReady && turnDone && - (localStream || !hasLocalStream)) { - setStatus('Connecting...'); - console.log('Creating PeerConnection.'); - createPeerConnection(); - - if (hasLocalStream) { - console.log('Adding local stream.'); - pc.addStream(localStream); - } else { - console.log('Not sending any stream.'); - } - started = true; - - if (initiator) - doCall(); - else - calleeStart(); - } -} - -function setStatus(state) { - document.getElementById('status').innerHTML = state; -} - -function doCall() { - var constraints = mergeConstraints(offerConstraints, sdpConstraints); - console.log('Sending offer to peer, with constraints: \n' + - ' \'' + JSON.stringify(constraints) + '\'.') - pc.createOffer(setLocalAndSendMessage, - onCreateSessionDescriptionError, constraints); -} - -function calleeStart() { - // Callee starts to process cached offer and other messages. - while (msgQueue.length > 0) { - processSignalingMessage(msgQueue.shift()); - } -} - -function doAnswer() { - console.log('Sending answer to peer.'); - pc.createAnswer(setLocalAndSendMessage, - onCreateSessionDescriptionError, sdpConstraints); -} - -function mergeConstraints(cons1, cons2) { - var merged = cons1; - for (var name in cons2.mandatory) { - merged.mandatory[name] = cons2.mandatory[name]; - } - merged.optional.concat(cons2.optional); - return merged; -} - -function setLocalAndSendMessage(sessionDescription) { - sessionDescription.sdp = maybePreferAudioReceiveCodec(sessionDescription.sdp); - pc.setLocalDescription(sessionDescription, - onSetSessionDescriptionSuccess, onSetSessionDescriptionError); - sendMessage(sessionDescription); -} - -function setRemote(message) { - // Set Opus in Stereo, if stereo enabled. - if (stereo) - message.sdp = addStereo(message.sdp); - message.sdp = maybePreferAudioSendCodec(message.sdp); - pc.setRemoteDescription(new RTCSessionDescription(message), - onSetRemoteDescriptionSuccess, onSetSessionDescriptionError); - - function onSetRemoteDescriptionSuccess() { - console.log("Set remote session description success."); - // By now all addstream events for the setRemoteDescription have fired. - // So we can know if the peer is sending any stream or is only receiving. - if (remoteStream) { - waitForRemoteVideo(); - } else { - console.log("Not receiving any stream."); - transitionToActive(); - } - } -} - -function sendMessage(message) { - var msgString = JSON.stringify(message); - console.log('C->S: ' + msgString); - // NOTE: AppRTCClient.java searches & parses this line; update there when - // changing here. - path = '/message?r=' + roomKey + '&u=' + me; - var xhr = new XMLHttpRequest(); - xhr.open('POST', path, true); - xhr.send(msgString); -} - -function processSignalingMessage(message) { - if (!started) { - messageError('peerConnection has not been created yet!'); - return; - } - - if (message.type === 'offer') { - setRemote(message); - doAnswer(); - } else if (message.type === 'answer') { - setRemote(message); - } else if (message.type === 'candidate') { - var candidate = new RTCIceCandidate({sdpMLineIndex: message.label, - candidate: message.candidate}); - noteIceCandidate("Remote", iceCandidateType(message.candidate)); - pc.addIceCandidate(candidate, - onAddIceCandidateSuccess, onAddIceCandidateError); - } else if (message.type === 'bye') { - onRemoteHangup(); - } -} - -function onAddIceCandidateSuccess() { - console.log('AddIceCandidate success.'); -} - -function onAddIceCandidateError(error) { - messageError('Failed to add Ice Candidate: ' + error.toString()); -} - -function onChannelOpened() { - console.log('Channel opened.'); - channelReady = true; - maybeStart(); -} - -function onChannelMessage(message) { - console.log('S->C: ' + message.data); - var msg = JSON.parse(message.data); - // Since the turn response is async and also GAE might disorder the - // Message delivery due to possible datastore query at server side, - // So callee needs to cache messages before peerConnection is created. - if (!initiator && !started) { - if (msg.type === 'offer') { - // Add offer to the beginning of msgQueue, since we can't handle - // Early candidates before offer at present. - msgQueue.unshift(msg); - // Callee creates PeerConnection - signalingReady = true; - maybeStart(); - } else { - msgQueue.push(msg); - } - } else { - processSignalingMessage(msg); - } -} - -function onChannelError() { - messageError('Channel error.'); -} - -function onChannelClosed() { - console.log('Channel closed.'); -} - -function messageError(msg) { - console.log(msg); - infoDivErrors.push(msg); - updateInfoDiv(); -} - -function onUserMediaSuccess(stream) { - console.log('User has granted access to local media.'); - // Call the polyfill wrapper to attach the media stream to this element. - attachMediaStream(localVideo, stream); - localVideo.style.opacity = 1; - localStream = stream; - // Caller creates PeerConnection. - maybeStart(); -} - -function onUserMediaError(error) { - messageError('Failed to get access to local media. Error code was ' + - error.code + '. Continuing without sending a stream.'); - alert('Failed to get access to local media. Error code was ' + - error.code + '. Continuing without sending a stream.'); - - hasLocalStream = false; - maybeStart(); -} - -function onCreateSessionDescriptionError(error) { - messageError('Failed to create session description: ' + error.toString()); -} - -function onSetSessionDescriptionSuccess() { - console.log('Set session description success.'); -} - -function onSetSessionDescriptionError(error) { - messageError('Failed to set session description: ' + error.toString()); -} - -function iceCandidateType(candidateSDP) { - if (candidateSDP.indexOf("typ relay ") >= 0) - return "TURN"; - if (candidateSDP.indexOf("typ srflx ") >= 0) - return "STUN"; - if (candidateSDP.indexOf("typ host ") >= 0) - return "HOST"; - return "UNKNOWN"; -} - -function onIceCandidate(event) { - if (event.candidate) { - sendMessage({type: 'candidate', - label: event.candidate.sdpMLineIndex, - id: event.candidate.sdpMid, - candidate: event.candidate.candidate}); - noteIceCandidate("Local", iceCandidateType(event.candidate.candidate)); - } else { - console.log('End of candidates.'); - } -} - -function onRemoteStreamAdded(event) { - console.log('Remote stream added.'); - attachMediaStream(remoteVideo, event.stream); - remoteStream = event.stream; -} - -function onRemoteStreamRemoved(event) { - console.log('Remote stream removed.'); -} - -function onSignalingStateChanged(event) { - updateInfoDiv(); -} - -function onIceConnectionStateChanged(event) { - updateInfoDiv(); -} - -function onHangup() { - console.log('Hanging up.'); - transitionToDone(); - localStream.stop(); - stop(); - // will trigger BYE from server - socket.close(); -} - -function onRemoteHangup() { - console.log('Session terminated.'); - initiator = 0; - transitionToWaiting(); - stop(); -} - -function stop() { - started = false; - signalingReady = false; - isAudioMuted = false; - isVideoMuted = false; - pc.close(); - pc = null; - remoteStream = null; - msgQueue.length = 0; -} - -function waitForRemoteVideo() { - // Call the getVideoTracks method via adapter.js. - videoTracks = remoteStream.getVideoTracks(); - if (videoTracks.length === 0 || remoteVideo.currentTime > 0) { - transitionToActive(); - } else { - setTimeout(waitForRemoteVideo, 100); - } -} - -function transitionToActive() { - reattachMediaStream(miniVideo, localVideo); - remoteVideo.style.opacity = 1; - card.style.webkitTransform = 'rotateY(180deg)'; - setTimeout(function() { localVideo.src = ''; }, 500); - setTimeout(function() { miniVideo.style.opacity = 1; }, 1000); - // Reset window display according to the asperio of remote video. - window.onresize(); - setStatus(''); -} - -function transitionToWaiting() { - card.style.webkitTransform = 'rotateY(0deg)'; - setTimeout(function() { - localVideo.src = miniVideo.src; - miniVideo.src = ''; - remoteVideo.src = '' }, 500); - miniVideo.style.opacity = 0; - remoteVideo.style.opacity = 0; - resetStatus(); -} - -function transitionToDone() { - localVideo.style.opacity = 0; - remoteVideo.style.opacity = 0; - miniVideo.style.opacity = 0; - setStatus('You have left the call. \ - Click here to rejoin.'); -} - -function enterFullScreen() { - container.webkitRequestFullScreen(); -} - -function noteIceCandidate(location, type) { - if (gatheredIceCandidateTypes[location][type]) - return; - gatheredIceCandidateTypes[location][type] = 1; - updateInfoDiv(); -} - -function getInfoDiv() { - return document.getElementById("infoDiv"); -} - -function updateInfoDiv() { - var contents = "
Gathered ICE Candidates\n";
-  for (var endpoint in gatheredIceCandidateTypes) {
-    contents += endpoint + ":\n";
-    for (var type in gatheredIceCandidateTypes[endpoint])
-      contents += "  " + type + "\n";
-  }
-  if (pc != null) {
-    contents += "Gathering: " + pc.iceGatheringState + "\n";
-    contents += "
\n"; - contents += "
PC State:\n";
-    contents += "Signaling: " + pc.signalingState + "\n";
-    contents += "ICE: " + pc.iceConnectionState + "\n";
-  }
-  var div = getInfoDiv();
-  div.innerHTML = contents + "
"; - - for (var msg in infoDivErrors) { - div.innerHTML += '

' + - infoDivErrors[msg] + '

'; - } - if (infoDivErrors.length) - showInfoDiv(); -} - -function toggleInfoDiv() { - var div = getInfoDiv(); - if (div.style.display == "block") { - div.style.display = "none"; - } else { - showInfoDiv(); - } -} - -function showInfoDiv() { - var div = getInfoDiv(); - div.style.display = "block"; -} - -function toggleVideoMute() { - // Call the getVideoTracks method via adapter.js. - videoTracks = localStream.getVideoTracks(); - - if (videoTracks.length === 0) { - console.log('No local video available.'); - return; - } - - if (isVideoMuted) { - for (i = 0; i < videoTracks.length; i++) { - videoTracks[i].enabled = true; - } - console.log('Video unmuted.'); - } else { - for (i = 0; i < videoTracks.length; i++) { - videoTracks[i].enabled = false; - } - console.log('Video muted.'); - } - - isVideoMuted = !isVideoMuted; -} - -function toggleAudioMute() { - // Call the getAudioTracks method via adapter.js. - audioTracks = localStream.getAudioTracks(); - - if (audioTracks.length === 0) { - console.log('No local audio available.'); - return; - } - - if (isAudioMuted) { - for (i = 0; i < audioTracks.length; i++) { - audioTracks[i].enabled = true; - } - console.log('Audio unmuted.'); - } else { - for (i = 0; i < audioTracks.length; i++){ - audioTracks[i].enabled = false; - } - console.log('Audio muted.'); - } - - isAudioMuted = !isAudioMuted; -} - -// Mac: hotkey is Command. -// Non-Mac: hotkey is Control. -// -D: toggle audio mute. -// -E: toggle video mute. -// -I: toggle Info box. -// Return false to screen out original Chrome shortcuts. -document.onkeydown = function(event) { - var hotkey = event.ctrlKey; - if (navigator.appVersion.indexOf('Mac') != -1) - hotkey = event.metaKey; - if (!hotkey) - return; - switch (event.keyCode) { - case 68: - toggleAudioMute(); - return false; - case 69: - toggleVideoMute(); - return false; - case 73: - toggleInfoDiv(); - return false; - default: - return; - } -} - -function maybePreferAudioSendCodec(sdp) { - if (audio_send_codec == '') { - console.log('No preference on audio send codec.'); - return sdp; - } - console.log('Prefer audio send codec: ' + audio_send_codec); - return preferAudioCodec(sdp, audio_send_codec); -} - -function maybePreferAudioReceiveCodec(sdp) { - if (audio_receive_codec == '') { - console.log('No preference on audio receive codec.'); - return sdp; - } - console.log('Prefer audio receive codec: ' + audio_receive_codec); - return preferAudioCodec(sdp, audio_receive_codec); -} - -// Set |codec| as the default audio codec if it's present. -// The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'. -function preferAudioCodec(sdp, codec) { - var fields = codec.split('/'); - if (fields.length != 2) { - console.log('Invalid codec setting: ' + codec); - return sdp; - } - var name = fields[0]; - var rate = fields[1]; - var sdpLines = sdp.split('\r\n'); - - // Search for m line. - for (var i = 0; i < sdpLines.length; i++) { - if (sdpLines[i].search('m=audio') !== -1) { - var mLineIndex = i; - break; - } - } - if (mLineIndex === null) - return sdp; - - // If the codec is available, set it as the default in m line. - for (var i = 0; i < sdpLines.length; i++) { - if (sdpLines[i].search(name + '/' + rate) !== -1) { - var regexp = new RegExp(':(\\d+) ' + name + '\\/' + rate, 'i'); - var payload = extractSdp(sdpLines[i], regexp); - if (payload) - sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], - payload); - break; - } - } - - // Remove CN in m line and sdp. - sdpLines = removeCN(sdpLines, mLineIndex); - - sdp = sdpLines.join('\r\n'); - return sdp; -} - -// Set Opus in stereo if stereo is enabled. -function addStereo(sdp) { - var sdpLines = sdp.split('\r\n'); - - // Find opus payload. - for (var i = 0; i < sdpLines.length; i++) { - if (sdpLines[i].search('opus/48000') !== -1) { - var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i); - break; - } - } - - // Find the payload in fmtp line. - for (var i = 0; i < sdpLines.length; i++) { - if (sdpLines[i].search('a=fmtp') !== -1) { - var payload = extractSdp(sdpLines[i], /a=fmtp:(\d+)/ ); - if (payload === opusPayload) { - var fmtpLineIndex = i; - break; - } - } - } - // No fmtp line found. - if (fmtpLineIndex === null) - return sdp; - - // Append stereo=1 to fmtp line. - sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat(' stereo=1'); - - sdp = sdpLines.join('\r\n'); - return sdp; -} - -function extractSdp(sdpLine, pattern) { - var result = sdpLine.match(pattern); - return (result && result.length == 2)? result[1]: null; -} - -// Set the selected codec to the first in m line. -function setDefaultCodec(mLine, payload) { - var elements = mLine.split(' '); - var newLine = new Array(); - var index = 0; - for (var i = 0; i < elements.length; i++) { - if (index === 3) // Format of media starts from the fourth. - newLine[index++] = payload; // Put target payload to the first. - if (elements[i] !== payload) - newLine[index++] = elements[i]; - } - return newLine.join(' '); -} - -// Strip CN from sdp before CN constraints is ready. -function removeCN(sdpLines, mLineIndex) { - var mLineElements = sdpLines[mLineIndex].split(' '); - // Scan from end for the convenience of removing an item. - for (var i = sdpLines.length-1; i >= 0; i--) { - var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i); - if (payload) { - var cnPos = mLineElements.indexOf(payload); - if (cnPos !== -1) { - // Remove CN payload from m line. - mLineElements.splice(cnPos, 1); - } - // Remove CN line in sdp - sdpLines.splice(i, 1); - } - } - - sdpLines[mLineIndex] = mLineElements.join(' '); - return sdpLines; -} - -// Send BYE on refreshing(or leaving) a demo page -// to ensure the room is cleaned for next session. -window.onbeforeunload = function() { - sendMessage({type: 'bye'}); -} - -// Set the video diplaying in the center of window. -window.onresize = function(){ - var aspectRatio; - if (remoteVideo.style.opacity === '1') { - aspectRatio = remoteVideo.videoWidth/remoteVideo.videoHeight; - } else if (localVideo.style.opacity === '1') { - aspectRatio = localVideo.videoWidth/localVideo.videoHeight; - } else { - return; - } - - var innerHeight = this.innerHeight; - var innerWidth = this.innerWidth; - var videoWidth = innerWidth < aspectRatio * window.innerHeight ? - innerWidth : aspectRatio * window.innerHeight; - var videoHeight = innerHeight < window.innerWidth / aspectRatio ? - innerHeight : window.innerWidth / aspectRatio; - containerDiv = document.getElementById('container'); - containerDiv.style.width = videoWidth + 'px'; - containerDiv.style.height = videoHeight + 'px'; - containerDiv.style.left = (innerWidth - videoWidth) / 2 + 'px'; - containerDiv.style.top = (innerHeight - videoHeight) / 2 + 'px'; -}; diff --git a/samples/js/apprtc/test/test_channel.html b/samples/js/apprtc/test/test_channel.html deleted file mode 100644 index 1668ce0df..000000000 --- a/samples/js/apprtc/test/test_channel.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - -

-
-
diff --git a/samples/js/apprtc/turn-prober/README b/samples/js/apprtc/turn-prober/README
deleted file mode 100644
index 58ba3398a..000000000
--- a/samples/js/apprtc/turn-prober/README
+++ /dev/null
@@ -1,9 +0,0 @@
-This script contains a simple prober that verifies that:
-- CEOD vends TURN server URIs with credentials on demand (mimicking apprtc)
-- rfc5766-turn-server vends TURN candidates from the servers vended by CEOD.
-
-To use simply run ./turn-prober.sh
-If it prints "PASS" (and exits 0) then all is well.
-If it prints a mess of logs (and exits non-0) then something has gone sideways
-and apprtc.appspot.com is probably not working well (b/c of missing TURN
-functionality).
diff --git a/samples/js/apprtc/turn-prober/turn-prober.html b/samples/js/apprtc/turn-prober/turn-prober.html
deleted file mode 100644
index 94cf68ecf..000000000
--- a/samples/js/apprtc/turn-prober/turn-prober.html
+++ /dev/null
@@ -1,132 +0,0 @@
-
-  
-    
-  
-  
-  
-
diff --git a/samples/js/apprtc/turn-prober/turn-prober.sh b/samples/js/apprtc/turn-prober/turn-prober.sh
deleted file mode 100755
index 2a063c58e..000000000
--- a/samples/js/apprtc/turn-prober/turn-prober.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash -e
-
-function chrome_pids() {
-  ps axuwww|grep $D|grep c[h]rome|awk '{print $2}'
-}
-
-cd $(dirname $0)
-export D=$(mktemp -d)
-
-CHROME_LOG_FILE="${D}/chrome_debug.log"
-touch $CHROME_LOG_FILE
-
-XVFB="xvfb-run -a -e $CHROME_LOG_FILE -s '-screen 0 1024x768x24'"
-if [ -n "$DISPLAY" ]; then
-  XVFB=""
-fi
-
-# "eval" below is required by $XVFB containing a quoted argument.
-eval $XVFB chrome \
-  --enable-logging=stderr \
-  --no-first-run \
-  --disable-web-security \
-  --user-data-dir=$D \
-  --vmodule="*media/*=3,*turn*=3" \
-  "file://${PWD}/turn-prober.html" > $CHROME_LOG_FILE 2>&1 &
-CHROME_PID=$!
-
-while ! grep -q DONE $CHROME_LOG_FILE && chrome_pids|grep -q .; do
-  sleep 0.1
-done
-
-# Suppress bash's Killed message for the chrome above.
-exec 3>&2
-exec 2>/dev/null
-while [ ! -z "$(chrome_pids)" ]; do
-  kill -9 $(chrome_pids)
-done
-exec 2>&3
-exec 3>&-
-
-DONE=$(grep DONE $CHROME_LOG_FILE)
-EXIT_CODE=0
-if ! grep -q "DONE: PASS" $CHROME_LOG_FILE; then
-  cat $CHROME_LOG_FILE
-  EXIT_CODE=1
-fi
-
-rm -rf $D
-exit $EXIT_CODE
diff --git a/samples/js/base/adapter.js b/samples/js/base/adapter.js
deleted file mode 100644
index 3dd894784..000000000
--- a/samples/js/base/adapter.js
+++ /dev/null
@@ -1,198 +0,0 @@
-var RTCPeerConnection = null;
-var getUserMedia = null;
-var attachMediaStream = null;
-var reattachMediaStream = null;
-var webrtcDetectedBrowser = null;
-var webrtcDetectedVersion = null;
-
-function trace(text) {
-  // This function is used for logging.
-  if (text[text.length - 1] == '\n') {
-    text = text.substring(0, text.length - 1);
-  }
-  console.log((performance.now() / 1000).toFixed(3) + ": " + text);
-}
-function maybeFixConfiguration(pcConfig) {
-  if (pcConfig == null) {
-    return;
-  }
-  for (var i = 0; i < pcConfig.iceServers.length; i++) {
-    if (pcConfig.iceServers[i].hasOwnProperty('urls')){
-      pcConfig.iceServers[i]['url'] = pcConfig.iceServers[i]['urls'];
-      delete pcConfig.iceServers[i]['urls'];
-    }
-  }
-}
-
-if (navigator.mozGetUserMedia) {
-  console.log("This appears to be Firefox");
-
-  webrtcDetectedBrowser = "firefox";
-
-  webrtcDetectedVersion =
-           parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
-
-  // The RTCPeerConnection object.
-  var RTCPeerConnection = function(pcConfig, pcConstraints) {
-    // .urls is not supported in FF yet.
-    maybeFixConfiguration(pcConfig);
-    return new mozRTCPeerConnection(pcConfig, pcConstraints);
-  }
-
-  // The RTCSessionDescription object.
-  RTCSessionDescription = mozRTCSessionDescription;
-
-  // The RTCIceCandidate object.
-  RTCIceCandidate = mozRTCIceCandidate;
-
-  // Get UserMedia (only difference is the prefix).
-  // Code from Adam Barth.
-  getUserMedia = navigator.mozGetUserMedia.bind(navigator);
-  navigator.getUserMedia = getUserMedia;
-
-  // Creates iceServer from the url for FF.
-  createIceServer = function(url, username, password) {
-    var iceServer = null;
-    var url_parts = url.split(':');
-    if (url_parts[0].indexOf('stun') === 0) {
-      // Create iceServer with stun url.
-      iceServer = { 'url': url };
-    } else if (url_parts[0].indexOf('turn') === 0) {
-      if (webrtcDetectedVersion < 27) {
-        // Create iceServer with turn url.
-        // Ignore the transport parameter from TURN url for FF version <=27.
-        var turn_url_parts = url.split("?");
-        // Return null for createIceServer if transport=tcp.
-        if (turn_url_parts.length === 1 ||
-            turn_url_parts[1].indexOf('transport=udp') === 0) {
-          iceServer = {'url': turn_url_parts[0],
-                       'credential': password,
-                       'username': username};
-        }
-      } else {
-        // FF 27 and above supports transport parameters in TURN url,
-        // So passing in the full url to create iceServer.
-        iceServer = {'url': url,
-                     'credential': password,
-                     'username': username};
-      }
-    }
-    return iceServer;
-  };
-
-  createIceServers = function(urls, username, password) {
-    var iceServers = [];
-    // Use .url for FireFox.
-    for (i = 0; i < urls.length; i++) {
-      var iceServer = createIceServer(urls[i],
-                                      username,
-                                      password);
-      if (iceServer !== null) {
-        iceServers.push(iceServer);
-      }
-    }
-    return iceServers;
-  }
-
-  // Attach a media stream to an element.
-  attachMediaStream = function(element, stream) {
-    console.log("Attaching media stream");
-    element.mozSrcObject = stream;
-    element.play();
-  };
-
-  reattachMediaStream = function(to, from) {
-    console.log("Reattaching media stream");
-    to.mozSrcObject = from.mozSrcObject;
-    to.play();
-  };
-
-  // Fake get{Video,Audio}Tracks
-  if (!MediaStream.prototype.getVideoTracks) {
-    MediaStream.prototype.getVideoTracks = function() {
-      return [];
-    };
-  }
-
-  if (!MediaStream.prototype.getAudioTracks) {
-    MediaStream.prototype.getAudioTracks = function() {
-      return [];
-    };
-  }
-} else if (navigator.webkitGetUserMedia) {
-  console.log("This appears to be Chrome");
-
-  webrtcDetectedBrowser = "chrome";
-  webrtcDetectedVersion =
-         parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
-
-  // Creates iceServer from the url for Chrome M33 and earlier.
-  createIceServer = function(url, username, password) {
-    var iceServer = null;
-    var url_parts = url.split(':');
-    if (url_parts[0].indexOf('stun') === 0) {
-      // Create iceServer with stun url.
-      iceServer = { 'url': url };
-    } else if (url_parts[0].indexOf('turn') === 0) {
-      // Chrome M28 & above uses below TURN format.
-      iceServer = {'url': url,
-                   'credential': password,
-                   'username': username};
-    }
-    return iceServer;
-  };
-
-  // Creates iceServers from the urls for Chrome M34 and above.
-  createIceServers = function(urls, username, password) {
-    var iceServers = [];
-    if (webrtcDetectedVersion >= 34) {
-      // .urls is supported since Chrome M34.
-      iceServers = {'urls': urls,
-                    'credential': password,
-                    'username': username };
-    } else {
-      for (i = 0; i < urls.length; i++) {
-        var iceServer = createIceServer(urls[i],
-                                        username,
-                                        password);
-        if (iceServer !== null) {
-          iceServers.push(iceServer);
-        }
-      }
-    }
-    return iceServers;
-  };
-
-  // The RTCPeerConnection object.
-  var RTCPeerConnection = function(pcConfig, pcConstraints) {
-    // .urls is supported since Chrome M34.
-    if (webrtcDetectedVersion < 34) {
-      maybeFixConfiguration(pcConfig);
-    }
-    return new webkitRTCPeerConnection(pcConfig, pcConstraints);
-  }
-
-  // Get UserMedia (only difference is the prefix).
-  // Code from Adam Barth.
-  getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
-  navigator.getUserMedia = getUserMedia;
-
-  // Attach a media stream to an element.
-  attachMediaStream = function(element, stream) {
-    if (typeof element.srcObject !== 'undefined') {
-      element.srcObject = stream;
-    } else if (typeof element.mozSrcObject !== 'undefined') {
-      element.mozSrcObject = stream;
-    } else if (typeof element.src !== 'undefined') {
-      element.src = URL.createObjectURL(stream);
-    } else {
-      console.log('Error attaching stream to element.');
-    }
-  };
-
-  reattachMediaStream = function(to, from) {
-    to.src = from.src;
-  };
-} else {
-  console.log("Browser does not appear to be WebRTC-capable");
-}
diff --git a/samples/js/demos/html/THESE_FILES_ARE_MOVING b/samples/js/demos/html/THESE_FILES_ARE_MOVING
deleted file mode 100644
index eae7e1abc..000000000
--- a/samples/js/demos/html/THESE_FILES_ARE_MOVING
+++ /dev/null
@@ -1,5 +0,0 @@
-These demos are moving to Github: https://github.com/GoogleChrome/webrtc.
-
-Please file bugs and patches there from now on.
-
-Thanks!
diff --git a/samples/js/demos/html/constraints-and-stats.html b/samples/js/demos/html/constraints-and-stats.html
deleted file mode 100644
index 509776b4c..000000000
--- a/samples/js/demos/html/constraints-and-stats.html
+++ /dev/null
@@ -1,377 +0,0 @@
-
-
-Constraints and Statistics
-
-
-
-
-
-
-
-
-

Constraints and Statistics

-This page is meant to give some hints on how one can use constraints and statistics in WebRTC applications. -

-The form to the left gives constraints you can set on the getUserMedia call. -When you hit "open", it will (re)open the camera with these constraints. -

-The left picture is the local preview. The right picture is the picture -after being passed through the PeerConnection (locally). -

-Underneath the picture you will see a running display of how many Kbits/sec -the video feed uses for transmission. -


- - - - - - - - - - - - -
-

getUserMedia constraints

- -
MinMax -
Horizontal - - -300-640 -
Vertical - - -200-480 -
-FrameRate - -30 -
- -
-

addStream constraints

-Maximum bitrate - -1000 -
- -
- - - -
- -
-Bitrate unknown -
-
-
-

Statistics report display

- - - -
Sender sideReceiver side -
Stats will appear here.
-
Stats will appear here.
-
- - diff --git a/samples/js/demos/html/create-offer.html b/samples/js/demos/html/create-offer.html deleted file mode 100644 index aa541bfa1..000000000 --- a/samples/js/demos/html/create-offer.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - -Show createOffer Output Demo - - - - -

WebRTC createOffer Test Page

-

This page tests the createOffer method for a WebRTC implementation. It - creates a PeerConnection, and then prints out the SDP generated by - createOffer, with the number of desired audio MediaStreamTracks and the - checked createOffer constraints. Currently, only audio tracks can be added, - as there is no programmatic way to generate video tracks. (Web Audio is - used to generate the audio tracks.)

-

Tracks

-

Number of Audio Tracks

-

Constraints:

-Offer To Receive Audio
-Offer To Receive Video
-Voice Activity Detection
-Ice Restart
-
-
- - - - - - diff --git a/samples/js/demos/html/dc1.html b/samples/js/demos/html/dc1.html deleted file mode 100644 index abfc3ef92..000000000 --- a/samples/js/demos/html/dc1.html +++ /dev/null @@ -1,240 +0,0 @@ - - - -Data Channel Demo 1 - - - -
-
-

Send data

- -
-
-

Received Data

- -
-
-
-

Choose SCTP or RTP for transmitting data.

- - - - -
- - - - - - - - diff --git a/samples/js/demos/html/device-switch.html b/samples/js/demos/html/device-switch.html deleted file mode 100644 index ea6c36854..000000000 --- a/samples/js/demos/html/device-switch.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - -Device Switch Demo - - - - - -
-

Select an audio and video source, then click Start.

-Audio source: -Video source:
- - - -
- - - diff --git a/samples/js/demos/html/dtmf1.html b/samples/js/demos/html/dtmf1.html deleted file mode 100644 index 5141410dc..000000000 --- a/samples/js/demos/html/dtmf1.html +++ /dev/null @@ -1,208 +0,0 @@ - - -PeerConnection DTMF Demo 1 - - - - - -
- -

Send Dtmf Tones

-
-

-duration: - -

-tone-gap: - -

-tones: - - -

- - -

-
- - - - diff --git a/samples/js/demos/html/face.html b/samples/js/demos/html/face.html deleted file mode 100644 index cded7dd3f..000000000 --- a/samples/js/demos/html/face.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - WebRTC Face Reco Demo Application - - - - - - - -
JS Face Detect by Liu Liu
- - diff --git a/samples/js/demos/html/gum1.html b/samples/js/demos/html/gum1.html deleted file mode 100644 index f431faab9..000000000 --- a/samples/js/demos/html/gum1.html +++ /dev/null @@ -1,35 +0,0 @@ - - - -getUserMedia Demo 1 - - - - - - -
- - - - - diff --git a/samples/js/demos/html/gum2.html b/samples/js/demos/html/gum2.html deleted file mode 100644 index 4dce7f3b7..000000000 --- a/samples/js/demos/html/gum2.html +++ /dev/null @@ -1,50 +0,0 @@ - - - -getUserMedia Demo 2 - - - - - - - -
- - - - - - diff --git a/samples/js/demos/html/gum3.html b/samples/js/demos/html/gum3.html deleted file mode 100644 index c22eec2d7..000000000 --- a/samples/js/demos/html/gum3.html +++ /dev/null @@ -1,76 +0,0 @@ - - - -getUserMedia Demo 3 - - - - - - - -
- - - - - - - diff --git a/samples/js/demos/html/gum4.html b/samples/js/demos/html/gum4.html deleted file mode 100644 index 47709cc6f..000000000 --- a/samples/js/demos/html/gum4.html +++ /dev/null @@ -1,95 +0,0 @@ - - - -getUserMedia Demo 4 - - - - - - - -
- - - - - - - - - diff --git a/samples/js/demos/html/ice-servers.html b/samples/js/demos/html/ice-servers.html deleted file mode 100644 index dd1cb4145..000000000 --- a/samples/js/demos/html/ice-servers.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - -ICE Candidate Gathering Demo - - - - -

WebRTC Trickle ICE Test Page

-

This page tests the trickle ICE functionality in a WebRTC implementation. It - creates a PeerConnection with the specified ICEServers, and then starts - candidate gathering for a session with a single audio stream. As candidates - are gathered, they are displayed in the text box below, along with an - indication when candidate gathering is complete.

-

Individual STUN and TURN servers can be added using the Add Server/Remove - Server controls below; in addition, the type of candidates released to the - application can be controlled via the IceTransports contraint.

-

ICE Servers

- -
-STUN or TURN URI: - -
-TURN Username: - -TURN Password: - -
- - -

ICE Constraints

-IceTransports value: - All - Relay - None -
-
- -
- - - - - diff --git a/samples/js/demos/html/local-audio-rendering.html b/samples/js/demos/html/local-audio-rendering.html deleted file mode 100644 index 201b9fe5a..000000000 --- a/samples/js/demos/html/local-audio-rendering.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - -Local Audio Rendering Demo - - - - - -

Rendering of a local media stream using <audio>

-

Demonstrates usage of a local media stream connected to an HTML5 audio tag.
- Press Start, select a microphone and listen to your own voice in loopback.

- -

- - - - diff --git a/samples/js/demos/html/local-audio-volume.html b/samples/js/demos/html/local-audio-volume.html deleted file mode 100644 index 8b85aff4e..000000000 --- a/samples/js/demos/html/local-audio-volume.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - -Local Audio Rendering Demo - - - - - - -

Measuring the volume of an audio stream using WebAudio

-

Demonstrates measuring the volume of a local media stream - using WebAudio.
- Press Start, select a microphone, listen to your own voice in loopback, - and see the numbers change as you speak.

- The "instant" volume changes approximately every 50 ms; the "slow" - volume approximates the average volume over about a second. -
- Note that you will NOT hear your own voice; use the - local audio rendering demo - for that. -

- -

- Volume (instant): Not set
- Volume (slow): Not set
- Volume
- Slow
- Clipping - - - diff --git a/samples/js/demos/html/multiple-relay.html b/samples/js/demos/html/multiple-relay.html deleted file mode 100644 index 051aad525..000000000 --- a/samples/js/demos/html/multiple-relay.html +++ /dev/null @@ -1,98 +0,0 @@ - - - -PeerConnection Demo 1 - - - - - - - - -
- - - - -
- - - - diff --git a/samples/js/demos/html/multiple.html b/samples/js/demos/html/multiple.html deleted file mode 100644 index 79b37fd79..000000000 --- a/samples/js/demos/html/multiple.html +++ /dev/null @@ -1,196 +0,0 @@ - - - -PeerConnection Demo 1 - - - - - - - - -
- - - -
- - - - - diff --git a/samples/js/demos/html/pc1-audio.html b/samples/js/demos/html/pc1-audio.html deleted file mode 100644 index 74f38dc32..000000000 --- a/samples/js/demos/html/pc1-audio.html +++ /dev/null @@ -1,126 +0,0 @@ - - - -PeerConnection Audio Only Demo 1 - - - - - -

Local-Audio

- -

Remote-Audio

- -

- - -

- - - diff --git a/samples/js/demos/html/pc1.html b/samples/js/demos/html/pc1.html deleted file mode 100644 index f2cf4d033..000000000 --- a/samples/js/demos/html/pc1.html +++ /dev/null @@ -1,152 +0,0 @@ - - - -PeerConnection Demo 1 - - - - - - - -
- - - -
- - - - - - - diff --git a/samples/js/demos/html/pc1_sdp_munge.html b/samples/js/demos/html/pc1_sdp_munge.html deleted file mode 100644 index 6396f72d2..000000000 --- a/samples/js/demos/html/pc1_sdp_munge.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - -PC1 SDP Munge Demo - - - -
-

Local Preview

- -

Offer SDP

-

-
- -
- - -
-
Select an audio & video source, then click GetUserMedia:
-
- - - - - - - -
- - - diff --git a/samples/js/demos/html/pranswer.html b/samples/js/demos/html/pranswer.html deleted file mode 100644 index 36e0d2dd5..000000000 --- a/samples/js/demos/html/pranswer.html +++ /dev/null @@ -1,154 +0,0 @@ - - - -PeerConnection PRANSWER Demo - - - - - - - -
- - - - - - - diff --git a/samples/js/demos/html/states.html b/samples/js/demos/html/states.html deleted file mode 100644 index ab7f7cb0d..000000000 --- a/samples/js/demos/html/states.html +++ /dev/null @@ -1,246 +0,0 @@ - - - -RTCPeerState & RTCIceConnectionState Demo 1 - - - - - - - -
- - - -
-
-
- - -
- - -
- - -
- - -
- - - - diff --git a/samples/js/demos/html/webaudio-and-webrtc.html b/samples/js/demos/html/webaudio-and-webrtc.html deleted file mode 100644 index 0438bcc31..000000000 --- a/samples/js/demos/html/webaudio-and-webrtc.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - -Audio effects with WebAudio in WebRTC - - - - - -

Capture microphone input and stream it out to a peer with a processing - effect applied to the audio.

-

The audio stream is:

- o Recorded using live-audio - input.
- o Filtered using an HP filter with fc=1500 Hz.
- o Encoded using - Opus.
- o Transmitted (in loopback) to remote peer using - RTCPeerConnection where it is decoded.
- o Finally, the received remote stream is used as source to an <audio> - tag and played out locally.
-
Press any key to add an effect to the transmitted audio while talking. -

-

Please note that:

- o Linux is currently not supported.
- o Sample rate and channel configuration must be the same for input and - output sides on Windows.
- o Only the Default microphone device can be used for capturing. -

-

For more information, see - WebRTC integration with the Web Audio API. -

- -

- -

- Add local audio to output:

-

-
-
diff --git a/samples/js/demos/images/webrtc_black_20p.png b/samples/js/demos/images/webrtc_black_20p.png
deleted file mode 100644
index a35c1dff2..000000000
Binary files a/samples/js/demos/images/webrtc_black_20p.png and /dev/null differ
diff --git a/samples/js/demos/index.html b/samples/js/demos/index.html
deleted file mode 100644
index cd56b7619..000000000
--- a/samples/js/demos/index.html
+++ /dev/null
@@ -1,140 +0,0 @@
-
-
-WebRTC Samples
-
-
-

WebRTC Samples

-

-Here are some sample pages that demonstrate basic -WebRTC concepts. If you are new to WebRTC, -you may want to check out this - -WebRTC overview first. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- getUserMedia Samples
- gum1.html - Shows how to access the webcam and display the local video in a <video/> element.
- gum2.html - Shows how to capture the current frame of video to a <canvas/>.
- gum3.html - Shows how to apply CSS filters to a <video/> and <canvas/>
- face.html - Shows how to perform face tracking using webcam video.
- local-audio-rendering.html - - Shows usage of a local media stream connected to an HTML5 audio tag.
- local-audio-volume.html - Shows how to display the volume of a local audio track.
   
- PeerConnection Samples
- pc1-audio.htmlShows how to set up a simple 1:1 audio only call.
- pc1.html - Shows how to set up a simple 1:1 audio/video call.
- pc1_sdp_munge.html - Allows you to modify offer/answer sdp with pc1 demo.
- states.html - Shows RTCPeerStates and RTCIceConnectionStates in a - simple 1:1 audio/video call.
- multiple.html - Shows how to set up multiple PeerConnections.
- constraints-and-stats.html - Shows how to pass constraints into the PeerConnection API, - and query it for statistics.
- dtmf1.html - Shows how to send DTMF tones using PeerConnection API.
- dc1.html - Shows how to send Data using PeerConnection API.
- webaudio-and-webrtc.html - Captures and filters microphone input using WebAudio and sends it to a - remote peer with an option to add an audio effect.
- create-offer.html - Shows the output of createOffer when various constraints - are supplied.
- ice-servers.html - Tests gathering candidates from arbitrary STUN and TURN servers. -
-

 

- - - diff --git a/samples/js/demos/index.yaml b/samples/js/demos/index.yaml deleted file mode 100644 index 8e6046de9..000000000 --- a/samples/js/demos/index.yaml +++ /dev/null @@ -1,12 +0,0 @@ -indexes: - -# AUTOGENERATED - -# This index.yaml is automatically updated whenever the dev_appserver -# detects that a new type of query is run. If you want to manage the -# index.yaml file manually, remove the above marker line (the line -# saying "# AUTOGENERATED"). If you want to manage some indexes -# manually, move them above the marker line. The index.yaml file is -# automatically uploaded to the admin console when you next deploy -# your application using appcfg.py. - diff --git a/samples/js/demos/js/ccv.js b/samples/js/demos/js/ccv.js deleted file mode 100644 index 8bb3e7647..000000000 --- a/samples/js/demos/js/ccv.js +++ /dev/null @@ -1,460 +0,0 @@ -if (parallable === undefined) { - var parallable = function (file, funct) { - parallable.core[funct.toString()] = funct().core; - return function () { - var i; - var async, worker_num, params; - if (arguments.length > 1) { - async = arguments[arguments.length - 2]; - worker_num = arguments[arguments.length - 1]; - params = new Array(arguments.length - 2); - for (i = 0; i < arguments.length - 2; i++) - params[i] = arguments[i]; - } else { - async = arguments[0].async; - worker_num = arguments[0].worker; - params = arguments[0]; - delete params["async"]; - delete params["worker"]; - params = [params]; - } - var scope = { "shared" : {} }; - var ctrl = funct.apply(scope, params); - if (async) { - return function (complete, error) { - var executed = 0; - var outputs = new Array(worker_num); - var inputs = ctrl.pre.apply(scope, [worker_num]); - /* sanitize scope shared because for Chrome/WebKit, worker only support JSONable data */ - for (i in scope.shared) - /* delete function, if any */ - if (typeof scope.shared[i] == "function") - delete scope.shared[i]; - /* delete DOM object, if any */ - else if (scope.shared[i].tagName !== undefined) - delete scope.shared[i]; - for (i = 0; i < worker_num; i++) { - var worker = new Worker(file); - worker.onmessage = (function (i) { - return function (event) { - outputs[i] = (typeof event.data == "string") ? JSON.parse(event.data) : event.data; - executed++; - if (executed == worker_num) - complete(ctrl.post.apply(scope, [outputs])); - } - })(i); - var msg = { "input" : inputs[i], - "name" : funct.toString(), - "shared" : scope.shared, - "id" : i, - "worker" : params.worker_num }; - try { - worker.postMessage(msg); - } catch (e) { - worker.postMessage(JSON.stringify(msg)); - } - } - } - } else { - return ctrl.post.apply(scope, [[ctrl.core.apply(scope, [ctrl.pre.apply(scope, [1])[0], 0, 1])]]); - } - } - }; - parallable.core = {}; -} - -function get_named_arguments(params, names) { - if (params.length > 1) { - var new_params = {}; - for (var i = 0; i < names.length; i++) - new_params[names[i]] = params[i]; - return new_params; - } else if (params.length == 1) { - return params[0]; - } else { - return {}; - } -} - -var ccv = { - pre : function (image) { - if (image.tagName.toLowerCase() == "img") { - var canvas = document.createElement("canvas"); - document.body.appendChild(image); - canvas.width = image.offsetWidth; - canvas.style.width = image.offsetWidth.toString() + "px"; - canvas.height = image.offsetHeight; - canvas.style.height = image.offsetHeight.toString() + "px"; - document.body.removeChild(image); - var ctx = canvas.getContext("2d"); - ctx.drawImage(image, 0, 0); - return canvas; - } - return image; - }, - - grayscale : function (canvas) { - var ctx = canvas.getContext("2d"); - var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - var data = imageData.data; - var pix1, pix2, pix = canvas.width * canvas.height * 4; - while (pix > 0) - data[pix -= 4] = data[pix1 = pix + 1] = data[pix2 = pix + 2] = (data[pix] * 0.3 + data[pix1] * 0.59 + data[pix2] * 0.11); - ctx.putImageData(imageData, 0, 0); - return canvas; - }, - - array_group : function (seq, gfunc) { - var i, j; - var node = new Array(seq.length); - for (i = 0; i < seq.length; i++) - node[i] = {"parent" : -1, - "element" : seq[i], - "rank" : 0}; - for (i = 0; i < seq.length; i++) { - if (!node[i].element) - continue; - var root = i; - while (node[root].parent != -1) - root = node[root].parent; - for (j = 0; j < seq.length; j++) { - if( i != j && node[j].element && gfunc(node[i].element, node[j].element)) { - var root2 = j; - - while (node[root2].parent != -1) - root2 = node[root2].parent; - - if(root2 != root) { - if(node[root].rank > node[root2].rank) - node[root2].parent = root; - else { - node[root].parent = root2; - if (node[root].rank == node[root2].rank) - node[root2].rank++; - root = root2; - } - - /* compress path from node2 to the root: */ - var temp, node2 = j; - while (node[node2].parent != -1) { - temp = node2; - node2 = node[node2].parent; - node[temp].parent = root; - } - - /* compress path from node to the root: */ - node2 = i; - while (node[node2].parent != -1) { - temp = node2; - node2 = node[node2].parent; - node[temp].parent = root; - } - } - } - } - } - var idx = new Array(seq.length); - var class_idx = 0; - for(i = 0; i < seq.length; i++) { - j = -1; - var node1 = i; - if(node[node1].element) { - while (node[node1].parent != -1) - node1 = node[node1].parent; - if(node[node1].rank >= 0) - node[node1].rank = ~class_idx++; - j = ~node[node1].rank; - } - idx[i] = j; - } - return {"index" : idx, "cat" : class_idx}; - }, - - detect_objects : parallable("ccv.js", function (canvas, cascade, interval, min_neighbors) { - if (this.shared !== undefined) { - var params = get_named_arguments(arguments, ["canvas", "cascade", "interval", "min_neighbors"]); - this.shared.canvas = params.canvas; - this.shared.interval = params.interval; - this.shared.min_neighbors = params.min_neighbors; - this.shared.cascade = params.cascade; - this.shared.scale = Math.pow(2, 1 / (params.interval + 1)); - this.shared.next = params.interval + 1; - this.shared.scale_upto = Math.floor(Math.log(Math.min(params.canvas.width / params.cascade.width, params.canvas.height / params.cascade.height)) / Math.log(this.shared.scale)); - var i; - for (i = 0; i < this.shared.cascade.stage_classifier.length; i++) - this.shared.cascade.stage_classifier[i].orig_feature = this.shared.cascade.stage_classifier[i].feature; - } - function pre(worker_num) { - var canvas = this.shared.canvas; - var interval = this.shared.interval; - var scale = this.shared.scale; - var next = this.shared.next; - var scale_upto = this.shared.scale_upto; - var pyr = new Array((scale_upto + next * 2) * 4); - var ret = new Array((scale_upto + next * 2) * 4); - pyr[0] = canvas; - ret[0] = { "width" : pyr[0].width, - "height" : pyr[0].height, - "data" : pyr[0].getContext("2d").getImageData(0, 0, pyr[0].width, pyr[0].height).data }; - var i; - for (i = 1; i <= interval; i++) { - pyr[i * 4] = document.createElement("canvas"); - pyr[i * 4].width = Math.floor(pyr[0].width / Math.pow(scale, i)); - pyr[i * 4].height = Math.floor(pyr[0].height / Math.pow(scale, i)); - pyr[i * 4].getContext("2d").drawImage(pyr[0], 0, 0, pyr[0].width, pyr[0].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height); - ret[i * 4] = { "width" : pyr[i * 4].width, - "height" : pyr[i * 4].height, - "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data }; - } - for (i = next; i < scale_upto + next * 2; i++) { - pyr[i * 4] = document.createElement("canvas"); - pyr[i * 4].width = Math.floor(pyr[i * 4 - next * 4].width / 2); - pyr[i * 4].height = Math.floor(pyr[i * 4 - next * 4].height / 2); - pyr[i * 4].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 0, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4].width, pyr[i * 4].height); - ret[i * 4] = { "width" : pyr[i * 4].width, - "height" : pyr[i * 4].height, - "data" : pyr[i * 4].getContext("2d").getImageData(0, 0, pyr[i * 4].width, pyr[i * 4].height).data }; - } - for (i = next * 2; i < scale_upto + next * 2; i++) { - pyr[i * 4 + 1] = document.createElement("canvas"); - pyr[i * 4 + 1].width = Math.floor(pyr[i * 4 - next * 4].width / 2); - pyr[i * 4 + 1].height = Math.floor(pyr[i * 4 - next * 4].height / 2); - pyr[i * 4 + 1].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 0, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height, 0, 0, pyr[i * 4 + 1].width - 2, pyr[i * 4 + 1].height); - ret[i * 4 + 1] = { "width" : pyr[i * 4 + 1].width, - "height" : pyr[i * 4 + 1].height, - "data" : pyr[i * 4 + 1].getContext("2d").getImageData(0, 0, pyr[i * 4 + 1].width, pyr[i * 4 + 1].height).data }; - pyr[i * 4 + 2] = document.createElement("canvas"); - pyr[i * 4 + 2].width = Math.floor(pyr[i * 4 - next * 4].width / 2); - pyr[i * 4 + 2].height = Math.floor(pyr[i * 4 - next * 4].height / 2); - pyr[i * 4 + 2].getContext("2d").drawImage(pyr[i * 4 - next * 4], 0, 1, pyr[i * 4 - next * 4].width, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height - 2); - ret[i * 4 + 2] = { "width" : pyr[i * 4 + 2].width, - "height" : pyr[i * 4 + 2].height, - "data" : pyr[i * 4 + 2].getContext("2d").getImageData(0, 0, pyr[i * 4 + 2].width, pyr[i * 4 + 2].height).data }; - pyr[i * 4 + 3] = document.createElement("canvas"); - pyr[i * 4 + 3].width = Math.floor(pyr[i * 4 - next * 4].width / 2); - pyr[i * 4 + 3].height = Math.floor(pyr[i * 4 - next * 4].height / 2); - pyr[i * 4 + 3].getContext("2d").drawImage(pyr[i * 4 - next * 4], 1, 1, pyr[i * 4 - next * 4].width - 1, pyr[i * 4 - next * 4].height - 1, 0, 0, pyr[i * 4 + 3].width - 2, pyr[i * 4 + 3].height - 2); - ret[i * 4 + 3] = { "width" : pyr[i * 4 + 3].width, - "height" : pyr[i * 4 + 3].height, - "data" : pyr[i * 4 + 3].getContext("2d").getImageData(0, 0, pyr[i * 4 + 3].width, pyr[i * 4 + 3].height).data }; - } - return [ret]; - }; - - function core(pyr, id, worker_num) { - var cascade = this.shared.cascade; - var interval = this.shared.interval; - var scale = this.shared.scale; - var next = this.shared.next; - var scale_upto = this.shared.scale_upto; - var i, j, k, x, y, q; - var scale_x = 1, scale_y = 1; - var dx = [0, 1, 0, 1]; - var dy = [0, 0, 1, 1]; - var seq = []; - for (i = 0; i < scale_upto; i++) { - var qw = pyr[i * 4 + next * 8].width - Math.floor(cascade.width / 4); - var qh = pyr[i * 4 + next * 8].height - Math.floor(cascade.height / 4); - var step = [pyr[i * 4].width * 4, pyr[i * 4 + next * 4].width * 4, pyr[i * 4 + next * 8].width * 4]; - var paddings = [pyr[i * 4].width * 16 - qw * 16, - pyr[i * 4 + next * 4].width * 8 - qw * 8, - pyr[i * 4 + next * 8].width * 4 - qw * 4]; - for (j = 0; j < cascade.stage_classifier.length; j++) { - var orig_feature = cascade.stage_classifier[j].orig_feature; - var feature = cascade.stage_classifier[j].feature = new Array(cascade.stage_classifier[j].count); - for (k = 0; k < cascade.stage_classifier[j].count; k++) { - feature[k] = {"size" : orig_feature[k].size, - "px" : new Array(orig_feature[k].size), - "pz" : new Array(orig_feature[k].size), - "nx" : new Array(orig_feature[k].size), - "nz" : new Array(orig_feature[k].size)}; - for (q = 0; q < orig_feature[k].size; q++) { - feature[k].px[q] = orig_feature[k].px[q] * 4 + orig_feature[k].py[q] * step[orig_feature[k].pz[q]]; - feature[k].pz[q] = orig_feature[k].pz[q]; - feature[k].nx[q] = orig_feature[k].nx[q] * 4 + orig_feature[k].ny[q] * step[orig_feature[k].nz[q]]; - feature[k].nz[q] = orig_feature[k].nz[q]; - } - } - } - for (q = 0; q < 4; q++) { - var u8 = [pyr[i * 4].data, pyr[i * 4 + next * 4].data, pyr[i * 4 + next * 8 + q].data]; - var u8o = [dx[q] * 8 + dy[q] * pyr[i * 4].width * 8, dx[q] * 4 + dy[q] * pyr[i * 4 + next * 4].width * 4, 0]; - for (y = 0; y < qh; y++) { - for (x = 0; x < qw; x++) { - var sum = 0; - var flag = true; - for (j = 0; j < cascade.stage_classifier.length; j++) { - sum = 0; - var alpha = cascade.stage_classifier[j].alpha; - var feature = cascade.stage_classifier[j].feature; - for (k = 0; k < cascade.stage_classifier[j].count; k++) { - var feature_k = feature[k]; - var p, pmin = u8[feature_k.pz[0]][u8o[feature_k.pz[0]] + feature_k.px[0]]; - var n, nmax = u8[feature_k.nz[0]][u8o[feature_k.nz[0]] + feature_k.nx[0]]; - if (pmin <= nmax) { - sum += alpha[k * 2]; - } else { - var f, shortcut = true; - for (f = 0; f < feature_k.size; f++) { - if (feature_k.pz[f] >= 0) { - p = u8[feature_k.pz[f]][u8o[feature_k.pz[f]] + feature_k.px[f]]; - if (p < pmin) { - if (p <= nmax) { - shortcut = false; - break; - } - pmin = p; - } - } - if (feature_k.nz[f] >= 0) { - n = u8[feature_k.nz[f]][u8o[feature_k.nz[f]] + feature_k.nx[f]]; - if (n > nmax) { - if (pmin <= n) { - shortcut = false; - break; - } - nmax = n; - } - } - } - sum += (shortcut) ? alpha[k * 2 + 1] : alpha[k * 2]; - } - } - if (sum < cascade.stage_classifier[j].threshold) { - flag = false; - break; - } - } - if (flag) { - seq.push({"x" : (x * 4 + dx[q] * 2) * scale_x, - "y" : (y * 4 + dy[q] * 2) * scale_y, - "width" : cascade.width * scale_x, - "height" : cascade.height * scale_y, - "neighbor" : 1, - "confidence" : sum}); - } - u8o[0] += 16; - u8o[1] += 8; - u8o[2] += 4; - } - u8o[0] += paddings[0]; - u8o[1] += paddings[1]; - u8o[2] += paddings[2]; - } - } - scale_x *= scale; - scale_y *= scale; - } - return seq; - }; - - function post(seq) { - var min_neighbors = this.shared.min_neighbors; - var cascade = this.shared.cascade; - var interval = this.shared.interval; - var scale = this.shared.scale; - var next = this.shared.next; - var scale_upto = this.shared.scale_upto; - var i, j; - for (i = 0; i < cascade.stage_classifier.length; i++) - cascade.stage_classifier[i].feature = cascade.stage_classifier[i].orig_feature; - seq = seq[0]; - if (!(min_neighbors > 0)) - return seq; - else { - var result = ccv.array_group(seq, function (r1, r2) { - var distance = Math.floor(r1.width * 0.25 + 0.5); - - return r2.x <= r1.x + distance && - r2.x >= r1.x - distance && - r2.y <= r1.y + distance && - r2.y >= r1.y - distance && - r2.width <= Math.floor(r1.width * 1.5 + 0.5) && - Math.floor(r2.width * 1.5 + 0.5) >= r1.width; - }); - var ncomp = result.cat; - var idx_seq = result.index; - var comps = new Array(ncomp + 1); - for (i = 0; i < comps.length; i++) - comps[i] = {"neighbors" : 0, - "x" : 0, - "y" : 0, - "width" : 0, - "height" : 0, - "confidence" : 0}; - - // count number of neighbors - for(i = 0; i < seq.length; i++) - { - var r1 = seq[i]; - var idx = idx_seq[i]; - - if (comps[idx].neighbors == 0) - comps[idx].confidence = r1.confidence; - - ++comps[idx].neighbors; - - comps[idx].x += r1.x; - comps[idx].y += r1.y; - comps[idx].width += r1.width; - comps[idx].height += r1.height; - comps[idx].confidence = Math.max(comps[idx].confidence, r1.confidence); - } - - var seq2 = []; - // calculate average bounding box - for(i = 0; i < ncomp; i++) - { - var n = comps[i].neighbors; - if (n >= min_neighbors) - seq2.push({"x" : (comps[i].x * 2 + n) / (2 * n), - "y" : (comps[i].y * 2 + n) / (2 * n), - "width" : (comps[i].width * 2 + n) / (2 * n), - "height" : (comps[i].height * 2 + n) / (2 * n), - "neighbors" : comps[i].neighbors, - "confidence" : comps[i].confidence}); - } - - var result_seq = []; - // filter out small face rectangles inside large face rectangles - for(i = 0; i < seq2.length; i++) - { - var r1 = seq2[i]; - var flag = true; - for(j = 0; j < seq2.length; j++) - { - var r2 = seq2[j]; - var distance = Math.floor(r2.width * 0.25 + 0.5); - - if(i != j && - r1.x >= r2.x - distance && - r1.y >= r2.y - distance && - r1.x + r1.width <= r2.x + r2.width + distance && - r1.y + r1.height <= r2.y + r2.height + distance && - (r2.neighbors > Math.max(3, r1.neighbors) || r1.neighbors < 3)) - { - flag = false; - break; - } - } - - if(flag) - result_seq.push(r1); - } - return result_seq; - } - }; - return { "pre" : pre, "core" : core, "post" : post }; - }) -} - -onmessage = function (event) { - var data = (typeof event.data == "string") ? JSON.parse(event.data) : event.data; - var scope = { "shared" : data.shared }; - var result = parallable.core[data.name].apply(scope, [data.input, data.id, data.worker]); - try { - postMessage(result); - } catch (e) { - postMessage(JSON.stringify(result)); - } -} diff --git a/samples/js/demos/js/demosite/app.yaml b/samples/js/demos/js/demosite/app.yaml deleted file mode 100644 index e1f043bcd..000000000 --- a/samples/js/demos/js/demosite/app.yaml +++ /dev/null @@ -1,14 +0,0 @@ -application: webrtc-demos -version: 1 -runtime: python27 -api_version: 1 -threadsafe: yes - -handlers: -- url: .* - script: main.app - secure: always - -libraries: -- name: webapp2 - version: "2.5.1" diff --git a/samples/js/demos/js/demosite/favicon.ico b/samples/js/demos/js/demosite/favicon.ico deleted file mode 100644 index 0062ab413..000000000 Binary files a/samples/js/demos/js/demosite/favicon.ico and /dev/null differ diff --git a/samples/js/demos/js/demosite/main.py b/samples/js/demos/js/demosite/main.py deleted file mode 100644 index 1ee3710f9..000000000 --- a/samples/js/demos/js/demosite/main.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import webapp2 - -class PageHandler(webapp2.RequestHandler): - def get(self): - base_url = self.request.path - if self.request.path == '/': - self.redirect("http://webrtc.googlecode.com/svn/trunk/" - + "samples/js/demos/index.html" - , permanent=True) - else: - self.redirect("http://webrtc.googlecode.com/svn/trunk/" - + "samples/js/demos" - + base_url, - permanent=True) - -app = webapp2.WSGIApplication([ - (r'/*.*', PageHandler), -], debug=True) - diff --git a/samples/js/demos/js/face.js b/samples/js/demos/js/face.js deleted file mode 100644 index 0054bde2f..000000000 --- a/samples/js/demos/js/face.js +++ /dev/null @@ -1 +0,0 @@ -var cascade = {"count" : 16, "width" : 24, "height" : 24, "stage_classifier" : [{"count":4,"threshold":-4.577530e+00,"feature":[{"size":4,"px":[3,5,8,11],"py":[2,2,6,3],"pz":[2,1,1,0],"nx":[8,4,0,0],"ny":[4,4,0,0],"nz":[1,1,-1,-1]},{"size":3,"px":[3,6,7],"py":[7,13,0],"pz":[1,0,-1],"nx":[2,3,4],"ny":[5,4,4],"nz":[2,1,1]},{"size":5,"px":[5,3,10,13,11],"py":[1,0,3,2,2],"pz":[1,2,0,0,0],"nx":[0,11,0,11,11],"ny":[0,2,3,1,1],"nz":[1,1,0,1,-1]},{"size":5,"px":[6,12,12,9,12],"py":[4,13,12,7,11],"pz":[1,0,0,1,0],"nx":[8,0,8,2,11],"ny":[4,0,8,5,1],"nz":[1,-1,-1,-1,-1]}],"alpha":[-2.879683e+00,2.879683e+00,-1.569341e+00,1.569341e+00,-1.286131e+00,1.286131e+00,-1.157626e+00,1.157626e+00]},{"count":4,"threshold":-4.339908e+00,"feature":[{"size":5,"px":[13,12,3,11,17],"py":[3,3,1,4,13],"pz":[0,0,2,0,0],"nx":[4,3,8,15,15],"ny":[4,5,4,8,8],"nz":[1,2,1,0,-1]},{"size":5,"px":[6,7,6,3,3],"py":[13,13,4,2,7],"pz":[0,0,1,2,1],"nx":[4,8,3,0,15],"ny":[4,4,4,3,8],"nz":[1,1,-1,-1,-1]},{"size":3,"px":[2,2,11],"py":[3,2,5],"pz":[2,2,0],"nx":[3,8,3],"ny":[4,4,4],"nz":[1,-1,-1]},{"size":5,"px":[15,13,9,11,7],"py":[2,1,2,1,0],"pz":[0,0,0,0,1],"nx":[23,11,23,22,23],"ny":[1,0,2,0,0],"nz":[0,1,0,0,0]}],"alpha":[-2.466029e+00,2.466029e+00,-1.839510e+00,1.839510e+00,-1.060559e+00,1.060559e+00,-1.094927e+00,1.094927e+00]},{"count":7,"threshold":-5.052474e+00,"feature":[{"size":5,"px":[17,13,3,11,10],"py":[13,2,1,4,3],"pz":[0,0,2,0,0],"nx":[4,8,8,3,7],"ny":[2,8,4,5,4],"nz":[2,0,1,2,1]},{"size":5,"px":[6,7,3,6,6],"py":[4,12,2,13,14],"pz":[1,0,2,0,0],"nx":[8,3,4,4,3],"ny":[4,4,2,0,2],"nz":[1,1,-1,-1,-1]},{"size":5,"px":[7,4,5,3,3],"py":[2,1,3,1,1],"pz":[0,1,0,1,-1],"nx":[1,0,1,1,0],"ny":[1,3,2,0,4],"nz":[0,0,0,0,0]},{"size":5,"px":[11,11,11,3,2],"py":[11,13,10,7,2],"pz":[0,0,0,1,2],"nx":[4,1,8,2,0],"ny":[4,1,12,0,4],"nz":[1,-1,-1,-1,-1]},{"size":3,"px":[9,13,1],"py":[7,19,4],"pz":[1,-1,-1],"nx":[4,7,4],"ny":[5,8,2],"nz":[2,1,2]},{"size":5,"px":[12,8,16,4,4],"py":[12,1,2,0,0],"pz":[0,1,0,2,-1],"nx":[11,22,11,23,23],"ny":[2,0,1,1,5],"nz":[1,0,1,0,0]},{"size":3,"px":[11,17,17],"py":[6,11,12],"pz":[0,0,0],"nx":[15,1,11],"ny":[9,1,1],"nz":[0,-1,-1]}],"alpha":[-2.156890e+00,2.156890e+00,-1.718246e+00,1.718246e+00,-9.651329e-01,9.651329e-01,-9.948090e-01,9.948090e-01,-8.802466e-01,8.802466e-01,-8.486741e-01,8.486741e-01,-8.141777e-01,8.141777e-01]},{"count":13,"threshold":-5.774400e+00,"feature":[{"size":5,"px":[6,10,3,12,14],"py":[5,3,1,2,2],"pz":[1,0,2,0,0],"nx":[3,4,14,8,4],"ny":[5,4,8,4,2],"nz":[2,1,0,1,2]},{"size":5,"px":[10,6,11,5,12],"py":[4,13,4,2,4],"pz":[0,0,0,1,0],"nx":[1,4,8,1,1],"ny":[2,4,4,4,3],"nz":[0,1,1,0,0]},{"size":3,"px":[18,6,12],"py":[12,4,8],"pz":[0,1,0],"nx":[7,4,8],"ny":[4,2,4],"nz":[1,-1,-1]},{"size":5,"px":[7,5,6,3,17],"py":[13,12,3,8,13],"pz":[0,0,1,1,0],"nx":[3,3,0,1,8],"ny":[4,5,5,10,4],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[16,7,16,7,7],"py":[1,1,2,0,0],"pz":[0,1,0,1,-1],"nx":[23,23,23,11,5],"ny":[2,14,1,2,1],"nz":[0,0,0,1,2]},{"size":3,"px":[9,18,16],"py":[7,14,2],"pz":[1,0,-1],"nx":[8,4,9],"ny":[10,2,4],"nz":[1,2,1]},{"size":4,"px":[3,16,1,22],"py":[7,4,5,11],"pz":[1,-1,-1,-1],"nx":[3,9,4,2],"ny":[4,9,7,5],"nz":[1,0,1,2]},{"size":5,"px":[4,7,8,8,9],"py":[0,2,2,1,1],"pz":[1,0,0,0,0],"nx":[0,0,1,0,0],"ny":[15,16,19,0,14],"nz":[0,0,0,1,0]},{"size":5,"px":[4,4,7,8,12],"py":[2,5,6,7,10],"pz":[2,2,1,1,0],"nx":[8,5,10,0,0],"ny":[4,2,5,3,14],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[11,0],"py":[13,4],"pz":[0,-1],"nx":[3,14],"ny":[4,16],"nz":[1,0]},{"size":5,"px":[17,8,18,4,4],"py":[3,1,3,0,0],"pz":[0,1,0,2,-1],"nx":[21,22,5,11,22],"ny":[0,1,0,1,2],"nz":[0,0,2,1,0]},{"size":4,"px":[7,8,2,11],"py":[13,12,2,7],"pz":[0,0,2,0],"nx":[4,0,23,3],"ny":[4,1,1,11],"nz":[1,-1,-1,-1]},{"size":5,"px":[4,18,8,9,15],"py":[4,16,7,7,23],"pz":[2,0,1,1,0],"nx":[0,1,1,1,1],"ny":[10,21,23,22,22],"nz":[1,0,0,0,-1]}],"alpha":[-1.956565e+00,1.956565e+00,-1.262438e+00,1.262438e+00,-1.056941e+00,1.056941e+00,-9.712509e-01,9.712509e-01,-8.261028e-01,8.261028e-01,-8.456506e-01,8.456506e-01,-6.652113e-01,6.652113e-01,-6.026287e-01,6.026287e-01,-6.915425e-01,6.915425e-01,-5.539286e-01,5.539286e-01,-5.515072e-01,5.515072e-01,-6.685884e-01,6.685884e-01,-4.656070e-01,4.656070e-01]},{"count":20,"threshold":-5.606853e+00,"feature":[{"size":5,"px":[17,11,6,14,9],"py":[13,4,4,3,3],"pz":[0,0,1,0,0],"nx":[14,4,8,7,8],"ny":[8,4,4,4,8],"nz":[0,1,1,1,0]},{"size":5,"px":[3,9,10,11,11],"py":[7,2,2,3,3],"pz":[1,0,0,0,-1],"nx":[3,8,4,2,5],"ny":[4,4,10,2,8],"nz":[1,1,1,2,1]},{"size":5,"px":[12,12,12,5,12],"py":[12,9,10,12,11],"pz":[0,0,0,0,0],"nx":[0,0,0,0,0],"ny":[2,1,3,0,0],"nz":[0,0,0,0,-1]},{"size":5,"px":[9,18,9,9,12],"py":[7,14,19,5,11],"pz":[1,-1,-1,-1,-1],"nx":[23,4,23,23,8],"ny":[13,5,14,16,4],"nz":[0,2,0,0,1]},{"size":5,"px":[12,12,12,6,1],"py":[13,11,12,6,5],"pz":[0,0,0,-1,-1],"nx":[4,6,8,4,9],"ny":[2,8,4,4,4],"nz":[2,1,1,1,1]},{"size":4,"px":[12,11,11,6],"py":[5,5,6,13],"pz":[0,0,0,0],"nx":[8,3,2,8],"ny":[4,4,17,2],"nz":[1,1,-1,-1]},{"size":5,"px":[3,14,12,15,13],"py":[0,2,2,2,2],"pz":[2,0,0,0,0],"nx":[22,23,22,23,7],"ny":[0,3,1,2,4],"nz":[0,0,0,0,1]},{"size":5,"px":[16,15,18,19,9],"py":[12,11,12,12,9],"pz":[0,0,0,0,1],"nx":[8,2,22,23,21],"ny":[4,1,1,2,20],"nz":[1,-1,-1,-1,-1]},{"size":3,"px":[4,7,7],"py":[0,2,2],"pz":[1,0,-1],"nx":[1,2,2],"ny":[2,0,2],"nz":[1,0,0]},{"size":3,"px":[4,11,11],"py":[6,9,8],"pz":[1,0,0],"nx":[9,2,8],"ny":[9,4,5],"nz":[0,-1,-1]},{"size":4,"px":[2,7,6,6],"py":[4,23,21,22],"pz":[2,0,0,0],"nx":[9,3,8,17],"ny":[21,2,5,1],"nz":[0,-1,-1,-1]},{"size":2,"px":[2,8],"py":[4,12],"pz":[2,0],"nx":[3,0],"ny":[4,4],"nz":[1,-1]},{"size":5,"px":[4,5,1,8,4],"py":[15,12,3,23,12],"pz":[0,0,2,0,0],"nx":[0,0,0,0,0],"ny":[23,10,22,21,11],"nz":[0,1,0,0,-1]},{"size":2,"px":[21,5],"py":[13,4],"pz":[0,2],"nx":[23,4],"ny":[23,5],"nz":[0,-1]},{"size":2,"px":[15,17],"py":[2,3],"pz":[0,0],"nx":[19,20],"ny":[2,1],"nz":[0,0]},{"size":5,"px":[12,1,8,17,4],"py":[14,2,13,6,12],"pz":[0,-1,-1,-1,-1],"nx":[8,13,15,15,7],"ny":[10,9,15,14,8],"nz":[1,0,0,0,1]},{"size":2,"px":[8,5],"py":[7,4],"pz":[1,-1],"nx":[4,13],"ny":[2,21],"nz":[2,0]},{"size":2,"px":[3,4],"py":[7,0],"pz":[1,-1],"nx":[4,2],"ny":[7,5],"nz":[1,2]},{"size":4,"px":[4,14,3,11],"py":[3,23,2,5],"pz":[2,0,2,0],"nx":[7,8,2,16],"ny":[8,0,1,15],"nz":[0,-1,-1,-1]},{"size":2,"px":[9,8],"py":[0,0],"pz":[0,0],"nx":[2,2],"ny":[3,5],"nz":[2,2]}],"alpha":[-1.957970e+00,1.957970e+00,-1.225984e+00,1.225984e+00,-8.310246e-01,8.310246e-01,-8.315741e-01,8.315741e-01,-7.973616e-01,7.973616e-01,-7.661959e-01,7.661959e-01,-6.042118e-01,6.042118e-01,-6.506833e-01,6.506833e-01,-4.808219e-01,4.808219e-01,-6.079504e-01,6.079504e-01,-5.163994e-01,5.163994e-01,-5.268142e-01,5.268142e-01,-4.935685e-01,4.935685e-01,-4.427544e-01,4.427544e-01,-4.053949e-01,4.053949e-01,-4.701274e-01,4.701274e-01,-4.387648e-01,4.387648e-01,-4.305499e-01,4.305499e-01,-4.042607e-01,4.042607e-01,-4.372088e-01,4.372088e-01]},{"count":22,"threshold":-5.679317e+00,"feature":[{"size":5,"px":[11,3,17,14,13],"py":[4,0,13,2,3],"pz":[0,2,0,0,0],"nx":[7,4,14,23,11],"ny":[8,4,8,4,0],"nz":[1,1,0,0,1]},{"size":5,"px":[7,12,6,12,12],"py":[12,8,3,10,9],"pz":[0,0,1,0,0],"nx":[4,9,8,15,15],"ny":[4,8,4,8,8],"nz":[1,0,1,0,-1]},{"size":3,"px":[4,2,10],"py":[1,4,1],"pz":[1,2,0],"nx":[2,3,8],"ny":[5,4,4],"nz":[2,1,-1]},{"size":5,"px":[3,17,6,6,16],"py":[2,12,4,14,12],"pz":[2,0,1,0,0],"nx":[8,3,7,5,15],"ny":[4,4,4,4,8],"nz":[1,1,-1,-1,-1]},{"size":5,"px":[5,6,7,4,8],"py":[3,3,3,1,3],"pz":[0,0,0,1,0],"nx":[0,0,0,0,1],"ny":[5,4,3,2,0],"nz":[0,0,0,0,0]},{"size":3,"px":[18,9,0],"py":[14,7,0],"pz":[0,1,-1],"nx":[8,14,8],"ny":[10,9,4],"nz":[1,0,1]},{"size":2,"px":[9,5],"py":[18,13],"pz":[0,0],"nx":[10,3],"ny":[16,4],"nz":[0,-1]},{"size":5,"px":[11,11,11,11,6],"py":[10,12,11,13,6],"pz":[0,0,0,0,-1],"nx":[5,21,22,22,22],"ny":[4,22,17,19,18],"nz":[2,0,0,0,0]},{"size":4,"px":[8,9,15,4],"py":[7,7,23,4],"pz":[1,1,0,2],"nx":[8,5,0,3],"ny":[4,18,4,9],"nz":[1,-1,-1,-1]},{"size":5,"px":[11,10,12,11,11],"py":[4,4,4,5,5],"pz":[0,0,0,0,-1],"nx":[4,6,8,2,8],"ny":[4,9,9,2,4],"nz":[1,1,0,2,1]},{"size":5,"px":[2,2,3,3,4],"py":[10,9,14,13,15],"pz":[1,1,0,0,0],"nx":[0,0,0,0,0],"ny":[5,9,10,19,18],"nz":[2,1,1,0,-1]},{"size":2,"px":[11,11],"py":[13,12],"pz":[0,0],"nx":[9,2],"ny":[15,2],"nz":[0,-1]},{"size":5,"px":[2,4,3,3,4],"py":[5,11,6,9,12],"pz":[1,0,1,0,0],"nx":[6,2,11,11,0],"ny":[9,1,5,20,18],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[18,9,17,19,16],"py":[2,0,2,2,1],"pz":[0,1,0,0,0],"nx":[22,23,11,23,23],"ny":[0,2,0,1,1],"nz":[0,0,1,0,-1]},{"size":5,"px":[5,5,6,7,6],"py":[17,16,15,23,22],"pz":[0,0,0,0,0],"nx":[7,6,2,5,23],"ny":[8,1,2,3,1],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[12,12,11,10,6],"py":[14,13,18,4,22],"pz":[0,-1,-1,-1,-1],"nx":[3,2,4,1,2],"ny":[19,4,23,13,16],"nz":[0,0,0,0,0]},{"size":4,"px":[11,16,11,17],"py":[7,11,8,12],"pz":[0,0,0,0],"nx":[7,14,10,4],"ny":[4,7,10,4],"nz":[1,0,-1,-1]},{"size":2,"px":[3,3],"py":[8,7],"pz":[1,1],"nx":[4,2],"ny":[10,2],"nz":[1,-1]},{"size":2,"px":[3,9],"py":[0,1],"pz":[1,0],"nx":[4,5],"ny":[1,0],"nz":[0,0]},{"size":2,"px":[14,16],"py":[3,3],"pz":[0,0],"nx":[9,14],"ny":[4,21],"nz":[1,0]},{"size":2,"px":[9,1],"py":[7,1],"pz":[1,-1],"nx":[8,9],"ny":[7,4],"nz":[1,1]},{"size":2,"px":[1,0],"py":[8,3],"pz":[0,2],"nx":[20,0],"ny":[3,3],"nz":[0,-1]}],"alpha":[-1.581077e+00,1.581077e+00,-1.389689e+00,1.389689e+00,-8.733094e-01,8.733094e-01,-8.525177e-01,8.525177e-01,-7.416304e-01,7.416304e-01,-6.609002e-01,6.609002e-01,-7.119043e-01,7.119043e-01,-6.204438e-01,6.204438e-01,-6.638519e-01,6.638519e-01,-5.518876e-01,5.518876e-01,-4.898991e-01,4.898991e-01,-5.508243e-01,5.508243e-01,-4.635525e-01,4.635525e-01,-5.163159e-01,5.163159e-01,-4.495338e-01,4.495338e-01,-4.515036e-01,4.515036e-01,-5.130473e-01,5.130473e-01,-4.694233e-01,4.694233e-01,-4.022514e-01,4.022514e-01,-4.055690e-01,4.055690e-01,-4.151817e-01,4.151817e-01,-3.352302e-01,3.352302e-01]},{"count":32,"threshold":-5.363782e+00,"feature":[{"size":5,"px":[12,9,6,8,14],"py":[4,2,13,3,3],"pz":[0,0,0,0,0],"nx":[0,15,0,9,5],"ny":[2,7,3,8,8],"nz":[0,0,0,0,1]},{"size":5,"px":[13,16,3,6,11],"py":[3,13,1,4,3],"pz":[0,0,2,1,0],"nx":[7,4,8,14,14],"ny":[4,4,4,8,8],"nz":[1,1,1,0,-1]},{"size":5,"px":[10,19,18,19,19],"py":[6,13,13,12,12],"pz":[1,0,0,0,-1],"nx":[23,5,23,23,11],"ny":[12,2,13,14,8],"nz":[0,2,0,0,1]},{"size":5,"px":[12,12,12,12,6],"py":[11,13,12,10,6],"pz":[0,0,0,0,1],"nx":[6,8,3,9,9],"ny":[8,4,4,4,4],"nz":[1,1,1,1,-1]},{"size":5,"px":[5,3,5,8,11],"py":[12,8,3,11,8],"pz":[0,1,1,0,0],"nx":[4,0,1,1,9],"ny":[4,3,4,3,4],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[13,3,12,14,12],"py":[1,0,1,2,3],"pz":[0,2,0,0,0],"nx":[7,9,8,4,4],"ny":[5,4,10,2,2],"nz":[1,1,1,2,-1]},{"size":5,"px":[18,16,12,15,8],"py":[12,23,7,11,8],"pz":[0,0,0,0,1],"nx":[8,6,10,12,4],"ny":[4,4,10,6,3],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[4,4,5,2,2],"py":[13,14,14,7,7],"pz":[0,0,0,1,-1],"nx":[0,0,0,0,1],"ny":[15,4,14,13,17],"nz":[0,2,0,0,0]},{"size":2,"px":[9,9],"py":[7,7],"pz":[1,-1],"nx":[4,7],"ny":[5,8],"nz":[2,1]},{"size":5,"px":[3,4,6,5,4],"py":[2,2,14,6,9],"pz":[1,1,0,1,1],"nx":[23,23,23,23,11],"ny":[0,3,2,1,0],"nz":[0,0,0,0,-1]},{"size":3,"px":[10,2,3],"py":[23,4,7],"pz":[0,2,1],"nx":[10,21,23],"ny":[21,9,2],"nz":[0,-1,-1]},{"size":5,"px":[20,21,21,10,12],"py":[13,12,8,8,12],"pz":[0,0,0,1,0],"nx":[8,16,3,3,11],"ny":[4,8,4,3,0],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[2,21],"py":[4,12],"pz":[2,-1],"nx":[2,3],"ny":[5,4],"nz":[2,1]},{"size":5,"px":[8,5,6,8,7],"py":[0,2,1,1,1],"pz":[0,0,0,0,0],"nx":[3,2,2,2,2],"ny":[0,0,1,2,2],"nz":[0,0,0,0,-1]},{"size":5,"px":[11,2,2,11,10],"py":[10,12,8,11,12],"pz":[0,0,0,0,0],"nx":[3,5,2,4,2],"ny":[4,1,4,2,2],"nz":[1,-1,-1,-1,-1]},{"size":4,"px":[15,16,8,17],"py":[2,1,0,2],"pz":[0,0,1,0],"nx":[19,20,0,8],"ny":[1,2,11,10],"nz":[0,0,-1,-1]},{"size":2,"px":[17,16],"py":[12,12],"pz":[0,0],"nx":[8,9],"ny":[5,1],"nz":[1,-1]},{"size":4,"px":[11,11,0,0],"py":[12,13,0,0],"pz":[0,0,-1,-1],"nx":[10,10,9,10],"ny":[10,12,13,11],"nz":[0,0,0,0]},{"size":3,"px":[11,10,8],"py":[5,2,6],"pz":[0,-1,-1],"nx":[8,12,4],"ny":[4,17,4],"nz":[1,0,1]},{"size":5,"px":[10,21,10,20,20],"py":[11,13,7,13,14],"pz":[1,0,1,0,0],"nx":[23,23,11,23,17],"ny":[23,22,11,21,21],"nz":[0,0,1,-1,-1]},{"size":2,"px":[4,7],"py":[3,9],"pz":[2,1],"nx":[9,23],"ny":[4,22],"nz":[1,-1]},{"size":4,"px":[3,2,2,5],"py":[11,5,4,20],"pz":[1,2,2,0],"nx":[4,23,11,23],"ny":[10,22,11,21],"nz":[1,-1,-1,-1]},{"size":2,"px":[7,5],"py":[13,4],"pz":[0,-1],"nx":[4,4],"ny":[8,6],"nz":[1,1]},{"size":2,"px":[2,5],"py":[4,9],"pz":[2,1],"nx":[10,10],"ny":[16,16],"nz":[0,-1]},{"size":2,"px":[4,2],"py":[6,3],"pz":[1,2],"nx":[3,0],"ny":[4,0],"nz":[1,-1]},{"size":5,"px":[7,3,12,13,6],"py":[11,5,23,23,7],"pz":[1,2,0,0,1],"nx":[1,0,0,0,0],"ny":[23,20,19,21,21],"nz":[0,0,0,0,-1]},{"size":5,"px":[0,0,0,0,0],"py":[10,9,6,13,13],"pz":[0,0,1,0,-1],"nx":[8,8,4,4,9],"ny":[4,11,5,4,5],"nz":[1,1,2,2,1]},{"size":2,"px":[9,18],"py":[8,15],"pz":[1,0],"nx":[15,4],"ny":[15,2],"nz":[0,-1]},{"size":2,"px":[5,13],"py":[6,17],"pz":[1,-1],"nx":[1,2],"ny":[2,4],"nz":[2,1]},{"size":5,"px":[19,10,20,18,18],"py":[2,0,2,2,2],"pz":[0,1,0,0,-1],"nx":[22,23,22,11,23],"ny":[1,3,0,1,2],"nz":[0,0,0,1,0]},{"size":5,"px":[4,2,2,2,6],"py":[7,2,5,4,14],"pz":[1,2,2,2,0],"nx":[16,7,9,15,23],"ny":[8,0,3,11,2],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[10,10,9,9,5],"py":[2,0,0,1,0],"pz":[0,0,0,0,1],"nx":[3,2,3,2,2],"ny":[11,3,9,5,5],"nz":[1,2,1,2,-1]}],"alpha":[-1.490426e+00,1.490426e+00,-1.214280e+00,1.214280e+00,-8.124863e-01,8.124863e-01,-7.307594e-01,7.307594e-01,-7.377259e-01,7.377259e-01,-5.982859e-01,5.982859e-01,-6.451736e-01,6.451736e-01,-6.117417e-01,6.117417e-01,-5.438949e-01,5.438949e-01,-4.563701e-01,4.563701e-01,-4.975362e-01,4.975362e-01,-4.707373e-01,4.707373e-01,-5.013868e-01,5.013868e-01,-5.139018e-01,5.139018e-01,-4.728007e-01,4.728007e-01,-4.839748e-01,4.839748e-01,-4.852528e-01,4.852528e-01,-5.768956e-01,5.768956e-01,-3.635091e-01,3.635091e-01,-4.190090e-01,4.190090e-01,-3.854715e-01,3.854715e-01,-3.409591e-01,3.409591e-01,-3.440222e-01,3.440222e-01,-3.375895e-01,3.375895e-01,-3.367032e-01,3.367032e-01,-3.708106e-01,3.708106e-01,-3.260956e-01,3.260956e-01,-3.657681e-01,3.657681e-01,-3.518800e-01,3.518800e-01,-3.845758e-01,3.845758e-01,-2.832236e-01,2.832236e-01,-2.865156e-01,2.865156e-01]},{"count":45,"threshold":-5.479836e+00,"feature":[{"size":5,"px":[15,6,17,6,9],"py":[2,13,13,4,3],"pz":[0,0,0,1,0],"nx":[3,9,4,8,14],"ny":[5,8,4,4,8],"nz":[2,0,1,1,0]},{"size":5,"px":[9,8,11,6,7],"py":[1,2,3,14,2],"pz":[0,0,0,0,0],"nx":[0,0,4,0,0],"ny":[4,2,4,1,0],"nz":[0,0,1,0,0]},{"size":5,"px":[2,2,11,11,11],"py":[2,4,10,8,6],"pz":[2,2,0,0,0],"nx":[8,4,3,23,23],"ny":[4,4,4,16,18],"nz":[1,1,-1,-1,-1]},{"size":5,"px":[18,16,17,15,9],"py":[2,2,2,2,1],"pz":[0,0,0,0,1],"nx":[22,22,21,23,23],"ny":[1,2,0,5,4],"nz":[0,0,0,0,0]},{"size":5,"px":[15,3,17,18,6],"py":[11,2,11,11,4],"pz":[0,2,0,0,1],"nx":[3,8,1,4,23],"ny":[4,4,3,9,4],"nz":[1,1,-1,-1,-1]},{"size":2,"px":[4,5],"py":[4,0],"pz":[2,-1],"nx":[7,4],"ny":[8,5],"nz":[1,2]},{"size":2,"px":[11,5],"py":[12,5],"pz":[0,-1],"nx":[4,9],"ny":[10,15],"nz":[1,0]},{"size":4,"px":[2,2,7,1],"py":[7,7,3,4],"pz":[1,-1,-1,-1],"nx":[0,2,1,2],"ny":[6,20,14,16],"nz":[1,0,0,0]},{"size":5,"px":[14,12,12,13,9],"py":[23,5,6,5,7],"pz":[0,0,0,0,1],"nx":[8,18,2,8,14],"ny":[4,9,0,12,7],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[3,10,13,11,9],"py":[0,3,2,3,2],"pz":[2,0,0,0,0],"nx":[3,11,22,22,22],"ny":[2,6,15,2,0],"nz":[2,1,0,0,0]},{"size":5,"px":[8,7,5,8,5],"py":[23,12,12,12,13],"pz":[0,0,0,0,0],"nx":[3,18,3,1,22],"ny":[4,4,4,2,0],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[22,22,22,21,22],"py":[9,11,10,14,12],"pz":[0,0,0,0,0],"nx":[23,23,11,1,22],"ny":[23,23,11,2,0],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[9,3],"py":[18,7],"pz":[0,1],"nx":[10,8],"ny":[16,19],"nz":[0,-1]},{"size":5,"px":[10,12,11,6,6],"py":[4,4,4,2,2],"pz":[0,0,0,1,-1],"nx":[3,8,7,8,4],"ny":[5,4,4,10,4],"nz":[2,1,1,0,1]},{"size":4,"px":[12,12,4,15],"py":[13,12,0,11],"pz":[0,0,-1,-1],"nx":[13,14,13,14],"ny":[9,12,10,13],"nz":[0,0,0,0]},{"size":2,"px":[4,4],"py":[3,3],"pz":[2,-1],"nx":[9,4],"ny":[4,2],"nz":[1,2]},{"size":3,"px":[9,7,0],"py":[7,5,5],"pz":[1,-1,-1],"nx":[4,15,9],"ny":[5,14,9],"nz":[2,0,1]},{"size":5,"px":[15,20,7,10,16],"py":[17,12,6,4,23],"pz":[0,0,1,1,0],"nx":[1,2,2,1,1],"ny":[3,0,1,2,2],"nz":[0,0,0,0,-1]},{"size":5,"px":[2,1,1,11,2],"py":[16,4,5,12,14],"pz":[0,1,1,0,0],"nx":[4,6,3,19,1],"ny":[4,2,5,19,2],"nz":[1,-1,-1,-1,-1]},{"size":3,"px":[15,14,14],"py":[1,1,0],"pz":[0,0,0],"nx":[4,8,4],"ny":[3,4,2],"nz":[2,1,2]},{"size":5,"px":[2,3,1,2,7],"py":[8,12,4,9,13],"pz":[1,0,2,1,0],"nx":[1,1,0,0,0],"ny":[21,20,18,17,9],"nz":[0,0,0,0,1]},{"size":5,"px":[17,15,17,16,16],"py":[12,12,22,23,12],"pz":[0,0,0,0,0],"nx":[7,3,16,1,0],"ny":[8,6,8,3,9],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[9,17,18,18,18],"py":[6,12,12,13,13],"pz":[1,0,0,0,-1],"nx":[23,23,20,11,11],"ny":[12,13,23,7,8],"nz":[0,0,0,1,1]},{"size":2,"px":[2,4],"py":[4,7],"pz":[2,1],"nx":[4,4],"ny":[10,5],"nz":[1,-1]},{"size":4,"px":[4,22,19,12],"py":[5,8,14,9],"pz":[2,0,0,0],"nx":[8,4,4,2],"ny":[4,4,1,2],"nz":[1,-1,-1,-1]},{"size":2,"px":[3,21],"py":[7,14],"pz":[1,-1],"nx":[4,2],"ny":[7,2],"nz":[1,2]},{"size":3,"px":[7,4,17],"py":[3,1,6],"pz":[0,1,-1],"nx":[3,4,5],"ny":[0,2,1],"nz":[1,0,0]},{"size":4,"px":[15,7,14,0],"py":[3,1,3,7],"pz":[0,1,0,-1],"nx":[8,18,17,18],"ny":[0,1,1,2],"nz":[1,0,0,0]},{"size":5,"px":[12,12,12,12,6],"py":[10,11,12,13,6],"pz":[0,0,0,0,-1],"nx":[8,15,15,4,8],"ny":[10,10,9,2,4],"nz":[0,0,0,2,1]},{"size":2,"px":[17,12],"py":[13,11],"pz":[0,-1],"nx":[9,8],"ny":[4,10],"nz":[1,1]},{"size":5,"px":[0,0,0,0,0],"py":[10,9,12,11,4],"pz":[0,0,0,0,1],"nx":[8,9,8,9,9],"ny":[10,4,4,5,5],"nz":[1,1,1,1,-1]},{"size":3,"px":[7,0,1],"py":[1,9,8],"pz":[0,-1,-1],"nx":[4,3,3],"ny":[7,15,16],"nz":[0,0,0]},{"size":2,"px":[4,7],"py":[15,23],"pz":[0,0],"nx":[9,18],"ny":[21,3],"nz":[0,-1]},{"size":5,"px":[17,4,19,18,8],"py":[12,3,12,17,6],"pz":[0,2,0,0,1],"nx":[23,23,11,22,16],"ny":[0,1,0,21,-1],"nz":[0,0,-1,-1,-1]},{"size":2,"px":[7,4],"py":[13,5],"pz":[0,-1],"nx":[4,2],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[21,20,10,10,21],"py":[13,14,10,7,11],"pz":[0,0,1,1,0],"nx":[4,4,4,5,5],"ny":[18,17,19,20,20],"nz":[0,0,0,0,-1]},{"size":2,"px":[2,3],"py":[11,13],"pz":[1,0],"nx":[12,4],"ny":[17,17],"nz":[0,-1]},{"size":2,"px":[11,5],"py":[13,1],"pz":[0,-1],"nx":[1,2],"ny":[1,4],"nz":[2,1]},{"size":2,"px":[15,7],"py":[17,7],"pz":[0,1],"nx":[14,4],"ny":[15,3],"nz":[0,-1]},{"size":2,"px":[3,11],"py":[3,8],"pz":[2,0],"nx":[13,13],"ny":[9,8],"nz":[0,0]},{"size":2,"px":[8,3],"py":[11,2],"pz":[0,-1],"nx":[8,4],"ny":[9,5],"nz":[0,1]},{"size":3,"px":[12,6,9],"py":[9,10,11],"pz":[0,-1,-1],"nx":[2,1,5],"ny":[2,1,6],"nz":[2,2,1]},{"size":4,"px":[4,5,5,1],"py":[11,11,11,3],"pz":[1,0,1,2],"nx":[0,0,5,4],"ny":[23,22,0,0],"nz":[0,0,-1,-1]},{"size":5,"px":[15,7,17,15,16],"py":[1,0,2,2,0],"pz":[0,1,0,0,0],"nx":[7,4,7,4,8],"ny":[5,2,4,3,4],"nz":[1,2,1,2,-1]},{"size":2,"px":[6,12],"py":[11,23],"pz":[1,0],"nx":[12,4],"ny":[21,2],"nz":[0,-1]}],"alpha":[-1.535800e+00,1.535800e+00,-8.580514e-01,8.580514e-01,-8.625210e-01,8.625210e-01,-7.177500e-01,7.177500e-01,-6.832222e-01,6.832222e-01,-5.736298e-01,5.736298e-01,-5.028217e-01,5.028217e-01,-5.091788e-01,5.091788e-01,-5.791940e-01,5.791940e-01,-4.924942e-01,4.924942e-01,-5.489055e-01,5.489055e-01,-4.528190e-01,4.528190e-01,-4.748324e-01,4.748324e-01,-4.150403e-01,4.150403e-01,-4.820464e-01,4.820464e-01,-4.840212e-01,4.840212e-01,-3.941872e-01,3.941872e-01,-3.663507e-01,3.663507e-01,-3.814835e-01,3.814835e-01,-3.936426e-01,3.936426e-01,-3.049970e-01,3.049970e-01,-3.604256e-01,3.604256e-01,-3.974041e-01,3.974041e-01,-4.203486e-01,4.203486e-01,-3.174435e-01,3.174435e-01,-3.426336e-01,3.426336e-01,-4.492150e-01,4.492150e-01,-3.538784e-01,3.538784e-01,-3.679703e-01,3.679703e-01,-3.985452e-01,3.985452e-01,-2.884028e-01,2.884028e-01,-2.797264e-01,2.797264e-01,-2.664214e-01,2.664214e-01,-2.484857e-01,2.484857e-01,-2.581492e-01,2.581492e-01,-2.943778e-01,2.943778e-01,-2.315507e-01,2.315507e-01,-2.979337e-01,2.979337e-01,-2.976173e-01,2.976173e-01,-2.847965e-01,2.847965e-01,-2.814763e-01,2.814763e-01,-2.489068e-01,2.489068e-01,-2.632427e-01,2.632427e-01,-3.308292e-01,3.308292e-01,-2.790170e-01,2.790170e-01]},{"count":61,"threshold":-5.239104e+00,"feature":[{"size":5,"px":[8,8,11,15,6],"py":[3,6,5,3,4],"pz":[0,1,0,0,1],"nx":[3,9,14,8,4],"ny":[4,8,8,7,2],"nz":[1,0,0,0,2]},{"size":5,"px":[11,12,10,6,9],"py":[3,3,2,13,2],"pz":[0,0,0,0,0],"nx":[0,0,5,2,2],"ny":[13,1,8,5,2],"nz":[0,1,1,2,2]},{"size":5,"px":[11,5,11,11,4],"py":[9,13,10,11,6],"pz":[0,0,0,0,1],"nx":[4,15,9,3,3],"ny":[5,8,9,4,4],"nz":[1,0,0,1,-1]},{"size":5,"px":[15,16,8,17,17],"py":[1,2,0,2,2],"pz":[0,0,1,0,-1],"nx":[23,23,23,23,23],"ny":[4,0,2,3,1],"nz":[0,0,0,0,0]},{"size":4,"px":[9,18,17,18],"py":[7,13,13,14],"pz":[1,0,0,0],"nx":[9,7,4,8],"ny":[4,10,2,4],"nz":[1,1,2,1]},{"size":5,"px":[12,11,12,12,6],"py":[6,5,14,5,3],"pz":[0,0,0,0,1],"nx":[13,8,14,7,7],"ny":[16,4,7,4,4],"nz":[0,1,0,1,-1]},{"size":5,"px":[12,6,3,7,12],"py":[7,12,7,11,8],"pz":[0,0,1,0,0],"nx":[16,4,4,4,7],"ny":[8,4,4,4,4],"nz":[0,1,-1,-1,-1]},{"size":5,"px":[6,4,5,3,3],"py":[2,3,2,0,0],"pz":[0,0,0,1,-1],"nx":[1,0,1,0,0],"ny":[0,3,1,1,2],"nz":[0,0,0,1,0]},{"size":2,"px":[15,9],"py":[11,6],"pz":[0,1],"nx":[14,5],"ny":[9,11],"nz":[0,-1]},{"size":5,"px":[10,19,19,10,20],"py":[7,20,14,6,12],"pz":[1,0,0,1,0],"nx":[23,22,11,23,23],"ny":[21,23,9,20,20],"nz":[0,0,1,0,-1]},{"size":5,"px":[1,1,5,1,1],"py":[8,6,6,9,4],"pz":[0,1,1,0,2],"nx":[3,3,3,2,5],"ny":[4,4,2,5,4],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[13,12,3,11,11],"py":[2,2,0,1,2],"pz":[0,0,2,0,0],"nx":[3,6,8,4,3],"ny":[2,9,4,4,5],"nz":[2,1,1,1,-1]},{"size":3,"px":[12,12,6],"py":[11,12,9],"pz":[0,0,-1],"nx":[2,1,9],"ny":[6,1,14],"nz":[0,2,0]},{"size":5,"px":[6,3,17,16,16],"py":[4,2,14,23,13],"pz":[1,2,0,0,0],"nx":[8,10,21,5,1],"ny":[4,10,11,0,0],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[5,6,1,3,3],"py":[15,14,4,7,7],"pz":[0,0,2,1,-1],"nx":[1,0,0,1,1],"ny":[5,8,7,18,17],"nz":[2,1,1,0,0]},{"size":4,"px":[6,12,5,3],"py":[6,12,2,7],"pz":[1,-1,-1,-1],"nx":[14,13,13,7],"ny":[12,10,9,8],"nz":[0,0,0,1]},{"size":2,"px":[3,6],"py":[7,15],"pz":[1,0],"nx":[3,3],"ny":[4,2],"nz":[1,-1]},{"size":4,"px":[11,10,12,2],"py":[18,18,18,3],"pz":[0,0,0,2],"nx":[11,17,4,16],"ny":[16,4,4,21],"nz":[0,-1,-1,-1]},{"size":5,"px":[9,8,8,5,2],"py":[4,4,4,2,3],"pz":[0,0,-1,-1,-1],"nx":[2,2,4,4,2],"ny":[1,2,10,5,4],"nz":[2,2,1,1,2]},{"size":4,"px":[8,18,14,18],"py":[7,16,23,15],"pz":[1,0,0,0],"nx":[14,3,1,0],"ny":[21,1,9,3],"nz":[0,-1,-1,-1]},{"size":2,"px":[12,3],"py":[9,5],"pz":[0,2],"nx":[8,1],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[9,9],"py":[1,1],"pz":[1,-1],"nx":[19,20],"ny":[1,2],"nz":[0,0]},{"size":3,"px":[10,10,10],"py":[6,6,8],"pz":[1,-1,-1],"nx":[22,21,22],"ny":[13,18,12],"nz":[0,0,0]},{"size":2,"px":[2,2],"py":[4,1],"pz":[2,-1],"nx":[2,4],"ny":[5,4],"nz":[2,1]},{"size":5,"px":[21,21,21,21,21],"py":[19,17,18,15,16],"pz":[0,0,0,0,0],"nx":[11,21,6,1,21],"ny":[17,1,10,0,2],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[7,3,4,4,4],"py":[23,13,14,16,13],"pz":[0,0,0,0,0],"nx":[21,22,22,22,22],"ny":[23,21,20,19,19],"nz":[0,0,0,0,-1]},{"size":2,"px":[11,8],"py":[6,6],"pz":[0,1],"nx":[8,4],"ny":[4,2],"nz":[1,-1]},{"size":5,"px":[23,23,11,23,23],"py":[8,12,6,11,10],"pz":[0,0,1,0,0],"nx":[4,4,3,8,8],"ny":[3,8,4,4,4],"nz":[1,1,1,1,-1]},{"size":5,"px":[8,9,4,7,10],"py":[2,1,0,2,1],"pz":[0,0,1,0,0],"nx":[5,5,6,4,4],"ny":[1,0,0,2,1],"nz":[0,0,0,0,-1]},{"size":2,"px":[12,2],"py":[13,6],"pz":[0,-1],"nx":[15,9],"ny":[15,4],"nz":[0,1]},{"size":2,"px":[2,4],"py":[4,9],"pz":[2,1],"nx":[3,13],"ny":[4,1],"nz":[1,-1]},{"size":3,"px":[3,6,2],"py":[10,22,4],"pz":[1,0,2],"nx":[4,2,1],"ny":[10,4,3],"nz":[1,-1,-1]},{"size":2,"px":[1,0],"py":[9,7],"pz":[0,1],"nx":[0,0],"ny":[23,22],"nz":[0,0]},{"size":2,"px":[8,7],"py":[0,1],"pz":[0,0],"nx":[4,4],"ny":[8,8],"nz":[1,-1]},{"size":5,"px":[7,4,4,6,3],"py":[8,4,5,5,3],"pz":[1,2,2,1,2],"nx":[1,0,2,0,0],"ny":[1,0,0,2,4],"nz":[0,2,0,1,-1]},{"size":3,"px":[10,4,4],"py":[6,1,5],"pz":[1,-1,-1],"nx":[5,23,22],"ny":[4,13,7],"nz":[2,0,0]},{"size":2,"px":[2,2],"py":[6,5],"pz":[1,1],"nx":[6,0],"ny":[9,2],"nz":[0,-1]},{"size":5,"px":[0,1,1,0,0],"py":[5,18,19,16,6],"pz":[2,0,0,0,1],"nx":[5,9,4,8,8],"ny":[8,7,3,7,7],"nz":[1,0,1,0,-1]},{"size":2,"px":[13,12],"py":[23,23],"pz":[0,0],"nx":[7,6],"ny":[8,10],"nz":[0,-1]},{"size":2,"px":[14,19],"py":[12,8],"pz":[0,0],"nx":[18,5],"ny":[8,11],"nz":[0,-1]},{"size":5,"px":[2,8,6,4,4],"py":[3,23,14,6,9],"pz":[2,0,0,1,1],"nx":[0,0,0,0,1],"ny":[21,20,5,19,23],"nz":[0,0,2,0,0]},{"size":2,"px":[11,22],"py":[4,14],"pz":[0,-1],"nx":[3,8],"ny":[1,4],"nz":[2,1]},{"size":5,"px":[1,1,0,1,1],"py":[6,8,3,12,7],"pz":[1,1,2,0,1],"nx":[21,21,19,10,10],"ny":[14,16,23,9,9],"nz":[0,0,0,1,-1]},{"size":2,"px":[10,3],"py":[23,2],"pz":[0,2],"nx":[10,3],"ny":[21,5],"nz":[0,-1]},{"size":2,"px":[9,9],"py":[7,0],"pz":[1,-1],"nx":[9,9],"ny":[11,10],"nz":[1,1]},{"size":5,"px":[23,11,23,23,23],"py":[18,10,19,20,16],"pz":[0,1,0,0,0],"nx":[3,3,2,3,2],"ny":[15,16,10,17,9],"nz":[0,0,1,0,-1]},{"size":2,"px":[9,14],"py":[7,18],"pz":[1,0],"nx":[7,10],"ny":[8,8],"nz":[1,-1]},{"size":2,"px":[12,5],"py":[6,4],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[4,5],"py":[13,4],"pz":[0,-1],"nx":[4,4],"ny":[17,19],"nz":[0,0]},{"size":3,"px":[2,3,3],"py":[11,17,19],"pz":[1,0,0],"nx":[7,7,4],"ny":[8,8,5],"nz":[1,-1,-1]},{"size":2,"px":[6,6],"py":[6,5],"pz":[1,-1],"nx":[2,9],"ny":[4,12],"nz":[1,0]},{"size":5,"px":[8,8,9,2,2],"py":[18,13,12,3,3],"pz":[0,0,0,2,-1],"nx":[23,11,23,11,11],"ny":[13,6,14,7,8],"nz":[0,1,0,1,1]},{"size":2,"px":[9,11],"py":[6,13],"pz":[1,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[8,10],"py":[0,6],"pz":[1,1],"nx":[9,4],"ny":[6,7],"nz":[1,-1]},{"size":3,"px":[3,10,9],"py":[8,6,0],"pz":[1,-1,-1],"nx":[2,2,2],"ny":[15,16,9],"nz":[0,0,1]},{"size":3,"px":[14,15,0],"py":[2,2,5],"pz":[0,0,-1],"nx":[17,17,18],"ny":[0,1,2],"nz":[0,0,0]},{"size":2,"px":[11,5],"py":[14,1],"pz":[0,-1],"nx":[10,9],"ny":[12,14],"nz":[0,0]},{"size":2,"px":[8,8],"py":[7,8],"pz":[1,1],"nx":[8,4],"ny":[4,4],"nz":[1,-1]},{"size":5,"px":[0,0,0,0,0],"py":[19,18,10,5,20],"pz":[0,0,1,2,0],"nx":[4,8,2,4,4],"ny":[4,15,5,10,10],"nz":[1,0,2,1,-1]},{"size":2,"px":[7,0],"py":[13,18],"pz":[0,-1],"nx":[4,3],"ny":[4,4],"nz":[1,1]},{"size":5,"px":[23,22,22,11,22],"py":[16,13,7,6,14],"pz":[0,0,0,1,0],"nx":[13,7,15,14,14],"ny":[6,3,7,6,6],"nz":[0,1,0,0,-1]}],"alpha":[-1.428861e+00,1.428861e+00,-8.591837e-01,8.591837e-01,-7.734305e-01,7.734305e-01,-6.534460e-01,6.534460e-01,-6.262547e-01,6.262547e-01,-5.231782e-01,5.231782e-01,-4.984303e-01,4.984303e-01,-4.913187e-01,4.913187e-01,-4.852198e-01,4.852198e-01,-4.906681e-01,4.906681e-01,-4.126248e-01,4.126248e-01,-4.590814e-01,4.590814e-01,-4.653825e-01,4.653825e-01,-4.179600e-01,4.179600e-01,-4.357392e-01,4.357392e-01,-4.087982e-01,4.087982e-01,-4.594812e-01,4.594812e-01,-4.858794e-01,4.858794e-01,-3.713580e-01,3.713580e-01,-3.894534e-01,3.894534e-01,-3.127168e-01,3.127168e-01,-4.012654e-01,4.012654e-01,-3.370552e-01,3.370552e-01,-3.534712e-01,3.534712e-01,-3.843450e-01,3.843450e-01,-2.688805e-01,2.688805e-01,-3.500203e-01,3.500203e-01,-2.827120e-01,2.827120e-01,-3.742119e-01,3.742119e-01,-3.219074e-01,3.219074e-01,-2.544953e-01,2.544953e-01,-3.355513e-01,3.355513e-01,-2.672670e-01,2.672670e-01,-2.932047e-01,2.932047e-01,-2.404618e-01,2.404618e-01,-2.354372e-01,2.354372e-01,-2.657955e-01,2.657955e-01,-2.293701e-01,2.293701e-01,-2.708918e-01,2.708918e-01,-2.340181e-01,2.340181e-01,-2.464815e-01,2.464815e-01,-2.944239e-01,2.944239e-01,-2.407960e-01,2.407960e-01,-3.029642e-01,3.029642e-01,-2.684602e-01,2.684602e-01,-2.495078e-01,2.495078e-01,-2.539708e-01,2.539708e-01,-2.989293e-01,2.989293e-01,-2.391309e-01,2.391309e-01,-2.531372e-01,2.531372e-01,-2.500390e-01,2.500390e-01,-2.295077e-01,2.295077e-01,-2.526125e-01,2.526125e-01,-2.337182e-01,2.337182e-01,-1.984756e-01,1.984756e-01,-3.089996e-01,3.089996e-01,-2.589053e-01,2.589053e-01,-2.962490e-01,2.962490e-01,-2.458660e-01,2.458660e-01,-2.515206e-01,2.515206e-01,-2.637299e-01,2.637299e-01]},{"count":80,"threshold":-5.185898e+00,"feature":[{"size":5,"px":[12,17,13,10,15],"py":[9,13,3,3,2],"pz":[0,0,0,0,0],"nx":[8,14,6,9,4],"ny":[10,9,8,8,2],"nz":[1,0,1,0,2]},{"size":5,"px":[3,11,8,10,9],"py":[7,4,3,3,3],"pz":[1,0,0,0,0],"nx":[2,1,5,0,0],"ny":[2,15,8,4,13],"nz":[2,0,1,0,0]},{"size":5,"px":[11,11,11,4,17],"py":[7,9,8,6,11],"pz":[0,0,0,1,0],"nx":[8,8,8,3,0],"ny":[4,8,8,8,13],"nz":[1,0,-1,-1,-1]},{"size":5,"px":[14,15,7,16,16],"py":[3,3,1,3,3],"pz":[0,0,1,0,-1],"nx":[23,22,23,22,22],"ny":[6,2,14,3,4],"nz":[0,0,0,0,0]},{"size":4,"px":[6,4,7,15],"py":[4,2,6,17],"pz":[1,2,1,0],"nx":[3,8,3,14],"ny":[4,4,10,22],"nz":[1,1,-1,-1]},{"size":3,"px":[3,5,22],"py":[7,7,5],"pz":[1,-1,-1],"nx":[2,2,4],"ny":[5,2,7],"nz":[2,2,1]},{"size":5,"px":[7,6,5,6,3],"py":[0,1,2,2,0],"pz":[0,0,0,0,1],"nx":[0,1,1,0,1],"ny":[0,2,1,2,0],"nz":[2,0,0,1,0]},{"size":5,"px":[11,11,11,11,5],"py":[11,10,13,12,6],"pz":[0,0,0,0,-1],"nx":[15,14,5,2,8],"ny":[9,8,10,2,10],"nz":[0,0,1,2,0]},{"size":5,"px":[8,5,6,8,7],"py":[12,12,12,23,12],"pz":[0,0,0,0,0],"nx":[3,17,5,2,8],"ny":[4,0,10,2,10],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[10,10,10,19,20],"py":[8,10,9,15,13],"pz":[1,1,1,0,0],"nx":[23,11,5,23,23],"ny":[20,10,5,19,19],"nz":[0,1,2,0,-1]},{"size":5,"px":[9,13,3,10,12],"py":[2,0,0,1,1],"pz":[0,0,2,0,0],"nx":[3,3,6,7,7],"ny":[5,2,11,4,4],"nz":[2,2,1,1,-1]},{"size":2,"px":[15,7],"py":[17,6],"pz":[0,1],"nx":[14,0],"ny":[16,10],"nz":[0,-1]},{"size":5,"px":[17,15,18,12,19],"py":[22,12,13,7,15],"pz":[0,0,0,0,0],"nx":[8,15,6,1,7],"ny":[4,8,22,5,4],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[10,9,18,19,8],"py":[2,1,3,3,1],"pz":[1,1,0,0,1],"nx":[23,23,23,11,11],"ny":[0,1,2,0,1],"nz":[0,0,0,1,-1]},{"size":5,"px":[12,23,0,1,8],"py":[14,5,0,17,1],"pz":[0,-1,-1,-1,-1],"nx":[8,14,15,18,14],"ny":[10,11,14,19,10],"nz":[1,0,0,0,0]},{"size":2,"px":[4,6],"py":[6,13],"pz":[1,0],"nx":[4,12],"ny":[10,14],"nz":[1,-1]},{"size":5,"px":[5,23,11,23,13],"py":[3,10,4,11,12],"pz":[2,0,1,0,0],"nx":[7,4,9,8,8],"ny":[4,2,4,4,4],"nz":[1,2,1,1,-1]},{"size":3,"px":[9,5,11],"py":[4,2,4],"pz":[0,1,-1],"nx":[5,2,4],"ny":[0,1,2],"nz":[0,2,0]},{"size":5,"px":[5,2,2,5,8],"py":[12,4,4,6,13],"pz":[0,2,1,1,0],"nx":[3,9,4,4,8],"ny":[4,0,2,2,4],"nz":[1,-1,-1,-1,-1]},{"size":3,"px":[9,5,22],"py":[7,4,20],"pz":[1,-1,-1],"nx":[8,19,4],"ny":[4,18,5],"nz":[1,0,2]},{"size":5,"px":[2,3,3,3,3],"py":[10,16,15,14,13],"pz":[1,0,0,0,0],"nx":[0,0,0,1,0],"ny":[10,20,5,23,21],"nz":[1,0,2,0,0]},{"size":2,"px":[12,11],"py":[4,18],"pz":[0,0],"nx":[11,23],"ny":[17,13],"nz":[0,-1]},{"size":2,"px":[17,8],"py":[16,7],"pz":[0,1],"nx":[8,3],"ny":[4,6],"nz":[1,-1]},{"size":5,"px":[13,5,14,12,3],"py":[4,7,4,5,3],"pz":[0,1,0,0,1],"nx":[21,20,21,21,21],"ny":[2,0,4,3,3],"nz":[0,0,0,0,-1]},{"size":4,"px":[20,20,20,10],"py":[21,19,20,8],"pz":[0,0,0,1],"nx":[8,11,0,2],"ny":[10,8,1,3],"nz":[1,-1,-1,-1]},{"size":4,"px":[6,7,12,8],"py":[12,12,8,11],"pz":[0,0,0,0],"nx":[9,5,5,18],"ny":[9,2,0,20],"nz":[0,-1,-1,-1]},{"size":3,"px":[11,5,9],"py":[0,0,0],"pz":[0,1,0],"nx":[2,6,3],"ny":[3,7,4],"nz":[2,0,1]},{"size":5,"px":[18,18,9,17,17],"py":[15,14,7,14,14],"pz":[0,0,1,0,-1],"nx":[21,21,21,22,20],"ny":[15,21,17,14,23],"nz":[0,0,0,0,0]},{"size":5,"px":[9,12,12,7,4],"py":[4,11,12,6,5],"pz":[1,0,0,1,2],"nx":[16,11,9,6,20],"ny":[8,4,11,10,23],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[12,11,10,11,11],"py":[23,4,4,5,23],"pz":[0,0,0,0,0],"nx":[11,11,7,3,20],"ny":[21,21,11,1,23],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[12,1],"py":[12,3],"pz":[0,-1],"nx":[10,10],"ny":[3,2],"nz":[1,1]},{"size":5,"px":[9,4,15,9,9],"py":[8,4,23,7,7],"pz":[1,2,0,1,-1],"nx":[5,3,3,3,2],"ny":[23,19,17,18,15],"nz":[0,0,0,0,0]},{"size":2,"px":[2,0],"py":[16,3],"pz":[0,2],"nx":[9,4],"ny":[15,2],"nz":[0,-1]},{"size":2,"px":[2,3],"py":[3,7],"pz":[2,1],"nx":[3,8],"ny":[4,10],"nz":[1,-1]},{"size":3,"px":[9,4,3],"py":[18,0,14],"pz":[0,-1,-1],"nx":[3,5,2],"ny":[5,8,5],"nz":[2,1,2]},{"size":3,"px":[1,1,10],"py":[2,1,7],"pz":[1,-1,-1],"nx":[0,0,0],"ny":[3,5,1],"nz":[0,0,1]},{"size":4,"px":[11,11,5,2],"py":[12,13,7,3],"pz":[0,0,-1,-1],"nx":[5,10,10,9],"ny":[6,9,10,13],"nz":[1,0,0,0]},{"size":2,"px":[4,8],"py":[3,6],"pz":[2,1],"nx":[9,1],"ny":[4,3],"nz":[1,-1]},{"size":5,"px":[0,0,1,1,0],"py":[4,10,12,13,5],"pz":[1,0,0,0,1],"nx":[4,4,8,7,7],"ny":[3,2,10,4,4],"nz":[2,2,1,1,-1]},{"size":3,"px":[3,4,3],"py":[1,1,2],"pz":[1,-1,-1],"nx":[4,5,3],"ny":[1,0,2],"nz":[0,0,0]},{"size":2,"px":[9,2],"py":[6,4],"pz":[1,-1],"nx":[8,4],"ny":[6,2],"nz":[1,2]},{"size":5,"px":[12,13,15,16,7],"py":[1,1,2,2,1],"pz":[0,0,0,0,1],"nx":[4,4,4,3,7],"ny":[2,2,4,2,4],"nz":[2,-1,-1,-1,-1]},{"size":5,"px":[9,3,2,11,5],"py":[23,7,4,10,6],"pz":[0,1,2,0,1],"nx":[21,20,11,21,21],"ny":[21,23,8,20,20],"nz":[0,0,1,0,-1]},{"size":4,"px":[12,6,13,12],"py":[7,3,5,6],"pz":[0,1,0,0],"nx":[3,0,5,10],"ny":[4,6,5,1],"nz":[1,-1,-1,-1]},{"size":2,"px":[10,4],"py":[4,0],"pz":[0,-1],"nx":[12,11],"ny":[2,1],"nz":[0,0]},{"size":4,"px":[2,3,22,5],"py":[6,1,18,5],"pz":[1,-1,-1,-1],"nx":[0,0,0,3],"ny":[14,3,12,18],"nz":[0,2,0,0]},{"size":3,"px":[10,20,21],"py":[10,18,15],"pz":[1,0,0],"nx":[15,1,2],"ny":[7,0,8],"nz":[0,-1,-1]},{"size":5,"px":[0,0,0,0,0],"py":[4,7,13,4,6],"pz":[1,1,0,2,1],"nx":[5,9,8,4,4],"ny":[3,7,7,3,3],"nz":[1,0,0,1,-1]},{"size":3,"px":[13,12,14],"py":[2,2,2],"pz":[0,0,0],"nx":[4,4,4],"ny":[2,2,5],"nz":[2,-1,-1]},{"size":5,"px":[5,4,6,2,12],"py":[7,9,7,4,10],"pz":[0,1,0,2,0],"nx":[6,1,2,5,2],"ny":[9,2,4,13,4],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[11,1],"py":[12,5],"pz":[0,-1],"nx":[1,0],"ny":[7,2],"nz":[0,2]},{"size":5,"px":[8,8,1,16,6],"py":[6,6,4,8,11],"pz":[1,-1,-1,-1,-1],"nx":[13,5,4,4,13],"ny":[12,1,2,5,11],"nz":[0,2,2,2,0]},{"size":2,"px":[5,6],"py":[4,14],"pz":[1,0],"nx":[9,5],"ny":[7,1],"nz":[0,-1]},{"size":2,"px":[2,6],"py":[4,14],"pz":[2,0],"nx":[9,2],"ny":[15,1],"nz":[0,-1]},{"size":5,"px":[10,19,20,10,9],"py":[1,2,3,0,0],"pz":[1,0,0,1,-1],"nx":[11,23,23,11,23],"ny":[0,3,1,1,2],"nz":[1,0,0,1,0]},{"size":2,"px":[2,9],"py":[3,12],"pz":[2,0],"nx":[2,6],"ny":[4,6],"nz":[1,-1]},{"size":5,"px":[0,0,0,0,0],"py":[4,10,11,9,9],"pz":[1,0,0,0,-1],"nx":[16,2,17,8,4],"ny":[10,2,9,4,4],"nz":[0,2,0,1,1]},{"size":2,"px":[12,0],"py":[5,4],"pz":[0,-1],"nx":[7,8],"ny":[4,8],"nz":[1,1]},{"size":2,"px":[21,21],"py":[9,10],"pz":[0,0],"nx":[11,8],"ny":[18,8],"nz":[0,-1]},{"size":2,"px":[14,7],"py":[23,9],"pz":[0,1],"nx":[7,13],"ny":[10,4],"nz":[1,-1]},{"size":5,"px":[12,12,12,6,2],"py":[11,13,12,6,4],"pz":[0,0,0,-1,-1],"nx":[0,0,0,0,0],"ny":[14,13,6,12,11],"nz":[0,0,1,0,0]},{"size":2,"px":[8,9],"py":[6,11],"pz":[1,-1],"nx":[15,15],"ny":[11,10],"nz":[0,0]},{"size":4,"px":[4,6,7,2],"py":[8,4,23,7],"pz":[1,-1,-1,-1],"nx":[4,20,19,17],"ny":[0,3,1,1],"nz":[2,0,0,0]},{"size":2,"px":[7,0],"py":[6,0],"pz":[1,-1],"nx":[7,4],"ny":[8,2],"nz":[1,2]},{"size":2,"px":[10,0],"py":[7,0],"pz":[1,-1],"nx":[15,15],"ny":[15,14],"nz":[0,0]},{"size":5,"px":[6,2,5,2,4],"py":[23,7,21,8,16],"pz":[0,1,0,1,0],"nx":[18,2,10,0,11],"ny":[9,3,23,5,3],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[9,9,8,10,4],"py":[0,2,2,1,1],"pz":[0,0,0,0,1],"nx":[4,3,2,2,5],"ny":[7,3,4,2,17],"nz":[0,1,2,2,0]},{"size":2,"px":[10,7],"py":[5,6],"pz":[1,-1],"nx":[11,11],"ny":[6,5],"nz":[1,1]},{"size":5,"px":[11,11,5,6,11],"py":[8,10,5,5,9],"pz":[0,0,1,1,0],"nx":[13,16,11,14,4],"ny":[9,13,11,20,23],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[7,14],"py":[14,22],"pz":[0,-1],"nx":[3,4],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[4,11],"py":[4,5],"pz":[2,-1],"nx":[2,4],"ny":[5,7],"nz":[2,1]},{"size":2,"px":[1,0],"py":[0,0],"pz":[0,1],"nx":[0,4],"ny":[0,2],"nz":[0,-1]},{"size":5,"px":[11,11,11,4,9],"py":[5,5,2,9,23],"pz":[0,-1,-1,-1,-1],"nx":[11,12,10,9,5],"ny":[2,2,2,2,1],"nz":[0,0,0,0,1]},{"size":3,"px":[16,14,15],"py":[1,1,0],"pz":[0,0,0],"nx":[4,7,4],"ny":[2,4,4],"nz":[2,1,-1]},{"size":2,"px":[5,0],"py":[14,5],"pz":[0,-1],"nx":[2,4],"ny":[5,17],"nz":[2,0]},{"size":5,"px":[18,7,16,19,4],"py":[13,6,23,13,3],"pz":[0,1,0,0,2],"nx":[5,2,3,4,4],"ny":[1,1,4,1,3],"nz":[0,1,0,0,0]},{"size":2,"px":[8,8],"py":[7,6],"pz":[1,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[2,1],"py":[10,4],"pz":[1,2],"nx":[4,4],"ny":[3,3],"nz":[2,-1]},{"size":2,"px":[10,5],"py":[19,1],"pz":[0,-1],"nx":[4,12],"ny":[10,17],"nz":[1,0]},{"size":5,"px":[12,6,2,4,11],"py":[14,4,2,1,5],"pz":[0,-1,-1,-1,-1],"nx":[3,4,3,4,3],"ny":[13,17,14,16,15],"nz":[0,0,0,0,0]}],"alpha":[-1.368326e+00,1.368326e+00,-7.706897e-01,7.706897e-01,-8.378147e-01,8.378147e-01,-6.120624e-01,6.120624e-01,-5.139189e-01,5.139189e-01,-4.759130e-01,4.759130e-01,-5.161374e-01,5.161374e-01,-5.407743e-01,5.407743e-01,-4.216105e-01,4.216105e-01,-4.418693e-01,4.418693e-01,-4.435335e-01,4.435335e-01,-4.052076e-01,4.052076e-01,-4.293050e-01,4.293050e-01,-3.431154e-01,3.431154e-01,-4.231203e-01,4.231203e-01,-3.917100e-01,3.917100e-01,-3.623450e-01,3.623450e-01,-3.202670e-01,3.202670e-01,-3.331602e-01,3.331602e-01,-3.552034e-01,3.552034e-01,-3.784556e-01,3.784556e-01,-3.295428e-01,3.295428e-01,-3.587038e-01,3.587038e-01,-2.861332e-01,2.861332e-01,-3.403258e-01,3.403258e-01,-3.989002e-01,3.989002e-01,-2.631159e-01,2.631159e-01,-3.272156e-01,3.272156e-01,-2.816567e-01,2.816567e-01,-3.125926e-01,3.125926e-01,-3.146982e-01,3.146982e-01,-2.521825e-01,2.521825e-01,-2.434554e-01,2.434554e-01,-3.435378e-01,3.435378e-01,-3.161172e-01,3.161172e-01,-2.805027e-01,2.805027e-01,-3.303579e-01,3.303579e-01,-2.725089e-01,2.725089e-01,-2.575051e-01,2.575051e-01,-3.210646e-01,3.210646e-01,-2.986997e-01,2.986997e-01,-2.408925e-01,2.408925e-01,-2.456291e-01,2.456291e-01,-2.836550e-01,2.836550e-01,-2.469860e-01,2.469860e-01,-2.915900e-01,2.915900e-01,-2.513559e-01,2.513559e-01,-2.433728e-01,2.433728e-01,-2.377905e-01,2.377905e-01,-2.089327e-01,2.089327e-01,-1.978434e-01,1.978434e-01,-3.017699e-01,3.017699e-01,-2.339661e-01,2.339661e-01,-1.932560e-01,1.932560e-01,-2.278285e-01,2.278285e-01,-2.438200e-01,2.438200e-01,-2.216769e-01,2.216769e-01,-1.941995e-01,1.941995e-01,-2.129081e-01,2.129081e-01,-2.270319e-01,2.270319e-01,-2.393942e-01,2.393942e-01,-2.132518e-01,2.132518e-01,-1.867741e-01,1.867741e-01,-2.394237e-01,2.394237e-01,-2.005917e-01,2.005917e-01,-2.445217e-01,2.445217e-01,-2.229078e-01,2.229078e-01,-2.342967e-01,2.342967e-01,-2.481784e-01,2.481784e-01,-2.735603e-01,2.735603e-01,-2.187604e-01,2.187604e-01,-1.677239e-01,1.677239e-01,-2.248867e-01,2.248867e-01,-2.505358e-01,2.505358e-01,-1.867706e-01,1.867706e-01,-1.904305e-01,1.904305e-01,-1.939881e-01,1.939881e-01,-2.249474e-01,2.249474e-01,-1.762483e-01,1.762483e-01,-2.299974e-01,2.299974e-01]},{"count":115,"threshold":-5.151920e+00,"feature":[{"size":5,"px":[7,14,7,10,6],"py":[3,3,12,4,4],"pz":[0,0,0,0,1],"nx":[14,3,14,9,3],"ny":[7,4,8,8,5],"nz":[0,1,0,0,2]},{"size":5,"px":[13,18,16,17,15],"py":[1,13,1,2,0],"pz":[0,0,0,0,0],"nx":[23,23,8,11,22],"ny":[3,4,4,8,0],"nz":[0,0,1,1,0]},{"size":5,"px":[16,6,6,7,12],"py":[12,13,4,12,5],"pz":[0,0,1,0,0],"nx":[0,0,8,4,0],"ny":[0,2,4,4,2],"nz":[0,0,1,1,-1]},{"size":3,"px":[12,13,7],"py":[13,18,6],"pz":[0,0,1],"nx":[13,5,6],"ny":[16,3,8],"nz":[0,-1,-1]},{"size":5,"px":[10,12,9,13,11],"py":[3,3,3,3,3],"pz":[0,0,0,0,0],"nx":[3,4,15,4,4],"ny":[2,5,10,4,4],"nz":[2,1,0,1,-1]},{"size":5,"px":[12,12,12,3,12],"py":[7,9,8,3,10],"pz":[0,0,0,2,0],"nx":[4,8,15,9,9],"ny":[4,4,8,8,8],"nz":[1,1,0,0,-1]},{"size":5,"px":[6,3,4,4,2],"py":[22,12,13,14,7],"pz":[0,0,0,0,1],"nx":[2,0,1,1,1],"ny":[23,5,22,21,21],"nz":[0,2,0,0,-1]},{"size":2,"px":[3,3],"py":[8,8],"pz":[1,-1],"nx":[3,4],"ny":[4,10],"nz":[1,1]},{"size":5,"px":[11,11,11,11,0],"py":[10,12,11,13,2],"pz":[0,0,0,-1,-1],"nx":[8,13,13,13,13],"ny":[10,8,9,11,10],"nz":[1,0,0,0,0]},{"size":5,"px":[16,16,15,17,18],"py":[12,23,11,12,12],"pz":[0,0,0,0,0],"nx":[8,8,9,3,13],"ny":[4,4,12,3,10],"nz":[1,-1,-1,-1,-1]},{"size":4,"px":[17,16,6,5],"py":[14,13,4,5],"pz":[0,0,-1,-1],"nx":[8,15,4,7],"ny":[10,14,4,8],"nz":[1,0,2,1]},{"size":5,"px":[20,10,20,21,19],"py":[14,7,13,12,22],"pz":[0,1,0,0,0],"nx":[22,23,11,23,23],"ny":[23,22,11,21,20],"nz":[0,0,1,0,-1]},{"size":4,"px":[12,13,1,18],"py":[14,23,3,5],"pz":[0,-1,-1,-1],"nx":[2,10,5,9],"ny":[2,9,8,14],"nz":[2,0,1,0]},{"size":5,"px":[10,4,7,9,8],"py":[1,0,2,0,1],"pz":[0,1,0,0,0],"nx":[2,3,5,3,3],"ny":[2,4,8,3,3],"nz":[2,1,1,1,-1]},{"size":4,"px":[11,2,2,11],"py":[6,4,5,7],"pz":[0,2,2,0],"nx":[3,0,5,3],"ny":[4,9,8,3],"nz":[1,-1,-1,-1]},{"size":5,"px":[12,10,9,12,12],"py":[11,2,1,10,10],"pz":[0,1,1,0,-1],"nx":[22,11,5,22,23],"ny":[1,1,0,0,3],"nz":[0,1,2,0,0]},{"size":4,"px":[5,10,7,11],"py":[14,3,0,4],"pz":[0,-1,-1,-1],"nx":[4,4,4,4],"ny":[17,18,15,16],"nz":[0,0,0,0]},{"size":5,"px":[2,2,3,2,2],"py":[16,12,20,15,17],"pz":[0,0,0,0,0],"nx":[12,8,4,15,15],"ny":[17,4,4,8,8],"nz":[0,1,1,0,-1]},{"size":5,"px":[12,12,1,6,12],"py":[11,10,3,6,10],"pz":[0,0,-1,-1,-1],"nx":[0,0,1,0,2],"ny":[4,0,2,1,0],"nz":[0,2,0,1,0]},{"size":5,"px":[21,20,21,21,14],"py":[9,16,11,8,12],"pz":[0,0,0,0,0],"nx":[17,6,15,0,2],"ny":[8,23,13,2,0],"nz":[0,-1,-1,-1,-1]},{"size":4,"px":[6,9,9,5],"py":[14,18,23,14],"pz":[0,0,0,0],"nx":[9,5,5,12],"ny":[21,5,3,1],"nz":[0,-1,-1,-1]},{"size":2,"px":[12,13],"py":[4,4],"pz":[0,0],"nx":[4,3],"ny":[4,1],"nz":[1,2]},{"size":5,"px":[7,8,11,4,10],"py":[3,3,2,1,2],"pz":[0,0,0,1,0],"nx":[19,20,19,20,20],"ny":[0,3,1,2,2],"nz":[0,0,0,0,-1]},{"size":2,"px":[9,1],"py":[7,4],"pz":[1,-1],"nx":[4,7],"ny":[5,9],"nz":[2,1]},{"size":5,"px":[11,10,1,5,1],"py":[10,12,6,6,5],"pz":[0,0,1,1,1],"nx":[16,3,2,4,4],"ny":[10,4,2,4,4],"nz":[0,1,2,1,-1]},{"size":2,"px":[15,0],"py":[17,0],"pz":[0,-1],"nx":[7,4],"ny":[8,5],"nz":[1,2]},{"size":5,"px":[8,10,9,9,9],"py":[2,2,2,1,1],"pz":[0,0,0,0,-1],"nx":[4,2,3,3,2],"ny":[0,3,2,1,4],"nz":[0,0,0,0,0]},{"size":4,"px":[11,15,17,16],"py":[8,10,11,11],"pz":[0,0,0,0],"nx":[14,1,1,2],"ny":[9,5,7,0],"nz":[0,-1,-1,-1]},{"size":3,"px":[3,5,9],"py":[8,6,12],"pz":[0,1,0],"nx":[3,4,18],"ny":[4,2,22],"nz":[1,-1,-1]},{"size":5,"px":[6,1,7,3,3],"py":[13,4,13,7,7],"pz":[0,2,0,1,-1],"nx":[0,0,0,0,0],"ny":[16,15,8,13,14],"nz":[0,0,1,0,0]},{"size":2,"px":[5,16],"py":[13,10],"pz":[0,-1],"nx":[3,4],"ny":[4,5],"nz":[1,1]},{"size":5,"px":[5,23,11,23,23],"py":[5,12,4,16,15],"pz":[2,0,1,0,0],"nx":[3,2,4,5,5],"ny":[4,2,4,11,11],"nz":[1,2,1,1,-1]},{"size":4,"px":[10,10,3,23],"py":[7,7,3,16],"pz":[1,-1,-1,-1],"nx":[5,23,11,22],"ny":[4,13,7,16],"nz":[2,0,1,0]},{"size":5,"px":[15,14,13,15,16],"py":[1,0,0,0,1],"pz":[0,0,0,0,0],"nx":[4,9,8,8,8],"ny":[2,4,9,4,4],"nz":[2,1,1,1,-1]},{"size":2,"px":[10,4],"py":[5,5],"pz":[0,-1],"nx":[3,15],"ny":[1,8],"nz":[2,0]},{"size":2,"px":[6,12],"py":[6,9],"pz":[1,0],"nx":[10,10],"ny":[10,10],"nz":[0,-1]},{"size":5,"px":[1,0,0,0,0],"py":[5,4,11,9,12],"pz":[0,1,0,0,0],"nx":[9,8,2,4,7],"ny":[7,7,2,4,7],"nz":[0,0,2,1,0]},{"size":2,"px":[4,8],"py":[4,7],"pz":[2,1],"nx":[9,8],"ny":[4,7],"nz":[1,-1]},{"size":2,"px":[5,6],"py":[4,1],"pz":[2,-1],"nx":[8,6],"ny":[7,3],"nz":[1,1]},{"size":5,"px":[8,5,7,6,11],"py":[12,5,13,13,22],"pz":[0,1,0,0,0],"nx":[23,23,23,22,22],"ny":[20,19,21,23,23],"nz":[0,0,0,0,-1]},{"size":2,"px":[3,17],"py":[6,9],"pz":[1,-1],"nx":[3,3],"ny":[10,9],"nz":[1,1]},{"size":2,"px":[14,11],"py":[23,5],"pz":[0,0],"nx":[7,3],"ny":[10,20],"nz":[1,-1]},{"size":2,"px":[3,4],"py":[8,8],"pz":[1,1],"nx":[9,4],"ny":[15,4],"nz":[0,-1]},{"size":2,"px":[2,4],"py":[4,7],"pz":[2,1],"nx":[2,4],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[23,11],"py":[21,10],"pz":[0,1],"nx":[2,3],"ny":[11,14],"nz":[1,0]},{"size":4,"px":[11,11,11,3],"py":[13,12,11,4],"pz":[0,0,0,-1],"nx":[14,13,13,6],"ny":[13,11,10,5],"nz":[0,0,0,1]},{"size":2,"px":[4,7],"py":[3,6],"pz":[2,1],"nx":[9,19],"ny":[4,14],"nz":[1,-1]},{"size":3,"px":[10,5,7],"py":[5,0,6],"pz":[1,-1,-1],"nx":[10,21,5],"ny":[0,5,3],"nz":[1,0,2]},{"size":2,"px":[16,13],"py":[3,15],"pz":[0,-1],"nx":[17,7],"ny":[23,8],"nz":[0,1]},{"size":3,"px":[4,2,2],"py":[15,7,19],"pz":[0,1,-1],"nx":[2,8,4],"ny":[5,14,9],"nz":[2,0,1]},{"size":3,"px":[8,3,6],"py":[10,2,4],"pz":[0,2,1],"nx":[3,8,4],"ny":[4,14,9],"nz":[1,-1,-1]},{"size":2,"px":[14,3],"py":[18,3],"pz":[0,-1],"nx":[12,14],"ny":[17,9],"nz":[0,0]},{"size":3,"px":[7,1,10],"py":[14,10,10],"pz":[0,-1,-1],"nx":[9,6,2],"ny":[13,18,2],"nz":[0,0,2]},{"size":2,"px":[11,8],"py":[13,11],"pz":[0,-1],"nx":[2,4],"ny":[7,18],"nz":[1,0]},{"size":2,"px":[5,4],"py":[21,17],"pz":[0,0],"nx":[9,3],"ny":[5,1],"nz":[1,-1]},{"size":2,"px":[6,6],"py":[4,0],"pz":[0,-1],"nx":[4,3],"ny":[2,0],"nz":[0,1]},{"size":2,"px":[2,1],"py":[1,5],"pz":[0,-1],"nx":[0,1],"ny":[1,0],"nz":[1,0]},{"size":2,"px":[18,1],"py":[13,5],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[0,0,0,0,1],"py":[4,3,2,12,15],"pz":[1,1,2,0,0],"nx":[5,9,4,8,8],"ny":[3,6,3,6,6],"nz":[1,0,1,0,-1]},{"size":2,"px":[2,5],"py":[0,2],"pz":[1,-1],"nx":[2,1],"ny":[0,1],"nz":[0,1]},{"size":4,"px":[7,15,4,20],"py":[8,23,4,8],"pz":[1,0,2,0],"nx":[6,0,3,4],"ny":[9,2,13,6],"nz":[0,-1,-1,-1]},{"size":4,"px":[11,11,10,20],"py":[10,9,11,8],"pz":[0,0,0,-1],"nx":[21,20,21,21],"ny":[18,23,19,17],"nz":[0,0,0,0]},{"size":2,"px":[3,8],"py":[7,5],"pz":[1,-1],"nx":[3,4],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[5,11],"py":[3,4],"pz":[2,1],"nx":[8,7],"ny":[5,12],"nz":[1,0]},{"size":2,"px":[4,1],"py":[1,3],"pz":[1,-1],"nx":[3,6],"ny":[0,0],"nz":[1,0]},{"size":2,"px":[19,9],"py":[16,8],"pz":[0,1],"nx":[14,6],"ny":[15,1],"nz":[0,-1]},{"size":2,"px":[12,6],"py":[13,5],"pz":[0,-1],"nx":[5,5],"ny":[1,2],"nz":[2,2]},{"size":5,"px":[16,14,4,15,12],"py":[1,1,1,2,1],"pz":[0,0,2,0,0],"nx":[6,4,3,2,10],"ny":[22,8,2,1,7],"nz":[0,1,1,2,0]},{"size":5,"px":[6,8,6,5,5],"py":[1,0,0,1,0],"pz":[0,0,0,0,0],"nx":[4,4,4,4,8],"ny":[4,3,2,5,10],"nz":[2,2,2,2,1]},{"size":2,"px":[9,8],"py":[17,0],"pz":[0,-1],"nx":[2,5],"ny":[5,8],"nz":[2,1]},{"size":2,"px":[8,0],"py":[7,3],"pz":[1,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[10,21],"py":[11,20],"pz":[1,0],"nx":[11,4],"ny":[17,1],"nz":[0,-1]},{"size":5,"px":[5,10,4,17,10],"py":[3,6,3,11,5],"pz":[1,0,1,0,0],"nx":[21,20,9,19,10],"ny":[4,3,0,2,1],"nz":[0,0,1,0,-1]},{"size":2,"px":[23,23],"py":[10,10],"pz":[0,-1],"nx":[23,23],"ny":[21,22],"nz":[0,0]},{"size":5,"px":[9,20,19,20,20],"py":[0,3,1,2,2],"pz":[1,0,0,0,-1],"nx":[11,23,11,23,5],"ny":[1,2,0,1,0],"nz":[1,0,1,0,2]},{"size":3,"px":[6,8,7],"py":[4,10,11],"pz":[1,0,0],"nx":[8,3,4],"ny":[9,4,4],"nz":[0,-1,-1]},{"size":4,"px":[13,13,10,4],"py":[14,23,1,5],"pz":[0,-1,-1,-1],"nx":[15,14,8,8],"ny":[13,12,8,9],"nz":[0,0,1,1]},{"size":2,"px":[11,9],"py":[5,8],"pz":[0,-1],"nx":[7,8],"ny":[7,4],"nz":[0,1]},{"size":5,"px":[4,8,4,7,7],"py":[2,3,3,11,11],"pz":[2,1,2,1,-1],"nx":[0,0,1,0,0],"ny":[4,6,15,3,2],"nz":[1,1,0,2,2]},{"size":2,"px":[6,1],"py":[12,1],"pz":[0,-1],"nx":[1,10],"ny":[2,11],"nz":[2,0]},{"size":5,"px":[0,0,2,3,7],"py":[0,1,4,3,11],"pz":[0,-1,-1,-1,-1],"nx":[9,11,9,6,12],"ny":[2,1,1,0,2],"nz":[0,0,0,1,0]},{"size":2,"px":[10,11],"py":[4,4],"pz":[0,0],"nx":[8,4],"ny":[4,2],"nz":[1,-1]},{"size":5,"px":[1,1,1,1,1],"py":[15,10,19,16,18],"pz":[0,1,0,0,0],"nx":[4,5,3,5,6],"ny":[4,19,9,18,19],"nz":[1,0,1,0,-1]},{"size":5,"px":[12,12,12,12,20],"py":[11,12,13,13,18],"pz":[0,0,0,-1,-1],"nx":[0,0,0,0,0],"ny":[4,2,7,6,12],"nz":[1,2,1,1,0]},{"size":2,"px":[0,0],"py":[9,11],"pz":[0,0],"nx":[10,4],"ny":[5,3],"nz":[1,-1]},{"size":2,"px":[11,8],"py":[9,6],"pz":[0,1],"nx":[13,13],"ny":[10,10],"nz":[0,-1]},{"size":2,"px":[6,3],"py":[5,3],"pz":[1,2],"nx":[3,3],"ny":[5,5],"nz":[2,-1]},{"size":2,"px":[19,9],"py":[10,6],"pz":[0,1],"nx":[4,1],"ny":[2,2],"nz":[2,-1]},{"size":2,"px":[14,4],"py":[19,12],"pz":[0,-1],"nx":[14,8],"ny":[17,10],"nz":[0,1]},{"size":4,"px":[4,2,13,2],"py":[12,6,9,3],"pz":[0,1,-1,-1],"nx":[1,0,1,0],"ny":[16,14,11,15],"nz":[0,0,1,0]},{"size":2,"px":[3,3],"py":[8,7],"pz":[1,1],"nx":[4,4],"ny":[4,8],"nz":[1,-1]},{"size":5,"px":[9,11,12,6,10],"py":[2,1,2,1,2],"pz":[0,0,0,1,0],"nx":[4,6,4,6,2],"ny":[4,0,9,1,8],"nz":[0,0,1,0,1]},{"size":5,"px":[4,4,7,2,2],"py":[19,20,23,8,9],"pz":[0,0,0,1,1],"nx":[7,0,5,6,2],"ny":[10,5,4,1,8],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[18,18,17,18,18],"py":[15,16,14,20,17],"pz":[0,0,0,0,0],"nx":[15,2,2,5,2],"ny":[8,0,2,9,4],"nz":[0,-1,-1,-1,-1]},{"size":4,"px":[13,13,13,18],"py":[11,12,12,20],"pz":[0,0,-1,-1],"nx":[1,3,10,10],"ny":[1,6,12,11],"nz":[2,0,0,0]},{"size":2,"px":[8,9],"py":[0,1],"pz":[1,1],"nx":[19,4],"ny":[2,2],"nz":[0,-1]},{"size":2,"px":[6,3],"py":[4,2],"pz":[1,2],"nx":[8,4],"ny":[4,0],"nz":[1,-1]},{"size":5,"px":[23,11,22,13,13],"py":[8,3,3,12,12],"pz":[0,1,0,0,-1],"nx":[15,7,14,13,8],"ny":[7,3,6,6,3],"nz":[0,1,0,0,1]},{"size":3,"px":[9,11,19],"py":[7,3,0],"pz":[1,-1,-1],"nx":[23,23,11],"ny":[16,12,7],"nz":[0,0,1]},{"size":2,"px":[15,8],"py":[23,7],"pz":[0,-1],"nx":[4,3],"ny":[5,4],"nz":[2,2]},{"size":2,"px":[4,10],"py":[6,13],"pz":[1,-1],"nx":[2,3],"ny":[4,10],"nz":[2,1]},{"size":2,"px":[4,1],"py":[11,2],"pz":[1,2],"nx":[9,2],"ny":[5,2],"nz":[1,-1]},{"size":2,"px":[22,22],"py":[22,21],"pz":[0,0],"nx":[3,0],"ny":[5,3],"nz":[1,-1]},{"size":2,"px":[20,10],"py":[12,6],"pz":[0,1],"nx":[20,10],"ny":[23,11],"nz":[0,-1]},{"size":4,"px":[10,3,3,4],"py":[5,3,4,9],"pz":[0,-1,-1,-1],"nx":[14,4,3,11],"ny":[2,1,1,3],"nz":[0,2,2,0]},{"size":3,"px":[15,15,3],"py":[1,1,4],"pz":[0,-1,-1],"nx":[7,4,4],"ny":[8,2,3],"nz":[1,2,2]},{"size":3,"px":[0,0,0],"py":[3,4,6],"pz":[2,2,1],"nx":[0,21,4],"ny":[23,14,3],"nz":[0,-1,-1]},{"size":5,"px":[4,4,5,3,4],"py":[9,11,8,4,8],"pz":[1,1,1,2,1],"nx":[21,21,10,19,19],"ny":[3,4,1,0,0],"nz":[0,0,1,0,-1]},{"size":4,"px":[21,20,20,21],"py":[18,21,20,17],"pz":[0,0,0,0],"nx":[8,1,4,2],"ny":[10,0,2,4],"nz":[1,-1,-1,-1]},{"size":2,"px":[3,6],"py":[7,14],"pz":[1,0],"nx":[3,5],"ny":[4,5],"nz":[1,-1]},{"size":3,"px":[12,0,23],"py":[20,2,13],"pz":[0,-1,-1],"nx":[12,2,9],"ny":[19,2,7],"nz":[0,2,0]},{"size":2,"px":[0,6],"py":[22,11],"pz":[0,-1],"nx":[20,18],"ny":[12,23],"nz":[0,0]},{"size":5,"px":[9,15,15,16,8],"py":[2,1,2,2,1],"pz":[1,0,0,0,1],"nx":[1,1,1,1,1],"ny":[16,10,17,18,18],"nz":[0,1,0,0,-1]},{"size":5,"px":[10,5,3,5,8],"py":[14,2,1,4,1],"pz":[0,-1,-1,-1,-1],"nx":[23,23,23,23,23],"ny":[18,15,16,14,17],"nz":[0,0,0,0,0]},{"size":5,"px":[2,2,2,3,2],"py":[16,17,15,20,11],"pz":[0,0,0,0,1],"nx":[8,22,2,1,23],"ny":[20,11,5,0,17],"nz":[0,-1,-1,-1,-1]}],"alpha":[-1.299972e+00,1.299972e+00,-7.630804e-01,7.630804e-01,-5.530378e-01,5.530378e-01,-5.444703e-01,5.444703e-01,-5.207701e-01,5.207701e-01,-5.035143e-01,5.035143e-01,-4.514416e-01,4.514416e-01,-4.897723e-01,4.897723e-01,-5.006264e-01,5.006264e-01,-4.626049e-01,4.626049e-01,-4.375402e-01,4.375402e-01,-3.742565e-01,3.742565e-01,-3.873996e-01,3.873996e-01,-3.715484e-01,3.715484e-01,-3.562480e-01,3.562480e-01,-3.216189e-01,3.216189e-01,-3.983409e-01,3.983409e-01,-3.191891e-01,3.191891e-01,-3.242173e-01,3.242173e-01,-3.528040e-01,3.528040e-01,-3.562318e-01,3.562318e-01,-3.592398e-01,3.592398e-01,-2.557584e-01,2.557584e-01,-2.747951e-01,2.747951e-01,-2.747554e-01,2.747554e-01,-2.980481e-01,2.980481e-01,-2.887670e-01,2.887670e-01,-3.895318e-01,3.895318e-01,-2.786896e-01,2.786896e-01,-2.763841e-01,2.763841e-01,-2.704816e-01,2.704816e-01,-2.075489e-01,2.075489e-01,-3.104773e-01,3.104773e-01,-2.580337e-01,2.580337e-01,-2.448334e-01,2.448334e-01,-3.054279e-01,3.054279e-01,-2.335804e-01,2.335804e-01,-2.972322e-01,2.972322e-01,-2.270521e-01,2.270521e-01,-2.134621e-01,2.134621e-01,-2.261655e-01,2.261655e-01,-2.091024e-01,2.091024e-01,-2.478928e-01,2.478928e-01,-2.468972e-01,2.468972e-01,-1.919746e-01,1.919746e-01,-2.756623e-01,2.756623e-01,-2.629717e-01,2.629717e-01,-2.198653e-01,2.198653e-01,-2.174434e-01,2.174434e-01,-2.193626e-01,2.193626e-01,-1.956262e-01,1.956262e-01,-1.720459e-01,1.720459e-01,-1.781067e-01,1.781067e-01,-1.773484e-01,1.773484e-01,-1.793871e-01,1.793871e-01,-1.973396e-01,1.973396e-01,-2.397262e-01,2.397262e-01,-2.164685e-01,2.164685e-01,-2.214348e-01,2.214348e-01,-2.265941e-01,2.265941e-01,-2.075436e-01,2.075436e-01,-2.244070e-01,2.244070e-01,-2.291992e-01,2.291992e-01,-2.223506e-01,2.223506e-01,-1.639398e-01,1.639398e-01,-1.732374e-01,1.732374e-01,-1.808631e-01,1.808631e-01,-1.860962e-01,1.860962e-01,-1.781604e-01,1.781604e-01,-2.108322e-01,2.108322e-01,-2.386390e-01,2.386390e-01,-1.942083e-01,1.942083e-01,-1.949161e-01,1.949161e-01,-1.953729e-01,1.953729e-01,-2.317591e-01,2.317591e-01,-2.335136e-01,2.335136e-01,-2.282835e-01,2.282835e-01,-2.148716e-01,2.148716e-01,-1.588127e-01,1.588127e-01,-1.566765e-01,1.566765e-01,-1.644839e-01,1.644839e-01,-2.386947e-01,2.386947e-01,-1.704126e-01,1.704126e-01,-2.213945e-01,2.213945e-01,-1.740398e-01,1.740398e-01,-2.451678e-01,2.451678e-01,-2.120524e-01,2.120524e-01,-1.886646e-01,1.886646e-01,-2.824447e-01,2.824447e-01,-1.900364e-01,1.900364e-01,-2.179183e-01,2.179183e-01,-2.257696e-01,2.257696e-01,-2.023404e-01,2.023404e-01,-1.886901e-01,1.886901e-01,-1.850663e-01,1.850663e-01,-2.035414e-01,2.035414e-01,-1.930174e-01,1.930174e-01,-1.898282e-01,1.898282e-01,-1.666640e-01,1.666640e-01,-1.646143e-01,1.646143e-01,-1.543475e-01,1.543475e-01,-1.366289e-01,1.366289e-01,-1.636837e-01,1.636837e-01,-2.547716e-01,2.547716e-01,-1.281869e-01,1.281869e-01,-1.509159e-01,1.509159e-01,-1.447827e-01,1.447827e-01,-1.626126e-01,1.626126e-01,-2.387014e-01,2.387014e-01,-2.571160e-01,2.571160e-01,-1.719175e-01,1.719175e-01,-1.646742e-01,1.646742e-01,-1.717041e-01,1.717041e-01,-2.039217e-01,2.039217e-01,-1.796907e-01,1.796907e-01]},{"count":153,"threshold":-4.971032e+00,"feature":[{"size":5,"px":[14,13,18,10,16],"py":[2,2,13,3,12],"pz":[0,0,0,0,0],"nx":[21,7,14,23,23],"ny":[16,7,8,3,13],"nz":[0,1,0,0,0]},{"size":5,"px":[12,12,12,15,14],"py":[9,10,11,3,3],"pz":[0,0,0,0,0],"nx":[9,9,8,14,3],"ny":[9,8,5,9,5],"nz":[0,0,1,0,2]},{"size":5,"px":[5,11,7,6,8],"py":[12,8,12,12,11],"pz":[0,0,0,0,0],"nx":[8,4,3,9,9],"ny":[4,4,4,9,9],"nz":[1,1,1,0,-1]},{"size":5,"px":[9,8,4,10,6],"py":[2,2,1,3,13],"pz":[0,0,1,0,0],"nx":[1,1,5,1,1],"ny":[2,3,8,4,16],"nz":[0,0,1,0,0]},{"size":5,"px":[3,16,6,17,15],"py":[2,17,4,12,12],"pz":[2,0,1,0,0],"nx":[4,8,15,1,1],"ny":[4,4,8,16,16],"nz":[1,1,-1,-1,-1]},{"size":4,"px":[18,15,8,17],"py":[12,23,6,12],"pz":[0,0,1,0],"nx":[15,4,10,5],"ny":[21,8,14,3],"nz":[0,-1,-1,-1]},{"size":5,"px":[18,17,9,19,19],"py":[3,1,0,3,3],"pz":[0,0,1,0,-1],"nx":[22,11,23,23,23],"ny":[0,1,2,3,4],"nz":[0,1,0,0,0]},{"size":4,"px":[9,5,5,10],"py":[18,15,14,18],"pz":[0,0,0,0],"nx":[10,11,2,0],"ny":[16,7,12,7],"nz":[0,-1,-1,-1]},{"size":2,"px":[2,12],"py":[4,6],"pz":[2,0],"nx":[3,12],"ny":[4,19],"nz":[1,-1]},{"size":5,"px":[3,4,5,2,2],"py":[3,3,3,1,1],"pz":[0,0,0,1,-1],"nx":[0,0,1,0,0],"ny":[3,4,0,1,2],"nz":[0,0,0,1,0]},{"size":5,"px":[12,12,12,8,10],"py":[13,12,12,1,18],"pz":[0,0,-1,-1,-1],"nx":[13,8,7,14,9],"ny":[10,10,7,13,4],"nz":[0,1,1,0,1]},{"size":5,"px":[15,4,12,14,12],"py":[12,3,9,10,8],"pz":[0,2,0,0,0],"nx":[14,7,11,2,9],"ny":[8,4,7,5,4],"nz":[0,1,-1,-1,-1]},{"size":3,"px":[3,9,7],"py":[7,23,15],"pz":[1,-1,-1],"nx":[4,4,2],"ny":[9,7,5],"nz":[1,1,2]},{"size":3,"px":[5,17,5],"py":[3,23,4],"pz":[2,0,2],"nx":[23,2,4],"ny":[23,16,4],"nz":[0,-1,-1]},{"size":5,"px":[4,9,9,10,8],"py":[1,0,1,0,2],"pz":[1,0,0,0,0],"nx":[2,5,4,2,2],"ny":[2,19,11,4,1],"nz":[2,0,1,2,2]},{"size":5,"px":[8,3,8,4,7],"py":[23,9,13,8,16],"pz":[0,1,0,1,0],"nx":[8,2,5,3,2],"ny":[8,15,1,1,1],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[11,5],"py":[14,5],"pz":[0,-1],"nx":[1,9],"ny":[3,13],"nz":[2,0]},{"size":5,"px":[5,8,1,8,6],"py":[12,12,3,23,12],"pz":[0,0,2,0,0],"nx":[1,1,2,1,1],"ny":[22,21,23,20,20],"nz":[0,0,0,0,-1]},{"size":5,"px":[14,21,19,21,20],"py":[13,8,20,10,7],"pz":[0,0,0,0,0],"nx":[16,0,14,23,1],"ny":[8,1,23,10,20],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[15,16,13,14,14],"py":[3,3,3,3,3],"pz":[0,0,0,0,-1],"nx":[18,19,18,9,17],"ny":[2,2,1,1,0],"nz":[0,0,0,1,0]},{"size":2,"px":[17,9],"py":[14,4],"pz":[0,-1],"nx":[9,18],"ny":[4,18],"nz":[1,0]},{"size":2,"px":[21,20],"py":[17,21],"pz":[0,0],"nx":[12,3],"ny":[17,10],"nz":[0,-1]},{"size":2,"px":[2,1],"py":[10,4],"pz":[1,2],"nx":[4,1],"ny":[10,5],"nz":[1,-1]},{"size":5,"px":[7,8,4,9,9],"py":[2,2,0,2,2],"pz":[0,0,1,0,-1],"nx":[5,5,4,6,3],"ny":[0,1,2,0,0],"nz":[0,0,0,0,1]},{"size":2,"px":[2,5],"py":[3,5],"pz":[2,-1],"nx":[3,2],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[0,0,0,0,0],"py":[0,1,3,4,4],"pz":[2,2,1,1,-1],"nx":[20,20,19,20,19],"ny":[21,20,23,19,22],"nz":[0,0,0,0,0]},{"size":2,"px":[9,18],"py":[8,16],"pz":[1,0],"nx":[14,6],"ny":[15,16],"nz":[0,-1]},{"size":3,"px":[3,4,7],"py":[3,3,9],"pz":[2,2,1],"nx":[8,9,7],"ny":[4,11,4],"nz":[1,-1,-1]},{"size":5,"px":[6,14,4,7,7],"py":[4,23,3,6,6],"pz":[1,0,2,1,-1],"nx":[2,0,2,1,3],"ny":[20,4,21,10,23],"nz":[0,2,0,1,0]},{"size":5,"px":[2,4,8,9,10],"py":[3,8,13,23,23],"pz":[2,1,0,0,0],"nx":[10,4,0,3,3],"ny":[21,3,0,3,23],"nz":[0,-1,-1,-1,-1]},{"size":3,"px":[11,10,11],"py":[6,5,5],"pz":[0,0,0],"nx":[14,6,1],"ny":[7,9,5],"nz":[0,1,-1]},{"size":5,"px":[11,11,11,11,6],"py":[11,12,10,13,6],"pz":[0,0,0,0,1],"nx":[9,13,13,13,4],"ny":[4,9,10,11,2],"nz":[1,0,0,0,-1]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,11],"ny":[4,7],"nz":[1,-1]},{"size":2,"px":[1,2],"py":[4,11],"pz":[2,0],"nx":[8,8],"ny":[15,15],"nz":[0,-1]},{"size":5,"px":[12,12,13,12,12],"py":[10,11,13,12,12],"pz":[0,0,0,0,-1],"nx":[0,0,0,1,0],"ny":[13,2,12,5,14],"nz":[0,2,0,0,0]},{"size":5,"px":[0,0,0,1,1],"py":[4,3,11,15,13],"pz":[1,2,0,0,0],"nx":[2,3,3,1,0],"ny":[2,4,4,5,14],"nz":[2,1,-1,-1,-1]},{"size":2,"px":[4,11],"py":[12,10],"pz":[0,-1],"nx":[1,2],"ny":[2,4],"nz":[2,1]},{"size":5,"px":[18,8,9,9,9],"py":[15,7,8,10,7],"pz":[0,1,1,1,1],"nx":[22,23,21,22,11],"ny":[20,16,23,19,9],"nz":[0,0,0,0,1]},{"size":5,"px":[14,12,13,14,15],"py":[1,0,0,0,1],"pz":[0,0,0,0,0],"nx":[4,9,4,7,7],"ny":[2,3,1,8,8],"nz":[2,1,2,1,-1]},{"size":2,"px":[13,9],"py":[14,19],"pz":[0,-1],"nx":[6,10],"ny":[0,2],"nz":[1,0]},{"size":2,"px":[13,12],"py":[4,4],"pz":[0,0],"nx":[3,3],"ny":[1,1],"nz":[2,-1]},{"size":3,"px":[14,5,5],"py":[18,3,4],"pz":[0,-1,-1],"nx":[8,7,8],"ny":[4,8,10],"nz":[1,1,1]},{"size":2,"px":[8,18],"py":[6,11],"pz":[1,0],"nx":[9,1],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[16,11],"py":[9,7],"pz":[0,0],"nx":[7,7],"ny":[4,4],"nz":[1,-1]},{"size":5,"px":[23,11,23,11,23],"py":[13,4,12,7,10],"pz":[0,1,0,1,0],"nx":[7,4,8,15,15],"ny":[9,2,4,8,8],"nz":[0,2,1,0,-1]},{"size":2,"px":[6,3],"py":[1,0],"pz":[0,1],"nx":[4,1],"ny":[1,2],"nz":[0,-1]},{"size":2,"px":[5,5],"py":[7,6],"pz":[0,1],"nx":[6,4],"ny":[9,11],"nz":[0,-1]},{"size":4,"px":[5,6,5,5],"py":[8,6,11,6],"pz":[1,1,1,0],"nx":[23,0,4,5],"ny":[0,2,2,1],"nz":[0,-1,-1,-1]},{"size":2,"px":[18,4],"py":[13,3],"pz":[0,-1],"nx":[15,4],"ny":[11,2],"nz":[0,2]},{"size":2,"px":[4,0],"py":[8,0],"pz":[1,-1],"nx":[9,2],"ny":[15,5],"nz":[0,2]},{"size":5,"px":[15,15,16,14,14],"py":[0,1,1,0,0],"pz":[0,0,0,0,-1],"nx":[4,4,8,8,15],"ny":[4,5,4,11,23],"nz":[2,2,1,1,0]},{"size":4,"px":[12,11,3,14],"py":[14,22,1,0],"pz":[0,-1,-1,-1],"nx":[8,15,7,16],"ny":[2,3,1,3],"nz":[1,0,1,0]},{"size":2,"px":[5,12],"py":[6,17],"pz":[1,-1],"nx":[2,1],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[13,12,12,7,7],"py":[5,6,5,14,14],"pz":[0,0,0,0,-1],"nx":[10,3,10,1,10],"ny":[13,8,11,3,10],"nz":[0,0,0,1,0]},{"size":2,"px":[4,4],"py":[15,0],"pz":[0,-1],"nx":[4,4],"ny":[16,17],"nz":[0,0]},{"size":5,"px":[1,4,2,1,2],"py":[4,0,1,1,0],"pz":[1,1,1,2,1],"nx":[4,9,1,5,1],"ny":[3,4,4,5,5],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[10,3],"py":[3,1],"pz":[0,2],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[16,0],"py":[21,0],"pz":[0,-1],"nx":[6,8],"ny":[8,4],"nz":[1,1]},{"size":2,"px":[7,11],"py":[4,18],"pz":[0,-1],"nx":[5,7],"ny":[0,2],"nz":[2,0]},{"size":2,"px":[9,7],"py":[0,3],"pz":[1,-1],"nx":[20,10],"ny":[0,1],"nz":[0,1]},{"size":4,"px":[10,4,1,5],"py":[0,6,8,4],"pz":[1,-1,-1,-1],"nx":[6,15,4,14],"ny":[3,5,1,5],"nz":[1,0,2,0]},{"size":2,"px":[4,4],"py":[3,4],"pz":[2,2],"nx":[9,2],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[8,4],"py":[3,4],"pz":[0,-1],"nx":[8,6],"ny":[2,1],"nz":[0,0]},{"size":2,"px":[2,0],"py":[6,3],"pz":[1,2],"nx":[0,7],"ny":[7,8],"nz":[1,-1]},{"size":2,"px":[10,0],"py":[7,3],"pz":[1,-1],"nx":[15,4],"ny":[14,4],"nz":[0,2]},{"size":4,"px":[3,1,2,2],"py":[20,7,18,17],"pz":[0,1,0,0],"nx":[9,5,5,4],"ny":[5,4,18,4],"nz":[1,-1,-1,-1]},{"size":2,"px":[5,4],"py":[3,1],"pz":[2,-1],"nx":[23,23],"ny":[14,13],"nz":[0,0]},{"size":2,"px":[12,4],"py":[6,1],"pz":[0,-1],"nx":[8,4],"ny":[4,4],"nz":[1,1]},{"size":5,"px":[22,22,11,11,11],"py":[12,13,4,6,6],"pz":[0,0,1,1,-1],"nx":[4,4,4,4,3],"ny":[16,15,18,14,11],"nz":[0,0,0,0,1]},{"size":2,"px":[4,10],"py":[0,1],"pz":[1,0],"nx":[2,2],"ny":[2,2],"nz":[2,-1]},{"size":2,"px":[15,6],"py":[4,4],"pz":[0,-1],"nx":[15,4],"ny":[2,1],"nz":[0,2]},{"size":2,"px":[11,2],"py":[10,20],"pz":[0,-1],"nx":[4,9],"ny":[1,2],"nz":[2,1]},{"size":2,"px":[4,19],"py":[3,8],"pz":[2,0],"nx":[8,21],"ny":[4,20],"nz":[1,-1]},{"size":5,"px":[4,6,7,6,2],"py":[6,15,13,14,3],"pz":[1,0,0,0,-1],"nx":[21,22,19,21,10],"ny":[6,12,0,3,2],"nz":[0,0,0,0,1]},{"size":5,"px":[8,12,15,14,13],"py":[0,0,0,0,0],"pz":[1,0,0,0,0],"nx":[4,3,1,3,4],"ny":[19,16,3,15,4],"nz":[0,0,2,0,1]},{"size":2,"px":[3,3],"py":[2,3],"pz":[2,2],"nx":[8,4],"ny":[4,1],"nz":[1,-1]},{"size":4,"px":[0,0,0,5],"py":[10,9,11,21],"pz":[1,1,-1,-1],"nx":[12,4,3,11],"ny":[3,1,1,3],"nz":[0,2,2,0]},{"size":2,"px":[3,1],"py":[0,0],"pz":[1,2],"nx":[1,4],"ny":[2,1],"nz":[1,-1]},{"size":5,"px":[2,5,1,0,1],"py":[14,23,7,5,9],"pz":[0,0,1,1,1],"nx":[0,0,7,9,11],"ny":[23,22,4,9,3],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[8,9],"py":[7,1],"pz":[1,-1],"nx":[8,8],"ny":[8,9],"nz":[1,1]},{"size":2,"px":[11,9],"py":[11,3],"pz":[1,-1],"nx":[3,2],"ny":[14,10],"nz":[0,1]},{"size":4,"px":[2,4,5,4],"py":[8,20,22,16],"pz":[1,0,0,0],"nx":[8,2,11,3],"ny":[7,4,15,4],"nz":[0,-1,-1,-1]},{"size":3,"px":[1,2,3],"py":[2,1,0],"pz":[0,0,0],"nx":[0,0,15],"ny":[1,0,11],"nz":[0,0,-1]},{"size":2,"px":[12,22],"py":[6,7],"pz":[0,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":3,"px":[13,0,5],"py":[19,10,2],"pz":[0,-1,-1],"nx":[3,4,6],"ny":[5,5,9],"nz":[2,2,1]},{"size":2,"px":[8,15],"py":[8,22],"pz":[1,0],"nx":[7,4],"ny":[10,7],"nz":[1,-1]},{"size":2,"px":[10,10],"py":[7,6],"pz":[1,1],"nx":[10,1],"ny":[9,0],"nz":[1,-1]},{"size":2,"px":[9,11],"py":[4,3],"pz":[0,-1],"nx":[5,9],"ny":[0,1],"nz":[1,0]},{"size":5,"px":[14,13,14,12,15],"py":[1,2,2,2,2],"pz":[0,0,0,0,0],"nx":[4,8,4,7,4],"ny":[2,4,3,4,4],"nz":[2,1,2,1,-1]},{"size":3,"px":[13,8,2],"py":[14,5,8],"pz":[0,-1,-1],"nx":[6,8,9],"ny":[3,2,2],"nz":[0,0,0]},{"size":3,"px":[3,6,8],"py":[7,4,12],"pz":[1,1,0],"nx":[3,8,9],"ny":[5,2,2],"nz":[1,-1,-1]},{"size":2,"px":[13,4],"py":[16,3],"pz":[0,2],"nx":[13,7],"ny":[15,5],"nz":[0,-1]},{"size":2,"px":[3,0],"py":[7,9],"pz":[1,-1],"nx":[2,8],"ny":[2,4],"nz":[2,1]},{"size":5,"px":[3,6,8,7,7],"py":[0,1,0,0,0],"pz":[1,0,0,0,-1],"nx":[7,9,4,3,4],"ny":[9,7,4,2,2],"nz":[1,1,1,2,2]},{"size":3,"px":[3,4,16],"py":[4,4,6],"pz":[1,2,0],"nx":[2,2,2],"ny":[0,0,1],"nz":[0,-1,-1]},{"size":2,"px":[0,0],"py":[1,0],"pz":[2,2],"nx":[5,5],"ny":[2,2],"nz":[1,-1]},{"size":2,"px":[9,3],"py":[7,20],"pz":[1,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[8,21],"py":[10,18],"pz":[0,-1],"nx":[9,4],"ny":[10,4],"nz":[0,1]},{"size":2,"px":[6,13],"py":[6,23],"pz":[1,-1],"nx":[10,10],"ny":[11,12],"nz":[0,0]},{"size":5,"px":[10,9,5,10,10],"py":[9,13,6,10,10],"pz":[0,0,1,0,-1],"nx":[21,21,21,10,21],"ny":[18,20,19,11,17],"nz":[0,0,0,1,0]},{"size":2,"px":[8,8],"py":[7,6],"pz":[1,1],"nx":[8,1],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[11,4],"py":[14,7],"pz":[0,-1],"nx":[13,13],"ny":[13,11],"nz":[0,0]},{"size":2,"px":[4,4],"py":[4,5],"pz":[2,2],"nx":[12,5],"ny":[16,2],"nz":[0,-1]},{"size":3,"px":[1,3,20],"py":[3,9,2],"pz":[2,-1,-1],"nx":[0,0,0],"ny":[7,4,13],"nz":[1,2,0]},{"size":2,"px":[0,0],"py":[4,2],"pz":[1,2],"nx":[1,0],"ny":[4,4],"nz":[1,-1]},{"size":3,"px":[8,9,11],"py":[2,1,2],"pz":[0,0,0],"nx":[2,2,0],"ny":[2,2,13],"nz":[2,-1,-1]},{"size":2,"px":[1,10],"py":[23,5],"pz":[0,-1],"nx":[3,6],"ny":[1,1],"nz":[2,1]},{"size":4,"px":[13,6,3,4],"py":[8,6,4,2],"pz":[0,-1,-1,-1],"nx":[1,1,1,4],"ny":[9,7,8,20],"nz":[1,1,1,0]},{"size":5,"px":[11,4,4,10,3],"py":[9,16,13,12,7],"pz":[0,0,0,0,0],"nx":[7,11,3,17,4],"ny":[8,11,9,0,4],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[6,6],"py":[6,8],"pz":[1,-1],"nx":[0,0],"ny":[1,2],"nz":[2,2]},{"size":2,"px":[10,5],"py":[7,2],"pz":[0,-1],"nx":[4,13],"ny":[5,9],"nz":[2,0]},{"size":2,"px":[10,5],"py":[8,2],"pz":[1,-1],"nx":[16,4],"ny":[14,5],"nz":[0,2]},{"size":2,"px":[1,1],"py":[16,15],"pz":[0,0],"nx":[1,20],"ny":[23,1],"nz":[0,-1]},{"size":2,"px":[2,3],"py":[4,7],"pz":[2,1],"nx":[2,3],"ny":[5,4],"nz":[2,-1]},{"size":2,"px":[19,8],"py":[5,4],"pz":[0,-1],"nx":[10,10],"ny":[1,3],"nz":[1,1]},{"size":2,"px":[21,21],"py":[18,16],"pz":[0,0],"nx":[10,3],"ny":[17,5],"nz":[0,-1]},{"size":2,"px":[9,2],"py":[23,4],"pz":[0,2],"nx":[5,11],"ny":[3,7],"nz":[2,1]},{"size":2,"px":[7,0],"py":[3,2],"pz":[0,-1],"nx":[3,6],"ny":[1,1],"nz":[1,0]},{"size":4,"px":[5,9,8,9],"py":[8,12,13,18],"pz":[0,0,0,0],"nx":[6,5,2,5],"ny":[8,4,7,11],"nz":[0,-1,-1,-1]},{"size":2,"px":[7,2],"py":[0,0],"pz":[0,2],"nx":[5,5],"ny":[3,4],"nz":[1,-1]},{"size":2,"px":[11,11],"py":[12,13],"pz":[0,0],"nx":[9,1],"ny":[14,3],"nz":[0,-1]},{"size":5,"px":[8,16,9,4,15],"py":[11,13,8,4,12],"pz":[1,0,1,2,0],"nx":[3,3,3,3,4],"ny":[4,2,1,3,0],"nz":[0,0,0,0,0]},{"size":2,"px":[9,5],"py":[7,6],"pz":[1,-1],"nx":[19,8],"ny":[17,11],"nz":[0,1]},{"size":5,"px":[14,15,12,13,13],"py":[2,2,2,2,2],"pz":[0,0,0,0,-1],"nx":[20,9,19,20,4],"ny":[14,2,5,15,1],"nz":[0,1,0,0,2]},{"size":2,"px":[18,8],"py":[20,7],"pz":[0,1],"nx":[4,9],"ny":[2,2],"nz":[2,-1]},{"size":2,"px":[6,3],"py":[11,5],"pz":[1,2],"nx":[13,19],"ny":[20,20],"nz":[0,-1]},{"size":3,"px":[12,11,3],"py":[20,20,5],"pz":[0,0,-1],"nx":[11,12,6],"ny":[21,21,10],"nz":[0,0,1]},{"size":2,"px":[3,6],"py":[7,14],"pz":[1,0],"nx":[3,13],"ny":[4,8],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[5,9],"pz":[2,1],"nx":[2,11],"ny":[8,6],"nz":[1,-1]},{"size":2,"px":[2,2],"py":[5,5],"pz":[1,-1],"nx":[0,0],"ny":[6,3],"nz":[1,2]},{"size":2,"px":[11,23],"py":[5,9],"pz":[1,0],"nx":[8,2],"ny":[11,0],"nz":[0,-1]},{"size":2,"px":[11,23],"py":[12,9],"pz":[0,-1],"nx":[11,22],"ny":[10,21],"nz":[1,0]},{"size":2,"px":[12,12],"py":[7,7],"pz":[0,-1],"nx":[5,4],"ny":[7,10],"nz":[1,1]},{"size":2,"px":[9,8],"py":[18,1],"pz":[0,-1],"nx":[5,4],"ny":[8,10],"nz":[1,1]},{"size":2,"px":[16,17],"py":[11,11],"pz":[0,0],"nx":[15,2],"ny":[9,4],"nz":[0,-1]},{"size":2,"px":[0,1],"py":[3,0],"pz":[2,-1],"nx":[9,10],"ny":[6,5],"nz":[1,1]},{"size":2,"px":[13,13],"py":[20,21],"pz":[0,-1],"nx":[2,2],"ny":[6,5],"nz":[1,1]},{"size":5,"px":[20,20,4,18,19],"py":[17,16,5,22,20],"pz":[0,0,2,0,0],"nx":[8,11,5,6,2],"ny":[10,15,11,10,1],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[11,11],"py":[4,4],"pz":[0,-1],"nx":[8,4],"ny":[4,4],"nz":[1,1]},{"size":3,"px":[6,5,6],"py":[8,10,10],"pz":[1,1,1],"nx":[11,8,22],"ny":[19,2,15],"nz":[0,-1,-1]},{"size":3,"px":[5,2,13],"py":[7,10,10],"pz":[1,-1,-1],"nx":[11,11,23],"ny":[8,9,14],"nz":[1,1,0]},{"size":5,"px":[3,6,1,5,10],"py":[7,14,1,9,2],"pz":[1,-1,-1,-1,-1],"nx":[11,0,1,5,1],"ny":[14,12,18,5,19],"nz":[0,0,0,1,0]},{"size":3,"px":[21,21,10],"py":[16,17,10],"pz":[0,0,1],"nx":[5,5,1],"ny":[9,9,18],"nz":[1,-1,-1]},{"size":2,"px":[6,21],"py":[6,17],"pz":[1,-1],"nx":[20,10],"ny":[7,4],"nz":[0,1]},{"size":2,"px":[10,11],"py":[0,0],"pz":[1,-1],"nx":[6,13],"ny":[2,4],"nz":[1,0]},{"size":4,"px":[4,4,7,9],"py":[3,4,10,3],"pz":[2,2,1,1],"nx":[21,2,15,5],"ny":[0,0,0,2],"nz":[0,-1,-1,-1]},{"size":3,"px":[11,11,11],"py":[7,6,9],"pz":[1,1,1],"nx":[23,4,9],"ny":[23,5,6],"nz":[0,-1,-1]},{"size":2,"px":[14,15],"py":[1,1],"pz":[0,0],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[11,23,11,23,23],"py":[11,22,10,21,20],"pz":[1,0,1,0,0],"nx":[10,9,19,10,10],"ny":[10,11,20,9,9],"nz":[1,1,0,1,-1]},{"size":2,"px":[7,23],"py":[13,22],"pz":[0,-1],"nx":[8,4],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[12,1],"py":[19,0],"pz":[0,-1],"nx":[11,12],"ny":[22,17],"nz":[0,0]},{"size":2,"px":[10,8],"py":[4,3],"pz":[1,-1],"nx":[5,23],"ny":[2,7],"nz":[2,0]},{"size":2,"px":[9,10],"py":[6,20],"pz":[1,-1],"nx":[8,8],"ny":[4,6],"nz":[1,1]}],"alpha":[-1.135386e+00,1.135386e+00,-9.090800e-01,9.090800e-01,-5.913780e-01,5.913780e-01,-5.556534e-01,5.556534e-01,-5.084150e-01,5.084150e-01,-4.464489e-01,4.464489e-01,-4.463241e-01,4.463241e-01,-4.985226e-01,4.985226e-01,-4.424638e-01,4.424638e-01,-4.300093e-01,4.300093e-01,-4.231341e-01,4.231341e-01,-4.087428e-01,4.087428e-01,-3.374480e-01,3.374480e-01,-3.230151e-01,3.230151e-01,-3.084427e-01,3.084427e-01,-3.235494e-01,3.235494e-01,-2.589281e-01,2.589281e-01,-2.970292e-01,2.970292e-01,-2.957065e-01,2.957065e-01,-3.997619e-01,3.997619e-01,-3.535901e-01,3.535901e-01,-2.725396e-01,2.725396e-01,-2.649725e-01,2.649725e-01,-3.103888e-01,3.103888e-01,-3.117775e-01,3.117775e-01,-2.589620e-01,2.589620e-01,-2.689202e-01,2.689202e-01,-2.127024e-01,2.127024e-01,-2.436322e-01,2.436322e-01,-3.120574e-01,3.120574e-01,-2.786010e-01,2.786010e-01,-2.649072e-01,2.649072e-01,-2.766509e-01,2.766509e-01,-2.367237e-01,2.367237e-01,-2.658049e-01,2.658049e-01,-2.103463e-01,2.103463e-01,-1.911522e-01,1.911522e-01,-2.535425e-01,2.535425e-01,-2.434696e-01,2.434696e-01,-2.180788e-01,2.180788e-01,-2.496873e-01,2.496873e-01,-2.700969e-01,2.700969e-01,-2.565479e-01,2.565479e-01,-2.737741e-01,2.737741e-01,-1.675507e-01,1.675507e-01,-2.551417e-01,2.551417e-01,-2.067648e-01,2.067648e-01,-1.636834e-01,1.636834e-01,-2.129306e-01,2.129306e-01,-1.656758e-01,1.656758e-01,-1.919369e-01,1.919369e-01,-2.031763e-01,2.031763e-01,-2.062327e-01,2.062327e-01,-2.577950e-01,2.577950e-01,-2.951823e-01,2.951823e-01,-2.023160e-01,2.023160e-01,-2.022234e-01,2.022234e-01,-2.132906e-01,2.132906e-01,-1.653278e-01,1.653278e-01,-1.648474e-01,1.648474e-01,-1.593352e-01,1.593352e-01,-1.735650e-01,1.735650e-01,-1.688778e-01,1.688778e-01,-1.519705e-01,1.519705e-01,-1.812202e-01,1.812202e-01,-1.967481e-01,1.967481e-01,-1.852954e-01,1.852954e-01,-2.317780e-01,2.317780e-01,-2.036251e-01,2.036251e-01,-1.609324e-01,1.609324e-01,-2.160205e-01,2.160205e-01,-2.026190e-01,2.026190e-01,-1.854761e-01,1.854761e-01,-1.832038e-01,1.832038e-01,-2.001141e-01,2.001141e-01,-1.418333e-01,1.418333e-01,-1.704773e-01,1.704773e-01,-1.586261e-01,1.586261e-01,-1.587582e-01,1.587582e-01,-1.899489e-01,1.899489e-01,-1.477160e-01,1.477160e-01,-2.260467e-01,2.260467e-01,-2.393598e-01,2.393598e-01,-1.582373e-01,1.582373e-01,-1.702498e-01,1.702498e-01,-1.737398e-01,1.737398e-01,-1.462529e-01,1.462529e-01,-1.396517e-01,1.396517e-01,-1.629625e-01,1.629625e-01,-1.446933e-01,1.446933e-01,-1.811657e-01,1.811657e-01,-1.336427e-01,1.336427e-01,-1.924813e-01,1.924813e-01,-1.457520e-01,1.457520e-01,-1.600259e-01,1.600259e-01,-1.297000e-01,1.297000e-01,-2.076199e-01,2.076199e-01,-1.510060e-01,1.510060e-01,-1.914568e-01,1.914568e-01,-2.138162e-01,2.138162e-01,-1.856916e-01,1.856916e-01,-1.843047e-01,1.843047e-01,-1.526846e-01,1.526846e-01,-1.328320e-01,1.328320e-01,-1.751311e-01,1.751311e-01,-1.643908e-01,1.643908e-01,-1.482706e-01,1.482706e-01,-1.622298e-01,1.622298e-01,-1.884979e-01,1.884979e-01,-1.633604e-01,1.633604e-01,-1.554166e-01,1.554166e-01,-1.405332e-01,1.405332e-01,-1.772398e-01,1.772398e-01,-1.410008e-01,1.410008e-01,-1.362301e-01,1.362301e-01,-1.709087e-01,1.709087e-01,-1.584613e-01,1.584613e-01,-1.188814e-01,1.188814e-01,-1.423888e-01,1.423888e-01,-1.345565e-01,1.345565e-01,-1.835986e-01,1.835986e-01,-1.445329e-01,1.445329e-01,-1.385826e-01,1.385826e-01,-1.558917e-01,1.558917e-01,-1.476053e-01,1.476053e-01,-1.370722e-01,1.370722e-01,-2.362666e-01,2.362666e-01,-2.907774e-01,2.907774e-01,-1.656360e-01,1.656360e-01,-1.644407e-01,1.644407e-01,-1.443394e-01,1.443394e-01,-1.438823e-01,1.438823e-01,-1.476964e-01,1.476964e-01,-1.956593e-01,1.956593e-01,-2.417519e-01,2.417519e-01,-1.659315e-01,1.659315e-01,-1.466254e-01,1.466254e-01,-2.034909e-01,2.034909e-01,-2.128771e-01,2.128771e-01,-1.665429e-01,1.665429e-01,-1.387131e-01,1.387131e-01,-1.298823e-01,1.298823e-01,-1.329495e-01,1.329495e-01,-1.769587e-01,1.769587e-01,-1.366530e-01,1.366530e-01,-1.254359e-01,1.254359e-01,-1.673022e-01,1.673022e-01,-1.602519e-01,1.602519e-01,-1.897245e-01,1.897245e-01,-1.893579e-01,1.893579e-01,-1.579350e-01,1.579350e-01,-1.472589e-01,1.472589e-01,-1.614193e-01,1.614193e-01]},{"count":203,"threshold":-4.769677e+00,"feature":[{"size":5,"px":[12,5,14,9,7],"py":[9,13,3,1,3],"pz":[0,0,0,0,0],"nx":[1,0,5,14,9],"ny":[5,3,8,8,9],"nz":[2,0,1,0,0]},{"size":5,"px":[14,13,11,17,12],"py":[2,2,4,13,3],"pz":[0,0,0,0,0],"nx":[7,22,8,23,22],"ny":[8,15,11,12,3],"nz":[1,0,1,0,0]},{"size":5,"px":[9,11,11,11,16],"py":[4,8,7,9,12],"pz":[0,0,0,0,0],"nx":[4,8,14,9,9],"ny":[4,4,8,8,8],"nz":[1,1,0,0,-1]},{"size":5,"px":[6,12,12,8,3],"py":[11,7,8,10,2],"pz":[0,0,0,0,2],"nx":[8,4,4,4,0],"ny":[4,4,4,11,0],"nz":[1,1,-1,-1,-1]},{"size":5,"px":[19,17,18,9,9],"py":[3,2,3,1,1],"pz":[0,0,0,1,-1],"nx":[21,21,10,22,22],"ny":[1,2,0,4,3],"nz":[0,0,1,0,0]},{"size":2,"px":[4,7],"py":[4,6],"pz":[2,1],"nx":[8,7],"ny":[4,10],"nz":[1,1]},{"size":5,"px":[14,17,17,13,12],"py":[18,15,16,18,18],"pz":[0,0,0,0,0],"nx":[13,19,5,20,6],"ny":[16,4,1,19,0],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[6,7,4,5,5],"py":[15,23,6,12,16],"pz":[0,0,1,0,0],"nx":[3,14,14,6,6],"ny":[4,11,11,9,0],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[16,9,6,3,11],"py":[2,2,5,3,2],"pz":[0,0,1,2,0],"nx":[3,4,2,5,5],"ny":[4,11,2,8,8],"nz":[1,1,2,1,-1]},{"size":5,"px":[6,1,5,3,3],"py":[14,4,15,7,7],"pz":[0,2,0,1,-1],"nx":[0,0,1,1,1],"ny":[7,8,18,17,5],"nz":[1,1,0,0,2]},{"size":5,"px":[12,12,9,5,3],"py":[14,14,0,3,7],"pz":[0,-1,-1,-1,-1],"nx":[7,7,14,8,13],"ny":[7,8,13,10,10],"nz":[1,1,0,1,0]},{"size":2,"px":[3,4],"py":[7,9],"pz":[1,-1],"nx":[2,4],"ny":[5,4],"nz":[2,1]},{"size":3,"px":[10,21,17],"py":[7,11,23],"pz":[1,0,0],"nx":[21,9,3],"ny":[23,5,5],"nz":[0,-1,-1]},{"size":5,"px":[8,11,9,10,11],"py":[2,0,1,1,2],"pz":[0,0,0,0,0],"nx":[4,5,6,4,3],"ny":[8,4,18,7,4],"nz":[1,1,0,1,-1]},{"size":5,"px":[20,22,3,19,10],"py":[20,9,4,22,3],"pz":[0,0,2,0,1],"nx":[8,20,8,3,2],"ny":[4,3,6,4,3],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[4,4],"py":[8,7],"pz":[1,1],"nx":[9,2],"ny":[15,5],"nz":[0,-1]},{"size":2,"px":[11,13],"py":[13,4],"pz":[0,-1],"nx":[20,21],"ny":[1,4],"nz":[0,0]},{"size":5,"px":[1,2,7,6,8],"py":[0,2,3,3,3],"pz":[2,1,0,0,0],"nx":[1,2,1,1,1],"ny":[0,0,4,3,3],"nz":[1,0,0,0,-1]},{"size":2,"px":[3,10],"py":[9,11],"pz":[0,0],"nx":[6,3],"ny":[9,2],"nz":[0,-1]},{"size":5,"px":[12,12,12,12,6],"py":[10,11,13,12,6],"pz":[0,0,0,0,-1],"nx":[10,2,1,10,10],"ny":[10,4,2,11,9],"nz":[0,1,2,0,0]},{"size":5,"px":[16,18,11,17,15],"py":[11,12,8,12,11],"pz":[0,0,0,0,0],"nx":[14,0,19,0,10],"ny":[9,3,14,8,9],"nz":[0,-1,-1,-1,-1]},{"size":4,"px":[5,9,5,8],"py":[21,18,20,23],"pz":[0,0,0,0],"nx":[8,4,3,1],"ny":[20,3,4,3],"nz":[0,-1,-1,-1]},{"size":2,"px":[2,3],"py":[3,2],"pz":[2,2],"nx":[3,12],"ny":[4,23],"nz":[1,-1]},{"size":5,"px":[0,1,1,1,1],"py":[2,16,14,13,12],"pz":[2,0,0,0,0],"nx":[8,4,9,4,7],"ny":[9,3,4,2,9],"nz":[1,2,1,2,1]},{"size":2,"px":[4,9],"py":[3,7],"pz":[2,-1],"nx":[4,9],"ny":[2,4],"nz":[2,1]},{"size":5,"px":[15,16,17,15,8],"py":[3,3,3,18,1],"pz":[0,0,0,0,1],"nx":[1,2,2,1,3],"ny":[5,3,2,6,0],"nz":[0,0,0,0,0]},{"size":2,"px":[4,17],"py":[4,14],"pz":[2,0],"nx":[15,7],"ny":[15,10],"nz":[0,-1]},{"size":3,"px":[14,12,3],"py":[3,13,3],"pz":[0,-1,-1],"nx":[4,17,4],"ny":[3,19,4],"nz":[2,0,2]},{"size":4,"px":[4,5,12,2],"py":[9,6,19,4],"pz":[1,1,0,2],"nx":[12,17,4,4],"ny":[18,19,4,4],"nz":[0,-1,-1,-1]},{"size":5,"px":[10,19,20,20,19],"py":[7,14,13,14,13],"pz":[1,0,0,0,-1],"nx":[11,23,23,23,23],"ny":[9,15,13,16,14],"nz":[1,0,0,0,0]},{"size":4,"px":[0,0,0,2],"py":[5,6,5,14],"pz":[1,1,2,0],"nx":[0,3,3,17],"ny":[23,5,5,9],"nz":[0,-1,-1,-1]},{"size":2,"px":[15,4],"py":[23,5],"pz":[0,2],"nx":[9,3],"ny":[4,4],"nz":[1,-1]},{"size":4,"px":[6,5,10,12],"py":[3,3,23,23],"pz":[1,1,0,0],"nx":[11,1,1,4],"ny":[21,3,5,5],"nz":[0,-1,-1,-1]},{"size":2,"px":[5,2],"py":[9,4],"pz":[1,2],"nx":[4,9],"ny":[4,2],"nz":[1,-1]},{"size":5,"px":[23,23,23,23,23],"py":[14,9,13,11,12],"pz":[0,0,0,0,0],"nx":[6,13,7,8,8],"ny":[9,6,3,3,3],"nz":[1,0,1,1,-1]},{"size":2,"px":[10,3],"py":[4,5],"pz":[0,-1],"nx":[3,8],"ny":[1,3],"nz":[2,1]},{"size":2,"px":[3,12],"py":[4,18],"pz":[2,0],"nx":[12,0],"ny":[16,3],"nz":[0,-1]},{"size":2,"px":[16,2],"py":[4,4],"pz":[0,-1],"nx":[16,4],"ny":[1,0],"nz":[0,2]},{"size":2,"px":[3,4],"py":[7,1],"pz":[1,-1],"nx":[5,3],"ny":[19,9],"nz":[0,1]},{"size":4,"px":[20,19,20,21],"py":[2,0,1,3],"pz":[0,0,0,0],"nx":[11,5,23,11],"ny":[0,0,1,1],"nz":[1,2,0,1]},{"size":2,"px":[12,13],"py":[7,5],"pz":[0,0],"nx":[8,5],"ny":[3,5],"nz":[1,-1]},{"size":5,"px":[22,21,22,22,22],"py":[20,22,18,19,16],"pz":[0,0,0,0,0],"nx":[2,3,3,15,15],"ny":[4,5,4,7,7],"nz":[1,2,1,0,-1]},{"size":3,"px":[15,14,14],"py":[1,1,1],"pz":[0,0,-1],"nx":[17,18,16],"ny":[1,2,1],"nz":[0,0,0]},{"size":4,"px":[17,16,16,15],"py":[2,1,0,0],"pz":[0,0,0,0],"nx":[7,4,2,11],"ny":[11,2,1,4],"nz":[1,2,-1,-1]},{"size":4,"px":[18,0,0,0],"py":[14,6,5,4],"pz":[0,-1,-1,-1],"nx":[19,19,19,19],"ny":[16,19,17,18],"nz":[0,0,0,0]},{"size":4,"px":[11,5,5,0],"py":[14,1,4,4],"pz":[0,-1,-1,-1],"nx":[11,8,2,15],"ny":[17,14,1,9],"nz":[0,0,2,0]},{"size":2,"px":[4,5],"py":[19,21],"pz":[0,0],"nx":[10,2],"ny":[15,4],"nz":[0,-1]},{"size":2,"px":[6,4],"py":[4,6],"pz":[1,1],"nx":[3,3],"ny":[4,5],"nz":[1,-1]},{"size":2,"px":[2,7],"py":[1,13],"pz":[2,0],"nx":[7,2],"ny":[1,4],"nz":[1,-1]},{"size":4,"px":[15,10,4,7],"py":[23,3,1,7],"pz":[0,1,2,1],"nx":[0,4,1,1],"ny":[0,2,0,-1900147915],"nz":[0,-1,-1,-1]},{"size":2,"px":[7,2],"py":[12,11],"pz":[0,-1],"nx":[2,4],"ny":[2,5],"nz":[2,1]},{"size":5,"px":[0,0,0,1,0],"py":[9,4,3,2,6],"pz":[0,1,2,1,1],"nx":[9,4,2,16,16],"ny":[7,4,2,8,8],"nz":[0,1,2,0,-1]},{"size":5,"px":[18,4,9,4,4],"py":[12,5,6,3,4],"pz":[0,2,1,2,-1],"nx":[4,3,3,2,3],"ny":[23,19,21,16,18],"nz":[0,0,0,0,0]},{"size":2,"px":[6,6],"py":[14,13],"pz":[0,0],"nx":[3,10],"ny":[4,7],"nz":[1,-1]},{"size":5,"px":[3,4,4,2,2],"py":[8,11,7,4,4],"pz":[1,1,1,2,-1],"nx":[20,18,19,20,19],"ny":[4,0,2,3,1],"nz":[0,0,0,0,0]},{"size":5,"px":[17,12,14,8,16],"py":[2,0,0,0,0],"pz":[0,0,0,1,0],"nx":[3,15,3,2,2],"ny":[2,9,7,2,2],"nz":[2,0,1,2,-1]},{"size":5,"px":[11,10,11,11,11],"py":[10,12,11,12,12],"pz":[0,0,0,0,-1],"nx":[13,13,20,10,13],"ny":[9,11,8,4,10],"nz":[0,0,0,1,0]},{"size":2,"px":[8,16],"py":[7,13],"pz":[1,0],"nx":[8,13],"ny":[4,11],"nz":[1,-1]},{"size":2,"px":[6,7],"py":[20,3],"pz":[0,-1],"nx":[3,4],"ny":[10,10],"nz":[1,1]},{"size":3,"px":[13,10,17],"py":[9,3,5],"pz":[0,-1,-1],"nx":[1,3,1],"ny":[5,16,6],"nz":[2,0,1]},{"size":2,"px":[0,0],"py":[5,5],"pz":[2,-1],"nx":[8,3],"ny":[14,10],"nz":[0,1]},{"size":4,"px":[11,9,12,10],"py":[2,2,2,2],"pz":[0,0,0,0],"nx":[4,4,4,10],"ny":[5,5,0,16],"nz":[1,-1,-1,-1]},{"size":3,"px":[7,9,12],"py":[2,2,2],"pz":[1,-1,-1],"nx":[4,7,2],"ny":[3,1,0],"nz":[0,0,2]},{"size":2,"px":[2,4],"py":[3,12],"pz":[2,0],"nx":[7,4],"ny":[6,5],"nz":[1,2]},{"size":4,"px":[12,12,6,3],"py":[12,11,21,7],"pz":[0,0,-1,-1],"nx":[1,0,0,0],"ny":[13,3,6,5],"nz":[0,2,1,1]},{"size":3,"px":[3,1,3],"py":[21,8,18],"pz":[0,1,0],"nx":[11,20,0],"ny":[17,17,6],"nz":[0,-1,-1]},{"size":2,"px":[2,8],"py":[3,12],"pz":[2,0],"nx":[2,20],"ny":[4,17],"nz":[1,-1]},{"size":5,"px":[2,3,4,3,2],"py":[10,14,14,15,13],"pz":[1,0,0,0,0],"nx":[0,0,1,0,0],"ny":[21,20,23,19,19],"nz":[0,0,0,0,-1]},{"size":2,"px":[2,15],"py":[7,4],"pz":[1,-1],"nx":[3,8],"ny":[4,14],"nz":[1,0]},{"size":5,"px":[19,14,12,15,4],"py":[8,12,10,16,2],"pz":[0,0,0,0,2],"nx":[8,0,12,4,0],"ny":[4,1,12,2,19],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[18,9],"py":[15,3],"pz":[0,-1],"nx":[8,15],"ny":[9,14],"nz":[1,0]},{"size":5,"px":[4,2,3,4,9],"py":[9,4,3,8,23],"pz":[1,2,1,1,0],"nx":[11,23,23,11,11],"ny":[0,2,3,1,1],"nz":[1,0,0,1,-1]},{"size":2,"px":[6,7],"py":[1,1],"pz":[0,0],"nx":[3,4],"ny":[10,5],"nz":[1,-1]},{"size":4,"px":[11,9,8,5],"py":[12,15,13,3],"pz":[0,-1,-1,-1],"nx":[3,12,14,13],"ny":[0,3,3,3],"nz":[2,0,0,0]},{"size":2,"px":[11,11],"py":[6,5],"pz":[0,0],"nx":[8,11],"ny":[4,20],"nz":[1,-1]},{"size":5,"px":[21,20,21,21,21],"py":[18,21,17,19,19],"pz":[0,0,0,0,-1],"nx":[2,5,4,4,5],"ny":[5,12,11,10,10],"nz":[1,0,0,0,0]},{"size":5,"px":[1,1,1,1,1],"py":[10,11,7,9,8],"pz":[0,0,0,0,0],"nx":[11,23,23,23,23],"ny":[10,20,21,19,19],"nz":[1,0,0,0,-1]},{"size":5,"px":[7,8,7,3,1],"py":[14,13,13,2,2],"pz":[0,0,-1,-1,-1],"nx":[1,10,2,2,10],"ny":[2,13,4,16,12],"nz":[2,0,1,0,0]},{"size":2,"px":[17,18],"py":[12,12],"pz":[0,0],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[17,0],"py":[5,20],"pz":[0,-1],"nx":[4,9],"ny":[0,2],"nz":[2,1]},{"size":5,"px":[22,22,22,11,23],"py":[16,15,14,6,13],"pz":[0,0,0,1,0],"nx":[16,15,7,9,9],"ny":[15,8,4,10,10],"nz":[0,0,1,1,-1]},{"size":2,"px":[13,3],"py":[3,1],"pz":[0,2],"nx":[8,3],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[5,6],"py":[4,1],"pz":[1,-1],"nx":[6,3],"ny":[4,2],"nz":[1,2]},{"size":3,"px":[4,2,6],"py":[6,3,4],"pz":[1,2,1],"nx":[10,0,4],"ny":[9,4,3],"nz":[0,-1,-1]},{"size":4,"px":[2,8,4,10],"py":[4,23,7,23],"pz":[2,0,1,0],"nx":[9,4,11,9],"ny":[21,5,16,0],"nz":[0,-1,-1,-1]},{"size":2,"px":[6,3],"py":[13,0],"pz":[0,-1],"nx":[8,2],"ny":[11,2],"nz":[0,2]},{"size":2,"px":[3,3],"py":[1,4],"pz":[1,-1],"nx":[3,5],"ny":[0,1],"nz":[1,0]},{"size":2,"px":[7,2],"py":[0,0],"pz":[0,2],"nx":[2,10],"ny":[1,6],"nz":[2,0]},{"size":2,"px":[10,2],"py":[7,0],"pz":[1,-1],"nx":[21,5],"ny":[15,4],"nz":[0,2]},{"size":2,"px":[1,1],"py":[10,9],"pz":[0,0],"nx":[0,3],"ny":[13,11],"nz":[0,-1]},{"size":2,"px":[11,9],"py":[13,0],"pz":[0,-1],"nx":[3,3],"ny":[4,3],"nz":[1,1]},{"size":5,"px":[14,13,13,14,14],"py":[12,10,11,13,13],"pz":[0,0,0,0,-1],"nx":[9,8,4,5,7],"ny":[4,4,2,2,4],"nz":[0,0,1,1,0]},{"size":3,"px":[2,4,1],"py":[2,0,0],"pz":[0,0,1],"nx":[0,7,4],"ny":[0,3,2],"nz":[1,-1,-1]},{"size":2,"px":[11,4],"py":[5,0],"pz":[0,-1],"nx":[8,6],"ny":[4,9],"nz":[1,1]},{"size":3,"px":[0,0,0],"py":[20,2,4],"pz":[0,-1,-1],"nx":[12,3,10],"ny":[3,1,3],"nz":[0,2,0]},{"size":5,"px":[5,11,10,13,13],"py":[0,0,0,2,2],"pz":[1,0,0,0,-1],"nx":[4,5,5,4,5],"ny":[14,0,2,6,1],"nz":[0,0,0,0,0]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,11],"ny":[4,1],"nz":[1,-1]},{"size":2,"px":[14,-1715597992],"py":[19,9],"pz":[0,-1],"nx":[7,14],"ny":[10,17],"nz":[1,0]},{"size":2,"px":[11,1],"py":[9,0],"pz":[0,-1],"nx":[1,12],"ny":[2,10],"nz":[2,0]},{"size":2,"px":[17,9],"py":[13,17],"pz":[0,-1],"nx":[8,4],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[0,7],"py":[1,9],"pz":[1,-1],"nx":[18,4],"ny":[14,2],"nz":[0,2]},{"size":2,"px":[14,7],"py":[23,9],"pz":[0,-1],"nx":[4,8],"ny":[5,10],"nz":[2,1]},{"size":2,"px":[8,7],"py":[17,9],"pz":[0,-1],"nx":[3,2],"ny":[0,3],"nz":[0,0]},{"size":2,"px":[13,4],"py":[20,1],"pz":[0,-1],"nx":[5,3],"ny":[21,17],"nz":[0,0]},{"size":3,"px":[0,0,1],"py":[3,6,15],"pz":[2,1,0],"nx":[10,8,3],"ny":[6,4,2],"nz":[0,-1,-1]},{"size":2,"px":[8,8],"py":[18,8],"pz":[0,-1],"nx":[5,4],"ny":[8,10],"nz":[1,1]},{"size":2,"px":[6,5],"py":[2,2],"pz":[1,1],"nx":[8,9],"ny":[4,3],"nz":[1,-1]},{"size":2,"px":[6,3],"py":[11,5],"pz":[1,2],"nx":[13,3],"ny":[19,2],"nz":[0,-1]},{"size":2,"px":[4,6],"py":[1,11],"pz":[2,-1],"nx":[3,2],"ny":[1,0],"nz":[1,2]},{"size":2,"px":[9,4],"py":[10,5],"pz":[1,2],"nx":[8,4],"ny":[10,4],"nz":[1,-1]},{"size":2,"px":[12,12],"py":[11,20],"pz":[0,-1],"nx":[0,0],"ny":[6,10],"nz":[1,0]},{"size":2,"px":[7,12],"py":[2,20],"pz":[0,-1],"nx":[2,2],"ny":[2,3],"nz":[2,2]},{"size":2,"px":[0,15],"py":[5,21],"pz":[1,-1],"nx":[10,9],"ny":[3,3],"nz":[0,1]},{"size":2,"px":[15,9],"py":[1,0],"pz":[0,1],"nx":[19,3],"ny":[0,3],"nz":[0,-1]},{"size":2,"px":[21,5],"py":[13,5],"pz":[0,2],"nx":[23,6],"ny":[23,5],"nz":[0,-1]},{"size":2,"px":[5,8],"py":[3,1],"pz":[2,-1],"nx":[9,9],"ny":[6,5],"nz":[1,1]},{"size":2,"px":[2,2],"py":[7,7],"pz":[1,-1],"nx":[5,3],"ny":[23,17],"nz":[0,0]},{"size":2,"px":[11,3],"py":[6,4],"pz":[0,-1],"nx":[2,4],"ny":[2,4],"nz":[2,1]},{"size":3,"px":[14,0,17],"py":[20,3,21],"pz":[0,-1,-1],"nx":[11,11,11],"ny":[7,9,10],"nz":[1,1,1]},{"size":5,"px":[11,11,23,23,12],"py":[10,11,21,20,12],"pz":[1,1,0,0,0],"nx":[8,3,6,7,7],"ny":[4,5,11,11,11],"nz":[1,2,1,1,-1]},{"size":2,"px":[11,11],"py":[11,10],"pz":[0,0],"nx":[9,3],"ny":[2,5],"nz":[1,-1]},{"size":2,"px":[12,14],"py":[19,19],"pz":[0,0],"nx":[12,13],"ny":[18,17],"nz":[0,-1]},{"size":5,"px":[13,14,12,15,14],"py":[0,0,1,1,1],"pz":[0,0,0,0,0],"nx":[4,8,4,7,7],"ny":[3,4,2,5,5],"nz":[2,1,2,1,-1]},{"size":2,"px":[17,5],"py":[10,2],"pz":[0,-1],"nx":[4,9],"ny":[2,3],"nz":[2,1]},{"size":2,"px":[18,10],"py":[6,10],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[8,18,8,4,16],"py":[6,12,9,4,13],"pz":[1,0,1,2,0],"nx":[3,4,3,5,5],"ny":[0,2,3,1,1],"nz":[1,0,0,0,-1]},{"size":2,"px":[3,6],"py":[2,4],"pz":[2,1],"nx":[8,0],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[4,5],"pz":[2,-1],"nx":[4,2],"ny":[14,7],"nz":[0,1]},{"size":4,"px":[3,4,4,3],"py":[11,12,12,2],"pz":[0,0,-1,-1],"nx":[1,2,1,2],"ny":[11,14,12,16],"nz":[0,0,0,0]},{"size":2,"px":[6,0],"py":[11,0],"pz":[0,-1],"nx":[3,4],"ny":[4,5],"nz":[1,1]},{"size":2,"px":[3,2],"py":[21,11],"pz":[0,1],"nx":[3,2],"ny":[10,0],"nz":[1,-1]},{"size":3,"px":[10,3,13],"py":[2,0,2],"pz":[0,2,0],"nx":[7,16,1],"ny":[10,4,1],"nz":[0,-1,-1]},{"size":2,"px":[6,12],"py":[2,5],"pz":[1,0],"nx":[6,18],"ny":[1,19],"nz":[1,-1]},{"size":2,"px":[3,16],"py":[0,16],"pz":[1,-1],"nx":[11,2],"ny":[5,1],"nz":[0,2]},{"size":2,"px":[11,10],"py":[13,1],"pz":[0,-1],"nx":[1,1],"ny":[22,21],"nz":[0,0]},{"size":2,"px":[11,10],"py":[18,18],"pz":[0,0],"nx":[5,8],"ny":[9,0],"nz":[1,-1]},{"size":2,"px":[3,2],"py":[20,18],"pz":[0,0],"nx":[8,3],"ny":[5,1],"nz":[1,-1]},{"size":2,"px":[14,2],"py":[17,1],"pz":[0,-1],"nx":[14,13],"ny":[15,15],"nz":[0,0]},{"size":2,"px":[3,4],"py":[2,3],"pz":[2,2],"nx":[8,3],"ny":[4,0],"nz":[1,-1]},{"size":5,"px":[8,18,18,8,7],"py":[6,11,11,7,9],"pz":[1,0,-1,-1,-1],"nx":[5,13,5,11,5],"ny":[3,11,0,8,2],"nz":[2,0,2,1,2]},{"size":5,"px":[12,0,5,4,7],"py":[15,0,4,0,9],"pz":[0,-1,-1,-1,-1],"nx":[8,7,4,16,6],"ny":[17,12,9,10,12],"nz":[0,0,1,0,0]},{"size":2,"px":[6,7],"py":[14,1],"pz":[0,-1],"nx":[5,4],"ny":[9,4],"nz":[1,1]},{"size":4,"px":[8,0,22,4],"py":[4,4,23,0],"pz":[0,-1,-1,-1],"nx":[2,4,2,5],"ny":[0,1,2,9],"nz":[2,1,2,1]},{"size":5,"px":[9,9,10,10,8],"py":[0,1,1,2,0],"pz":[1,1,1,1,1],"nx":[4,16,16,16,6],"ny":[2,11,11,11,12],"nz":[2,0,-1,-1,-1]},{"size":2,"px":[6,6],"py":[6,5],"pz":[1,1],"nx":[0,4],"ny":[3,2],"nz":[1,-1]},{"size":3,"px":[10,3,4],"py":[5,9,8],"pz":[1,-1,-1],"nx":[11,23,23],"ny":[7,12,11],"nz":[1,0,0]},{"size":3,"px":[13,12,7],"py":[19,19,10],"pz":[0,0,1],"nx":[13,5,19],"ny":[20,15,22],"nz":[0,-1,-1]},{"size":2,"px":[12,12],"py":[12,13],"pz":[0,0],"nx":[9,10],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[0,12],"py":[1,13],"pz":[2,-1],"nx":[2,7],"ny":[2,13],"nz":[2,0]},{"size":2,"px":[10,10],"py":[8,9],"pz":[1,1],"nx":[19,7],"ny":[23,13],"nz":[0,-1]},{"size":4,"px":[8,7,23,15],"py":[11,12,4,21],"pz":[0,0,-1,-1],"nx":[2,5,1,10],"ny":[6,6,2,13],"nz":[0,1,1,0]},{"size":2,"px":[10,9],"py":[3,3],"pz":[0,0],"nx":[2,3],"ny":[2,4],"nz":[2,-1]},{"size":2,"px":[5,2],"py":[3,4],"pz":[2,-1],"nx":[3,6],"ny":[1,2],"nz":[2,1]},{"size":2,"px":[7,11],"py":[20,16],"pz":[0,-1],"nx":[2,4],"ny":[5,20],"nz":[2,0]},{"size":2,"px":[9,7],"py":[7,5],"pz":[1,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[4,2],"py":[11,3],"pz":[1,2],"nx":[5,5],"ny":[3,5],"nz":[2,-1]},{"size":2,"px":[11,3],"py":[11,5],"pz":[1,-1],"nx":[4,1],"ny":[12,3],"nz":[0,2]},{"size":2,"px":[9,11],"py":[6,4],"pz":[1,-1],"nx":[10,20],"ny":[9,18],"nz":[1,0]},{"size":5,"px":[2,2,2,2,1],"py":[15,13,16,14,7],"pz":[0,0,0,0,1],"nx":[15,8,9,8,4],"ny":[11,6,5,5,4],"nz":[0,1,1,1,-1]},{"size":2,"px":[12,2],"py":[5,5],"pz":[0,-1],"nx":[3,2],"ny":[7,2],"nz":[1,2]},{"size":2,"px":[5,11],"py":[1,3],"pz":[2,1],"nx":[10,10],"ny":[3,3],"nz":[1,-1]},{"size":2,"px":[17,11],"py":[13,18],"pz":[0,-1],"nx":[6,9],"ny":[9,4],"nz":[1,1]},{"size":5,"px":[5,1,2,5,6],"py":[14,4,9,15,23],"pz":[0,2,1,0,0],"nx":[4,9,18,16,17],"ny":[0,1,1,0,0],"nz":[2,1,0,0,0]},{"size":2,"px":[16,17],"py":[0,0],"pz":[0,0],"nx":[23,23],"ny":[5,4],"nz":[0,-1]},{"size":2,"px":[13,8],"py":[20,6],"pz":[0,-1],"nx":[5,6],"ny":[12,10],"nz":[0,1]},{"size":2,"px":[6,15],"py":[15,0],"pz":[0,-1],"nx":[6,3],"ny":[16,4],"nz":[0,1]},{"size":2,"px":[18,20],"py":[7,8],"pz":[0,0],"nx":[18,11],"ny":[9,14],"nz":[0,-1]},{"size":2,"px":[9,4],"py":[12,6],"pz":[0,1],"nx":[3,15],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[5,2],"pz":[1,2],"nx":[5,5],"ny":[2,2],"nz":[1,-1]},{"size":2,"px":[5,20],"py":[1,20],"pz":[1,-1],"nx":[15,17],"ny":[1,2],"nz":[0,0]},{"size":2,"px":[7,2],"py":[16,4],"pz":[0,2],"nx":[4,0],"ny":[10,6],"nz":[1,-1]},{"size":2,"px":[3,8],"py":[5,0],"pz":[1,-1],"nx":[1,1],"ny":[10,18],"nz":[1,0]},{"size":2,"px":[22,0],"py":[3,0],"pz":[0,-1],"nx":[23,11],"ny":[4,1],"nz":[0,1]},{"size":3,"px":[19,10,20],"py":[21,8,18],"pz":[0,1,0],"nx":[3,6,20],"ny":[5,11,14],"nz":[2,-1,-1]},{"size":4,"px":[2,1,6,5],"py":[7,4,23,22],"pz":[1,2,0,0],"nx":[9,19,20,4],"ny":[8,11,9,2],"nz":[0,-1,-1,-1]},{"size":2,"px":[3,6],"py":[2,11],"pz":[2,1],"nx":[12,10],"ny":[21,9],"nz":[0,-1]},{"size":4,"px":[6,0,2,2],"py":[6,1,4,1],"pz":[1,-1,-1,-1],"nx":[0,0,0,0],"ny":[5,8,9,4],"nz":[1,0,0,1]},{"size":5,"px":[3,13,6,11,9],"py":[0,3,1,1,2],"pz":[2,0,1,0,0],"nx":[7,20,16,4,7],"ny":[7,2,19,2,6],"nz":[1,0,0,2,1]},{"size":4,"px":[7,5,2,6],"py":[7,7,4,11],"pz":[0,0,2,1],"nx":[7,1,21,0],"ny":[8,4,11,3],"nz":[0,-1,-1,-1]},{"size":2,"px":[2,2],"py":[3,2],"pz":[2,2],"nx":[8,9],"ny":[3,11],"nz":[1,-1]},{"size":2,"px":[7,13],"py":[3,5],"pz":[1,0],"nx":[4,3],"ny":[2,2],"nz":[1,-1]},{"size":4,"px":[3,12,13,11],"py":[0,1,1,1],"pz":[2,0,0,0],"nx":[8,9,13,0],"ny":[4,1,16,3],"nz":[1,-1,-1,-1]},{"size":2,"px":[10,1],"py":[4,14],"pz":[0,-1],"nx":[5,10],"ny":[1,2],"nz":[1,0]},{"size":2,"px":[11,12],"py":[21,21],"pz":[0,0],"nx":[10,11],"ny":[19,19],"nz":[0,0]},{"size":2,"px":[8,12],"py":[6,21],"pz":[1,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[11,7],"py":[19,0],"pz":[0,-1],"nx":[6,5],"ny":[9,11],"nz":[1,1]},{"size":5,"px":[11,11,11,10,10],"py":[10,12,11,13,13],"pz":[0,0,0,0,-1],"nx":[7,13,6,12,7],"ny":[10,6,3,6,11],"nz":[0,0,1,0,0]},{"size":2,"px":[12,11],"py":[6,12],"pz":[0,-1],"nx":[4,8],"ny":[4,4],"nz":[1,1]},{"size":5,"px":[16,15,16,15,17],"py":[1,0,0,1,1],"pz":[0,0,0,0,0],"nx":[13,7,6,12,12],"ny":[5,4,3,6,6],"nz":[0,1,1,0,-1]},{"size":2,"px":[2,3],"py":[1,3],"pz":[2,1],"nx":[1,5],"ny":[1,3],"nz":[2,-1]},{"size":2,"px":[6,3],"py":[13,6],"pz":[0,1],"nx":[4,9],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[0,3],"py":[4,3],"pz":[1,-1],"nx":[4,8],"ny":[3,6],"nz":[2,1]},{"size":2,"px":[6,3],"py":[2,1],"pz":[0,1],"nx":[5,5],"ny":[7,21],"nz":[1,-1]},{"size":2,"px":[8,4],"py":[0,0],"pz":[1,-1],"nx":[19,17],"ny":[1,0],"nz":[0,0]},{"size":4,"px":[8,11,5,0],"py":[6,1,1,22],"pz":[1,-1,-1,-1],"nx":[0,10,10,1],"ny":[6,12,13,4],"nz":[1,0,0,1]},{"size":2,"px":[8,17],"py":[6,13],"pz":[1,0],"nx":[14,17],"ny":[9,3],"nz":[0,-1]},{"size":2,"px":[5,8],"py":[0,4],"pz":[2,-1],"nx":[9,8],"ny":[1,1],"nz":[0,0]},{"size":2,"px":[11,14],"py":[13,9],"pz":[0,-1],"nx":[23,23],"ny":[21,19],"nz":[0,0]},{"size":2,"px":[10,9],"py":[9,3],"pz":[0,-1],"nx":[6,3],"ny":[2,1],"nz":[1,2]},{"size":2,"px":[11,1],"py":[4,4],"pz":[0,-1],"nx":[2,4],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[5,9],"py":[3,3],"pz":[2,-1],"nx":[17,9],"ny":[12,5],"nz":[0,1]},{"size":2,"px":[9,7],"py":[18,16],"pz":[0,-1],"nx":[5,2],"ny":[9,5],"nz":[1,2]},{"size":2,"px":[3,6],"py":[0,1],"pz":[1,-1],"nx":[4,5],"ny":[1,0],"nz":[0,0]}],"alpha":[-1.149973e+00,1.149973e+00,-6.844773e-01,6.844773e-01,-6.635048e-01,6.635048e-01,-4.888349e-01,4.888349e-01,-4.267976e-01,4.267976e-01,-4.258100e-01,4.258100e-01,-4.815853e-01,4.815853e-01,-4.091859e-01,4.091859e-01,-3.137414e-01,3.137414e-01,-3.339860e-01,3.339860e-01,-3.891196e-01,3.891196e-01,-4.167691e-01,4.167691e-01,-3.186609e-01,3.186609e-01,-2.957171e-01,2.957171e-01,-3.210062e-01,3.210062e-01,-2.725684e-01,2.725684e-01,-2.452176e-01,2.452176e-01,-2.812662e-01,2.812662e-01,-3.029622e-01,3.029622e-01,-3.293745e-01,3.293745e-01,-3.441536e-01,3.441536e-01,-2.946918e-01,2.946918e-01,-2.890545e-01,2.890545e-01,-1.949205e-01,1.949205e-01,-2.176102e-01,2.176102e-01,-2.595190e-01,2.595190e-01,-2.690931e-01,2.690931e-01,-2.130294e-01,2.130294e-01,-2.316308e-01,2.316308e-01,-2.798562e-01,2.798562e-01,-2.146988e-01,2.146988e-01,-2.332089e-01,2.332089e-01,-2.470614e-01,2.470614e-01,-2.204300e-01,2.204300e-01,-2.272045e-01,2.272045e-01,-2.583686e-01,2.583686e-01,-2.072299e-01,2.072299e-01,-1.834971e-01,1.834971e-01,-2.332656e-01,2.332656e-01,-3.271297e-01,3.271297e-01,-2.401937e-01,2.401937e-01,-2.006316e-01,2.006316e-01,-2.401947e-01,2.401947e-01,-2.475346e-01,2.475346e-01,-2.579532e-01,2.579532e-01,-2.466235e-01,2.466235e-01,-1.787582e-01,1.787582e-01,-2.036892e-01,2.036892e-01,-1.665028e-01,1.665028e-01,-1.576510e-01,1.576510e-01,-2.036997e-01,2.036997e-01,-2.040734e-01,2.040734e-01,-1.792532e-01,1.792532e-01,-2.174767e-01,2.174767e-01,-1.876948e-01,1.876948e-01,-1.883137e-01,1.883137e-01,-1.923872e-01,1.923872e-01,-2.620218e-01,2.620218e-01,-1.659873e-01,1.659873e-01,-1.475948e-01,1.475948e-01,-1.731607e-01,1.731607e-01,-2.059256e-01,2.059256e-01,-1.586309e-01,1.586309e-01,-1.607668e-01,1.607668e-01,-1.975101e-01,1.975101e-01,-2.130745e-01,2.130745e-01,-1.898872e-01,1.898872e-01,-2.052598e-01,2.052598e-01,-1.599397e-01,1.599397e-01,-1.770134e-01,1.770134e-01,-1.888249e-01,1.888249e-01,-1.515406e-01,1.515406e-01,-1.907771e-01,1.907771e-01,-1.698406e-01,1.698406e-01,-2.079535e-01,2.079535e-01,-1.966967e-01,1.966967e-01,-1.631391e-01,1.631391e-01,-2.158666e-01,2.158666e-01,-2.891774e-01,2.891774e-01,-1.581556e-01,1.581556e-01,-1.475359e-01,1.475359e-01,-1.806169e-01,1.806169e-01,-1.782238e-01,1.782238e-01,-1.660440e-01,1.660440e-01,-1.576919e-01,1.576919e-01,-1.741775e-01,1.741775e-01,-1.427265e-01,1.427265e-01,-1.695880e-01,1.695880e-01,-1.486712e-01,1.486712e-01,-1.533565e-01,1.533565e-01,-1.601464e-01,1.601464e-01,-1.978414e-01,1.978414e-01,-1.746566e-01,1.746566e-01,-1.794736e-01,1.794736e-01,-1.896567e-01,1.896567e-01,-1.666197e-01,1.666197e-01,-1.969351e-01,1.969351e-01,-2.321735e-01,2.321735e-01,-1.592485e-01,1.592485e-01,-1.671464e-01,1.671464e-01,-1.688885e-01,1.688885e-01,-1.868042e-01,1.868042e-01,-1.301138e-01,1.301138e-01,-1.330094e-01,1.330094e-01,-1.268423e-01,1.268423e-01,-1.820868e-01,1.820868e-01,-1.881020e-01,1.881020e-01,-1.580814e-01,1.580814e-01,-1.302653e-01,1.302653e-01,-1.787262e-01,1.787262e-01,-1.658453e-01,1.658453e-01,-1.240772e-01,1.240772e-01,-1.315621e-01,1.315621e-01,-1.756341e-01,1.756341e-01,-1.429438e-01,1.429438e-01,-1.351775e-01,1.351775e-01,-2.035692e-01,2.035692e-01,-1.267670e-01,1.267670e-01,-1.288470e-01,1.288470e-01,-1.393648e-01,1.393648e-01,-1.755962e-01,1.755962e-01,-1.308445e-01,1.308445e-01,-1.703894e-01,1.703894e-01,-1.461334e-01,1.461334e-01,-1.368683e-01,1.368683e-01,-1.244085e-01,1.244085e-01,-1.718163e-01,1.718163e-01,-1.415624e-01,1.415624e-01,-1.752024e-01,1.752024e-01,-1.666463e-01,1.666463e-01,-1.407325e-01,1.407325e-01,-1.258317e-01,1.258317e-01,-1.416511e-01,1.416511e-01,-1.420816e-01,1.420816e-01,-1.562547e-01,1.562547e-01,-1.542952e-01,1.542952e-01,-1.158829e-01,1.158829e-01,-1.392875e-01,1.392875e-01,-1.610095e-01,1.610095e-01,-1.546440e-01,1.546440e-01,-1.416235e-01,1.416235e-01,-2.028817e-01,2.028817e-01,-1.106779e-01,1.106779e-01,-9.231660e-02,9.231660e-02,-1.164460e-01,1.164460e-01,-1.701578e-01,1.701578e-01,-1.277995e-01,1.277995e-01,-1.946177e-01,1.946177e-01,-1.394509e-01,1.394509e-01,-1.370145e-01,1.370145e-01,-1.446031e-01,1.446031e-01,-1.665215e-01,1.665215e-01,-1.435822e-01,1.435822e-01,-1.559354e-01,1.559354e-01,-1.591860e-01,1.591860e-01,-1.193338e-01,1.193338e-01,-1.236954e-01,1.236954e-01,-1.209139e-01,1.209139e-01,-1.267385e-01,1.267385e-01,-1.232397e-01,1.232397e-01,-1.299632e-01,1.299632e-01,-1.302020e-01,1.302020e-01,-1.202975e-01,1.202975e-01,-1.525378e-01,1.525378e-01,-1.123073e-01,1.123073e-01,-1.605678e-01,1.605678e-01,-1.406867e-01,1.406867e-01,-1.354273e-01,1.354273e-01,-1.393192e-01,1.393192e-01,-1.278263e-01,1.278263e-01,-1.172073e-01,1.172073e-01,-1.153493e-01,1.153493e-01,-1.356318e-01,1.356318e-01,-1.316614e-01,1.316614e-01,-1.374489e-01,1.374489e-01,-1.018254e-01,1.018254e-01,-1.473336e-01,1.473336e-01,-1.289687e-01,1.289687e-01,-1.299183e-01,1.299183e-01,-1.178391e-01,1.178391e-01,-1.619059e-01,1.619059e-01,-1.842569e-01,1.842569e-01,-1.829095e-01,1.829095e-01,-1.939918e-01,1.939918e-01,-1.395362e-01,1.395362e-01,-1.774673e-01,1.774673e-01,-1.688216e-01,1.688216e-01,-1.671747e-01,1.671747e-01,-1.850178e-01,1.850178e-01,-1.106695e-01,1.106695e-01,-1.258323e-01,1.258323e-01,-1.246819e-01,1.246819e-01,-9.892193e-02,9.892193e-02,-1.399638e-01,1.399638e-01,-1.228375e-01,1.228375e-01,-1.756236e-01,1.756236e-01,-1.360307e-01,1.360307e-01,-1.266574e-01,1.266574e-01,-1.372135e-01,1.372135e-01,-1.175947e-01,1.175947e-01,-1.330075e-01,1.330075e-01,-1.396152e-01,1.396152e-01,-2.088443e-01,2.088443e-01]},{"count":301,"threshold":-4.887516e+00,"feature":[{"size":5,"px":[8,11,8,14,10],"py":[6,9,3,3,4],"pz":[1,0,0,0,0],"nx":[8,7,19,7,13],"ny":[11,8,8,5,8],"nz":[1,1,0,1,0]},{"size":5,"px":[14,3,13,12,12],"py":[4,6,4,4,8],"pz":[0,1,0,0,0],"nx":[2,5,2,10,10],"ny":[2,8,5,8,8],"nz":[2,1,2,0,-1]},{"size":5,"px":[6,5,3,7,7],"py":[2,3,1,2,2],"pz":[0,0,1,0,-1],"nx":[2,2,1,2,1],"ny":[3,1,2,2,2],"nz":[0,0,2,0,1]},{"size":5,"px":[3,3,6,12,8],"py":[4,2,4,10,17],"pz":[2,2,1,0,0],"nx":[4,8,8,2,1],"ny":[4,4,4,2,2],"nz":[1,1,-1,-1,-1]},{"size":5,"px":[18,19,17,9,16],"py":[1,2,2,0,2],"pz":[0,0,0,1,0],"nx":[23,23,22,22,22],"ny":[4,3,1,0,2],"nz":[0,0,0,0,0]},{"size":3,"px":[15,4,14],"py":[23,4,18],"pz":[0,2,0],"nx":[7,0,5],"ny":[10,4,9],"nz":[1,-1,-1]},{"size":5,"px":[11,11,16,11,17],"py":[8,6,11,7,11],"pz":[0,0,0,0,0],"nx":[8,4,14,14,1],"ny":[4,4,8,8,5],"nz":[1,1,0,-1,-1]},{"size":5,"px":[12,12,12,12,12],"py":[13,10,11,12,12],"pz":[0,0,0,0,-1],"nx":[4,4,1,2,9],"ny":[8,10,2,4,15],"nz":[0,1,2,1,0]},{"size":2,"px":[19,0],"py":[14,17],"pz":[0,-1],"nx":[20,19],"ny":[15,22],"nz":[0,0]},{"size":5,"px":[3,3,1,3,5],"py":[13,15,6,14,22],"pz":[0,0,1,0,0],"nx":[0,0,1,0,0],"ny":[11,21,23,5,5],"nz":[1,0,0,2,-1]},{"size":5,"px":[4,2,10,4,3],"py":[19,4,13,16,13],"pz":[0,1,0,0,0],"nx":[3,20,7,4,0],"ny":[4,19,5,1,5],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[11,5],"py":[4,4],"pz":[0,-1],"nx":[15,3],"ny":[15,1],"nz":[0,2]},{"size":4,"px":[17,17,12,11],"py":[14,15,18,18],"pz":[0,0,0,0],"nx":[11,4,1,0],"ny":[17,20,8,5],"nz":[0,-1,-1,-1]},{"size":5,"px":[6,2,1,2,11],"py":[14,4,1,1,18],"pz":[0,-1,-1,-1,-1],"nx":[5,5,3,5,2],"ny":[18,17,7,9,2],"nz":[0,0,1,1,2]},{"size":5,"px":[20,19,20,15,20],"py":[17,20,12,12,8],"pz":[0,0,0,0,0],"nx":[17,0,5,2,2],"ny":[8,4,9,2,2],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[6,8],"py":[7,11],"pz":[1,-1],"nx":[7,8],"ny":[7,10],"nz":[1,1]},{"size":5,"px":[15,16,14,8,8],"py":[2,2,2,0,0],"pz":[0,0,0,1,-1],"nx":[20,11,21,18,19],"ny":[3,6,5,1,2],"nz":[0,1,0,0,0]},{"size":4,"px":[17,18,9,8],"py":[23,21,7,8],"pz":[0,0,1,1],"nx":[8,17,10,18],"ny":[4,12,2,1],"nz":[1,-1,-1,-1]},{"size":5,"px":[2,2,9,4,8],"py":[7,3,12,12,23],"pz":[1,1,0,0,0],"nx":[0,0,0,0,0],"ny":[3,1,2,4,4],"nz":[0,0,0,0,-1]},{"size":3,"px":[7,8,5],"py":[22,23,9],"pz":[0,0,1],"nx":[9,4,2],"ny":[21,4,0],"nz":[0,-1,-1]},{"size":2,"px":[3,3],"py":[7,7],"pz":[1,-1],"nx":[3,2],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[15,11,10,3,17],"py":[0,1,2,3,1],"pz":[0,0,0,2,0],"nx":[5,8,4,3,3],"ny":[9,4,7,10,10],"nz":[1,1,1,1,-1]},{"size":3,"px":[22,11,22],"py":[12,5,14],"pz":[0,1,0],"nx":[23,23,3],"ny":[22,23,8],"nz":[0,0,-1]},{"size":2,"px":[3,11],"py":[7,5],"pz":[1,-1],"nx":[8,2],"ny":[14,5],"nz":[0,2]},{"size":4,"px":[17,16,2,4],"py":[14,13,5,0],"pz":[0,0,-1,-1],"nx":[8,9,15,8],"ny":[8,9,14,7],"nz":[1,1,0,1]},{"size":2,"px":[5,16],"py":[6,13],"pz":[1,-1],"nx":[2,1],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[1,0,1,2,1],"py":[15,2,16,19,12],"pz":[0,2,0,0,0],"nx":[8,7,4,9,9],"ny":[5,11,4,5,5],"nz":[1,1,1,1,-1]},{"size":2,"px":[8,7],"py":[11,12],"pz":[0,0],"nx":[9,1],"ny":[10,16],"nz":[0,-1]},{"size":2,"px":[15,13],"py":[17,10],"pz":[0,-1],"nx":[7,4],"ny":[8,4],"nz":[1,2]},{"size":5,"px":[11,10,7,8,9],"py":[0,0,1,1,1],"pz":[0,0,0,0,0],"nx":[4,5,4,5,6],"ny":[1,0,2,1,0],"nz":[0,0,0,0,-1]},{"size":2,"px":[2,2],"py":[4,3],"pz":[2,2],"nx":[3,21],"ny":[4,20],"nz":[1,-1]},{"size":5,"px":[10,11,5,2,11],"py":[12,10,6,11,11],"pz":[0,0,1,0,0],"nx":[4,15,16,7,7],"ny":[5,10,11,10,10],"nz":[1,0,0,0,-1]},{"size":5,"px":[13,14,1,11,11],"py":[2,2,3,2,2],"pz":[0,0,2,0,-1],"nx":[3,0,0,1,0],"ny":[23,15,14,9,8],"nz":[0,0,0,1,1]},{"size":2,"px":[17,2],"py":[13,5],"pz":[0,-1],"nx":[4,9],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[10,5],"py":[4,1],"pz":[0,-1],"nx":[11,3],"ny":[3,0],"nz":[0,2]},{"size":2,"px":[5,3],"py":[3,3],"pz":[2,-1],"nx":[11,23],"ny":[8,14],"nz":[1,0]},{"size":3,"px":[22,22,22],"py":[16,18,9],"pz":[0,0,0],"nx":[13,2,0],"ny":[17,3,5],"nz":[0,-1,-1]},{"size":5,"px":[13,10,13,14,11],"py":[2,2,1,2,1],"pz":[0,0,0,0,0],"nx":[3,3,8,6,6],"ny":[2,5,4,11,11],"nz":[2,2,1,1,-1]},{"size":3,"px":[12,1,1],"py":[14,0,1],"pz":[0,-1,-1],"nx":[8,15,7],"ny":[1,2,0],"nz":[1,0,1]},{"size":2,"px":[4,5],"py":[20,23],"pz":[0,0],"nx":[3,3],"ny":[10,2],"nz":[1,-1]},{"size":2,"px":[2,4],"py":[7,2],"pz":[1,-1],"nx":[4,3],"ny":[23,16],"nz":[0,0]},{"size":3,"px":[3,3,6],"py":[5,2,4],"pz":[2,2,1],"nx":[3,1,2],"ny":[5,17,0],"nz":[1,-1,-1]},{"size":2,"px":[14,8],"py":[17,6],"pz":[0,1],"nx":[13,10],"ny":[16,9],"nz":[0,-1]},{"size":5,"px":[15,7,14,13,14],"py":[1,0,0,0,1],"pz":[0,1,0,0,0],"nx":[4,4,4,8,8],"ny":[5,3,2,10,10],"nz":[2,2,2,1,-1]},{"size":5,"px":[8,9,4,5,4],"py":[13,12,9,5,7],"pz":[0,0,1,1,1],"nx":[22,21,22,22,22],"ny":[4,0,3,2,2],"nz":[0,0,0,0,-1]},{"size":2,"px":[17,17],"py":[16,13],"pz":[0,0],"nx":[14,21],"ny":[8,0],"nz":[0,-1]},{"size":2,"px":[16,10],"py":[4,9],"pz":[0,-1],"nx":[16,10],"ny":[3,3],"nz":[0,1]},{"size":5,"px":[1,1,0,1,0],"py":[17,16,7,15,8],"pz":[0,0,1,0,0],"nx":[4,3,8,9,7],"ny":[3,3,6,6,6],"nz":[1,1,0,0,-1]},{"size":2,"px":[3,3],"py":[2,3],"pz":[2,2],"nx":[8,3],"ny":[4,3],"nz":[1,-1]},{"size":2,"px":[10,2],"py":[17,4],"pz":[0,2],"nx":[10,12],"ny":[15,14],"nz":[0,-1]},{"size":2,"px":[11,11],"py":[14,12],"pz":[0,0],"nx":[9,10],"ny":[13,11],"nz":[0,0]},{"size":2,"px":[12,13],"py":[5,5],"pz":[0,0],"nx":[3,4],"ny":[4,1],"nz":[1,-1]},{"size":5,"px":[7,10,8,11,11],"py":[13,2,12,2,2],"pz":[0,0,0,0,-1],"nx":[10,1,1,10,1],"ny":[12,5,3,13,1],"nz":[0,1,1,0,2]},{"size":2,"px":[6,10],"py":[4,2],"pz":[1,-1],"nx":[4,6],"ny":[4,9],"nz":[1,1]},{"size":2,"px":[20,20],"py":[21,22],"pz":[0,0],"nx":[15,8],"ny":[5,5],"nz":[0,-1]},{"size":2,"px":[4,3],"py":[3,3],"pz":[2,2],"nx":[9,17],"ny":[4,15],"nz":[1,-1]},{"size":3,"px":[2,2,4],"py":[3,3,7],"pz":[2,-1,-1],"nx":[7,4,4],"ny":[6,5,4],"nz":[1,2,2]},{"size":5,"px":[8,9,16,17,17],"py":[1,2,1,1,1],"pz":[1,1,0,0,-1],"nx":[2,2,4,2,4],"ny":[16,14,22,15,21],"nz":[0,0,0,0,0]},{"size":2,"px":[9,9],"py":[18,0],"pz":[0,-1],"nx":[2,5],"ny":[5,8],"nz":[2,1]},{"size":2,"px":[7,8],"py":[11,11],"pz":[0,0],"nx":[15,5],"ny":[8,8],"nz":[0,-1]},{"size":2,"px":[0,3],"py":[4,3],"pz":[2,-1],"nx":[1,6],"ny":[4,14],"nz":[2,0]},{"size":2,"px":[6,12],"py":[7,11],"pz":[1,-1],"nx":[0,0],"ny":[7,12],"nz":[1,0]},{"size":2,"px":[3,7],"py":[10,22],"pz":[1,0],"nx":[4,3],"ny":[10,0],"nz":[1,-1]},{"size":2,"px":[5,19],"py":[4,21],"pz":[2,-1],"nx":[11,11],"ny":[8,9],"nz":[1,1]},{"size":2,"px":[3,3],"py":[8,7],"pz":[1,1],"nx":[4,20],"ny":[4,5],"nz":[1,-1]},{"size":5,"px":[11,23,23,23,23],"py":[7,13,19,20,21],"pz":[1,0,0,0,0],"nx":[4,3,2,8,8],"ny":[11,5,5,23,23],"nz":[1,1,2,0,-1]},{"size":2,"px":[4,1],"py":[0,2],"pz":[0,0],"nx":[0,6],"ny":[0,11],"nz":[0,-1]},{"size":2,"px":[11,8],"py":[12,1],"pz":[0,-1],"nx":[23,23],"ny":[13,12],"nz":[0,0]},{"size":5,"px":[23,11,23,11,11],"py":[13,7,12,5,6],"pz":[0,1,0,1,1],"nx":[6,3,8,7,7],"ny":[12,4,4,11,11],"nz":[0,1,1,0,-1]},{"size":2,"px":[20,5],"py":[15,5],"pz":[0,-1],"nx":[10,10],"ny":[11,10],"nz":[1,1]},{"size":2,"px":[11,4],"py":[19,8],"pz":[0,1],"nx":[11,19],"ny":[18,2],"nz":[0,-1]},{"size":2,"px":[14,6],"py":[3,4],"pz":[0,-1],"nx":[8,15],"ny":[1,0],"nz":[1,0]},{"size":4,"px":[14,5,13,12],"py":[23,3,23,23],"pz":[0,1,0,0],"nx":[12,0,1,4],"ny":[21,3,2,4],"nz":[0,-1,-1,-1]},{"size":2,"px":[19,5],"py":[12,2],"pz":[0,-1],"nx":[4,7],"ny":[3,5],"nz":[2,1]},{"size":2,"px":[0,8],"py":[5,3],"pz":[2,-1],"nx":[5,22],"ny":[3,11],"nz":[2,0]},{"size":2,"px":[2,6],"py":[3,12],"pz":[2,0],"nx":[3,5],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[5,5],"py":[0,6],"pz":[2,-1],"nx":[14,6],"ny":[4,2],"nz":[0,1]},{"size":2,"px":[16,11],"py":[1,0],"pz":[0,-1],"nx":[4,8],"ny":[4,10],"nz":[2,1]},{"size":2,"px":[9,4],"py":[4,3],"pz":[1,1],"nx":[5,8],"ny":[0,10],"nz":[2,-1]},{"size":2,"px":[16,1],"py":[22,1],"pz":[0,-1],"nx":[2,2],"ny":[4,2],"nz":[2,2]},{"size":2,"px":[12,2],"py":[11,2],"pz":[0,-1],"nx":[5,5],"ny":[1,0],"nz":[2,2]},{"size":2,"px":[11,11],"py":[4,3],"pz":[1,1],"nx":[7,5],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[9,2],"py":[22,3],"pz":[0,2],"nx":[4,9],"ny":[10,11],"nz":[1,-1]},{"size":2,"px":[2,4],"py":[8,10],"pz":[1,-1],"nx":[5,3],"ny":[23,18],"nz":[0,0]},{"size":2,"px":[12,6],"py":[21,9],"pz":[0,-1],"nx":[11,23],"ny":[6,10],"nz":[1,0]},{"size":2,"px":[9,9],"py":[8,7],"pz":[1,1],"nx":[18,8],"ny":[18,6],"nz":[0,-1]},{"size":2,"px":[13,3],"py":[19,0],"pz":[0,-1],"nx":[6,5],"ny":[9,11],"nz":[1,1]},{"size":5,"px":[2,10,9,7,8],"py":[0,1,0,1,0],"pz":[2,0,0,0,0],"nx":[3,4,6,8,8],"ny":[2,4,9,4,4],"nz":[2,1,1,1,-1]},{"size":2,"px":[8,4],"py":[6,3],"pz":[1,2],"nx":[9,4],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[0,4],"py":[23,3],"pz":[0,-1],"nx":[12,9],"ny":[2,2],"nz":[0,0]},{"size":2,"px":[4,2],"py":[10,3],"pz":[1,2],"nx":[0,2],"ny":[23,5],"nz":[0,-1]},{"size":2,"px":[12,14],"py":[18,0],"pz":[0,-1],"nx":[12,8],"ny":[16,10],"nz":[0,1]},{"size":4,"px":[10,18,7,5],"py":[14,8,0,3],"pz":[0,-1,-1,-1],"nx":[8,6,8,5],"ny":[11,12,5,5],"nz":[0,0,1,1]},{"size":2,"px":[6,5],"py":[2,2],"pz":[1,1],"nx":[8,8],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[12,10],"py":[20,20],"pz":[0,0],"nx":[11,10],"ny":[19,19],"nz":[0,0]},{"size":2,"px":[17,10],"py":[16,20],"pz":[0,-1],"nx":[8,7],"ny":[4,8],"nz":[1,1]},{"size":3,"px":[2,1,3],"py":[20,4,21],"pz":[0,2,0],"nx":[3,4,0],"ny":[10,1,0],"nz":[1,-1,-1]},{"size":5,"px":[6,7,3,6,6],"py":[15,14,7,16,19],"pz":[0,0,1,0,0],"nx":[0,0,0,0,0],"ny":[18,19,16,17,17],"nz":[0,0,0,0,-1]},{"size":2,"px":[8,16],"py":[6,12],"pz":[1,0],"nx":[8,15],"ny":[4,10],"nz":[1,-1]},{"size":5,"px":[0,0,0,0,0],"py":[1,3,2,0,4],"pz":[2,2,2,2,1],"nx":[13,8,14,4,7],"ny":[23,6,23,3,9],"nz":[0,1,0,2,-1]},{"size":2,"px":[3,6],"py":[3,5],"pz":[2,1],"nx":[10,8],"ny":[11,6],"nz":[0,-1]},{"size":2,"px":[11,10],"py":[4,4],"pz":[0,0],"nx":[8,5],"ny":[4,9],"nz":[1,-1]},{"size":5,"px":[15,18,9,16,4],"py":[12,13,6,23,3],"pz":[0,0,1,0,2],"nx":[6,3,6,2,7],"ny":[2,3,0,1,0],"nz":[0,0,0,1,0]},{"size":2,"px":[4,18],"py":[12,13],"pz":[0,-1],"nx":[2,8],"ny":[3,4],"nz":[2,1]},{"size":2,"px":[4,2],"py":[10,4],"pz":[1,2],"nx":[3,3],"ny":[5,0],"nz":[2,-1]},{"size":2,"px":[9,19],"py":[7,8],"pz":[1,0],"nx":[8,3],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[6,0],"py":[6,0],"pz":[0,-1],"nx":[0,0],"ny":[7,2],"nz":[1,2]},{"size":2,"px":[8,8],"py":[0,0],"pz":[1,-1],"nx":[17,18],"ny":[0,2],"nz":[0,0]},{"size":4,"px":[13,4,4,1],"py":[14,7,3,5],"pz":[0,-1,-1,-1],"nx":[3,16,3,7],"ny":[1,15,5,13],"nz":[2,0,2,0]},{"size":2,"px":[4,9],"py":[6,11],"pz":[1,0],"nx":[3,23],"ny":[4,8],"nz":[1,-1]},{"size":5,"px":[9,17,4,16,16],"py":[2,3,1,3,3],"pz":[1,0,2,0,-1],"nx":[2,3,3,2,3],"ny":[1,7,2,3,3],"nz":[2,1,1,1,1]},{"size":2,"px":[10,5],"py":[22,9],"pz":[0,1],"nx":[10,3],"ny":[21,2],"nz":[0,-1]},{"size":2,"px":[11,11],"py":[6,3],"pz":[0,-1],"nx":[8,5],"ny":[4,3],"nz":[1,1]},{"size":2,"px":[10,5],"py":[8,3],"pz":[0,-1],"nx":[14,5],"ny":[14,2],"nz":[0,2]},{"size":2,"px":[7,8],"py":[3,2],"pz":[0,-1],"nx":[8,2],"ny":[18,2],"nz":[0,2]},{"size":2,"px":[1,1],"py":[19,11],"pz":[0,1],"nx":[9,4],"ny":[5,1],"nz":[0,-1]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,3],"ny":[4,4],"nz":[1,-1]},{"size":5,"px":[7,15,13,14,4],"py":[6,12,9,11,4],"pz":[1,0,0,0,2],"nx":[7,3,8,4,5],"ny":[0,3,0,2,1],"nz":[0,0,0,0,0]},{"size":5,"px":[10,13,7,8,9],"py":[0,1,1,0,1],"pz":[0,0,0,0,0],"nx":[7,4,4,4,8],"ny":[8,3,4,2,4],"nz":[1,2,2,2,1]},{"size":2,"px":[6,1],"py":[6,0],"pz":[1,-1],"nx":[11,7],"ny":[3,2],"nz":[0,1]},{"size":2,"px":[13,0],"py":[13,2],"pz":[0,-1],"nx":[0,1],"ny":[13,16],"nz":[0,0]},{"size":2,"px":[8,17],"py":[6,13],"pz":[1,0],"nx":[8,1],"ny":[4,16],"nz":[1,-1]},{"size":5,"px":[12,11,3,6,17],"py":[4,4,1,2,14],"pz":[0,0,2,1,0],"nx":[6,23,23,6,23],"ny":[5,7,6,6,14],"nz":[1,0,0,1,0]},{"size":2,"px":[5,22],"py":[4,17],"pz":[2,-1],"nx":[4,8],"ny":[5,7],"nz":[2,1]},{"size":2,"px":[15,14],"py":[1,1],"pz":[0,0],"nx":[4,7],"ny":[2,4],"nz":[2,-1]},{"size":2,"px":[15,17],"py":[12,7],"pz":[0,-1],"nx":[14,10],"ny":[11,4],"nz":[0,1]},{"size":4,"px":[10,2,9,15],"py":[5,11,1,13],"pz":[0,-1,-1,-1],"nx":[11,3,3,13],"ny":[1,1,0,1],"nz":[0,2,2,0]},{"size":2,"px":[7,21],"py":[15,22],"pz":[0,-1],"nx":[4,9],"ny":[8,14],"nz":[1,0]},{"size":2,"px":[6,5],"py":[21,2],"pz":[0,-1],"nx":[3,5],"ny":[11,21],"nz":[1,0]},{"size":2,"px":[17,7],"py":[2,0],"pz":[0,-1],"nx":[4,8],"ny":[5,11],"nz":[2,1]},{"size":2,"px":[11,8],"py":[10,4],"pz":[0,-1],"nx":[13,12],"ny":[3,3],"nz":[0,0]},{"size":2,"px":[6,5],"py":[2,2],"pz":[1,1],"nx":[7,1],"ny":[8,2],"nz":[0,-1]},{"size":5,"px":[0,0,1,0,0],"py":[12,4,14,0,2],"pz":[0,1,0,2,2],"nx":[9,5,8,4,4],"ny":[6,3,6,3,3],"nz":[0,1,0,1,-1]},{"size":5,"px":[8,0,0,3,2],"py":[6,5,0,8,2],"pz":[1,-1,-1,-1,-1],"nx":[23,7,22,11,4],"ny":[12,6,14,4,3],"nz":[0,1,0,1,2]},{"size":4,"px":[12,12,4,8],"py":[12,11,3,10],"pz":[0,0,-1,-1],"nx":[0,0,0,0],"ny":[2,1,0,3],"nz":[1,2,2,1]},{"size":2,"px":[10,6],"py":[7,6],"pz":[1,-1],"nx":[16,4],"ny":[12,2],"nz":[0,2]},{"size":5,"px":[2,1,3,3,3],"py":[14,8,20,21,21],"pz":[0,1,0,0,-1],"nx":[20,10,21,21,21],"ny":[23,11,21,23,20],"nz":[0,1,0,0,0]},{"size":2,"px":[6,13],"py":[2,4],"pz":[1,0],"nx":[7,21],"ny":[8,0],"nz":[0,-1]},{"size":2,"px":[12,3],"py":[17,4],"pz":[0,2],"nx":[11,10],"ny":[15,7],"nz":[0,-1]},{"size":4,"px":[11,0,19,2],"py":[15,2,23,10],"pz":[0,-1,-1,-1],"nx":[6,8,16,2],"ny":[13,11,10,2],"nz":[0,0,0,2]},{"size":2,"px":[6,3],"py":[14,7],"pz":[0,1],"nx":[3,1],"ny":[4,1],"nz":[1,-1]},{"size":4,"px":[12,17,5,10],"py":[19,15,14,3],"pz":[0,-1,-1,-1],"nx":[4,12,6,12],"ny":[4,18,9,22],"nz":[1,0,1,0]},{"size":2,"px":[8,3],"py":[13,5],"pz":[0,-1],"nx":[3,4],"ny":[4,9],"nz":[1,1]},{"size":5,"px":[6,5,4,5,3],"py":[2,1,2,2,0],"pz":[0,0,0,0,1],"nx":[7,4,9,18,18],"ny":[4,4,7,14,14],"nz":[1,1,1,0,-1]},{"size":4,"px":[8,3,20,1],"py":[6,3,18,0],"pz":[1,-1,-1,-1],"nx":[13,11,5,22],"ny":[12,6,2,17],"nz":[0,1,2,0]},{"size":2,"px":[6,3],"py":[6,3],"pz":[1,2],"nx":[8,5],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[21,7],"py":[14,7],"pz":[0,1],"nx":[16,11],"ny":[14,6],"nz":[0,-1]},{"size":2,"px":[10,4],"py":[3,1],"pz":[0,-1],"nx":[9,5],"ny":[0,0],"nz":[0,1]},{"size":2,"px":[4,10],"py":[5,8],"pz":[2,1],"nx":[5,14],"ny":[9,7],"nz":[1,-1]},{"size":2,"px":[9,2],"py":[23,4],"pz":[0,2],"nx":[2,2],"ny":[5,5],"nz":[2,-1]},{"size":5,"px":[10,9,11,10,10],"py":[2,2,1,1,1],"pz":[0,0,0,0,-1],"nx":[2,3,2,4,5],"ny":[4,10,2,4,3],"nz":[2,1,1,0,0]},{"size":2,"px":[11,4],"py":[13,4],"pz":[0,-1],"nx":[8,4],"ny":[4,1],"nz":[1,2]},{"size":2,"px":[17,5],"py":[15,1],"pz":[0,-1],"nx":[20,19],"ny":[14,14],"nz":[0,0]},{"size":2,"px":[2,2],"py":[20,18],"pz":[0,0],"nx":[2,1],"ny":[23,5],"nz":[0,-1]},{"size":2,"px":[10,1],"py":[18,3],"pz":[0,2],"nx":[11,3],"ny":[16,5],"nz":[0,-1]},{"size":2,"px":[3,8],"py":[6,10],"pz":[1,0],"nx":[9,0],"ny":[9,3],"nz":[0,-1]},{"size":2,"px":[20,10],"py":[21,7],"pz":[0,1],"nx":[7,2],"ny":[3,5],"nz":[1,-1]},{"size":2,"px":[10,6],"py":[4,7],"pz":[1,-1],"nx":[23,5],"ny":[9,2],"nz":[0,2]},{"size":5,"px":[2,4,5,3,4],"py":[0,1,1,2,2],"pz":[1,0,0,0,0],"nx":[1,0,1,1,1],"ny":[2,1,0,1,1],"nz":[0,1,0,0,-1]},{"size":2,"px":[8,16],"py":[7,13],"pz":[1,0],"nx":[8,3],"ny":[4,16],"nz":[1,-1]},{"size":2,"px":[17,15],"py":[7,19],"pz":[0,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[4,3],"py":[11,5],"pz":[1,2],"nx":[7,8],"ny":[9,4],"nz":[1,-1]},{"size":2,"px":[23,11],"py":[9,6],"pz":[0,1],"nx":[22,22],"ny":[23,23],"nz":[0,-1]},{"size":2,"px":[23,23],"py":[21,20],"pz":[0,0],"nx":[2,2],"ny":[5,4],"nz":[1,-1]},{"size":2,"px":[17,4],"py":[12,2],"pz":[0,-1],"nx":[9,8],"ny":[4,5],"nz":[1,1]},{"size":2,"px":[6,14],"py":[2,4],"pz":[1,0],"nx":[7,18],"ny":[1,1],"nz":[1,-1]},{"size":2,"px":[20,22],"py":[1,2],"pz":[0,0],"nx":[23,23],"ny":[1,1],"nz":[0,-1]},{"size":2,"px":[0,1],"py":[9,10],"pz":[1,1],"nx":[8,0],"ny":[15,0],"nz":[0,-1]},{"size":3,"px":[11,11,6],"py":[10,11,11],"pz":[0,0,-1],"nx":[23,23,23],"ny":[19,21,20],"nz":[0,0,0]},{"size":5,"px":[23,23,23,6,6],"py":[21,22,22,3,6],"pz":[0,0,-1,-1,-1],"nx":[8,8,8,17,4],"ny":[7,10,8,16,5],"nz":[1,1,1,0,2]},{"size":2,"px":[10,23],"py":[1,22],"pz":[0,-1],"nx":[7,2],"ny":[11,2],"nz":[0,2]},{"size":2,"px":[7,14],"py":[3,10],"pz":[1,-1],"nx":[5,3],"ny":[2,1],"nz":[0,1]},{"size":2,"px":[5,3],"py":[13,7],"pz":[0,1],"nx":[4,10],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[10,0],"py":[15,6],"pz":[0,-1],"nx":[3,6],"ny":[1,2],"nz":[2,1]},{"size":2,"px":[13,4],"py":[18,17],"pz":[0,-1],"nx":[7,6],"ny":[10,7],"nz":[1,1]},{"size":2,"px":[12,11],"py":[3,8],"pz":[0,-1],"nx":[7,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[17,4],"py":[5,7],"pz":[0,1],"nx":[17,10],"ny":[4,0],"nz":[0,-1]},{"size":5,"px":[16,8,16,15,15],"py":[0,0,1,0,1],"pz":[0,1,0,0,0],"nx":[7,4,7,4,4],"ny":[7,5,8,1,1],"nz":[1,2,1,2,-1]},{"size":2,"px":[13,11],"py":[5,6],"pz":[0,-1],"nx":[4,5],"ny":[2,2],"nz":[1,1]},{"size":2,"px":[3,6],"py":[3,6],"pz":[2,1],"nx":[8,4],"ny":[4,3],"nz":[1,-1]},{"size":2,"px":[10,16],"py":[8,10],"pz":[0,0],"nx":[7,2],"ny":[3,3],"nz":[1,-1]},{"size":2,"px":[6,8],"py":[4,11],"pz":[1,0],"nx":[10,1],"ny":[9,20],"nz":[0,-1]},{"size":2,"px":[5,1],"py":[4,2],"pz":[2,-1],"nx":[23,23],"ny":[15,16],"nz":[0,0]},{"size":5,"px":[9,8,2,4,9],"py":[1,1,0,1,2],"pz":[0,0,2,1,0],"nx":[8,3,8,4,4],"ny":[6,2,4,2,2],"nz":[1,2,1,2,-1]},{"size":2,"px":[13,6],"py":[10,5],"pz":[0,-1],"nx":[13,7],"ny":[6,3],"nz":[0,1]},{"size":2,"px":[11,5],"py":[10,5],"pz":[1,2],"nx":[10,8],"ny":[10,9],"nz":[1,-1]},{"size":2,"px":[7,4],"py":[6,3],"pz":[1,2],"nx":[9,14],"ny":[4,9],"nz":[1,-1]},{"size":3,"px":[5,2,15],"py":[3,1,22],"pz":[1,-1,-1],"nx":[15,9,4],"ny":[0,1,0],"nz":[0,1,2]},{"size":2,"px":[10,19],"py":[9,21],"pz":[1,0],"nx":[2,17],"ny":[5,14],"nz":[2,-1]},{"size":3,"px":[16,2,1],"py":[2,10,4],"pz":[0,-1,-1],"nx":[4,4,9],"ny":[3,2,6],"nz":[2,2,1]},{"size":2,"px":[10,2],"py":[6,10],"pz":[1,-1],"nx":[21,22],"ny":[16,12],"nz":[0,0]},{"size":2,"px":[7,16],"py":[4,23],"pz":[0,-1],"nx":[7,3],"ny":[3,3],"nz":[0,1]},{"size":2,"px":[1,1],"py":[13,14],"pz":[0,0],"nx":[1,2],"ny":[18,3],"nz":[0,-1]},{"size":2,"px":[18,5],"py":[13,4],"pz":[0,-1],"nx":[4,13],"ny":[2,11],"nz":[2,0]},{"size":2,"px":[18,17],"py":[3,3],"pz":[0,0],"nx":[19,19],"ny":[1,1],"nz":[0,-1]},{"size":2,"px":[9,5],"py":[0,5],"pz":[1,-1],"nx":[12,3],"ny":[5,1],"nz":[0,2]},{"size":2,"px":[5,3],"py":[2,1],"pz":[1,2],"nx":[18,4],"ny":[4,1],"nz":[0,-1]},{"size":5,"px":[13,13,2,10,15],"py":[11,12,13,17,23],"pz":[0,-1,-1,-1,-1],"nx":[12,13,4,3,8],"ny":[4,4,1,0,3],"nz":[0,0,2,2,1]},{"size":2,"px":[9,3],"py":[2,2],"pz":[0,-1],"nx":[4,2],"ny":[7,2],"nz":[1,2]},{"size":2,"px":[13,4],"py":[5,1],"pz":[0,-1],"nx":[18,4],"ny":[12,2],"nz":[0,2]},{"size":2,"px":[19,4],"py":[11,1],"pz":[0,-1],"nx":[4,7],"ny":[2,2],"nz":[2,1]},{"size":2,"px":[4,2],"py":[6,3],"pz":[1,2],"nx":[3,2],"ny":[4,5],"nz":[1,-1]},{"size":2,"px":[4,0],"py":[7,7],"pz":[0,-1],"nx":[4,9],"ny":[0,2],"nz":[2,1]},{"size":2,"px":[4,9],"py":[0,2],"pz":[2,1],"nx":[6,4],"ny":[3,4],"nz":[0,-1]},{"size":2,"px":[4,2],"py":[9,4],"pz":[1,2],"nx":[13,5],"ny":[18,2],"nz":[0,-1]},{"size":3,"px":[5,23,23],"py":[2,8,7],"pz":[2,0,0],"nx":[10,12,1],"ny":[4,1,0],"nz":[1,-1,-1]},{"size":2,"px":[13,0],"py":[3,3],"pz":[0,-1],"nx":[4,4],"ny":[2,3],"nz":[2,2]},{"size":2,"px":[6,5],"py":[10,5],"pz":[0,-1],"nx":[0,0],"ny":[4,11],"nz":[1,0]},{"size":2,"px":[11,2],"py":[14,11],"pz":[0,-1],"nx":[10,11],"ny":[4,13],"nz":[1,0]},{"size":2,"px":[5,6],"py":[21,23],"pz":[0,0],"nx":[7,0],"ny":[21,3],"nz":[0,-1]},{"size":2,"px":[8,4],"py":[6,3],"pz":[1,2],"nx":[8,5],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[7,6],"py":[8,8],"pz":[0,0],"nx":[6,14],"ny":[9,15],"nz":[0,-1]},{"size":2,"px":[16,6],"py":[4,8],"pz":[0,-1],"nx":[16,8],"ny":[0,1],"nz":[0,1]},{"size":4,"px":[3,6,0,9],"py":[0,8,5,23],"pz":[1,-1,-1,-1],"nx":[12,2,6,10],"ny":[5,0,3,5],"nz":[0,2,1,0]},{"size":2,"px":[3,6],"py":[7,13],"pz":[1,0],"nx":[3,9],"ny":[4,9],"nz":[1,-1]},{"size":2,"px":[2,5],"py":[8,23],"pz":[1,0],"nx":[8,9],"ny":[15,0],"nz":[0,-1]},{"size":2,"px":[13,18],"py":[8,0],"pz":[0,-1],"nx":[1,1],"ny":[9,8],"nz":[1,1]},{"size":2,"px":[2,7],"py":[4,21],"pz":[2,0],"nx":[13,11],"ny":[8,9],"nz":[0,-1]},{"size":2,"px":[5,4],"py":[8,8],"pz":[0,0],"nx":[6,1],"ny":[8,5],"nz":[0,-1]},{"size":2,"px":[7,3],"py":[20,7],"pz":[0,-1],"nx":[4,3],"ny":[10,4],"nz":[1,1]},{"size":2,"px":[9,9],"py":[8,7],"pz":[1,-1],"nx":[1,2],"ny":[4,9],"nz":[2,1]},{"size":2,"px":[5,10],"py":[5,13],"pz":[1,-1],"nx":[3,6],"ny":[1,2],"nz":[2,1]},{"size":2,"px":[12,5],"py":[6,3],"pz":[0,-1],"nx":[8,4],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[10,10],"py":[4,4],"pz":[1,-1],"nx":[5,11],"ny":[2,5],"nz":[2,1]},{"size":5,"px":[11,23,11,23,11],"py":[4,9,5,10,6],"pz":[1,0,1,0,1],"nx":[7,14,13,7,3],"ny":[9,5,6,4,4],"nz":[0,0,0,1,-1]},{"size":2,"px":[8,5],"py":[0,0],"pz":[1,-1],"nx":[9,20],"ny":[1,4],"nz":[1,0]},{"size":2,"px":[19,20],"py":[0,3],"pz":[0,0],"nx":[4,6],"ny":[11,3],"nz":[1,-1]},{"size":4,"px":[13,5,20,5],"py":[14,3,23,4],"pz":[0,-1,-1,-1],"nx":[8,15,7,16],"ny":[8,14,6,15],"nz":[1,0,1,0]},{"size":2,"px":[10,20],"py":[5,17],"pz":[0,-1],"nx":[7,3],"ny":[10,1],"nz":[0,2]},{"size":3,"px":[1,12,7],"py":[3,7,10],"pz":[2,0,0],"nx":[2,2,3],"ny":[3,2,2],"nz":[1,-1,-1]},{"size":3,"px":[10,5,7],"py":[7,10,10],"pz":[1,-1,-1],"nx":[10,10,18],"ny":[10,9,23],"nz":[1,1,0]},{"size":3,"px":[14,14,4],"py":[3,3,4],"pz":[0,-1,-1],"nx":[4,4,8],"ny":[3,2,6],"nz":[2,2,1]},{"size":2,"px":[4,12],"py":[4,17],"pz":[2,0],"nx":[13,1],"ny":[15,4],"nz":[0,-1]},{"size":2,"px":[10,20],"py":[9,22],"pz":[0,-1],"nx":[9,4],"ny":[2,0],"nz":[1,2]},{"size":2,"px":[11,2],"py":[3,6],"pz":[0,-1],"nx":[2,4],"ny":[2,4],"nz":[2,1]},{"size":3,"px":[15,10,1],"py":[12,2,3],"pz":[0,-1,-1],"nx":[7,5,10],"ny":[2,1,1],"nz":[0,1,0]},{"size":5,"px":[9,11,10,12,12],"py":[0,0,0,0,0],"pz":[0,0,0,0,-1],"nx":[8,4,16,5,10],"ny":[4,4,10,3,6],"nz":[1,1,0,1,0]},{"size":2,"px":[0,10],"py":[3,5],"pz":[2,-1],"nx":[3,6],"ny":[0,1],"nz":[2,1]},{"size":5,"px":[7,8,7,2,12],"py":[14,13,13,16,0],"pz":[0,0,-1,-1,-1],"nx":[10,1,10,1,1],"ny":[13,2,12,4,9],"nz":[0,2,0,1,0]},{"size":3,"px":[6,14,13],"py":[1,2,1],"pz":[1,0,0],"nx":[8,21,10],"ny":[4,23,12],"nz":[1,-1,-1]},{"size":2,"px":[19,19],"py":[22,21],"pz":[0,0],"nx":[20,1],"ny":[22,5],"nz":[0,-1]},{"size":2,"px":[13,12],"py":[19,22],"pz":[0,-1],"nx":[2,3],"ny":[0,1],"nz":[2,1]},{"size":4,"px":[11,9,21,4],"py":[13,3,19,5],"pz":[0,-1,-1,-1],"nx":[9,9,9,5],"ny":[13,14,12,6],"nz":[0,0,0,1]},{"size":4,"px":[11,12,13,14],"py":[22,22,22,22],"pz":[0,0,0,0],"nx":[13,2,4,5],"ny":[20,0,0,6],"nz":[0,-1,-1,-1]},{"size":2,"px":[4,2],"py":[6,3],"pz":[1,2],"nx":[3,1],"ny":[4,3],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[0,1],"pz":[2,2],"nx":[9,4],"ny":[6,5],"nz":[1,-1]},{"size":2,"px":[17,0],"py":[10,1],"pz":[0,-1],"nx":[9,4],"ny":[3,2],"nz":[1,2]},{"size":2,"px":[10,4],"py":[3,1],"pz":[1,2],"nx":[12,18],"ny":[17,4],"nz":[0,-1]},{"size":3,"px":[2,3,4],"py":[4,3,9],"pz":[2,2,1],"nx":[0,3,17],"ny":[0,1,18],"nz":[0,-1,-1]},{"size":2,"px":[7,3],"py":[12,6],"pz":[0,1],"nx":[5,1],"ny":[11,1],"nz":[1,-1]},{"size":2,"px":[10,17],"py":[20,6],"pz":[0,-1],"nx":[5,2],"ny":[9,5],"nz":[1,2]},{"size":2,"px":[8,11],"py":[18,2],"pz":[0,-1],"nx":[5,4],"ny":[9,9],"nz":[1,1]},{"size":2,"px":[16,15],"py":[2,2],"pz":[0,0],"nx":[17,12],"ny":[2,2],"nz":[0,-1]},{"size":2,"px":[18,4],"py":[5,5],"pz":[0,-1],"nx":[7,5],"ny":[23,19],"nz":[0,0]},{"size":2,"px":[12,13],"py":[23,23],"pz":[0,0],"nx":[7,11],"ny":[10,20],"nz":[1,-1]},{"size":2,"px":[5,10],"py":[3,18],"pz":[2,-1],"nx":[9,9],"ny":[5,6],"nz":[1,1]},{"size":2,"px":[5,10],"py":[2,4],"pz":[1,0],"nx":[4,23],"ny":[4,20],"nz":[1,-1]},{"size":2,"px":[2,3],"py":[8,1],"pz":[1,-1],"nx":[15,12],"ny":[2,1],"nz":[0,0]},{"size":2,"px":[4,7],"py":[3,10],"pz":[2,1],"nx":[10,1],"ny":[20,4],"nz":[0,-1]},{"size":2,"px":[11,11],"py":[10,11],"pz":[0,0],"nx":[22,3],"ny":[5,4],"nz":[0,-1]},{"size":5,"px":[8,17,17,9,18],"py":[0,1,0,1,0],"pz":[1,0,0,1,0],"nx":[11,8,9,4,4],"ny":[23,4,6,2,2],"nz":[0,1,0,2,-1]},{"size":2,"px":[5,5],"py":[4,4],"pz":[1,-1],"nx":[13,4],"ny":[9,2],"nz":[0,2]},{"size":5,"px":[9,4,8,7,7],"py":[3,1,3,3,3],"pz":[0,1,0,0,-1],"nx":[4,2,5,3,2],"ny":[1,15,1,4,13],"nz":[0,0,0,0,0]},{"size":2,"px":[17,7],"py":[13,7],"pz":[0,-1],"nx":[4,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[1,2],"py":[1,12],"pz":[2,0],"nx":[9,21],"ny":[5,4],"nz":[0,-1]},{"size":2,"px":[12,0],"py":[14,1],"pz":[0,-1],"nx":[1,1],"ny":[19,10],"nz":[0,1]},{"size":2,"px":[16,1],"py":[5,9],"pz":[0,-1],"nx":[16,15],"ny":[3,3],"nz":[0,0]},{"size":2,"px":[4,8],"py":[3,6],"pz":[2,1],"nx":[8,4],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[11,6],"py":[17,15],"pz":[0,0],"nx":[11,0],"ny":[16,4],"nz":[0,-1]},{"size":4,"px":[12,11,0,3],"py":[16,8,7,1],"pz":[0,-1,-1,-1],"nx":[10,5,10,5],"ny":[11,9,10,8],"nz":[0,1,0,1]},{"size":2,"px":[3,6],"py":[7,13],"pz":[1,0],"nx":[4,14],"ny":[4,16],"nz":[1,-1]},{"size":2,"px":[7,17],"py":[6,13],"pz":[0,-1],"nx":[4,8],"ny":[4,9],"nz":[2,1]},{"size":2,"px":[15,11],"py":[3,2],"pz":[0,-1],"nx":[4,15],"ny":[1,2],"nz":[2,0]},{"size":2,"px":[10,11],"py":[18,4],"pz":[0,-1],"nx":[5,5],"ny":[8,9],"nz":[1,1]},{"size":2,"px":[8,4],"py":[7,4],"pz":[1,2],"nx":[4,3],"ny":[5,7],"nz":[2,-1]},{"size":2,"px":[12,4],"py":[15,4],"pz":[0,-1],"nx":[11,8],"ny":[14,19],"nz":[0,0]},{"size":2,"px":[18,13],"py":[13,20],"pz":[0,0],"nx":[13,4],"ny":[18,2],"nz":[0,-1]},{"size":2,"px":[12,4],"py":[6,3],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[21,5,11,5,10],"py":[1,1,3,0,0],"pz":[0,2,1,2,1],"nx":[7,14,15,4,8],"ny":[3,6,11,3,4],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[10,6],"py":[15,10],"pz":[0,-1],"nx":[21,22],"ny":[14,12],"nz":[0,0]},{"size":2,"px":[18,0],"py":[20,0],"pz":[0,-1],"nx":[2,3],"ny":[2,4],"nz":[2,1]},{"size":5,"px":[12,6,13,11,7],"py":[1,1,1,2,1],"pz":[0,1,0,0,1],"nx":[7,6,8,5,5],"ny":[4,15,4,16,16],"nz":[1,0,1,0,-1]},{"size":3,"px":[22,21,21],"py":[14,15,17],"pz":[0,0,0],"nx":[5,9,4],"ny":[0,5,0],"nz":[2,-1,-1]},{"size":2,"px":[10,2],"py":[14,1],"pz":[0,-1],"nx":[23,11],"ny":[16,8],"nz":[0,1]},{"size":4,"px":[21,21,0,18],"py":[14,15,5,4],"pz":[0,0,-1,-1],"nx":[8,8,9,4],"ny":[7,8,10,5],"nz":[1,1,1,2]},{"size":2,"px":[15,5],"py":[18,1],"pz":[0,-1],"nx":[23,23],"ny":[16,18],"nz":[0,0]},{"size":2,"px":[15,14],"py":[1,1],"pz":[0,0],"nx":[4,4],"ny":[2,3],"nz":[2,-1]},{"size":2,"px":[2,6],"py":[6,5],"pz":[1,-1],"nx":[14,11],"ny":[1,1],"nz":[0,0]},{"size":2,"px":[3,17],"py":[2,8],"pz":[2,0],"nx":[8,3],"ny":[4,9],"nz":[1,-1]},{"size":2,"px":[17,8],"py":[13,10],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[0,0],"py":[8,3],"pz":[0,1],"nx":[1,11],"ny":[4,7],"nz":[1,-1]},{"size":2,"px":[6,8],"py":[5,0],"pz":[1,-1],"nx":[0,0],"ny":[3,1],"nz":[1,2]},{"size":2,"px":[0,0],"py":[5,3],"pz":[1,2],"nx":[1,18],"ny":[5,7],"nz":[1,-1]},{"size":2,"px":[7,3],"py":[6,6],"pz":[0,1],"nx":[7,12],"ny":[5,20],"nz":[0,-1]},{"size":2,"px":[8,1],"py":[0,5],"pz":[0,-1],"nx":[4,2],"ny":[9,3],"nz":[1,2]},{"size":2,"px":[0,0],"py":[10,11],"pz":[0,0],"nx":[0,5],"ny":[5,9],"nz":[0,-1]},{"size":2,"px":[8,1],"py":[23,4],"pz":[0,2],"nx":[0,0],"ny":[13,2],"nz":[0,-1]},{"size":2,"px":[4,1],"py":[6,4],"pz":[0,-1],"nx":[4,4],"ny":[4,5],"nz":[2,2]},{"size":2,"px":[7,6],"py":[6,5],"pz":[1,1],"nx":[3,9],"ny":[4,16],"nz":[1,-1]},{"size":2,"px":[5,3],"py":[9,13],"pz":[0,-1],"nx":[4,10],"ny":[3,7],"nz":[1,0]},{"size":5,"px":[13,9,6,10,10],"py":[2,2,1,2,2],"pz":[0,0,1,0,-1],"nx":[7,5,6,5,6],"ny":[0,2,2,1,1],"nz":[0,0,0,0,0]}],"alpha":[-1.119615e+00,1.119615e+00,-8.169953e-01,8.169953e-01,-5.291213e-01,5.291213e-01,-4.904488e-01,4.904488e-01,-4.930982e-01,4.930982e-01,-4.106179e-01,4.106179e-01,-4.246842e-01,4.246842e-01,-3.802383e-01,3.802383e-01,-3.364358e-01,3.364358e-01,-3.214186e-01,3.214186e-01,-3.210798e-01,3.210798e-01,-2.993167e-01,2.993167e-01,-3.426336e-01,3.426336e-01,-3.199184e-01,3.199184e-01,-3.061071e-01,3.061071e-01,-2.758972e-01,2.758972e-01,-3.075590e-01,3.075590e-01,-3.009565e-01,3.009565e-01,-2.015739e-01,2.015739e-01,-2.603266e-01,2.603266e-01,-2.772993e-01,2.772993e-01,-2.184913e-01,2.184913e-01,-2.306681e-01,2.306681e-01,-1.983223e-01,1.983223e-01,-2.194760e-01,2.194760e-01,-2.528421e-01,2.528421e-01,-2.436416e-01,2.436416e-01,-3.032886e-01,3.032886e-01,-2.556071e-01,2.556071e-01,-2.562170e-01,2.562170e-01,-1.930298e-01,1.930298e-01,-2.735898e-01,2.735898e-01,-1.814703e-01,1.814703e-01,-2.054824e-01,2.054824e-01,-1.986146e-01,1.986146e-01,-1.769226e-01,1.769226e-01,-1.775257e-01,1.775257e-01,-2.167927e-01,2.167927e-01,-1.823633e-01,1.823633e-01,-1.584280e-01,1.584280e-01,-1.778321e-01,1.778321e-01,-1.826777e-01,1.826777e-01,-1.979903e-01,1.979903e-01,-1.898326e-01,1.898326e-01,-1.835506e-01,1.835506e-01,-1.967860e-01,1.967860e-01,-1.871528e-01,1.871528e-01,-1.772414e-01,1.772414e-01,-1.985514e-01,1.985514e-01,-2.144078e-01,2.144078e-01,-2.742303e-01,2.742303e-01,-2.240550e-01,2.240550e-01,-2.132534e-01,2.132534e-01,-1.552127e-01,1.552127e-01,-1.568276e-01,1.568276e-01,-1.630086e-01,1.630086e-01,-1.458232e-01,1.458232e-01,-1.559541e-01,1.559541e-01,-1.720131e-01,1.720131e-01,-1.708434e-01,1.708434e-01,-1.624431e-01,1.624431e-01,-1.814161e-01,1.814161e-01,-1.552639e-01,1.552639e-01,-1.242354e-01,1.242354e-01,-1.552139e-01,1.552139e-01,-1.694359e-01,1.694359e-01,-1.801481e-01,1.801481e-01,-1.387182e-01,1.387182e-01,-1.409679e-01,1.409679e-01,-1.486724e-01,1.486724e-01,-1.779553e-01,1.779553e-01,-1.524595e-01,1.524595e-01,-1.788086e-01,1.788086e-01,-1.671479e-01,1.671479e-01,-1.376197e-01,1.376197e-01,-1.511808e-01,1.511808e-01,-1.524632e-01,1.524632e-01,-1.198986e-01,1.198986e-01,-1.382641e-01,1.382641e-01,-1.148901e-01,1.148901e-01,-1.131803e-01,1.131803e-01,-1.273508e-01,1.273508e-01,-1.405125e-01,1.405125e-01,-1.322132e-01,1.322132e-01,-1.386966e-01,1.386966e-01,-1.275621e-01,1.275621e-01,-1.180573e-01,1.180573e-01,-1.238803e-01,1.238803e-01,-1.428389e-01,1.428389e-01,-1.694437e-01,1.694437e-01,-1.290855e-01,1.290855e-01,-1.520260e-01,1.520260e-01,-1.398282e-01,1.398282e-01,-1.890736e-01,1.890736e-01,-2.280428e-01,2.280428e-01,-1.325099e-01,1.325099e-01,-1.342873e-01,1.342873e-01,-1.463841e-01,1.463841e-01,-1.983567e-01,1.983567e-01,-1.585711e-01,1.585711e-01,-1.260154e-01,1.260154e-01,-1.426774e-01,1.426774e-01,-1.554278e-01,1.554278e-01,-1.361201e-01,1.361201e-01,-1.181856e-01,1.181856e-01,-1.255941e-01,1.255941e-01,-1.113275e-01,1.113275e-01,-1.506576e-01,1.506576e-01,-1.202859e-01,1.202859e-01,-2.159751e-01,2.159751e-01,-1.443150e-01,1.443150e-01,-1.379194e-01,1.379194e-01,-1.805758e-01,1.805758e-01,-1.465612e-01,1.465612e-01,-1.328856e-01,1.328856e-01,-1.532173e-01,1.532173e-01,-1.590635e-01,1.590635e-01,-1.462229e-01,1.462229e-01,-1.350012e-01,1.350012e-01,-1.195634e-01,1.195634e-01,-1.173221e-01,1.173221e-01,-1.192867e-01,1.192867e-01,-1.595013e-01,1.595013e-01,-1.209751e-01,1.209751e-01,-1.571290e-01,1.571290e-01,-1.527274e-01,1.527274e-01,-1.373708e-01,1.373708e-01,-1.318313e-01,1.318313e-01,-1.273391e-01,1.273391e-01,-1.271365e-01,1.271365e-01,-1.528693e-01,1.528693e-01,-1.590476e-01,1.590476e-01,-1.581911e-01,1.581911e-01,-1.183023e-01,1.183023e-01,-1.559822e-01,1.559822e-01,-1.214999e-01,1.214999e-01,-1.283378e-01,1.283378e-01,-1.542583e-01,1.542583e-01,-1.336377e-01,1.336377e-01,-1.800416e-01,1.800416e-01,-1.710931e-01,1.710931e-01,-1.621737e-01,1.621737e-01,-1.239002e-01,1.239002e-01,-1.432928e-01,1.432928e-01,-1.392447e-01,1.392447e-01,-1.383938e-01,1.383938e-01,-1.357633e-01,1.357633e-01,-1.175842e-01,1.175842e-01,-1.085318e-01,1.085318e-01,-1.148885e-01,1.148885e-01,-1.320396e-01,1.320396e-01,-1.351204e-01,1.351204e-01,-1.581518e-01,1.581518e-01,-1.459574e-01,1.459574e-01,-1.180068e-01,1.180068e-01,-1.464196e-01,1.464196e-01,-1.179543e-01,1.179543e-01,-1.004204e-01,1.004204e-01,-1.294660e-01,1.294660e-01,-1.534244e-01,1.534244e-01,-1.378970e-01,1.378970e-01,-1.226545e-01,1.226545e-01,-1.281182e-01,1.281182e-01,-1.201471e-01,1.201471e-01,-1.448701e-01,1.448701e-01,-1.290980e-01,1.290980e-01,-1.388764e-01,1.388764e-01,-9.605773e-02,9.605773e-02,-1.411021e-01,1.411021e-01,-1.295693e-01,1.295693e-01,-1.371739e-01,1.371739e-01,-1.167579e-01,1.167579e-01,-1.400486e-01,1.400486e-01,-1.214224e-01,1.214224e-01,-1.287835e-01,1.287835e-01,-1.197646e-01,1.197646e-01,-1.192358e-01,1.192358e-01,-1.218651e-01,1.218651e-01,-1.564816e-01,1.564816e-01,-1.172391e-01,1.172391e-01,-1.342268e-01,1.342268e-01,-1.492471e-01,1.492471e-01,-1.157299e-01,1.157299e-01,-1.046703e-01,1.046703e-01,-1.255571e-01,1.255571e-01,-1.100135e-01,1.100135e-01,-1.501592e-01,1.501592e-01,-1.155712e-01,1.155712e-01,-1.145563e-01,1.145563e-01,-1.013425e-01,1.013425e-01,-1.145783e-01,1.145783e-01,-1.328031e-01,1.328031e-01,-1.077413e-01,1.077413e-01,-1.064996e-01,1.064996e-01,-1.191170e-01,1.191170e-01,-1.213217e-01,1.213217e-01,-1.260969e-01,1.260969e-01,-1.156494e-01,1.156494e-01,-1.268126e-01,1.268126e-01,-1.070999e-01,1.070999e-01,-1.112365e-01,1.112365e-01,-1.243916e-01,1.243916e-01,-1.283152e-01,1.283152e-01,-1.166925e-01,1.166925e-01,-8.997633e-02,8.997633e-02,-1.583840e-01,1.583840e-01,-1.211178e-01,1.211178e-01,-1.090830e-01,1.090830e-01,-1.030818e-01,1.030818e-01,-1.440600e-01,1.440600e-01,-1.458713e-01,1.458713e-01,-1.559082e-01,1.559082e-01,-1.058868e-01,1.058868e-01,-1.010130e-01,1.010130e-01,-1.642301e-01,1.642301e-01,-1.236850e-01,1.236850e-01,-1.467589e-01,1.467589e-01,-1.109359e-01,1.109359e-01,-1.673655e-01,1.673655e-01,-1.239984e-01,1.239984e-01,-1.039509e-01,1.039509e-01,-1.089378e-01,1.089378e-01,-1.545085e-01,1.545085e-01,-1.200862e-01,1.200862e-01,-1.105608e-01,1.105608e-01,-1.235262e-01,1.235262e-01,-8.496153e-02,8.496153e-02,-1.181372e-01,1.181372e-01,-1.139467e-01,1.139467e-01,-1.189317e-01,1.189317e-01,-1.266519e-01,1.266519e-01,-9.470736e-02,9.470736e-02,-1.336735e-01,1.336735e-01,-8.726601e-02,8.726601e-02,-1.304782e-01,1.304782e-01,-1.186529e-01,1.186529e-01,-1.355944e-01,1.355944e-01,-9.568801e-02,9.568801e-02,-1.282618e-01,1.282618e-01,-1.625632e-01,1.625632e-01,-1.167652e-01,1.167652e-01,-1.001301e-01,1.001301e-01,-1.292419e-01,1.292419e-01,-1.904213e-01,1.904213e-01,-1.511542e-01,1.511542e-01,-9.814394e-02,9.814394e-02,-1.171564e-01,1.171564e-01,-9.806486e-02,9.806486e-02,-9.217615e-02,9.217615e-02,-8.505645e-02,8.505645e-02,-1.573637e-01,1.573637e-01,-1.419174e-01,1.419174e-01,-1.298601e-01,1.298601e-01,-1.120613e-01,1.120613e-01,-1.158363e-01,1.158363e-01,-1.090957e-01,1.090957e-01,-1.204516e-01,1.204516e-01,-1.139852e-01,1.139852e-01,-9.642479e-02,9.642479e-02,-1.410872e-01,1.410872e-01,-1.142779e-01,1.142779e-01,-1.043991e-01,1.043991e-01,-9.736463e-02,9.736463e-02,-1.451046e-01,1.451046e-01,-1.205668e-01,1.205668e-01,-9.881445e-02,9.881445e-02,-1.612822e-01,1.612822e-01,-1.175681e-01,1.175681e-01,-1.522528e-01,1.522528e-01,-1.617520e-01,1.617520e-01,-1.582938e-01,1.582938e-01,-1.208202e-01,1.208202e-01,-1.016003e-01,1.016003e-01,-1.232059e-01,1.232059e-01,-9.583025e-02,9.583025e-02,-1.013990e-01,1.013990e-01,-1.178752e-01,1.178752e-01,-1.215972e-01,1.215972e-01,-1.294932e-01,1.294932e-01,-1.158270e-01,1.158270e-01,-1.008645e-01,1.008645e-01,-9.699190e-02,9.699190e-02,-1.022144e-01,1.022144e-01,-9.878768e-02,9.878768e-02,-1.339052e-01,1.339052e-01,-9.279961e-02,9.279961e-02,-1.047606e-01,1.047606e-01,-1.141163e-01,1.141163e-01,-1.267600e-01,1.267600e-01,-1.252763e-01,1.252763e-01,-9.775003e-02,9.775003e-02,-9.169116e-02,9.169116e-02,-1.006496e-01,1.006496e-01,-9.493293e-02,9.493293e-02,-1.213694e-01,1.213694e-01,-1.109243e-01,1.109243e-01,-1.115973e-01,1.115973e-01,-7.979327e-02,7.979327e-02,-9.220953e-02,9.220953e-02,-1.028913e-01,1.028913e-01,-1.253510e-01,1.253510e-01]},{"count":391,"threshold":-4.665692e+00,"feature":[{"size":5,"px":[14,9,11,17,12],"py":[2,3,9,13,3],"pz":[0,0,0,0,0],"nx":[21,8,7,20,13],"ny":[16,10,7,7,9],"nz":[0,1,1,0,0]},{"size":5,"px":[12,10,6,11,13],"py":[9,3,13,3,4],"pz":[0,0,0,0,0],"nx":[10,4,5,10,2],"ny":[9,10,8,8,2],"nz":[0,1,1,0,2]},{"size":5,"px":[6,9,7,8,8],"py":[3,3,3,3,3],"pz":[0,0,0,0,-1],"nx":[0,0,0,4,9],"ny":[4,2,3,10,8],"nz":[0,0,0,1,0]},{"size":5,"px":[6,2,16,6,8],"py":[16,2,11,4,11],"pz":[0,2,0,1,0],"nx":[3,8,4,1,1],"ny":[4,4,4,5,13],"nz":[1,1,-1,-1,-1]},{"size":3,"px":[16,13,9],"py":[23,18,10],"pz":[0,0,1],"nx":[14,15,8],"ny":[21,22,3],"nz":[0,-1,-1]},{"size":5,"px":[9,16,19,17,17],"py":[1,2,3,2,2],"pz":[1,0,0,0,-1],"nx":[23,23,23,23,23],"ny":[6,2,1,3,5],"nz":[0,0,0,0,0]},{"size":5,"px":[12,12,12,12,12],"py":[10,11,12,13,13],"pz":[0,0,0,0,-1],"nx":[4,8,14,4,6],"ny":[2,4,7,4,8],"nz":[2,1,0,1,1]},{"size":5,"px":[1,2,3,6,4],"py":[6,10,12,23,13],"pz":[1,1,0,0,0],"nx":[2,0,0,1,1],"ny":[23,5,10,21,21],"nz":[0,2,1,0,-1]},{"size":5,"px":[12,16,12,4,12],"py":[6,17,7,2,8],"pz":[0,0,0,2,0],"nx":[8,8,12,0,6],"ny":[4,4,16,0,8],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[9,2],"py":[18,4],"pz":[0,-1],"nx":[4,9],"ny":[10,16],"nz":[1,0]},{"size":5,"px":[9,9,2,0,12],"py":[6,6,21,4,8],"pz":[1,-1,-1,-1,-1],"nx":[8,4,9,7,7],"ny":[10,2,4,5,8],"nz":[1,2,1,1,1]},{"size":5,"px":[10,10,10,18,19],"py":[10,8,7,14,14],"pz":[1,1,1,0,0],"nx":[21,23,22,22,11],"ny":[23,19,21,22,10],"nz":[0,0,0,0,-1]},{"size":5,"px":[12,3,15,4,19],"py":[14,0,5,5,14],"pz":[0,-1,-1,-1,-1],"nx":[12,17,15,3,8],"ny":[18,18,14,2,10],"nz":[0,0,0,2,0]},{"size":5,"px":[8,11,3,11,4],"py":[23,7,9,8,8],"pz":[0,0,1,0,1],"nx":[8,0,10,0,8],"ny":[8,2,8,4,10],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[10,11,12,8,4],"py":[3,0,0,1,1],"pz":[0,0,0,0,1],"nx":[2,3,4,3,3],"ny":[14,5,0,1,2],"nz":[0,0,0,0,0]},{"size":2,"px":[3,11],"py":[7,0],"pz":[1,-1],"nx":[5,2],"ny":[9,5],"nz":[1,2]},{"size":5,"px":[7,1,0,10,1],"py":[0,0,2,12,6],"pz":[0,2,2,0,1],"nx":[4,6,2,8,8],"ny":[4,11,2,4,4],"nz":[1,1,2,1,-1]},{"size":2,"px":[4,15],"py":[4,12],"pz":[2,0],"nx":[4,6],"ny":[5,11],"nz":[2,-1]},{"size":5,"px":[9,4,16,14,14],"py":[8,4,23,18,18],"pz":[1,2,0,0,-1],"nx":[0,2,1,1,0],"ny":[2,0,3,2,3],"nz":[1,0,0,0,1]},{"size":5,"px":[17,7,7,18,19],"py":[7,11,8,7,7],"pz":[0,1,1,0,0],"nx":[17,5,8,2,0],"ny":[8,0,7,5,3],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[5,14],"py":[12,3],"pz":[0,-1],"nx":[4,3],"ny":[5,4],"nz":[1,1]},{"size":5,"px":[10,8,16,11,11],"py":[5,6,12,4,4],"pz":[0,1,0,0,-1],"nx":[14,13,5,9,5],"ny":[13,10,1,4,2],"nz":[0,0,2,1,2]},{"size":5,"px":[15,14,16,8,8],"py":[2,2,2,0,0],"pz":[0,0,0,1,-1],"nx":[9,18,19,18,17],"ny":[0,0,2,1,0],"nz":[1,0,0,0,0]},{"size":2,"px":[17,15],"py":[12,11],"pz":[0,0],"nx":[14,4],"ny":[9,15],"nz":[0,-1]},{"size":3,"px":[5,11,11],"py":[3,4,5],"pz":[2,1,1],"nx":[14,3,18],"ny":[6,5,0],"nz":[0,1,-1]},{"size":5,"px":[16,14,17,15,9],"py":[2,2,2,2,1],"pz":[0,0,0,0,1],"nx":[21,20,11,21,21],"ny":[2,0,7,3,3],"nz":[0,0,1,0,-1]},{"size":5,"px":[2,1,1,1,5],"py":[12,9,7,3,6],"pz":[0,0,1,1,1],"nx":[4,8,3,4,17],"ny":[4,4,0,8,0],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[8,4],"py":[6,3],"pz":[1,2],"nx":[9,2],"ny":[4,17],"nz":[1,-1]},{"size":2,"px":[8,5],"py":[16,9],"pz":[0,1],"nx":[10,17],"ny":[16,8],"nz":[0,-1]},{"size":4,"px":[11,5,9,15],"py":[14,9,11,5],"pz":[0,-1,-1,-1],"nx":[10,1,9,4],"ny":[9,2,13,7],"nz":[0,2,0,1]},{"size":5,"px":[2,5,10,7,10],"py":[7,12,2,13,3],"pz":[1,-1,-1,-1,-1],"nx":[5,2,3,3,2],"ny":[23,15,17,16,14],"nz":[0,0,0,0,0]},{"size":2,"px":[11,7],"py":[8,10],"pz":[0,-1],"nx":[7,14],"ny":[5,8],"nz":[1,0]},{"size":2,"px":[9,16],"py":[7,23],"pz":[1,0],"nx":[4,4],"ny":[2,1],"nz":[2,-1]},{"size":5,"px":[16,14,18,4,17],"py":[0,0,4,0,1],"pz":[0,0,0,2,0],"nx":[8,8,16,9,9],"ny":[5,4,11,7,7],"nz":[1,1,0,0,-1]},{"size":5,"px":[12,13,7,8,4],"py":[9,12,6,11,5],"pz":[0,0,1,1,2],"nx":[23,23,16,9,9],"ny":[0,1,11,7,7],"nz":[0,-1,-1,-1,-1]},{"size":3,"px":[6,7,2],"py":[21,23,4],"pz":[0,0,2],"nx":[4,1,16],"ny":[10,5,11],"nz":[1,-1,-1]},{"size":2,"px":[2,2],"py":[3,4],"pz":[2,2],"nx":[3,1],"ny":[4,5],"nz":[1,-1]},{"size":5,"px":[1,2,1,0,1],"py":[7,13,12,4,13],"pz":[0,0,0,2,0],"nx":[18,9,9,19,19],"ny":[23,5,11,19,19],"nz":[0,1,1,0,-1]},{"size":3,"px":[4,10,12],"py":[6,2,5],"pz":[1,-1,-1],"nx":[10,0,0],"ny":[12,1,3],"nz":[0,2,2]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,0],"ny":[4,3],"nz":[1,-1]},{"size":5,"px":[19,17,10,14,18],"py":[2,1,7,0,1],"pz":[0,0,1,0,0],"nx":[3,3,3,7,5],"ny":[9,10,7,23,18],"nz":[1,1,1,0,0]},{"size":2,"px":[10,10],"py":[8,7],"pz":[1,1],"nx":[14,4],"ny":[15,6],"nz":[0,-1]},{"size":2,"px":[7,15],"py":[1,3],"pz":[1,0],"nx":[16,19],"ny":[1,3],"nz":[0,-1]},{"size":5,"px":[11,11,1,2,11],"py":[11,12,1,13,12],"pz":[0,0,-1,-1,-1],"nx":[12,17,8,16,8],"ny":[7,12,11,16,6],"nz":[0,0,0,0,1]},{"size":5,"px":[13,11,10,12,5],"py":[0,0,0,0,0],"pz":[0,0,0,0,1],"nx":[8,4,3,4,4],"ny":[4,5,2,4,4],"nz":[1,1,2,1,-1]},{"size":5,"px":[6,1,3,2,3],"py":[13,3,3,4,10],"pz":[0,2,1,1,1],"nx":[0,1,0,0,0],"ny":[2,0,5,4,4],"nz":[0,0,0,0,-1]},{"size":2,"px":[15,1],"py":[4,3],"pz":[0,-1],"nx":[16,15],"ny":[2,2],"nz":[0,0]},{"size":2,"px":[3,7],"py":[7,13],"pz":[1,0],"nx":[3,0],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[14,15],"py":[18,14],"pz":[0,-1],"nx":[4,14],"ny":[4,16],"nz":[1,0]},{"size":2,"px":[4,6],"py":[3,4],"pz":[2,1],"nx":[9,5],"ny":[14,2],"nz":[0,-1]},{"size":2,"px":[16,6],"py":[1,5],"pz":[0,-1],"nx":[4,9],"ny":[0,4],"nz":[2,1]},{"size":2,"px":[9,0],"py":[4,2],"pz":[0,-1],"nx":[5,3],"ny":[1,0],"nz":[1,2]},{"size":5,"px":[1,1,1,0,0],"py":[16,15,17,6,9],"pz":[0,0,0,1,0],"nx":[9,5,4,9,8],"ny":[7,3,3,6,7],"nz":[0,1,1,0,-1]},{"size":2,"px":[9,1],"py":[8,15],"pz":[1,-1],"nx":[9,8],"ny":[9,4],"nz":[1,1]},{"size":2,"px":[20,19],"py":[19,22],"pz":[0,0],"nx":[7,0],"ny":[3,0],"nz":[1,-1]},{"size":5,"px":[8,4,2,5,5],"py":[12,6,3,5,5],"pz":[0,1,2,1,-1],"nx":[22,21,20,21,22],"ny":[17,20,22,19,16],"nz":[0,0,0,0,0]},{"size":2,"px":[6,12],"py":[2,6],"pz":[1,0],"nx":[8,3],"ny":[3,2],"nz":[1,-1]},{"size":2,"px":[11,11],"py":[9,4],"pz":[1,1],"nx":[12,4],"ny":[17,5],"nz":[0,-1]},{"size":3,"px":[0,1,0],"py":[5,13,3],"pz":[2,0,2],"nx":[0,4,11],"ny":[23,5,1],"nz":[0,-1,-1]},{"size":2,"px":[10,5],"py":[6,3],"pz":[0,1],"nx":[4,4],"ny":[3,0],"nz":[1,-1]},{"size":2,"px":[6,5],"py":[7,3],"pz":[0,-1],"nx":[0,1],"ny":[4,10],"nz":[2,1]},{"size":5,"px":[12,13,12,12,12],"py":[12,13,11,10,10],"pz":[0,0,0,0,-1],"nx":[10,8,8,16,15],"ny":[7,4,10,11,10],"nz":[0,1,0,0,0]},{"size":2,"px":[4,8],"py":[3,6],"pz":[2,1],"nx":[4,2],"ny":[5,5],"nz":[2,-1]},{"size":2,"px":[9,17],"py":[17,7],"pz":[0,-1],"nx":[5,2],"ny":[9,4],"nz":[1,2]},{"size":2,"px":[4,4],"py":[3,5],"pz":[2,2],"nx":[12,8],"ny":[16,2],"nz":[0,-1]},{"size":2,"px":[1,1],"py":[2,0],"pz":[1,1],"nx":[0,4],"ny":[0,1],"nz":[2,-1]},{"size":2,"px":[11,1],"py":[5,0],"pz":[0,-1],"nx":[2,3],"ny":[2,4],"nz":[2,1]},{"size":4,"px":[0,6,4,22],"py":[23,2,4,12],"pz":[0,-1,-1,-1],"nx":[7,6,8,5],"ny":[1,1,2,1],"nz":[1,1,1,1]},{"size":2,"px":[4,10],"py":[0,9],"pz":[1,-1],"nx":[2,4],"ny":[3,10],"nz":[2,1]},{"size":2,"px":[11,8],"py":[15,13],"pz":[0,-1],"nx":[23,11],"ny":[13,5],"nz":[0,1]},{"size":2,"px":[18,4],"py":[5,4],"pz":[0,-1],"nx":[18,20],"ny":[4,7],"nz":[0,0]},{"size":5,"px":[21,20,20,10,20],"py":[17,22,19,10,21],"pz":[0,0,0,1,0],"nx":[5,5,3,14,7],"ny":[9,9,0,8,4],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[3,7,13,7,3],"py":[6,12,3,0,3],"pz":[1,-1,-1,-1,-1],"nx":[1,5,0,0,2],"ny":[16,6,13,5,4],"nz":[0,1,0,1,0]},{"size":2,"px":[7,4],"py":[6,3],"pz":[1,2],"nx":[9,5],"ny":[4,6],"nz":[1,-1]},{"size":3,"px":[14,9,13],"py":[19,22,8],"pz":[0,-1,-1],"nx":[13,4,4],"ny":[17,2,5],"nz":[0,2,2]},{"size":2,"px":[16,4],"py":[9,3],"pz":[0,2],"nx":[7,4],"ny":[4,5],"nz":[1,-1]},{"size":4,"px":[10,2,4,2],"py":[23,4,8,3],"pz":[0,2,1,2],"nx":[14,0,4,11],"ny":[19,3,5,3],"nz":[0,-1,-1,-1]},{"size":5,"px":[9,10,8,7,11],"py":[2,2,2,2,2],"pz":[0,0,0,0,0],"nx":[6,5,3,4,4],"ny":[0,1,0,2,2],"nz":[0,0,1,0,-1]},{"size":2,"px":[6,4],"py":[13,6],"pz":[0,-1],"nx":[15,4],"ny":[8,4],"nz":[0,1]},{"size":2,"px":[0,8],"py":[1,2],"pz":[2,-1],"nx":[5,4],"ny":[2,2],"nz":[1,1]},{"size":5,"px":[16,13,14,15,15],"py":[1,0,0,0,0],"pz":[0,0,0,0,-1],"nx":[4,9,4,18,8],"ny":[5,9,4,18,11],"nz":[2,1,2,0,1]},{"size":2,"px":[5,6],"py":[2,6],"pz":[2,1],"nx":[22,9],"ny":[23,9],"nz":[0,-1]},{"size":2,"px":[19,19],"py":[5,5],"pz":[0,-1],"nx":[21,22],"ny":[2,4],"nz":[0,0]},{"size":2,"px":[2,5],"py":[8,6],"pz":[0,1],"nx":[3,4],"ny":[4,9],"nz":[1,-1]},{"size":2,"px":[18,14],"py":[13,17],"pz":[0,0],"nx":[14,4],"ny":[16,3],"nz":[0,-1]},{"size":2,"px":[6,6],"py":[6,3],"pz":[1,-1],"nx":[1,0],"ny":[2,2],"nz":[1,2]},{"size":2,"px":[23,21],"py":[21,14],"pz":[0,-1],"nx":[7,5],"ny":[0,0],"nz":[0,1]},{"size":2,"px":[15,10],"py":[23,7],"pz":[0,-1],"nx":[9,4],"ny":[4,5],"nz":[1,2]},{"size":2,"px":[4,18],"py":[3,8],"pz":[2,0],"nx":[8,4],"ny":[4,5],"nz":[1,-1]},{"size":2,"px":[13,7],"py":[2,11],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":5,"px":[2,3,5,6,1],"py":[7,14,2,2,4],"pz":[1,0,0,0,2],"nx":[8,4,4,7,7],"ny":[7,5,4,9,9],"nz":[1,2,2,1,-1]},{"size":2,"px":[5,3],"py":[6,3],"pz":[1,-1],"nx":[1,2],"ny":[2,4],"nz":[2,1]},{"size":5,"px":[7,20,4,10,10],"py":[9,16,4,10,8],"pz":[1,0,2,1,1],"nx":[4,2,3,5,3],"ny":[11,5,6,12,5],"nz":[0,1,1,0,-1]},{"size":2,"px":[6,11],"py":[4,18],"pz":[1,-1],"nx":[8,6],"ny":[4,9],"nz":[1,1]},{"size":2,"px":[2,8],"py":[5,23],"pz":[2,0],"nx":[9,4],"ny":[0,2],"nz":[1,-1]},{"size":5,"px":[3,1,2,2,2],"py":[12,6,12,11,11],"pz":[0,1,0,0,-1],"nx":[0,0,0,0,0],"ny":[13,12,11,14,7],"nz":[0,0,0,0,1]},{"size":2,"px":[3,6],"py":[1,2],"pz":[2,1],"nx":[8,4],"ny":[4,14],"nz":[1,-1]},{"size":5,"px":[11,23,23,22,22],"py":[8,12,6,13,14],"pz":[1,0,0,0,0],"nx":[13,8,7,6,6],"ny":[6,3,3,9,9],"nz":[0,1,1,0,-1]},{"size":4,"px":[9,23,23,22],"py":[7,12,6,13],"pz":[1,-1,-1,-1],"nx":[11,23,23,23],"ny":[6,13,17,10],"nz":[1,0,0,0]},{"size":5,"px":[0,0,0,0,0],"py":[19,5,9,16,10],"pz":[0,2,1,0,1],"nx":[5,2,1,2,2],"ny":[18,10,5,9,9],"nz":[0,1,2,1,-1]},{"size":2,"px":[11,5],"py":[10,4],"pz":[1,2],"nx":[23,14],"ny":[23,3],"nz":[0,-1]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,1],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[8,10],"py":[4,8],"pz":[0,-1],"nx":[8,8],"ny":[2,3],"nz":[0,0]},{"size":3,"px":[7,10,11],"py":[1,6,13],"pz":[0,-1,-1],"nx":[4,4,2],"ny":[3,8,2],"nz":[1,1,2]},{"size":2,"px":[8,4],"py":[8,2],"pz":[1,2],"nx":[10,5],"ny":[10,0],"nz":[0,-1]},{"size":2,"px":[7,16],"py":[20,21],"pz":[0,-1],"nx":[2,4],"ny":[5,10],"nz":[2,1]},{"size":2,"px":[3,10],"py":[7,8],"pz":[1,-1],"nx":[7,4],"ny":[20,7],"nz":[0,1]},{"size":5,"px":[11,11,11,11,11],"py":[10,12,13,11,11],"pz":[0,0,0,0,-1],"nx":[11,12,16,3,8],"ny":[6,6,10,1,8],"nz":[0,0,0,2,0]},{"size":2,"px":[12,6],"py":[4,2],"pz":[0,1],"nx":[7,7],"ny":[8,1],"nz":[0,-1]},{"size":5,"px":[23,23,23,23,23],"py":[22,20,21,19,19],"pz":[0,0,0,0,-1],"nx":[4,6,3,4,3],"ny":[19,23,15,20,16],"nz":[0,0,0,0,0]},{"size":3,"px":[8,4,14],"py":[12,3,8],"pz":[0,-1,-1],"nx":[4,2,10],"ny":[10,3,13],"nz":[1,2,0]},{"size":2,"px":[11,18],"py":[13,23],"pz":[0,-1],"nx":[5,5],"ny":[1,2],"nz":[2,2]},{"size":3,"px":[11,2,10],"py":[17,4,17],"pz":[0,2,0],"nx":[11,0,22],"ny":[15,2,4],"nz":[0,-1,-1]},{"size":3,"px":[11,3,0],"py":[15,4,8],"pz":[0,-1,-1],"nx":[14,11,4],"ny":[9,17,7],"nz":[0,0,1]},{"size":2,"px":[17,16],"py":[2,1],"pz":[0,0],"nx":[9,11],"ny":[4,6],"nz":[1,-1]},{"size":2,"px":[3,4],"py":[21,23],"pz":[0,0],"nx":[4,0],"ny":[3,3],"nz":[1,-1]},{"size":2,"px":[18,2],"py":[20,0],"pz":[0,-1],"nx":[4,9],"ny":[5,10],"nz":[2,1]},{"size":2,"px":[9,1],"py":[19,3],"pz":[0,-1],"nx":[0,0],"ny":[9,21],"nz":[1,0]},{"size":2,"px":[19,19],"py":[21,22],"pz":[0,0],"nx":[19,0],"ny":[23,0],"nz":[0,-1]},{"size":4,"px":[11,2,3,2],"py":[6,6,9,4],"pz":[0,-1,-1,-1],"nx":[4,9,19,19],"ny":[5,10,17,18],"nz":[2,1,0,0]},{"size":2,"px":[2,4],"py":[4,8],"pz":[2,1],"nx":[4,9],"ny":[10,10],"nz":[1,-1]},{"size":2,"px":[23,22],"py":[8,12],"pz":[0,-1],"nx":[7,4],"ny":[11,2],"nz":[0,2]},{"size":2,"px":[12,1],"py":[5,2],"pz":[0,-1],"nx":[9,11],"ny":[2,1],"nz":[0,0]},{"size":2,"px":[4,4],"py":[2,2],"pz":[0,-1],"nx":[3,2],"ny":[1,2],"nz":[0,0]},{"size":2,"px":[17,9],"py":[13,7],"pz":[0,1],"nx":[9,5],"ny":[4,0],"nz":[1,-1]},{"size":4,"px":[0,0,9,13],"py":[3,3,7,3],"pz":[2,-1,-1,-1],"nx":[2,4,4,11],"ny":[1,2,8,5],"nz":[2,1,1,0]},{"size":5,"px":[3,6,5,6,6],"py":[0,0,2,1,1],"pz":[1,0,0,0,-1],"nx":[2,2,2,1,1],"ny":[21,19,20,16,17],"nz":[0,0,0,0,0]},{"size":2,"px":[13,3],"py":[22,10],"pz":[0,-1],"nx":[7,4],"ny":[10,5],"nz":[1,2]},{"size":2,"px":[3,2],"py":[7,3],"pz":[1,2],"nx":[8,4],"ny":[4,5],"nz":[1,-1]},{"size":5,"px":[17,8,15,7,15],"py":[13,6,16,5,12],"pz":[0,1,0,1,0],"nx":[5,4,6,3,4],"ny":[1,2,1,0,3],"nz":[0,0,0,1,-1]},{"size":5,"px":[12,9,11,12,10],"py":[0,1,2,2,0],"pz":[0,0,0,0,0],"nx":[8,16,7,4,4],"ny":[9,23,9,3,2],"nz":[1,0,1,2,-1]},{"size":2,"px":[4,11],"py":[1,4],"pz":[2,-1],"nx":[8,7],"ny":[4,4],"nz":[0,0]},{"size":4,"px":[7,4,5,8],"py":[13,2,1,3],"pz":[0,-1,-1,-1],"nx":[9,4,9,9],"ny":[9,5,10,11],"nz":[0,1,0,0]},{"size":2,"px":[10,11],"py":[10,11],"pz":[0,-1],"nx":[2,6],"ny":[2,2],"nz":[2,1]},{"size":2,"px":[21,3],"py":[11,2],"pz":[0,-1],"nx":[22,22],"ny":[20,18],"nz":[0,0]},{"size":2,"px":[7,6],"py":[1,2],"pz":[0,0],"nx":[5,10],"ny":[1,0],"nz":[0,-1]},{"size":2,"px":[21,3],"py":[18,1],"pz":[0,-1],"nx":[16,15],"ny":[4,4],"nz":[0,0]},{"size":2,"px":[12,7],"py":[4,1],"pz":[0,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[13,11],"py":[23,17],"pz":[0,0],"nx":[11,21],"ny":[16,0],"nz":[0,-1]},{"size":2,"px":[1,2],"py":[0,6],"pz":[1,-1],"nx":[16,16],"ny":[9,11],"nz":[0,0]},{"size":2,"px":[12,13],"py":[20,20],"pz":[0,0],"nx":[11,3],"ny":[21,7],"nz":[0,-1]},{"size":3,"px":[19,20,9],"py":[21,18,11],"pz":[0,0,1],"nx":[17,4,11],"ny":[19,2,0],"nz":[0,-1,-1]},{"size":2,"px":[12,5],"py":[5,2],"pz":[0,1],"nx":[7,9],"ny":[7,8],"nz":[0,-1]},{"size":5,"px":[8,4,4,8,4],"py":[4,4,5,10,3],"pz":[1,1,2,0,2],"nx":[11,22,11,23,23],"ny":[0,0,1,3,3],"nz":[1,0,1,0,-1]},{"size":2,"px":[8,14],"py":[10,23],"pz":[1,0],"nx":[7,2],"ny":[10,9],"nz":[1,-1]},{"size":2,"px":[5,14],"py":[6,23],"pz":[1,-1],"nx":[1,2],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[11,2],"py":[19,3],"pz":[0,-1],"nx":[10,12],"ny":[18,18],"nz":[0,0]},{"size":2,"px":[12,3],"py":[4,1],"pz":[0,2],"nx":[6,6],"ny":[11,11],"nz":[1,-1]},{"size":5,"px":[0,0,0,0,0],"py":[18,10,20,19,19],"pz":[0,1,0,0,-1],"nx":[11,10,14,12,13],"ny":[2,2,2,2,2],"nz":[0,0,0,0,0]},{"size":3,"px":[12,2,9],"py":[14,5,10],"pz":[0,-1,-1],"nx":[11,10,5],"ny":[10,13,5],"nz":[0,0,1]},{"size":2,"px":[2,3],"py":[3,7],"pz":[2,1],"nx":[3,10],"ny":[4,13],"nz":[1,-1]},{"size":2,"px":[9,3],"py":[21,7],"pz":[0,-1],"nx":[10,21],"ny":[7,15],"nz":[1,0]},{"size":2,"px":[21,10],"py":[16,8],"pz":[0,1],"nx":[8,2],"ny":[10,8],"nz":[1,-1]},{"size":2,"px":[8,8],"py":[6,7],"pz":[1,-1],"nx":[12,11],"ny":[11,7],"nz":[0,1]},{"size":2,"px":[3,11],"py":[4,20],"pz":[2,0],"nx":[11,10],"ny":[19,1],"nz":[0,-1]},{"size":2,"px":[17,5],"py":[13,3],"pz":[0,-1],"nx":[7,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[7,1],"py":[23,3],"pz":[0,2],"nx":[14,6],"ny":[12,9],"nz":[0,-1]},{"size":2,"px":[12,5],"py":[11,2],"pz":[0,-1],"nx":[11,7],"ny":[3,1],"nz":[0,1]},{"size":2,"px":[9,6],"py":[2,17],"pz":[0,-1],"nx":[4,6],"ny":[4,12],"nz":[1,0]},{"size":2,"px":[14,19],"py":[5,6],"pz":[0,-1],"nx":[9,3],"ny":[9,1],"nz":[0,2]},{"size":5,"px":[12,13,13,13,12],"py":[9,11,12,13,10],"pz":[0,0,0,0,0],"nx":[2,4,4,4,4],"ny":[7,18,17,14,14],"nz":[1,0,0,0,-1]},{"size":2,"px":[10,10],"py":[6,6],"pz":[1,-1],"nx":[20,18],"ny":[18,23],"nz":[0,0]},{"size":2,"px":[5,6],"py":[4,14],"pz":[1,-1],"nx":[9,4],"ny":[2,1],"nz":[1,2]},{"size":2,"px":[11,9],"py":[4,18],"pz":[0,-1],"nx":[4,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[15,0],"py":[18,4],"pz":[0,-1],"nx":[3,4],"ny":[5,4],"nz":[2,2]},{"size":4,"px":[7,3,6,6],"py":[8,4,6,5],"pz":[1,2,1,1],"nx":[10,4,13,0],"ny":[10,4,9,22],"nz":[0,-1,-1,-1]},{"size":2,"px":[10,8],"py":[18,11],"pz":[0,-1],"nx":[5,4],"ny":[8,10],"nz":[1,1]},{"size":4,"px":[17,2,10,2],"py":[14,1,10,3],"pz":[0,-1,-1,-1],"nx":[8,8,17,8],"ny":[4,5,12,6],"nz":[1,1,0,1]},{"size":5,"px":[9,11,9,4,10],"py":[1,1,0,0,1],"pz":[0,0,0,1,0],"nx":[8,4,7,15,15],"ny":[7,2,4,17,17],"nz":[1,2,1,0,-1]},{"size":2,"px":[4,3],"py":[11,8],"pz":[0,-1],"nx":[2,2],"ny":[1,2],"nz":[2,2]},{"size":2,"px":[11,3],"py":[13,8],"pz":[0,-1],"nx":[1,1],"ny":[5,2],"nz":[1,2]},{"size":2,"px":[6,2],"py":[8,3],"pz":[0,2],"nx":[3,1],"ny":[5,2],"nz":[1,-1]},{"size":5,"px":[10,5,7,8,6],"py":[9,7,7,7,7],"pz":[0,0,0,0,0],"nx":[7,3,0,2,15],"ny":[8,0,1,18,17],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[17,8],"py":[12,6],"pz":[0,1],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":5,"px":[3,11,8,10,12],"py":[0,2,10,2,3],"pz":[2,0,0,0,0],"nx":[3,2,10,2,2],"ny":[6,4,11,3,3],"nz":[0,1,0,1,-1]},{"size":2,"px":[3,6],"py":[2,4],"pz":[2,1],"nx":[8,19],"ny":[4,16],"nz":[1,-1]},{"size":2,"px":[2,2],"py":[1,1],"pz":[2,-1],"nx":[7,17],"ny":[1,2],"nz":[1,0]},{"size":5,"px":[16,15,14,13,7],"py":[0,0,0,0,0],"pz":[0,0,0,0,-1],"nx":[6,4,8,3,11],"ny":[3,4,4,1,6],"nz":[1,1,1,2,0]},{"size":2,"px":[11,1],"py":[8,5],"pz":[0,-1],"nx":[13,4],"ny":[10,2],"nz":[0,2]},{"size":2,"px":[4,9],"py":[0,2],"pz":[2,1],"nx":[4,11],"ny":[0,2],"nz":[0,-1]},{"size":2,"px":[15,15],"py":[2,2],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[8,17],"py":[9,22],"pz":[1,0],"nx":[8,20],"ny":[10,2],"nz":[1,-1]},{"size":2,"px":[10,10],"py":[14,22],"pz":[0,-1],"nx":[3,11],"ny":[3,3],"nz":[1,0]},{"size":2,"px":[4,2],"py":[1,0],"pz":[1,2],"nx":[5,8],"ny":[3,9],"nz":[0,-1]},{"size":2,"px":[2,3],"py":[4,8],"pz":[2,1],"nx":[9,5],"ny":[15,19],"nz":[0,-1]},{"size":2,"px":[5,2],"py":[1,1],"pz":[0,1],"nx":[10,10],"ny":[6,6],"nz":[0,-1]},{"size":2,"px":[17,6],"py":[10,2],"pz":[0,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":3,"px":[13,7,3],"py":[5,2,6],"pz":[0,1,-1],"nx":[17,16,17],"ny":[1,1,2],"nz":[0,0,0]},{"size":2,"px":[11,10],"py":[3,3],"pz":[0,0],"nx":[8,4],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[4,8],"py":[0,8],"pz":[2,-1],"nx":[3,4],"ny":[0,0],"nz":[1,1]},{"size":5,"px":[9,2,4,1,2],"py":[13,3,9,2,5],"pz":[0,2,1,2,2],"nx":[9,5,10,4,10],"ny":[5,1,3,0,0],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[6,12],"py":[5,9],"pz":[1,0],"nx":[0,2],"ny":[23,9],"nz":[0,-1]},{"size":2,"px":[22,11],"py":[21,8],"pz":[0,1],"nx":[10,0],"ny":[17,2],"nz":[0,-1]},{"size":2,"px":[3,1],"py":[22,9],"pz":[0,1],"nx":[22,5],"ny":[11,2],"nz":[0,2]},{"size":2,"px":[4,2],"py":[6,3],"pz":[1,2],"nx":[5,6],"ny":[10,9],"nz":[1,-1]},{"size":4,"px":[7,3,17,7],"py":[8,2,10,11],"pz":[0,2,0,1],"nx":[6,10,5,23],"ny":[9,21,1,23],"nz":[0,-1,-1,-1]},{"size":2,"px":[8,3],"py":[7,2],"pz":[1,2],"nx":[8,9],"ny":[4,9],"nz":[1,-1]},{"size":2,"px":[9,5],"py":[14,6],"pz":[0,1],"nx":[8,8],"ny":[13,13],"nz":[0,-1]},{"size":3,"px":[11,6,8],"py":[20,3,20],"pz":[0,-1,-1],"nx":[5,3,12],"ny":[9,5,18],"nz":[1,2,0]},{"size":2,"px":[3,9],"py":[1,3],"pz":[1,0],"nx":[2,8],"ny":[5,8],"nz":[0,-1]},{"size":2,"px":[15,9],"py":[21,3],"pz":[0,-1],"nx":[3,4],"ny":[5,5],"nz":[2,2]},{"size":2,"px":[2,9],"py":[7,11],"pz":[1,-1],"nx":[2,2],"ny":[8,9],"nz":[1,1]},{"size":4,"px":[3,4,3,1],"py":[14,21,19,6],"pz":[0,0,0,1],"nx":[10,16,4,5],"ny":[8,1,7,6],"nz":[0,-1,-1,-1]},{"size":4,"px":[10,4,3,1],"py":[5,21,19,6],"pz":[1,-1,-1,-1],"nx":[21,10,5,11],"ny":[4,2,3,4],"nz":[0,1,2,1]},{"size":2,"px":[4,17],"py":[3,8],"pz":[2,0],"nx":[17,2],"ny":[9,22],"nz":[0,-1]},{"size":2,"px":[17,12],"py":[14,20],"pz":[0,-1],"nx":[7,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[10,12],"py":[9,20],"pz":[0,-1],"nx":[11,23],"ny":[8,18],"nz":[1,0]},{"size":2,"px":[5,11],"py":[4,7],"pz":[2,1],"nx":[8,15],"ny":[7,5],"nz":[1,-1]},{"size":2,"px":[11,15],"py":[13,8],"pz":[0,-1],"nx":[11,11],"ny":[6,7],"nz":[1,1]},{"size":2,"px":[6,15],"py":[14,8],"pz":[0,-1],"nx":[4,4],"ny":[12,13],"nz":[0,0]},{"size":2,"px":[5,5],"py":[0,1],"pz":[2,2],"nx":[15,4],"ny":[5,5],"nz":[0,-1]},{"size":2,"px":[16,17],"py":[2,2],"pz":[0,0],"nx":[20,8],"ny":[3,7],"nz":[0,-1]},{"size":3,"px":[6,3,2],"py":[10,6,1],"pz":[0,-1,-1],"nx":[4,3,2],"ny":[3,4,2],"nz":[1,1,2]},{"size":2,"px":[10,6],"py":[4,6],"pz":[0,-1],"nx":[6,13],"ny":[0,1],"nz":[1,0]},{"size":2,"px":[10,10],"py":[8,7],"pz":[1,1],"nx":[8,2],"ny":[7,2],"nz":[1,-1]},{"size":2,"px":[7,1],"py":[12,4],"pz":[0,-1],"nx":[3,4],"ny":[5,5],"nz":[1,1]},{"size":2,"px":[11,15],"py":[15,14],"pz":[0,-1],"nx":[3,11],"ny":[4,13],"nz":[1,0]},{"size":5,"px":[13,9,11,14,12],"py":[0,2,0,0,2],"pz":[0,0,0,0,0],"nx":[5,4,4,3,4],"ny":[4,4,18,7,17],"nz":[1,1,0,1,0]},{"size":3,"px":[13,12,11],"py":[22,22,22],"pz":[0,0,0],"nx":[11,12,13],"ny":[20,20,20],"nz":[0,0,0]},{"size":2,"px":[6,13],"py":[2,4],"pz":[1,0],"nx":[7,6],"ny":[8,9],"nz":[0,-1]},{"size":2,"px":[0,0],"py":[23,4],"pz":[0,-1],"nx":[5,9],"ny":[1,1],"nz":[1,0]},{"size":2,"px":[14,14],"py":[19,19],"pz":[0,-1],"nx":[11,11],"ny":[10,9],"nz":[1,1]},{"size":2,"px":[23,23],"py":[11,9],"pz":[0,0],"nx":[23,23],"ny":[0,11],"nz":[0,-1]},{"size":2,"px":[23,3],"py":[23,5],"pz":[0,-1],"nx":[4,1],"ny":[23,10],"nz":[0,1]},{"size":2,"px":[9,1],"py":[7,4],"pz":[1,-1],"nx":[19,10],"ny":[20,9],"nz":[0,1]},{"size":2,"px":[16,1],"py":[9,4],"pz":[0,-1],"nx":[7,8],"ny":[3,3],"nz":[1,1]},{"size":2,"px":[7,6],"py":[13,13],"pz":[0,0],"nx":[4,5],"ny":[4,11],"nz":[1,-1]},{"size":5,"px":[19,20,20,10,10],"py":[0,0,2,0,1],"pz":[0,0,0,1,1],"nx":[7,7,15,4,4],"ny":[4,13,7,4,4],"nz":[1,0,0,1,-1]},{"size":2,"px":[12,23],"py":[6,5],"pz":[0,-1],"nx":[18,18],"ny":[17,16],"nz":[0,0]},{"size":2,"px":[6,3],"py":[9,2],"pz":[1,2],"nx":[14,18],"ny":[9,1],"nz":[0,-1]},{"size":2,"px":[9,13],"py":[16,5],"pz":[0,-1],"nx":[5,4],"ny":[7,9],"nz":[1,1]},{"size":2,"px":[10,10],"py":[8,10],"pz":[1,1],"nx":[4,1],"ny":[5,3],"nz":[2,-1]},{"size":2,"px":[12,11],"py":[13,4],"pz":[0,-1],"nx":[0,0],"ny":[14,15],"nz":[0,0]},{"size":2,"px":[2,1],"py":[20,17],"pz":[0,0],"nx":[12,12],"ny":[22,2],"nz":[0,-1]},{"size":2,"px":[2,3],"py":[6,7],"pz":[1,-1],"nx":[21,21],"ny":[13,12],"nz":[0,0]},{"size":2,"px":[3,10],"py":[4,23],"pz":[2,0],"nx":[10,2],"ny":[21,5],"nz":[0,-1]},{"size":2,"px":[6,12],"py":[3,6],"pz":[1,0],"nx":[11,0],"ny":[17,1],"nz":[0,-1]},{"size":2,"px":[11,4],"py":[21,9],"pz":[0,-1],"nx":[2,3],"ny":[18,22],"nz":[0,0]},{"size":2,"px":[13,5],"py":[18,9],"pz":[0,-1],"nx":[6,7],"ny":[8,9],"nz":[1,1]},{"size":2,"px":[21,4],"py":[16,3],"pz":[0,-1],"nx":[23,23],"ny":[16,15],"nz":[0,0]},{"size":2,"px":[2,0],"py":[7,4],"pz":[1,-1],"nx":[3,8],"ny":[7,4],"nz":[1,1]},{"size":2,"px":[15,16],"py":[11,12],"pz":[0,0],"nx":[8,5],"ny":[4,5],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[7,5],"pz":[0,0],"nx":[17,17],"ny":[11,10],"nz":[0,-1]},{"size":5,"px":[8,13,12,3,3],"py":[6,23,23,3,3],"pz":[1,0,0,2,-1],"nx":[0,1,0,0,0],"ny":[2,13,4,5,6],"nz":[2,0,1,1,1]},{"size":2,"px":[0,1],"py":[7,8],"pz":[1,-1],"nx":[0,0],"ny":[1,0],"nz":[2,2]},{"size":2,"px":[2,12],"py":[1,7],"pz":[1,-1],"nx":[0,0],"ny":[12,14],"nz":[0,0]},{"size":2,"px":[5,1],"py":[7,4],"pz":[1,2],"nx":[8,0],"ny":[15,14],"nz":[0,-1]},{"size":2,"px":[7,4],"py":[14,8],"pz":[0,-1],"nx":[2,4],"ny":[1,4],"nz":[2,1]},{"size":2,"px":[5,3],"py":[3,1],"pz":[2,-1],"nx":[9,9],"ny":[5,6],"nz":[1,1]},{"size":2,"px":[4,5],"py":[2,3],"pz":[1,-1],"nx":[11,12],"ny":[23,23],"nz":[0,0]},{"size":2,"px":[10,5],"py":[7,0],"pz":[1,-1],"nx":[22,22],"ny":[19,18],"nz":[0,0]},{"size":3,"px":[10,2,9],"py":[20,9,4],"pz":[0,-1,-1],"nx":[1,10,11],"ny":[2,11,9],"nz":[2,0,0]},{"size":2,"px":[4,8],"py":[3,6],"pz":[2,1],"nx":[9,3],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[17,6],"py":[7,16],"pz":[0,-1],"nx":[17,17],"ny":[9,6],"nz":[0,0]},{"size":3,"px":[8,1,9],"py":[6,3,4],"pz":[1,-1,-1],"nx":[2,9,2],"ny":[5,13,3],"nz":[2,0,2]},{"size":4,"px":[10,10,9,2],"py":[12,11,2,10],"pz":[0,0,-1,-1],"nx":[6,11,3,13],"ny":[2,4,1,4],"nz":[1,0,2,0]},{"size":2,"px":[3,3],"py":[7,1],"pz":[1,-1],"nx":[4,3],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[0,0],"py":[4,8],"pz":[2,1],"nx":[4,4],"ny":[15,5],"nz":[0,-1]},{"size":2,"px":[5,0],"py":[4,8],"pz":[1,-1],"nx":[13,13],"ny":[9,10],"nz":[0,0]},{"size":2,"px":[6,3],"py":[2,1],"pz":[1,2],"nx":[8,17],"ny":[4,12],"nz":[1,-1]},{"size":2,"px":[15,16],"py":[11,6],"pz":[0,0],"nx":[16,17],"ny":[5,12],"nz":[0,-1]},{"size":2,"px":[13,11],"py":[9,7],"pz":[0,-1],"nx":[0,1],"ny":[9,20],"nz":[1,0]},{"size":3,"px":[16,11,20],"py":[4,7,23],"pz":[0,-1,-1],"nx":[8,9,4],"ny":[4,6,4],"nz":[1,1,2]},{"size":2,"px":[1,1],"py":[18,17],"pz":[0,0],"nx":[9,6],"ny":[7,11],"nz":[0,-1]},{"size":3,"px":[4,4,19],"py":[3,2,9],"pz":[2,2,0],"nx":[2,14,11],"ny":[5,3,9],"nz":[1,-1,-1]},{"size":2,"px":[11,19],"py":[13,9],"pz":[0,-1],"nx":[11,11],"ny":[4,5],"nz":[1,1]},{"size":2,"px":[13,7],"py":[19,2],"pz":[0,-1],"nx":[3,5],"ny":[6,12],"nz":[1,0]},{"size":4,"px":[9,4,4,2],"py":[13,9,8,4],"pz":[0,1,1,2],"nx":[13,0,0,14],"ny":[18,11,6,1],"nz":[0,-1,-1,-1]},{"size":2,"px":[11,15],"py":[8,10],"pz":[0,0],"nx":[14,11],"ny":[9,2],"nz":[0,-1]},{"size":2,"px":[3,2],"py":[8,5],"pz":[1,2],"nx":[4,4],"ny":[10,10],"nz":[1,-1]},{"size":4,"px":[4,6,16,14],"py":[1,1,1,7],"pz":[2,1,0,0],"nx":[10,1,1,2],"ny":[8,5,10,3],"nz":[0,-1,-1,-1]},{"size":4,"px":[2,3,1,2],"py":[3,1,0,2],"pz":[0,0,1,0],"nx":[0,0,0,0],"ny":[1,1,2,0],"nz":[0,1,0,1]},{"size":2,"px":[8,8],"py":[6,7],"pz":[1,1],"nx":[8,0],"ny":[4,1],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[3,0],"pz":[0,1],"nx":[2,2],"ny":[1,16],"nz":[1,-1]},{"size":2,"px":[6,6],"py":[19,18],"pz":[0,0],"nx":[2,10],"ny":[5,8],"nz":[2,-1]},{"size":2,"px":[8,5],"py":[21,11],"pz":[0,-1],"nx":[3,2],"ny":[11,5],"nz":[1,2]},{"size":2,"px":[4,9],"py":[4,7],"pz":[2,1],"nx":[8,7],"ny":[10,4],"nz":[1,-1]},{"size":5,"px":[4,18,19,16,19],"py":[3,12,12,23,13],"pz":[2,0,0,0,0],"nx":[2,8,3,2,2],"ny":[4,23,10,5,5],"nz":[2,0,1,2,-1]},{"size":2,"px":[4,8],"py":[6,11],"pz":[1,0],"nx":[8,3],"ny":[4,7],"nz":[1,-1]},{"size":2,"px":[3,12],"py":[4,13],"pz":[2,0],"nx":[10,5],"ny":[15,21],"nz":[0,-1]},{"size":2,"px":[2,9],"py":[4,23],"pz":[2,0],"nx":[19,4],"ny":[9,3],"nz":[0,2]},{"size":2,"px":[3,6],"py":[8,15],"pz":[1,0],"nx":[6,1],"ny":[18,5],"nz":[0,-1]},{"size":2,"px":[9,0],"py":[20,3],"pz":[0,-1],"nx":[2,10],"ny":[5,17],"nz":[2,0]},{"size":3,"px":[10,6,3],"py":[2,7,3],"pz":[0,-1,-1],"nx":[5,4,2],"ny":[9,7,2],"nz":[1,1,2]},{"size":2,"px":[14,6],"py":[12,7],"pz":[0,-1],"nx":[2,10],"ny":[0,1],"nz":[2,0]},{"size":3,"px":[10,5,1],"py":[15,5,4],"pz":[0,-1,-1],"nx":[9,4,18],"ny":[2,0,4],"nz":[1,2,0]},{"size":2,"px":[17,2],"py":[12,6],"pz":[0,-1],"nx":[8,16],"ny":[4,11],"nz":[1,0]},{"size":3,"px":[7,13,4],"py":[0,0,1],"pz":[1,0,-1],"nx":[18,4,4],"ny":[13,2,3],"nz":[0,2,2]},{"size":2,"px":[1,11],"py":[10,6],"pz":[0,-1],"nx":[0,1],"ny":[15,17],"nz":[0,0]},{"size":3,"px":[9,12,8],"py":[8,17,11],"pz":[1,0,1],"nx":[12,0,20],"ny":[16,9,13],"nz":[0,-1,-1]},{"size":2,"px":[11,4],"py":[5,8],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[16,3],"py":[9,8],"pz":[0,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[6,3],"py":[11,5],"pz":[1,2],"nx":[11,5],"ny":[21,5],"nz":[0,-1]},{"size":2,"px":[11,13],"py":[1,1],"pz":[0,0],"nx":[4,4],"ny":[5,5],"nz":[1,-1]},{"size":2,"px":[14,4],"py":[4,3],"pz":[0,-1],"nx":[12,10],"ny":[2,2],"nz":[0,0]},{"size":2,"px":[3,6],"py":[2,4],"pz":[2,1],"nx":[9,7],"ny":[9,7],"nz":[0,-1]},{"size":3,"px":[5,6,6],"py":[4,4,4],"pz":[1,-1,-1],"nx":[13,8,7],"ny":[8,3,4],"nz":[0,1,1]},{"size":2,"px":[5,5],"py":[2,11],"pz":[1,1],"nx":[10,11],"ny":[22,22],"nz":[0,0]},{"size":2,"px":[16,9],"py":[13,7],"pz":[0,1],"nx":[8,14],"ny":[4,12],"nz":[1,-1]},{"size":2,"px":[13,5],"py":[13,3],"pz":[0,2],"nx":[16,22],"ny":[13,6],"nz":[0,-1]},{"size":4,"px":[4,4,3,4],"py":[4,3,4,5],"pz":[2,2,2,2],"nx":[21,5,17,7],"ny":[0,2,5,23],"nz":[0,-1,-1,-1]},{"size":2,"px":[4,16],"py":[0,1],"pz":[2,0],"nx":[15,1],"ny":[23,10],"nz":[0,-1]},{"size":2,"px":[4,6],"py":[11,2],"pz":[0,-1],"nx":[15,6],"ny":[2,1],"nz":[0,1]},{"size":2,"px":[6,3],"py":[2,1],"pz":[1,2],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":3,"px":[13,14,5],"py":[9,15,2],"pz":[0,-1,-1],"nx":[11,1,11],"ny":[10,3,11],"nz":[0,1,0]},{"size":2,"px":[5,1],"py":[6,2],"pz":[1,-1],"nx":[1,1],"ny":[2,5],"nz":[2,1]},{"size":2,"px":[11,5],"py":[1,0],"pz":[1,2],"nx":[10,4],"ny":[2,3],"nz":[1,-1]},{"size":2,"px":[11,11],"py":[8,9],"pz":[1,1],"nx":[23,4],"ny":[23,2],"nz":[0,-1]},{"size":2,"px":[5,2],"py":[10,2],"pz":[0,-1],"nx":[18,10],"ny":[0,1],"nz":[0,1]},{"size":2,"px":[20,4],"py":[7,3],"pz":[0,2],"nx":[8,4],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[10,4],"py":[5,4],"pz":[1,-1],"nx":[11,11],"ny":[5,6],"nz":[1,1]},{"size":3,"px":[14,15,16],"py":[0,0,1],"pz":[0,0,0],"nx":[8,5,15],"ny":[7,2,10],"nz":[1,-1,-1]},{"size":2,"px":[2,2],"py":[1,1],"pz":[2,-1],"nx":[17,18],"ny":[2,2],"nz":[0,0]},{"size":2,"px":[13,8],"py":[15,7],"pz":[0,-1],"nx":[9,4],"ny":[5,2],"nz":[0,1]},{"size":2,"px":[4,0],"py":[6,17],"pz":[1,-1],"nx":[3,2],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[14,8],"py":[17,9],"pz":[0,-1],"nx":[7,6],"ny":[8,8],"nz":[1,1]},{"size":2,"px":[10,4],"py":[7,1],"pz":[1,-1],"nx":[15,6],"ny":[14,4],"nz":[0,1]},{"size":2,"px":[3,12],"py":[8,19],"pz":[1,0],"nx":[13,10],"ny":[17,9],"nz":[0,-1]},{"size":2,"px":[7,12],"py":[2,4],"pz":[1,0],"nx":[6,11],"ny":[3,2],"nz":[0,-1]},{"size":4,"px":[2,1,6,1],"py":[10,3,23,8],"pz":[1,2,0,1],"nx":[17,10,23,0],"ny":[9,2,20,3],"nz":[0,-1,-1,-1]},{"size":2,"px":[9,9],"py":[2,8],"pz":[0,-1],"nx":[2,2],"ny":[4,2],"nz":[2,2]},{"size":2,"px":[3,16],"py":[1,6],"pz":[2,0],"nx":[8,4],"ny":[2,5],"nz":[1,-1]},{"size":2,"px":[3,6],"py":[1,2],"pz":[2,1],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[5,6],"py":[3,0],"pz":[2,-1],"nx":[9,5],"ny":[2,1],"nz":[0,1]},{"size":2,"px":[3,16],"py":[5,23],"pz":[1,-1],"nx":[0,0],"ny":[6,3],"nz":[1,2]},{"size":4,"px":[0,0,0,0],"py":[3,2,12,5],"pz":[2,2,0,1],"nx":[2,3,2,13],"ny":[5,5,2,19],"nz":[1,-1,-1,-1]},{"size":2,"px":[11,11],"py":[10,11],"pz":[0,0],"nx":[5,5],"ny":[1,1],"nz":[2,-1]},{"size":2,"px":[5,2],"py":[0,4],"pz":[2,-1],"nx":[2,2],"ny":[10,8],"nz":[1,1]},{"size":4,"px":[16,2,8,4],"py":[14,0,11,5],"pz":[0,-1,-1,-1],"nx":[18,14,7,7],"ny":[13,14,8,6],"nz":[0,0,1,1]},{"size":2,"px":[8,9],"py":[2,2],"pz":[0,0],"nx":[5,14],"ny":[4,14],"nz":[1,-1]},{"size":2,"px":[3,5],"py":[11,20],"pz":[1,0],"nx":[11,4],"ny":[0,2],"nz":[0,-1]},{"size":2,"px":[2,2],"py":[3,4],"pz":[2,2],"nx":[3,4],"ny":[4,2],"nz":[1,-1]},{"size":3,"px":[10,4,3],"py":[5,5,3],"pz":[0,-1,-1],"nx":[11,3,10],"ny":[2,0,2],"nz":[0,2,0]},{"size":2,"px":[15,15],"py":[1,1],"pz":[0,-1],"nx":[7,4],"ny":[5,2],"nz":[1,2]},{"size":4,"px":[9,5,2,6],"py":[22,8,4,19],"pz":[0,1,2,0],"nx":[9,5,0,3],"ny":[20,5,22,4],"nz":[0,-1,-1,-1]},{"size":3,"px":[1,4,10],"py":[3,9,12],"pz":[2,1,0],"nx":[0,10,0],"ny":[0,5,0],"nz":[0,-1,-1]},{"size":2,"px":[1,6],"py":[0,7],"pz":[0,-1],"nx":[20,19],"ny":[14,14],"nz":[0,0]},{"size":2,"px":[13,4],"py":[14,15],"pz":[0,-1],"nx":[2,1],"ny":[5,7],"nz":[0,0]},{"size":2,"px":[17,7],"py":[9,11],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[17,9],"py":[12,6],"pz":[0,1],"nx":[15,10],"ny":[9,8],"nz":[0,-1]},{"size":2,"px":[0,0],"py":[0,1],"pz":[2,2],"nx":[9,7],"ny":[6,17],"nz":[1,-1]},{"size":3,"px":[3,3,15],"py":[3,4,6],"pz":[2,1,0],"nx":[0,2,22],"ny":[5,8,9],"nz":[0,-1,-1]},{"size":4,"px":[15,15,15,1],"py":[12,6,6,1],"pz":[0,-1,-1,-1],"nx":[4,7,13,4],"ny":[4,7,12,2],"nz":[2,1,0,2]},{"size":2,"px":[3,15],"py":[12,6],"pz":[0,-1],"nx":[9,1],"ny":[14,2],"nz":[0,2]},{"size":2,"px":[12,12],"py":[11,12],"pz":[0,0],"nx":[9,5],"ny":[4,4],"nz":[1,-1]},{"size":3,"px":[23,6,7],"py":[23,3,4],"pz":[0,-1,-1],"nx":[19,16,17],"ny":[17,14,15],"nz":[0,0,0]},{"size":2,"px":[9,5],"py":[2,7],"pz":[1,-1],"nx":[11,23],"ny":[10,18],"nz":[1,0]},{"size":3,"px":[0,0,0],"py":[4,9,2],"pz":[1,0,2],"nx":[2,0,0],"ny":[9,2,1],"nz":[0,-1,-1]},{"size":2,"px":[12,0],"py":[11,9],"pz":[0,-1],"nx":[1,0],"ny":[18,5],"nz":[0,2]},{"size":2,"px":[5,4],"py":[10,6],"pz":[0,1],"nx":[10,6],"ny":[10,18],"nz":[0,-1]},{"size":2,"px":[13,12],"py":[13,13],"pz":[0,-1],"nx":[5,11],"ny":[1,3],"nz":[2,1]},{"size":2,"px":[10,19],"py":[5,22],"pz":[1,-1],"nx":[4,12],"ny":[1,5],"nz":[2,0]},{"size":2,"px":[8,6],"py":[0,0],"pz":[0,0],"nx":[3,12],"ny":[0,3],"nz":[0,-1]},{"size":2,"px":[9,6],"py":[7,0],"pz":[1,-1],"nx":[12,12],"ny":[10,11],"nz":[0,0]},{"size":4,"px":[3,1,3,2],"py":[20,9,21,19],"pz":[0,1,0,0],"nx":[20,20,5,12],"ny":[10,15,2,10],"nz":[0,-1,-1,-1]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,1],"ny":[4,6],"nz":[1,-1]},{"size":3,"px":[5,11,11],"py":[1,3,4],"pz":[2,1,1],"nx":[3,3,7],"ny":[5,5,0],"nz":[1,-1,-1]},{"size":3,"px":[8,6,7],"py":[10,5,6],"pz":[1,1,1],"nx":[23,3,7],"ny":[0,5,0],"nz":[0,-1,-1]},{"size":2,"px":[2,7],"py":[2,14],"pz":[1,-1],"nx":[7,3],"ny":[12,4],"nz":[0,1]},{"size":2,"px":[5,3],"py":[6,3],"pz":[1,2],"nx":[13,3],"ny":[12,4],"nz":[0,-1]},{"size":2,"px":[11,18],"py":[11,4],"pz":[0,-1],"nx":[23,11],"ny":[19,10],"nz":[0,1]},{"size":2,"px":[7,2],"py":[12,3],"pz":[0,-1],"nx":[8,4],"ny":[11,5],"nz":[0,1]},{"size":2,"px":[11,11],"py":[0,11],"pz":[1,-1],"nx":[3,3],"ny":[19,18],"nz":[0,0]},{"size":2,"px":[11,1],"py":[11,11],"pz":[1,-1],"nx":[13,15],"ny":[6,5],"nz":[0,0]},{"size":2,"px":[8,8],"py":[9,9],"pz":[0,-1],"nx":[5,11],"ny":[1,3],"nz":[2,1]},{"size":4,"px":[6,4,8,3],"py":[6,2,4,3],"pz":[0,2,1,2],"nx":[7,0,15,8],"ny":[8,8,16,7],"nz":[0,-1,-1,-1]},{"size":2,"px":[4,3],"py":[22,20],"pz":[0,0],"nx":[2,8],"ny":[5,4],"nz":[2,-1]},{"size":2,"px":[12,6],"py":[11,0],"pz":[0,-1],"nx":[0,0],"ny":[3,1],"nz":[1,2]},{"size":2,"px":[0,0],"py":[12,7],"pz":[0,1],"nx":[3,1],"ny":[23,9],"nz":[0,-1]},{"size":2,"px":[7,0],"py":[11,5],"pz":[1,-1],"nx":[0,0],"ny":[2,3],"nz":[2,2]},{"size":2,"px":[8,8],"py":[10,10],"pz":[0,-1],"nx":[4,3],"ny":[5,4],"nz":[2,2]},{"size":2,"px":[13,3],"py":[2,4],"pz":[0,-1],"nx":[4,3],"ny":[3,5],"nz":[2,2]},{"size":2,"px":[1,1],"py":[23,22],"pz":[0,0],"nx":[9,0],"ny":[7,3],"nz":[0,-1]},{"size":2,"px":[1,0],"py":[16,15],"pz":[0,0],"nx":[0,14],"ny":[23,12],"nz":[0,-1]},{"size":2,"px":[13,8],"py":[22,0],"pz":[0,-1],"nx":[5,3],"ny":[0,1],"nz":[1,1]},{"size":2,"px":[13,13],"py":[7,7],"pz":[0,-1],"nx":[3,2],"ny":[17,10],"nz":[0,1]},{"size":2,"px":[20,20],"py":[15,16],"pz":[0,0],"nx":[7,3],"ny":[9,17],"nz":[1,-1]},{"size":5,"px":[10,12,11,13,11],"py":[2,2,1,2,2],"pz":[0,0,0,0,0],"nx":[10,18,21,21,19],"ny":[3,1,13,11,2],"nz":[1,0,0,0,0]},{"size":2,"px":[16,3],"py":[6,1],"pz":[0,2],"nx":[15,18],"ny":[8,1],"nz":[0,-1]},{"size":2,"px":[19,3],"py":[8,1],"pz":[0,-1],"nx":[9,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[10,3],"py":[15,18],"pz":[0,-1],"nx":[3,3],"ny":[0,1],"nz":[2,2]},{"size":2,"px":[3,3],"py":[2,3],"pz":[2,2],"nx":[7,3],"ny":[11,1],"nz":[1,-1]},{"size":2,"px":[11,10],"py":[17,9],"pz":[0,-1],"nx":[11,10],"ny":[15,15],"nz":[0,0]},{"size":2,"px":[5,10],"py":[2,4],"pz":[1,0],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[9,10],"py":[3,4],"pz":[0,-1],"nx":[9,10],"ny":[2,1],"nz":[0,0]},{"size":2,"px":[23,11],"py":[13,10],"pz":[0,1],"nx":[14,7],"ny":[5,14],"nz":[0,-1]},{"size":2,"px":[4,4],"py":[5,4],"pz":[2,2],"nx":[9,8],"ny":[3,3],"nz":[1,-1]},{"size":3,"px":[12,4,15],"py":[5,4,7],"pz":[0,-1,-1],"nx":[3,4,2],"ny":[7,11,5],"nz":[1,1,2]},{"size":2,"px":[11,4],"py":[15,4],"pz":[0,-1],"nx":[5,9],"ny":[7,15],"nz":[1,0]},{"size":2,"px":[9,7],"py":[0,1],"pz":[1,-1],"nx":[11,11],"ny":[8,7],"nz":[1,1]},{"size":5,"px":[1,1,1,1,1],"py":[11,12,10,9,9],"pz":[0,0,0,0,-1],"nx":[4,5,8,16,11],"ny":[4,3,8,8,6],"nz":[1,1,0,0,0]}],"alpha":[-1.059083e+00,1.059083e+00,-7.846122e-01,7.846122e-01,-4.451160e-01,4.451160e-01,-4.483277e-01,4.483277e-01,-3.905999e-01,3.905999e-01,-3.789250e-01,3.789250e-01,-3.874610e-01,3.874610e-01,-3.110541e-01,3.110541e-01,-3.565056e-01,3.565056e-01,-3.812617e-01,3.812617e-01,-3.325142e-01,3.325142e-01,-2.787282e-01,2.787282e-01,-3.238869e-01,3.238869e-01,-2.993499e-01,2.993499e-01,-2.807737e-01,2.807737e-01,-2.855285e-01,2.855285e-01,-2.277550e-01,2.277550e-01,-2.031261e-01,2.031261e-01,-2.071574e-01,2.071574e-01,-2.534142e-01,2.534142e-01,-2.266871e-01,2.266871e-01,-2.229078e-01,2.229078e-01,-2.716325e-01,2.716325e-01,-3.046938e-01,3.046938e-01,-2.271601e-01,2.271601e-01,-1.987651e-01,1.987651e-01,-1.953664e-01,1.953664e-01,-2.178737e-01,2.178737e-01,-2.285148e-01,2.285148e-01,-1.891073e-01,1.891073e-01,-2.926469e-01,2.926469e-01,-2.094783e-01,2.094783e-01,-1.478037e-01,1.478037e-01,-1.707579e-01,1.707579e-01,-1.464390e-01,1.464390e-01,-2.462321e-01,2.462321e-01,-2.319978e-01,2.319978e-01,-1.781651e-01,1.781651e-01,-1.471349e-01,1.471349e-01,-1.953006e-01,1.953006e-01,-2.145108e-01,2.145108e-01,-1.567881e-01,1.567881e-01,-2.024617e-01,2.024617e-01,-1.883198e-01,1.883198e-01,-1.996976e-01,1.996976e-01,-1.292330e-01,1.292330e-01,-2.142242e-01,2.142242e-01,-2.473748e-01,2.473748e-01,-1.880902e-01,1.880902e-01,-1.874572e-01,1.874572e-01,-1.495984e-01,1.495984e-01,-1.608525e-01,1.608525e-01,-1.698402e-01,1.698402e-01,-1.898871e-01,1.898871e-01,-1.350238e-01,1.350238e-01,-1.727032e-01,1.727032e-01,-1.593352e-01,1.593352e-01,-1.476968e-01,1.476968e-01,-1.428431e-01,1.428431e-01,-1.766261e-01,1.766261e-01,-1.453226e-01,1.453226e-01,-1.929885e-01,1.929885e-01,-1.337582e-01,1.337582e-01,-1.629078e-01,1.629078e-01,-9.973085e-02,9.973085e-02,-1.172760e-01,1.172760e-01,-1.399242e-01,1.399242e-01,-1.613189e-01,1.613189e-01,-1.145695e-01,1.145695e-01,-1.191093e-01,1.191093e-01,-1.225900e-01,1.225900e-01,-1.641114e-01,1.641114e-01,-1.419878e-01,1.419878e-01,-2.183465e-01,2.183465e-01,-1.566968e-01,1.566968e-01,-1.288216e-01,1.288216e-01,-1.422831e-01,1.422831e-01,-2.000107e-01,2.000107e-01,-1.817265e-01,1.817265e-01,-1.793796e-01,1.793796e-01,-1.428926e-01,1.428926e-01,-1.182032e-01,1.182032e-01,-1.150421e-01,1.150421e-01,-1.336584e-01,1.336584e-01,-1.656178e-01,1.656178e-01,-1.386549e-01,1.386549e-01,-1.387461e-01,1.387461e-01,-1.313023e-01,1.313023e-01,-1.360391e-01,1.360391e-01,-1.305505e-01,1.305505e-01,-1.323399e-01,1.323399e-01,-1.502891e-01,1.502891e-01,-1.488859e-01,1.488859e-01,-1.126628e-01,1.126628e-01,-1.233623e-01,1.233623e-01,-1.702106e-01,1.702106e-01,-1.629639e-01,1.629639e-01,-1.337706e-01,1.337706e-01,-1.290384e-01,1.290384e-01,-1.165519e-01,1.165519e-01,-1.412778e-01,1.412778e-01,-1.470204e-01,1.470204e-01,-2.213780e-01,2.213780e-01,-1.472619e-01,1.472619e-01,-1.357071e-01,1.357071e-01,-1.416513e-01,1.416513e-01,-1.050208e-01,1.050208e-01,-1.480033e-01,1.480033e-01,-1.899871e-01,1.899871e-01,-1.466249e-01,1.466249e-01,-1.076952e-01,1.076952e-01,-1.035096e-01,1.035096e-01,-1.566970e-01,1.566970e-01,-1.364115e-01,1.364115e-01,-1.512889e-01,1.512889e-01,-1.252851e-01,1.252851e-01,-1.206300e-01,1.206300e-01,-1.059134e-01,1.059134e-01,-1.140398e-01,1.140398e-01,-1.359912e-01,1.359912e-01,-1.231201e-01,1.231201e-01,-1.231867e-01,1.231867e-01,-9.789923e-02,9.789923e-02,-1.590213e-01,1.590213e-01,-1.002206e-01,1.002206e-01,-1.518339e-01,1.518339e-01,-1.055203e-01,1.055203e-01,-1.012579e-01,1.012579e-01,-1.094956e-01,1.094956e-01,-1.429592e-01,1.429592e-01,-1.108838e-01,1.108838e-01,-1.116475e-01,1.116475e-01,-1.735371e-01,1.735371e-01,-1.067758e-01,1.067758e-01,-1.290406e-01,1.290406e-01,-1.156822e-01,1.156822e-01,-9.668217e-02,9.668217e-02,-1.170053e-01,1.170053e-01,-1.252092e-01,1.252092e-01,-1.135158e-01,1.135158e-01,-1.105896e-01,1.105896e-01,-1.038175e-01,1.038175e-01,-1.210459e-01,1.210459e-01,-1.078878e-01,1.078878e-01,-1.050808e-01,1.050808e-01,-1.428227e-01,1.428227e-01,-1.664600e-01,1.664600e-01,-1.013508e-01,1.013508e-01,-1.206930e-01,1.206930e-01,-1.088972e-01,1.088972e-01,-1.381026e-01,1.381026e-01,-1.109115e-01,1.109115e-01,-7.921549e-02,7.921549e-02,-1.057832e-01,1.057832e-01,-9.385827e-02,9.385827e-02,-1.486035e-01,1.486035e-01,-1.247401e-01,1.247401e-01,-9.451327e-02,9.451327e-02,-1.272805e-01,1.272805e-01,-9.616206e-02,9.616206e-02,-9.051084e-02,9.051084e-02,-1.138458e-01,1.138458e-01,-1.047581e-01,1.047581e-01,-1.382394e-01,1.382394e-01,-1.122203e-01,1.122203e-01,-1.052936e-01,1.052936e-01,-1.239318e-01,1.239318e-01,-1.241439e-01,1.241439e-01,-1.259012e-01,1.259012e-01,-1.211701e-01,1.211701e-01,-1.344131e-01,1.344131e-01,-1.127778e-01,1.127778e-01,-1.609745e-01,1.609745e-01,-1.901382e-01,1.901382e-01,-1.618962e-01,1.618962e-01,-1.230398e-01,1.230398e-01,-1.319311e-01,1.319311e-01,-1.431410e-01,1.431410e-01,-1.143306e-01,1.143306e-01,-9.390938e-02,9.390938e-02,-1.154161e-01,1.154161e-01,-1.141205e-01,1.141205e-01,-1.098048e-01,1.098048e-01,-8.870072e-02,8.870072e-02,-1.122444e-01,1.122444e-01,-1.114147e-01,1.114147e-01,-1.185710e-01,1.185710e-01,-1.107775e-01,1.107775e-01,-1.259167e-01,1.259167e-01,-1.105176e-01,1.105176e-01,-1.020691e-01,1.020691e-01,-9.607863e-02,9.607863e-02,-9.573700e-02,9.573700e-02,-1.054349e-01,1.054349e-01,-1.137856e-01,1.137856e-01,-1.192043e-01,1.192043e-01,-1.113264e-01,1.113264e-01,-1.093137e-01,1.093137e-01,-1.010919e-01,1.010919e-01,-9.625901e-02,9.625901e-02,-9.338459e-02,9.338459e-02,-1.142944e-01,1.142944e-01,-1.038877e-01,1.038877e-01,-9.772862e-02,9.772862e-02,-1.375298e-01,1.375298e-01,-1.394776e-01,1.394776e-01,-9.454765e-02,9.454765e-02,-1.203246e-01,1.203246e-01,-8.684943e-02,8.684943e-02,-1.135622e-01,1.135622e-01,-1.058181e-01,1.058181e-01,-1.082152e-01,1.082152e-01,-1.411355e-01,1.411355e-01,-9.978846e-02,9.978846e-02,-1.057874e-01,1.057874e-01,-1.415366e-01,1.415366e-01,-9.981014e-02,9.981014e-02,-9.261151e-02,9.261151e-02,-1.737173e-01,1.737173e-01,-1.580335e-01,1.580335e-01,-9.594668e-02,9.594668e-02,-9.336013e-02,9.336013e-02,-1.102373e-01,1.102373e-01,-8.546557e-02,8.546557e-02,-9.945057e-02,9.945057e-02,-1.146358e-01,1.146358e-01,-1.324734e-01,1.324734e-01,-1.422296e-01,1.422296e-01,-9.937990e-02,9.937990e-02,-8.381049e-02,8.381049e-02,-1.270714e-01,1.270714e-01,-1.091738e-01,1.091738e-01,-1.314881e-01,1.314881e-01,-1.085159e-01,1.085159e-01,-9.247554e-02,9.247554e-02,-8.121645e-02,8.121645e-02,-1.059589e-01,1.059589e-01,-8.307793e-02,8.307793e-02,-1.033103e-01,1.033103e-01,-1.056706e-01,1.056706e-01,-1.032803e-01,1.032803e-01,-1.266840e-01,1.266840e-01,-9.341601e-02,9.341601e-02,-7.683570e-02,7.683570e-02,-1.030530e-01,1.030530e-01,-1.051872e-01,1.051872e-01,-9.114946e-02,9.114946e-02,-1.329341e-01,1.329341e-01,-9.270830e-02,9.270830e-02,-1.141750e-01,1.141750e-01,-9.889318e-02,9.889318e-02,-8.856485e-02,8.856485e-02,-1.054210e-01,1.054210e-01,-1.092704e-01,1.092704e-01,-8.729085e-02,8.729085e-02,-1.141057e-01,1.141057e-01,-1.530774e-01,1.530774e-01,-8.129720e-02,8.129720e-02,-1.143335e-01,1.143335e-01,-1.175777e-01,1.175777e-01,-1.371729e-01,1.371729e-01,-1.394356e-01,1.394356e-01,-1.016308e-01,1.016308e-01,-1.125547e-01,1.125547e-01,-9.672600e-02,9.672600e-02,-1.036631e-01,1.036631e-01,-8.702514e-02,8.702514e-02,-1.264807e-01,1.264807e-01,-1.465688e-01,1.465688e-01,-8.781464e-02,8.781464e-02,-8.552605e-02,8.552605e-02,-1.145072e-01,1.145072e-01,-1.378489e-01,1.378489e-01,-1.013312e-01,1.013312e-01,-1.020083e-01,1.020083e-01,-1.015816e-01,1.015816e-01,-8.407101e-02,8.407101e-02,-8.296485e-02,8.296485e-02,-8.033655e-02,8.033655e-02,-9.003615e-02,9.003615e-02,-7.504954e-02,7.504954e-02,-1.224941e-01,1.224941e-01,-9.347814e-02,9.347814e-02,-9.555575e-02,9.555575e-02,-9.810025e-02,9.810025e-02,-1.237068e-01,1.237068e-01,-1.283586e-01,1.283586e-01,-1.082763e-01,1.082763e-01,-1.018145e-01,1.018145e-01,-1.175161e-01,1.175161e-01,-1.252279e-01,1.252279e-01,-1.370559e-01,1.370559e-01,-9.941339e-02,9.941339e-02,-8.506938e-02,8.506938e-02,-1.260902e-01,1.260902e-01,-1.014152e-01,1.014152e-01,-9.728694e-02,9.728694e-02,-9.374910e-02,9.374910e-02,-9.587429e-02,9.587429e-02,-9.516036e-02,9.516036e-02,-7.375173e-02,7.375173e-02,-9.332487e-02,9.332487e-02,-9.020733e-02,9.020733e-02,-1.133381e-01,1.133381e-01,-1.542180e-01,1.542180e-01,-9.692168e-02,9.692168e-02,-7.960904e-02,7.960904e-02,-8.947089e-02,8.947089e-02,-7.830286e-02,7.830286e-02,-9.900050e-02,9.900050e-02,-1.041293e-01,1.041293e-01,-9.572501e-02,9.572501e-02,-8.230575e-02,8.230575e-02,-9.194901e-02,9.194901e-02,-1.076971e-01,1.076971e-01,-1.027782e-01,1.027782e-01,-1.028538e-01,1.028538e-01,-1.013992e-01,1.013992e-01,-9.087585e-02,9.087585e-02,-1.100706e-01,1.100706e-01,-1.094934e-01,1.094934e-01,-1.107879e-01,1.107879e-01,-1.026915e-01,1.026915e-01,-1.017572e-01,1.017572e-01,-7.984776e-02,7.984776e-02,-9.015413e-02,9.015413e-02,-1.299870e-01,1.299870e-01,-9.164982e-02,9.164982e-02,-1.062788e-01,1.062788e-01,-1.160203e-01,1.160203e-01,-8.858603e-02,8.858603e-02,-9.762964e-02,9.762964e-02,-1.070694e-01,1.070694e-01,-9.549046e-02,9.549046e-02,-1.533034e-01,1.533034e-01,-8.663316e-02,8.663316e-02,-9.303018e-02,9.303018e-02,-9.853582e-02,9.853582e-02,-9.733371e-02,9.733371e-02,-1.048555e-01,1.048555e-01,-9.056041e-02,9.056041e-02,-7.552283e-02,7.552283e-02,-8.780631e-02,8.780631e-02,-1.123953e-01,1.123953e-01,-1.452948e-01,1.452948e-01,-1.156423e-01,1.156423e-01,-8.701142e-02,8.701142e-02,-9.713334e-02,9.713334e-02,-9.970888e-02,9.970888e-02,-8.614129e-02,8.614129e-02,-7.459861e-02,7.459861e-02,-9.253517e-02,9.253517e-02,-9.570092e-02,9.570092e-02,-9.485535e-02,9.485535e-02,-1.148365e-01,1.148365e-01,-1.063193e-01,1.063193e-01,-9.986686e-02,9.986686e-02,-7.523412e-02,7.523412e-02,-1.005881e-01,1.005881e-01,-8.249716e-02,8.249716e-02,-1.055866e-01,1.055866e-01,-1.343050e-01,1.343050e-01,-1.371056e-01,1.371056e-01,-9.604689e-02,9.604689e-02,-1.224268e-01,1.224268e-01,-9.211478e-02,9.211478e-02,-1.108371e-01,1.108371e-01,-1.100547e-01,1.100547e-01,-8.938970e-02,8.938970e-02,-8.655951e-02,8.655951e-02,-7.085816e-02,7.085816e-02,-8.101028e-02,8.101028e-02,-8.338046e-02,8.338046e-02,-8.309588e-02,8.309588e-02,-9.090584e-02,9.090584e-02,-8.124564e-02,8.124564e-02,-9.367843e-02,9.367843e-02,-1.011747e-01,1.011747e-01,-9.885045e-02,9.885045e-02,-8.944266e-02,8.944266e-02,-8.453859e-02,8.453859e-02,-8.308847e-02,8.308847e-02,-1.367280e-01,1.367280e-01,-1.295144e-01,1.295144e-01,-1.063965e-01,1.063965e-01,-7.752328e-02,7.752328e-02,-9.681524e-02,9.681524e-02,-7.862345e-02,7.862345e-02,-8.767746e-02,8.767746e-02,-9.198041e-02,9.198041e-02,-9.686489e-02,9.686489e-02]},{"count":564,"threshold":-4.517456e+00,"feature":[{"size":5,"px":[15,9,8,12,11],"py":[3,6,3,0,8],"pz":[0,1,0,0,0],"nx":[6,14,9,22,23],"ny":[8,7,8,17,3],"nz":[1,0,0,0,0]},{"size":5,"px":[12,13,11,14,12],"py":[9,4,4,4,5],"pz":[0,0,0,0,0],"nx":[4,6,10,4,15],"ny":[3,8,7,10,9],"nz":[1,1,0,1,0]},{"size":5,"px":[7,5,6,8,8],"py":[2,13,2,1,1],"pz":[0,0,0,0,-1],"nx":[3,0,4,1,0],"ny":[4,3,10,3,13],"nz":[1,1,1,0,0]},{"size":5,"px":[11,2,2,11,16],"py":[9,4,2,7,11],"pz":[0,2,2,0,0],"nx":[8,4,1,14,0],"ny":[4,4,16,5,13],"nz":[1,1,-1,-1,-1]},{"size":2,"px":[14,14],"py":[18,18],"pz":[0,-1],"nx":[8,13],"ny":[10,16],"nz":[1,0]},{"size":5,"px":[15,17,16,8,18],"py":[1,2,1,0,2],"pz":[0,0,0,1,0],"nx":[21,22,22,22,22],"ny":[1,5,3,4,2],"nz":[0,0,0,0,-1]},{"size":2,"px":[15,4],"py":[23,3],"pz":[0,2],"nx":[7,3],"ny":[10,6],"nz":[1,-1]},{"size":5,"px":[3,6,4,3,11],"py":[10,11,8,3,8],"pz":[1,0,1,1,0],"nx":[3,5,6,3,0],"ny":[4,9,9,9,0],"nz":[1,-1,-1,-1,-1]},{"size":3,"px":[11,11,2],"py":[11,13,16],"pz":[0,0,-1],"nx":[10,10,9],"ny":[10,11,14],"nz":[0,0,0]},{"size":2,"px":[8,4],"py":[12,6],"pz":[0,1],"nx":[4,5],"ny":[11,11],"nz":[1,-1]},{"size":5,"px":[10,11,13,3,12],"py":[3,4,3,0,1],"pz":[0,0,0,2,0],"nx":[14,18,20,19,15],"ny":[13,1,15,2,18],"nz":[0,0,0,0,0]},{"size":5,"px":[20,14,10,12,12],"py":[12,12,4,10,11],"pz":[0,0,1,0,0],"nx":[9,2,9,9,9],"ny":[4,12,5,9,14],"nz":[1,-1,-1,-1,-1]},{"size":5,"px":[3,3,3,4,2],"py":[15,16,14,21,12],"pz":[0,0,0,0,0],"nx":[0,0,0,0,0],"ny":[20,10,5,21,21],"nz":[0,1,2,0,-1]},{"size":2,"px":[18,8],"py":[16,7],"pz":[0,1],"nx":[14,0],"ny":[8,10],"nz":[0,-1]},{"size":4,"px":[12,4,16,1],"py":[14,3,8,3],"pz":[0,-1,-1,-1],"nx":[14,10,20,13],"ny":[13,5,16,9],"nz":[0,1,0,0]},{"size":5,"px":[3,8,2,3,3],"py":[7,2,1,2,4],"pz":[1,-1,-1,-1,-1],"nx":[1,9,2,1,1],"ny":[3,14,9,7,2],"nz":[1,0,1,1,1]},{"size":5,"px":[4,1,3,2,3],"py":[2,1,2,4,3],"pz":[0,1,0,0,0],"nx":[0,0,0,0,0],"ny":[3,1,2,0,0],"nz":[0,1,0,2,-1]},{"size":4,"px":[4,8,7,9],"py":[6,11,11,10],"pz":[1,0,0,0],"nx":[3,10,2,20],"ny":[4,4,4,8],"nz":[1,-1,-1,-1]},{"size":2,"px":[1,8],"py":[3,11],"pz":[2,-1],"nx":[8,2],"ny":[15,5],"nz":[0,2]},{"size":2,"px":[17,0],"py":[13,10],"pz":[0,-1],"nx":[14,14],"ny":[11,10],"nz":[0,0]},{"size":5,"px":[22,22,22,5,22],"py":[16,18,17,2,15],"pz":[0,0,0,2,0],"nx":[8,4,15,6,6],"ny":[4,2,7,11,11],"nz":[1,2,0,1,-1]},{"size":5,"px":[16,9,8,17,15],"py":[12,6,6,22,12],"pz":[0,1,1,0,0],"nx":[11,23,23,23,22],"ny":[11,23,22,21,23],"nz":[1,0,0,0,-1]},{"size":5,"px":[5,2,4,4,9],"py":[22,3,15,20,18],"pz":[0,2,0,0,0],"nx":[9,4,23,7,22],"ny":[8,4,22,19,23],"nz":[0,-1,-1,-1,-1]},{"size":5,"px":[8,6,9,7,3],"py":[3,3,3,3,1],"pz":[0,0,0,0,1],"nx":[5,5,4,4,4],"ny":[0,1,1,2,0],"nz":[0,0,0,0,-1]},{"size":2,"px":[2,3],"py":[3,3],"pz":[2,2],"nx":[3,6],"ny":[4,6],"nz":[1,-1]},{"size":5,"px":[1,1,0,1,0],"py":[17,15,6,16,10],"pz":[0,0,1,0,0],"nx":[4,4,7,4,8],"ny":[2,5,9,4,4],"nz":[2,2,1,2,-1]},{"size":5,"px":[12,12,12,13,13],"py":[10,9,11,13,13],"pz":[0,0,0,0,-1],"nx":[4,3,3,5,3],"ny":[21,18,17,23,16],"nz":[0,0,0,0,0]},{"size":4,"px":[5,6,5,9],"py":[13,7,9,23],"pz":[0,0,1,0],"nx":[6,15,7,5],"ny":[9,20,7,23],"nz":[0,-1,-1,-1]},{"size":2,"px":[6,3],"py":[4,2],"pz":[1,2],"nx":[8,23],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[9,7],"py":[18,0],"pz":[0,0],"nx":[5,7],"ny":[8,10],"nz":[1,1]},{"size":2,"px":[4,6],"py":[11,16],"pz":[1,0],"nx":[10,9],"ny":[16,7],"nz":[0,-1]},{"size":4,"px":[11,11,11,11],"py":[11,10,12,13],"pz":[0,0,0,0],"nx":[13,13,13,9],"ny":[11,9,10,4],"nz":[0,0,0,1]},{"size":4,"px":[12,6,7,6],"py":[7,11,8,4],"pz":[0,1,1,1],"nx":[10,0,19,7],"ny":[21,3,12,11],"nz":[0,-1,-1,-1]},{"size":2,"px":[4,4],"py":[3,4],"pz":[2,2],"nx":[9,1],"ny":[4,7],"nz":[1,-1]},{"size":2,"px":[19,19],"py":[21,20],"pz":[0,0],"nx":[7,7],"ny":[3,13],"nz":[1,-1]},{"size":5,"px":[12,9,13,11,5],"py":[0,2,2,0,0],"pz":[0,0,0,0,1],"nx":[6,4,5,5,5],"ny":[1,3,5,2,6],"nz":[0,0,1,0,1]},{"size":5,"px":[4,3,2,5,7],"py":[11,3,3,7,17],"pz":[1,2,2,0,0],"nx":[23,5,11,5,5],"ny":[0,4,10,2,6],"nz":[0,-1,-1,-1,-1]},{"size":2,"px":[20,17],"py":[12,3],"pz":[0,-1],"nx":[20,19],"ny":[21,23],"nz":[0,0]},{"size":2,"px":[2,1],"py":[12,8],"pz":[0,0],"nx":[2,8],"ny":[2,16],"nz":[2,-1]},{"size":2,"px":[16,5],"py":[4,5],"pz":[0,-1],"nx":[7,8],"ny":[9,1],"nz":[1,1]},{"size":2,"px":[2,2],"py":[0,1],"pz":[1,1],"nx":[1,8],"ny":[5,1],"nz":[0,-1]},{"size":2,"px":[1,1],"py":[12,10],"pz":[0,1],"nx":[2,20],"ny":[23,9],"nz":[0,-1]},{"size":4,"px":[11,0,0,2],"py":[14,3,9,22],"pz":[0,-1,-1,-1],"nx":[13,14,7,3],"ny":[6,7,11,1],"nz":[0,0,0,2]},{"size":2,"px":[14,0],"py":[2,3],"pz":[0,-1],"nx":[4,4],"ny":[4,3],"nz":[2,2]},{"size":2,"px":[23,11],"py":[18,11],"pz":[0,1],"nx":[3,2],"ny":[1,21],"nz":[1,-1]},{"size":2,"px":[9,9],"py":[17,14],"pz":[0,-1],"nx":[4,5],"ny":[10,8],"nz":[1,1]},{"size":2,"px":[9,18],"py":[7,14],"pz":[1,0],"nx":[18,9],"ny":[17,8],"nz":[0,-1]},{"size":2,"px":[2,8],"py":[4,22],"pz":[2,0],"nx":[4,3],"ny":[10,1],"nz":[1,-1]},{"size":2,"px":[5,22],"py":[4,9],"pz":[2,-1],"nx":[11,23],"ny":[8,14],"nz":[1,0]},{"size":3,"px":[23,5,5],"py":[8,2,1],"pz":[0,2,2],"nx":[10,10,2],"ny":[4,4,2],"nz":[1,-1,-1]},{"size":2,"px":[11,11],"py":[14,23],"pz":[0,-1],"nx":[3,11],"ny":[4,13],"nz":[1,0]},{"size":2,"px":[3,2],"py":[7,0],"pz":[1,-1],"nx":[4,3],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[12,1],"py":[19,13],"pz":[0,-1],"nx":[9,12],"ny":[10,18],"nz":[1,0]},{"size":2,"px":[10,10],"py":[11,10],"pz":[1,1],"nx":[4,1],"ny":[5,11],"nz":[2,-1]},{"size":5,"px":[9,12,4,8,8],"py":[3,5,2,9,8],"pz":[1,0,2,1,1],"nx":[23,23,23,23,23],"ny":[3,4,6,5,5],"nz":[0,0,0,0,-1]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,9],"ny":[4,6],"nz":[1,-1]},{"size":5,"px":[13,13,13,7,7],"py":[11,10,9,6,6],"pz":[0,0,0,1,-1],"nx":[5,5,15,5,2],"ny":[5,15,9,9,1],"nz":[0,0,0,1,2]},{"size":2,"px":[19,7],"py":[21,7],"pz":[0,1],"nx":[14,10],"ny":[15,4],"nz":[0,-1]},{"size":2,"px":[5,5],"py":[3,4],"pz":[2,2],"nx":[21,0],"ny":[23,5],"nz":[0,-1]},{"size":2,"px":[2,0],"py":[0,0],"pz":[1,-1],"nx":[3,2],"ny":[1,2],"nz":[0,0]},{"size":2,"px":[9,0],"py":[4,0],"pz":[0,-1],"nx":[5,12],"ny":[0,1],"nz":[1,0]},{"size":5,"px":[14,16,12,15,13],"py":[0,1,0,0,0],"pz":[0,0,0,0,0],"nx":[4,8,8,4,9],"ny":[2,3,4,1,3],"nz":[2,1,1,2,-1]},{"size":3,"px":[4,17,2],"py":[11,14,1],"pz":[1,-1,-1],"nx":[9,8,17],"ny":[1,4,0],"nz":[1,1,0]},{"size":2,"px":[18,9],"py":[17,7],"pz":[0,1],"nx":[8,4],"ny":[4,7],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[3,0],"pz":[1,2],"nx":[10,11],"ny":[6,5],"nz":[1,-1]},{"size":5,"px":[21,21,21,21,20],"py":[17,16,19,18,21],"pz":[0,0,0,0,0],"nx":[0,0,0,0,0],"ny":[4,9,11,6,6],"nz":[1,0,0,1,-1]},{"size":2,"px":[12,0],"py":[7,1],"pz":[0,-1],"nx":[8,11],"ny":[4,17],"nz":[1,0]},{"size":4,"px":[13,0,0,0],"py":[15,0,0,0],"pz":[0,-1,-1,-1],"nx":[3,7,4,6],"ny":[2,7,5,9],"nz":[2,1,2,1]},{"size":2,"px":[2,9],"py":[3,12],"pz":[2,0],"nx":[2,0],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[10,3],"py":[6,1],"pz":[1,-1],"nx":[20,21],"ny":[19,14],"nz":[0,0]},{"size":5,"px":[5,22,22,11,22],"py":[1,4,3,3,2],"pz":[2,0,0,1,-1],"nx":[7,13,14,8,15],"ny":[3,6,6,3,7],"nz":[1,0,0,1,0]},{"size":2,"px":[12,19],"py":[5,15],"pz":[0,-1],"nx":[16,4],"ny":[8,2],"nz":[0,2]},{"size":2,"px":[1,0],"py":[11,9],"pz":[1,1],"nx":[5,0],"ny":[3,3],"nz":[1,-1]},{"size":4,"px":[8,3,4,2],"py":[6,7,5,3],"pz":[1,-1,-1,-1],"nx":[13,14,11,11],"ny":[11,13,3,5],"nz":[0,0,1,1]},{"size":2,"px":[11,11],"py":[5,6],"pz":[0,0],"nx":[8,4],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[5,9],"py":[6,17],"pz":[1,0],"nx":[9,4],"ny":[15,11],"nz":[0,-1]},{"size":3,"px":[6,3,6],"py":[6,3,5],"pz":[1,2,1],"nx":[11,10,4],"ny":[8,11,5],"nz":[0,0,-1]},{"size":2,"px":[8,16],"py":[0,1],"pz":[1,-1],"nx":[19,17],"ny":[1,0],"nz":[0,0]},{"size":2,"px":[21,20],"py":[4,1],"pz":[0,0],"nx":[11,5],"ny":[0,0],"nz":[1,2]},{"size":2,"px":[8,4],"py":[6,3],"pz":[1,2],"nx":[8,9],"ny":[4,10],"nz":[1,-1]},{"size":2,"px":[10,1],"py":[0,0],"pz":[1,-1],"nx":[13,12],"ny":[6,5],"nz":[0,0]},{"size":2,"px":[5,4],"py":[3,11],"pz":[1,-1],"nx":[3,17],"ny":[1,3],"nz":[2,0]},{"size":2,"px":[12,13],"py":[4,4],"pz":[0,0],"nx":[3,3],"ny":[1,1],"nz":[2,-1]},{"size":2,"px":[3,18],"py":[2,7],"pz":[2,0],"nx":[8,1],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[16,6],"py":[8,2],"pz":[0,1],"nx":[8,9],"ny":[4,19],"nz":[1,-1]},{"size":3,"px":[12,3,14],"py":[13,3,15],"pz":[0,-1,-1],"nx":[0,1,0],"ny":[16,18,15],"nz":[0,0,0]},{"size":2,"px":[3,1],"py":[3,4],"pz":[2,-1],"nx":[7,14],"ny":[10,14],"nz":[1,0]},{"size":2,"px":[9,16],"py":[6,10],"pz":[1,0],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[7,11],"py":[4,4],"pz":[0,0],"nx":[7,23],"ny":[3,11],"nz":[0,-1]},{"size":5,"px":[2,4,3,4,4],"py":[1,2,0,1,1],"pz":[1,0,1,0,-1],"nx":[11,9,4,9,5],"ny":[6,5,3,6,3],"nz":[0,0,1,0,1]},{"size":2,"px":[6,0],"py":[14,1],"pz":[0,-1],"nx":[2,5],"ny":[2,9],"nz":[2,1]},{"size":2,"px":[6,7],"py":[7,12],"pz":[0,0],"nx":[3,22],"ny":[3,16],"nz":[1,-1]},{"size":2,"px":[10,4],"py":[1,1],"pz":[0,1],"nx":[2,6],"ny":[2,21],"nz":[2,-1]},{"size":2,"px":[13,1],"py":[11,6],"pz":[0,-1],"nx":[12,6],"ny":[5,2],"nz":[0,1]},{"size":5,"px":[10,5,11,10,10],"py":[4,3,4,6,5],"pz":[0,1,0,0,0],"nx":[4,7,13,8,4],"ny":[2,8,9,4,4],"nz":[2,1,0,1,-1]},{"size":4,"px":[7,8,7,8],"py":[11,3,4,7],"pz":[1,1,1,1],"nx":[0,7,3,8],"ny":[0,12,2,4],"nz":[0,-1,-1,-1]},{"size":2,"px":[0,0],"py":[4,7],"pz":[2,1],"nx":[10,1],"ny":[7,0],"nz":[0,-1]},{"size":2,"px":[11,5],"py":[19,5],"pz":[0,-1],"nx":[11,5],"ny":[17,10],"nz":[0,1]},{"size":2,"px":[11,12],"py":[4,4],"pz":[0,0],"nx":[7,5],"ny":[8,3],"nz":[0,-1]},{"size":3,"px":[4,8,4],"py":[2,9,4],"pz":[2,1,2],"nx":[3,19,3],"ny":[1,16,5],"nz":[1,-1,-1]},{"size":2,"px":[3,7],"py":[0,1],"pz":[1,0],"nx":[2,3],"ny":[15,2],"nz":[0,-1]},{"size":2,"px":[0,4],"py":[2,0],"pz":[2,-1],"nx":[9,16],"ny":[5,11],"nz":[1,0]},{"size":2,"px":[14,15],"py":[23,16],"pz":[0,0],"nx":[13,3],"ny":[15,1],"nz":[0,-1]},{"size":2,"px":[4,3],"py":[0,1],"pz":[1,-1],"nx":[3,7],"ny":[0,0],"nz":[1,0]},{"size":2,"px":[7,6],"py":[12,12],"pz":[0,0],"nx":[4,8],"ny":[5,4],"nz":[1,-1]},{"size":5,"px":[4,1,2,4,5],"py":[1,0,0,0,6],"pz":[0,2,1,0,1],"nx":[4,8,7,8,6],"ny":[4,10,11,4,4],"nz":[1,0,0,1,1]},{"size":2,"px":[12,12],"py":[15,8],"pz":[0,-1],"nx":[7,15],"ny":[16,14],"nz":[0,0]},{"size":2,"px":[4,8],"py":[3,6],"pz":[2,1],"nx":[4,6],"ny":[2,8],"nz":[2,-1]},{"size":2,"px":[14,4],"py":[19,23],"pz":[0,-1],"nx":[7,14],"ny":[11,18],"nz":[1,0]},{"size":2,"px":[4,2],"py":[7,4],"pz":[1,2],"nx":[2,22],"ny":[5,19],"nz":[2,-1]},{"size":2,"px":[8,15],"py":[7,17],"pz":[1,0],"nx":[14,4],"ny":[15,5],"nz":[0,2]},{"size":2,"px":[10,11],"py":[9,8],"pz":[1,-1],"nx":[23,5],"ny":[19,4],"nz":[0,2]},{"size":2,"px":[11,1],"py":[7,9],"pz":[0,-1],"nx":[4,4],"ny":[4,5],"nz":[1,1]},{"size":2,"px":[14,7],"py":[6,9],"pz":[0,0],"nx":[4,11],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[5,4],"py":[0,5],"pz":[0,-1],"nx":[2,2],"ny":[0,4],"nz":[1,0]},{"size":2,"px":[10,22],"py":[5,20],"pz":[0,-1],"nx":[3,4],"ny":[1,2],"nz":[2,2]},{"size":3,"px":[23,11,11],"py":[17,9,8],"pz":[0,1,1],"nx":[13,8,8],"ny":[5,3,3],"nz":[0,1,-1]},{"size":2,"px":[18,9],"py":[0,21],"pz":[0,-1],"nx":[10,10],"ny":[2,1],"nz":[1,1]},{"size":5,"px":[11,10,11,11,11],"py":[11,13,10,12,12],"pz":[0,0,0,0,-1],"nx":[11,13,12,3,8],"ny":[5,5,5,1,10],"nz":[0,0,0,2,0]},{"size":2,"px":[7,8],"py":[11,11],"pz":[0,0],"nx":[9,16],"ny":[9,19],"nz":[0,-1]},{"size":2,"px":[9,18],"py":[23,7],"pz":[0,-1],"nx":[21,21],"ny":[7,13],"nz":[0,0]},{"size":2,"px":[8,8],"py":[7,8],"pz":[1,1],"nx":[5,21],"ny":[9,13],"nz":[1,-1]},{"size":2,"px":[17,8],"py":[22,8],"pz":[0,-1],"nx":[4,8],"ny":[5,10],"nz":[2,1]},{"size":5,"px":[2,5,8,8,4],"py":[3,9,13,23,7],"pz":[2,1,0,0,1],"nx":[9,17,18,19,20],"ny":[0,0,0,2,3],"nz":[1,0,0,0,0]},{"size":3,"px":[16,15,2],"py":[3,3,13],"pz":[0,0,-1],"nx":[4,8,4],"ny":[3,6,2],"nz":[2,1,2]},{"size":2,"px":[4,7],"py":[3,7],"pz":[2,1],"nx":[15,1],"ny":[15,0],"nz":[0,-1]},{"size":2,"px":[3,6],"py":[2,3],"pz":[2,1],"nx":[3,18],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[2,4],"py":[2,4],"pz":[2,1],"nx":[3,0],"ny":[5,0],"nz":[1,-1]},{"size":2,"px":[10,0],"py":[10,0],"pz":[0,-1],"nx":[9,4],"ny":[2,0],"nz":[1,2]},{"size":2,"px":[2,0],"py":[8,3],"pz":[1,-1],"nx":[4,8],"ny":[4,14],"nz":[1,0]},{"size":2,"px":[13,18],"py":[14,14],"pz":[0,-1],"nx":[1,1],"ny":[15,13],"nz":[0,0]},{"size":3,"px":[3,2,2],"py":[17,10,15],"pz":[0,1,0],"nx":[13,2,7],"ny":[19,11,0],"nz":[0,-1,-1]},{"size":2,"px":[4,17],"py":[0,2],"pz":[2,0],"nx":[8,5],"ny":[11,3],"nz":[1,-1]},{"size":2,"px":[15,21],"py":[5,4],"pz":[0,-1],"nx":[15,10],"ny":[3,0],"nz":[0,1]},{"size":2,"px":[7,3],"py":[13,8],"pz":[0,-1],"nx":[8,4],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[7,22],"py":[3,4],"pz":[1,-1],"nx":[4,2],"ny":[2,3],"nz":[1,1]},{"size":4,"px":[6,2,6,5],"py":[21,10,22,20],"pz":[0,1,0,0],"nx":[2,3,4,4],"ny":[11,21,23,23],"nz":[1,0,0,-1]},{"size":2,"px":[7,2],"py":[6,8],"pz":[1,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":4,"px":[11,11,5,11],"py":[6,5,2,4],"pz":[1,1,2,1],"nx":[13,7,8,3],"ny":[7,3,5,2],"nz":[0,1,-1,-1]},{"size":2,"px":[3,3],"py":[7,8],"pz":[1,0],"nx":[3,11],"ny":[4,2],"nz":[1,-1]},{"size":3,"px":[16,1,5],"py":[3,3,11],"pz":[0,-1,-1],"nx":[16,4,8],"ny":[2,0,1],"nz":[0,2,1]},{"size":2,"px":[10,0],"py":[8,1],"pz":[0,-1],"nx":[19,18],"ny":[20,23],"nz":[0,0]},{"size":2,"px":[17,4],"py":[10,4],"pz":[0,-1],"nx":[4,14],"ny":[2,9],"nz":[2,0]},{"size":5,"px":[11,12,9,10,11],"py":[2,3,2,2,3],"pz":[0,0,0,0,0],"nx":[6,4,2,2,2],"ny":[18,9,3,2,2],"nz":[0,1,2,2,-1]},{"size":2,"px":[0,1],"py":[6,16],"pz":[1,0],"nx":[8,16],"ny":[5,16],"nz":[0,-1]},{"size":2,"px":[3,3],"py":[2,3],"pz":[2,2],"nx":[8,17],"ny":[4,9],"nz":[1,-1]},{"size":3,"px":[2,5,2],"py":[5,6,4],"pz":[1,-1,-1],"nx":[0,0,0],"ny":[3,5,6],"nz":[2,1,1]},{"size":5,"px":[0,0,0,0,0],"py":[6,15,16,13,14],"pz":[1,0,0,0,0],"nx":[4,5,8,6,8],"ny":[4,16,8,15,4],"nz":[1,0,0,0,-1]},{"size":2,"px":[4,2],"py":[6,3],"pz":[1,2],"nx":[3,5],"ny":[4,16],"nz":[1,-1]},{"size":5,"px":[21,19,21,21,21],"py":[17,23,18,19,20],"pz":[0,0,0,0,0],"nx":[5,2,3,6,6],"ny":[12,5,5,12,12],"nz":[0,1,1,0,-1]},{"size":2,"px":[5,2],"py":[11,1],"pz":[1,-1],"nx":[5,11],"ny":[3,5],"nz":[2,1]},{"size":2,"px":[10,5],"py":[5,3],"pz":[0,1],"nx":[6,15],"ny":[11,5],"nz":[1,-1]},{"size":2,"px":[6,2],"py":[4,2],"pz":[1,-1],"nx":[4,3],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[10,6],"py":[20,6],"pz":[0,-1],"nx":[5,10],"ny":[11,17],"nz":[1,0]},{"size":4,"px":[8,4,7,11],"py":[7,4,5,8],"pz":[1,2,1,0],"nx":[13,10,5,21],"ny":[9,3,5,4],"nz":[0,-1,-1,-1]},{"size":2,"px":[7,13],"py":[10,7],"pz":[0,0],"nx":[10,8],"ny":[9,18],"nz":[0,-1]},{"size":2,"px":[3,3],"py":[1,0],"pz":[2,2],"nx":[8,5],"ny":[4,2],"nz":[1,-1]},{"size":5,"px":[5,2,5,8,4],"py":[8,4,14,23,7],"pz":[1,2,0,0,1],"nx":[18,4,16,17,17],"ny":[1,0,0,1,1],"nz":[0,2,0,0,-1]},{"size":2,"px":[6,2],"py":[2,4],"pz":[1,-1],"nx":[8,8],"ny":[4,3],"nz":[1,1]},{"size":2,"px":[6,1],"py":[8,15],"pz":[0,-1],"nx":[8,3],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[10,1],"py":[7,2],"pz":[1,-1],"nx":[6,6],"ny":[9,4],"nz":[1,1]},{"size":2,"px":[4,1],"py":[6,2],"pz":[1,-1],"nx":[1,10],"ny":[16,12],"nz":[0,0]},{"size":2,"px":[8,4],"py":[7,2],"pz":[1,-1],"nx":[8,9],"ny":[8,10],"nz":[1,1]},{"size":5,"px":[4,8,7,6,6],"py":[0,0,0,1,1],"pz":[1,0,0,0,-1],"nx":[11,5,8,4,10],"ny":[5,3,4,4,5],"nz":[0,1,1,1,0]},{"size":2,"px":[5,6],"py":[8,5],"pz":[0,0],"nx":[6,6],"ny":[8,3],"nz":[0,-1]},{"size":2,"px":[18,5],"py":[19,5],"pz":[0,-1],"nx":[4,21],"ny":[5,19],"nz":[2,0]},{"size":2,"px":[9,5],"py":[13,6],"pz":[0,1],"nx":[2,2],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[10,4],"py":[17,6],"pz":[0,1],"nx":[10,2],"ny":[15,4],"nz":[0,-1]},{"size":3,"px":[13,13,19],"py":[11,12,8],"pz":[0,0,-1],"nx":[12,3,8],"ny":[4,1,4],"nz":[0,2,1]},{"size":3,"px":[11,7,4],"py":[5,2,1],"pz":[0,-1,-1],"nx":[9,2,4],"ny":[11,3,6],"nz":[0,2,1]},{"size":2,"px":[10,7],"py":[15,2],"pz":[0,-1],"nx":[4,4],"ny":[0,1],"nz":[2,2]},{"size":5,"px":[8,9,16,18,18],"py":[0,1,1,1,1],"pz":[1,1,0,0,-1],"nx":[5,5,6,4,4],"ny":[21,20,23,17,18],"nz":[0,0,0,0,0]},{"size":2,"px":[6,7],"py":[1,1],"pz":[1,1],"nx":[20,19],"ny":[2,1],"nz":[0,0]},{"size":2,"px":[2,2],"py":[10,11],"pz":[1,1],"nx":[3,3],"ny":[10,10],"nz":[1,-1]},{"size":2,"px":[9,5],"py":[23,1],"pz":[0,-1],"nx":[4,3],"ny":[10,4],"nz":[1,1]},{"size":2,"px":[1,10],"py":[4,7],"pz":[2,-1],"nx":[4,3],"ny":[23,21],"nz":[0,0]},{"size":2,"px":[10,21],"py":[11,18],"pz":[1,0],"nx":[10,4],"ny":[18,1],"nz":[0,-1]},{"size":2,"px":[11,23],"py":[11,15],"pz":[0,-1],"nx":[11,11],"ny":[7,9],"nz":[1,1]},{"size":2,"px":[10,1],"py":[7,7],"pz":[1,-1],"nx":[15,4],"ny":[14,4],"nz":[0,2]},{"size":2,"px":[1,2],"py":[9,20],"pz":[1,0],"nx":[21,3],"ny":[12,20],"nz":[0,-1]},{"size":2,"px":[7,4],"py":[0,0],"pz":[1,2],"nx":[4,2],"ny":[0,19],"nz":[0,-1]},{"size":2,"px":[2,4],"py":[3,6],"pz":[2,1],"nx":[3,0],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[5,1],"py":[5,0],"pz":[1,-1],"nx":[12,10],"ny":[11,4],"nz":[0,1]},{"size":2,"px":[11,12],"py":[11,14],"pz":[1,-1],"nx":[18,16],"ny":[21,15],"nz":[0,0]},{"size":2,"px":[3,18],"py":[1,5],"pz":[2,-1],"nx":[4,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[9,10],"py":[18,7],"pz":[0,-1],"nx":[3,6],"ny":[0,0],"nz":[2,1]},{"size":2,"px":[19,2],"py":[1,4],"pz":[0,-1],"nx":[22,22],"ny":[13,15],"nz":[0,0]},{"size":3,"px":[13,15,20],"py":[14,21,10],"pz":[0,-1,-1],"nx":[15,7,7],"ny":[13,6,8],"nz":[0,1,1]},{"size":2,"px":[9,9],"py":[6,7],"pz":[1,1],"nx":[8,7],"ny":[4,8],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[5,3],"pz":[1,2],"nx":[5,10],"ny":[2,9],"nz":[1,-1]},{"size":2,"px":[14,11],"py":[7,16],"pz":[0,-1],"nx":[1,0],"ny":[17,4],"nz":[0,2]},{"size":2,"px":[14,18],"py":[17,18],"pz":[0,-1],"nx":[8,14],"ny":[10,16],"nz":[1,0]},{"size":2,"px":[6,11],"py":[13,11],"pz":[0,-1],"nx":[8,9],"ny":[12,9],"nz":[0,0]},{"size":2,"px":[8,9],"py":[2,2],"pz":[0,0],"nx":[3,3],"ny":[2,2],"nz":[2,-1]},{"size":3,"px":[21,21,21],"py":[14,16,15],"pz":[0,0,0],"nx":[14,12,0],"ny":[5,12,6],"nz":[0,-1,-1]},{"size":2,"px":[4,21],"py":[6,15],"pz":[1,-1],"nx":[5,1],"ny":[6,5],"nz":[1,1]},{"size":2,"px":[6,3],"py":[2,1],"pz":[1,2],"nx":[8,0],"ny":[4,20],"nz":[1,-1]},{"size":2,"px":[13,2],"py":[9,1],"pz":[0,-1],"nx":[3,5],"ny":[1,2],"nz":[2,1]},{"size":2,"px":[16,1],"py":[5,4],"pz":[0,-1],"nx":[17,8],"ny":[3,2],"nz":[0,1]},{"size":2,"px":[9,2],"py":[7,1],"pz":[1,-1],"nx":[20,20],"ny":[17,16],"nz":[0,0]},{"size":2,"px":[5,7],"py":[3,6],"pz":[2,-1],"nx":[9,9],"ny":[6,5],"nz":[1,1]},{"size":2,"px":[11,17],"py":[4,1],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[15,2],"py":[11,0],"pz":[0,-1],"nx":[5,14],"ny":[1,12],"nz":[2,0]},{"size":2,"px":[22,19],"py":[3,0],"pz":[0,-1],"nx":[9,4],"ny":[6,4],"nz":[1,1]},{"size":2,"px":[1,22],"py":[3,21],"pz":[0,-1],"nx":[0,0],"ny":[1,0],"nz":[2,2]},{"size":2,"px":[11,11],"py":[11,12],"pz":[0,0],"nx":[1,2],"ny":[1,4],"nz":[2,-1]},{"size":2,"px":[18,3],"py":[8,1],"pz":[0,2],"nx":[13,1],"ny":[8,5],"nz":[0,-1]},{"size":2,"px":[13,6],"py":[21,3],"pz":[0,-1],"nx":[11,11],"ny":[6,5],"nz":[1,1]},{"size":2,"px":[15,14],"py":[4,4],"pz":[0,0],"nx":[17,1],"ny":[12,5],"nz":[0,-1]},{"size":2,"px":[11,3],"py":[12,1],"pz":[0,-1],"nx":[1,2],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[3,2],"py":[7,3],"pz":[0,1],"nx":[16,2],"ny":[3,5],"nz":[0,-1]},{"size":2,"px":[10,5],"py":[7,20],"pz":[1,-1],"nx":[9,8],"ny":[4,6],"nz":[1,1]},{"size":2,"px":[19,2],"py":[10,2],"pz":[0,-1],"nx":[9,4],"ny":[3,1],"nz":[1,2]},{"size":2,"px":[14,9],"py":[0,23],"pz":[0,-1],"nx":[4,4],"ny":[3,2],"nz":[2,2]},{"size":2,"px":[6,9],"py":[4,10],"pz":[1,0],"nx":[10,9],"ny":[9,0],"nz":[0,-1]},{"size":4,"px":[6,9,10,8],"py":[20,23,18,23],"pz":[0,0,0,0],"nx":[9,22,1,2],"ny":[21,14,2,5],"nz":[0,-1,-1,-1]},{"size":2,"px":[17,18],"py":[13,6],"pz":[0,-1],"nx":[6,7],"ny":[9,11],"nz":[1,1]},{"size":5,"px":[18,19,20,19,20],"py":[15,19,16,20,17],"pz":[0,0,0,0,0],"nx":[11,22,23,23,23],"ny":[10,22,20,19,19],"nz":[1,0,0,0,-1]},{"size":2,"px":[10,10],"py":[1,0],"pz":[1,1],"nx":[21,11],"ny":[0,4],"nz":[0,-1]},{"size":2,"px":[11,0],"py":[9,3],"pz":[0,-1],"nx":[9,4],"ny":[2,1],"nz":[1,2]},{"size":2,"px":[14,23],"py":[2,18],"pz":[0,-1],"nx":[15,18],"ny":[1,2],"nz":[0,0]},{"size":2,"px":[9,3],"py":[0,0],"pz":[1,-1],"nx":[3,12],"ny":[1,5],"nz":[2,0]},{"size":2,"px":[8,8],"py":[7,8],"pz":[1,1],"nx":[8,8],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[1,0],"py":[1,3],"pz":[2,-1],"nx":[7,19],"ny":[9,15],"nz":[1,0]},{"size":3,"px":[16,6,4],"py":[21,5,4],"pz":[0,-1,-1],"nx":[4,19,8],"ny":[5,21,11],"nz":[2,0,1]},{"size":2,"px":[5,5],"py":[6,6],"pz":[1,-1],"nx":[10,10],"ny":[10,12],"nz":[0,0]},{"size":2,"px":[6,11],"py":[2,5],"pz":[1,0],"nx":[3,4],"ny":[4,7],"nz":[1,-1]},{"size":3,"px":[8,6,2],"py":[4,10,2],"pz":[1,1,2],"nx":[2,18,5],"ny":[0,11,5],"nz":[0,-1,-1]},{"size":2,"px":[11,7],"py":[9,7],"pz":[0,-1],"nx":[12,3],"ny":[9,5],"nz":[0,1]},{"size":2,"px":[14,13],"py":[20,20],"pz":[0,0],"nx":[13,3],"ny":[21,5],"nz":[0,-1]},{"size":2,"px":[13,7],"py":[5,3],"pz":[0,-1],"nx":[3,4],"ny":[1,4],"nz":[2,1]},{"size":2,"px":[6,2],"py":[21,5],"pz":[0,-1],"nx":[2,3],"ny":[5,10],"nz":[2,1]},{"size":2,"px":[23,5],"py":[6,0],"pz":[0,2],"nx":[21,4],"ny":[6,1],"nz":[0,-1]},{"size":2,"px":[9,9],"py":[7,6],"pz":[1,1],"nx":[8,2],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[22,11],"py":[20,9],"pz":[0,1],"nx":[8,8],"ny":[10,10],"nz":[1,-1]},{"size":2,"px":[8,16],"py":[21,12],"pz":[0,-1],"nx":[2,7],"ny":[5,23],"nz":[2,0]},{"size":5,"px":[0,1,1,1,1],"py":[3,1,9,4,7],"pz":[2,2,1,1,1],"nx":[11,22,22,23,23],"ny":[10,21,22,19,20],"nz":[1,0,0,0,-1]},{"size":2,"px":[17,5],"py":[12,4],"pz":[0,-1],"nx":[8,8],"ny":[4,5],"nz":[1,1]},{"size":2,"px":[16,4],"py":[7,10],"pz":[0,-1],"nx":[9,15],"ny":[4,6],"nz":[1,0]},{"size":2,"px":[3,6],"py":[3,5],"pz":[2,1],"nx":[11,12],"ny":[11,23],"nz":[0,-1]},{"size":2,"px":[5,2],"py":[14,7],"pz":[0,1],"nx":[4,17],"ny":[18,16],"nz":[0,-1]},{"size":3,"px":[10,1,1],"py":[12,5,4],"pz":[0,-1,-1],"nx":[7,11,5],"ny":[1,2,1],"nz":[1,0,1]},{"size":2,"px":[7,6],"py":[3,9],"pz":[0,-1],"nx":[2,2],"ny":[2,3],"nz":[2,2]},{"size":2,"px":[13,6],"py":[22,9],"pz":[0,-1],"nx":[8,4],"ny":[4,3],"nz":[1,2]},{"size":5,"px":[12,9,10,11,11],"py":[0,0,0,0,0],"pz":[0,0,0,0,-1],"nx":[16,5,10,4,8],"ny":[10,3,6,4,4],"nz":[0,1,0,1,1]},{"size":2,"px":[18,19],"py":[23,20],"pz":[0,0],"nx":[8,5],"ny":[11,3],"nz":[1,-1]},{"size":2,"px":[8,3],"py":[7,2],"pz":[1,2],"nx":[8,4],"ny":[4,3],"nz":[1,-1]},{"size":5,"px":[8,14,8,7,4],"py":[6,12,8,6,3],"pz":[1,0,1,1,2],"nx":[2,6,6,7,7],"ny":[0,1,2,0,0],"nz":[2,0,0,0,-1]},{"size":3,"px":[1,2,3],"py":[15,18,21],"pz":[0,0,0],"nx":[19,5,18],"ny":[23,5,8],"nz":[0,-1,-1]},{"size":2,"px":[6,2],"py":[6,1],"pz":[1,-1],"nx":[0,0],"ny":[12,4],"nz":[0,1]},{"size":2,"px":[3,5],"py":[5,11],"pz":[2,1],"nx":[14,5],"ny":[19,5],"nz":[0,-1]},{"size":2,"px":[10,4],"py":[4,4],"pz":[1,-1],"nx":[11,5],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[18,4],"py":[6,4],"pz":[0,-1],"nx":[4,8],"ny":[5,4],"nz":[1,1]},{"size":2,"px":[6,12],"py":[2,4],"pz":[1,0],"nx":[8,8],"ny":[3,4],"nz":[1,-1]},{"size":2,"px":[1,0],"py":[1,1],"pz":[1,2],"nx":[7,2],"ny":[4,7],"nz":[0,-1]},{"size":2,"px":[8,0],"py":[20,0],"pz":[0,-1],"nx":[4,5],"ny":[10,11],"nz":[1,1]},{"size":2,"px":[6,14],"py":[5,2],"pz":[1,-1],"nx":[0,0],"ny":[0,2],"nz":[1,0]},{"size":2,"px":[5,15],"py":[4,7],"pz":[1,-1],"nx":[4,7],"ny":[1,2],"nz":[2,1]},{"size":2,"px":[7,5],"py":[2,1],"pz":[0,1],"nx":[3,1],"ny":[4,1],"nz":[1,-1]},{"size":2,"px":[8,9],"py":[4,2],"pz":[0,-1],"nx":[11,9],"ny":[1,3],"nz":[0,0]},{"size":2,"px":[6,3],"py":[2,4],"pz":[1,-1],"nx":[4,8],"ny":[4,4],"nz":[1,1]},{"size":2,"px":[3,7],"py":[3,7],"pz":[2,1],"nx":[6,8],"ny":[14,4],"nz":[0,-1]},{"size":2,"px":[3,0],"py":[21,3],"pz":[0,2],"nx":[20,8],"ny":[10,4],"nz":[0,-1]},{"size":2,"px":[6,3],"py":[5,8],"pz":[0,-1],"nx":[4,3],"ny":[4,2],"nz":[0,1]},{"size":2,"px":[3,6],"py":[7,13],"pz":[1,0],"nx":[3,2],"ny":[4,3],"nz":[1,-1]},{"size":2,"px":[16,10],"py":[9,7],"pz":[0,1],"nx":[7,9],"ny":[3,10],"nz":[1,-1]},{"size":2,"px":[13,10],"py":[6,7],"pz":[0,-1],"nx":[8,17],"ny":[4,12],"nz":[1,0]},{"size":2,"px":[5,10],"py":[4,10],"pz":[2,1],"nx":[5,4],"ny":[9,2],"nz":[1,-1]},{"size":4,"px":[15,3,5,0],"py":[12,4,2,3],"pz":[0,-1,-1,-1],"nx":[13,7,5,7],"ny":[12,6,0,7],"nz":[0,1,2,1]},{"size":4,"px":[2,3,16,17],"py":[3,4,6,6],"pz":[2,1,0,0],"nx":[16,16,8,16],"ny":[8,3,10,13],"nz":[0,-1,-1,-1]},{"size":2,"px":[16,8],"py":[1,4],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[9,14],"py":[6,2],"pz":[1,-1],"nx":[8,8],"ny":[6,4],"nz":[1,1]},{"size":2,"px":[8,4],"py":[10,4],"pz":[1,2],"nx":[10,0],"ny":[5,7],"nz":[1,-1]},{"size":2,"px":[9,10],"py":[4,4],"pz":[0,0],"nx":[9,7],"ny":[3,5],"nz":[0,-1]},{"size":5,"px":[11,10,13,6,12],"py":[2,2,2,1,2],"pz":[0,0,0,1,0],"nx":[4,18,18,13,13],"ny":[2,18,19,7,7],"nz":[2,0,0,0,-1]},{"size":4,"px":[13,13,13,2],"py":[13,12,11,3],"pz":[0,0,0,-1],"nx":[4,6,8,11],"ny":[2,2,4,4],"nz":[2,1,1,0]},{"size":2,"px":[4,7],"py":[6,13],"pz":[1,0],"nx":[8,10],"ny":[4,22],"nz":[1,-1]},{"size":2,"px":[0,7],"py":[4,17],"pz":[1,-1],"nx":[0,1],"ny":[5,21],"nz":[2,0]},{"size":2,"px":[12,13],"py":[22,22],"pz":[0,0],"nx":[2,2],"ny":[13,13],"nz":[0,-1]},{"size":3,"px":[4,4,3],"py":[22,23,19],"pz":[0,0,0],"nx":[8,12,3],"ny":[22,15,2],"nz":[0,-1,-1]},{"size":2,"px":[10,12],"py":[3,13],"pz":[0,-1],"nx":[15,2],"ny":[10,2],"nz":[0,2]},{"size":2,"px":[1,1],"py":[3,3],"pz":[2,-1],"nx":[8,4],"ny":[0,0],"nz":[1,2]},{"size":2,"px":[6,12],"py":[6,18],"pz":[1,0],"nx":[12,19],"ny":[17,16],"nz":[0,-1]},{"size":2,"px":[10,5],"py":[2,1],"pz":[0,1],"nx":[5,4],"ny":[4,17],"nz":[0,-1]},{"size":3,"px":[3,12,11],"py":[5,23,23],"pz":[2,0,0],"nx":[12,4,4],"ny":[21,17,1],"nz":[0,-1,-1]},{"size":2,"px":[12,0],"py":[21,5],"pz":[0,-1],"nx":[0,0],"ny":[7,9],"nz":[1,1]},{"size":2,"px":[17,17],"py":[12,11],"pz":[0,0],"nx":[8,11],"ny":[4,11],"nz":[1,-1]},{"size":2,"px":[11,0],"py":[22,1],"pz":[0,-1],"nx":[4,6],"ny":[1,0],"nz":[1,1]},{"size":2,"px":[11,11],"py":[9,5],"pz":[1,1],"nx":[23,11],"ny":[23,20],"nz":[0,-1]},{"size":5,"px":[4,12,11,9,8],"py":[0,1,1,0,1],"pz":[1,0,0,0,0],"nx":[4,17,8,7,7],"ny":[2,13,4,4,4],"nz":[2,0,1,1,-1]},{"size":2,"px":[11,13],"py":[12,12],"pz":[0,-1],"nx":[1,1],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[23,4],"py":[23,2],"pz":[0,-1],"nx":[5,2],"ny":[23,6],"nz":[0,1]},{"size":3,"px":[8,16,0],"py":[5,15,6],"pz":[1,-1,-1],"nx":[23,23,11],"ny":[18,17,8],"nz":[0,0,1]},{"size":2,"px":[1,16],"py":[4,15],"pz":[2,-1],"nx":[2,2],"ny":[3,2],"nz":[2,2]},{"size":2,"px":[3,8],"py":[7,9],"pz":[1,-1],"nx":[4,2],"ny":[10,5],"nz":[1,2]},{"size":3,"px":[22,1,9],"py":[23,2,3],"pz":[0,-1,-1],"nx":[2,2,5],"ny":[5,4,19],"nz":[2,2,0]},{"size":2,"px":[2,20],"py":[5,15],"pz":[1,-1],"nx":[2,1],"ny":[1,2],"nz":[2,2]},{"size":2,"px":[4,8],"py":[1,19],"pz":[1,-1],"nx":[2,2],"ny":[5,4],"nz":[2,2]},{"size":2,"px":[9,10],"py":[21,0],"pz":[0,-1],"nx":[6,5],"ny":[1,1],"nz":[1,1]},{"size":2,"px":[4,8],"py":[3,6],"pz":[2,1],"nx":[9,2],"ny":[4,1],"nz":[1,-1]},{"size":3,"px":[17,3,10],"py":[8,0,2],"pz":[0,2,0],"nx":[13,2,6],"ny":[15,5,1],"nz":[0,-1,-1]},{"size":2,"px":[9,6],"py":[20,21],"pz":[0,-1],"nx":[4,2],"ny":[10,5],"nz":[1,2]},{"size":2,"px":[3,7],"py":[0,1],"pz":[2,1],"nx":[7,20],"ny":[1,19],"nz":[0,-1]},{"size":2,"px":[4,5],"py":[0,1],"pz":[1,0],"nx":[3,2],"ny":[4,2],"nz":[0,-1]},{"size":2,"px":[2,7],"py":[4,19],"pz":[2,0],"nx":[5,2],"ny":[10,2],"nz":[1,-1]},{"size":5,"px":[3,3,4,7,7],"py":[1,0,0,0,1],"pz":[1,1,1,0,0],"nx":[5,4,10,8,8],"ny":[3,3,5,4,4],"nz":[1,1,0,1,-1]},{"size":2,"px":[1,5],"py":[0,3],"pz":[1,-1],"nx":[1,0],"ny":[0,1],"nz":[0,1]},{"size":2,"px":[10,0],"py":[5,5],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[0,9],"py":[0,4],"pz":[2,-1],"nx":[13,10],"ny":[0,0],"nz":[0,0]},{"size":2,"px":[13,4],"py":[14,5],"pz":[0,-1],"nx":[4,2],"ny":[0,0],"nz":[0,1]},{"size":2,"px":[17,4],"py":[13,3],"pz":[0,-1],"nx":[4,2],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[1,0],"py":[6,2],"pz":[1,-1],"nx":[1,6],"ny":[2,12],"nz":[2,0]},{"size":2,"px":[12,4],"py":[6,0],"pz":[0,-1],"nx":[3,3],"ny":[8,9],"nz":[1,1]},{"size":2,"px":[1,5],"py":[1,5],"pz":[1,-1],"nx":[17,17],"ny":[13,7],"nz":[0,0]},{"size":2,"px":[7,3],"py":[12,6],"pz":[0,1],"nx":[3,4],"ny":[4,11],"nz":[1,-1]},{"size":2,"px":[6,17],"py":[2,8],"pz":[1,0],"nx":[3,3],"ny":[1,2],"nz":[1,-1]},{"size":3,"px":[13,6,6],"py":[22,11,10],"pz":[0,1,1],"nx":[13,12,11],"ny":[20,20,20],"nz":[0,0,0]},{"size":2,"px":[4,2],"py":[6,3],"pz":[1,2],"nx":[3,12],"ny":[4,20],"nz":[1,-1]},{"size":2,"px":[5,2],"py":[1,1],"pz":[1,-1],"nx":[13,6],"ny":[0,0],"nz":[0,1]},{"size":2,"px":[2,8],"py":[3,9],"pz":[2,0],"nx":[8,16],"ny":[5,17],"nz":[0,-1]},{"size":2,"px":[16,15],"py":[1,1],"pz":[0,0],"nx":[7,11],"ny":[8,0],"nz":[1,-1]},{"size":2,"px":[11,18],"py":[21,23],"pz":[0,-1],"nx":[1,1],"ny":[4,3],"nz":[1,2]},{"size":2,"px":[1,5],"py":[0,2],"pz":[1,-1],"nx":[15,11],"ny":[8,7],"nz":[0,0]},{"size":2,"px":[5,4],"py":[7,8],"pz":[1,-1],"nx":[9,10],"ny":[13,11],"nz":[0,0]},{"size":2,"px":[7,4],"py":[10,4],"pz":[1,2],"nx":[22,4],"ny":[0,2],"nz":[0,-1]},{"size":2,"px":[11,3],"py":[3,1],"pz":[0,2],"nx":[8,0],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[5,21],"py":[11,22],"pz":[0,-1],"nx":[10,11],"ny":[11,9],"nz":[0,0]},{"size":2,"px":[5,5],"py":[0,1],"pz":[2,2],"nx":[2,21],"ny":[6,14],"nz":[0,-1]},{"size":3,"px":[10,10,1],"py":[11,0,5],"pz":[0,-1,-1],"nx":[6,12,5],"ny":[2,5,2],"nz":[1,0,1]},{"size":2,"px":[9,10],"py":[5,6],"pz":[0,0],"nx":[12,19],"ny":[23,5],"nz":[0,-1]},{"size":2,"px":[11,5],"py":[9,6],"pz":[0,1],"nx":[21,0],"ny":[23,0],"nz":[0,-1]},{"size":2,"px":[13,12],"py":[19,15],"pz":[0,0],"nx":[13,0],"ny":[17,0],"nz":[0,-1]},{"size":2,"px":[14,0],"py":[17,3],"pz":[0,-1],"nx":[7,16],"ny":[8,19],"nz":[1,0]},{"size":2,"px":[3,6],"py":[2,4],"pz":[2,1],"nx":[8,1],"ny":[4,4],"nz":[1,-1]},{"size":2,"px":[13,10],"py":[23,20],"pz":[0,-1],"nx":[4,7],"ny":[5,10],"nz":[2,1]},{"size":2,"px":[16,9],"py":[22,5],"pz":[0,-1],"nx":[4,2],"ny":[10,3],"nz":[1,2]},{"size":4,"px":[3,1,1,5],"py":[4,2,1,2],"pz":[0,2,2,1],"nx":[13,5,8,0],"ny":[22,2,9,2],"nz":[0,-1,-1,-1]},{"size":2,"px":[9,9],"py":[0,0],"pz":[1,-1],"nx":[19,20],"ny":[1,2],"nz":[0,0]},{"size":2,"px":[7,22],"py":[6,8],"pz":[1,0],"nx":[4,4],"ny":[2,4],"nz":[2,-1]},{"size":2,"px":[3,6],"py":[4,4],"pz":[2,1],"nx":[10,20],"ny":[10,6],"nz":[0,-1]},{"size":2,"px":[6,12],"py":[6,15],"pz":[1,-1],"nx":[0,0],"ny":[2,5],"nz":[2,1]},{"size":2,"px":[2,7],"py":[4,10],"pz":[2,-1],"nx":[3,6],"ny":[4,8],"nz":[2,1]},{"size":3,"px":[11,11,4],"py":[0,5,7],"pz":[1,-1,-1],"nx":[6,12,12],"ny":[1,1,2],"nz":[1,0,0]},{"size":2,"px":[11,17],"py":[4,18],"pz":[0,-1],"nx":[8,2],"ny":[10,2],"nz":[0,2]},{"size":2,"px":[17,17],"py":[10,18],"pz":[0,-1],"nx":[8,8],"ny":[2,3],"nz":[1,1]},{"size":2,"px":[9,9],"py":[7,7],"pz":[1,-1],"nx":[7,4],"ny":[6,3],"nz":[1,2]},{"size":2,"px":[18,21],"py":[0,0],"pz":[0,-1],"nx":[11,6],"ny":[5,3],"nz":[0,1]},{"size":2,"px":[5,2],"py":[8,4],"pz":[0,2],"nx":[5,8],"ny":[9,16],"nz":[0,-1]},{"size":2,"px":[12,2],"py":[5,4],"pz":[0,-1],"nx":[4,15],"ny":[4,8],"nz":[1,0]},{"size":2,"px":[1,1],"py":[4,6],"pz":[1,1],"nx":[11,3],"ny":[7,9],"nz":[0,-1]},{"size":2,"px":[2,1],"py":[3,3],"pz":[2,2],"nx":[2,2],"ny":[15,16],"nz":[0,0]},{"size":2,"px":[17,18],"py":[5,5],"pz":[0,0],"nx":[9,21],"ny":[2,10],"nz":[1,-1]},{"size":2,"px":[6,3],"py":[14,7],"pz":[0,1],"nx":[3,4],"ny":[4,5],"nz":[1,-1]},{"size":2,"px":[0,3],"py":[3,1],"pz":[1,-1],"nx":[19,10],"ny":[12,4],"nz":[0,1]},{"size":2,"px":[6,16],"py":[3,8],"pz":[1,0],"nx":[8,10],"ny":[20,4],"nz":[0,-1]},{"size":3,"px":[5,5,2],"py":[21,8,4],"pz":[0,1,2],"nx":[10,6,3],"ny":[15,2,1],"nz":[0,-1,-1]},{"size":2,"px":[11,10],"py":[10,12],"pz":[0,0],"nx":[11,11],"ny":[2,1],"nz":[1,-1]},{"size":2,"px":[10,10],"py":[3,2],"pz":[1,1],"nx":[8,11],"ny":[3,5],"nz":[1,-1]},{"size":2,"px":[13,3],"py":[5,8],"pz":[0,-1],"nx":[12,3],"ny":[3,1],"nz":[0,2]},{"size":2,"px":[13,7],"py":[2,1],"pz":[0,1],"nx":[5,5],"ny":[1,1],"nz":[0,-1]},{"size":2,"px":[11,10],"py":[10,8],"pz":[0,-1],"nx":[14,16],"ny":[10,15],"nz":[0,0]},{"size":2,"px":[2,10],"py":[7,8],"pz":[1,-1],"nx":[2,6],"ny":[5,6],"nz":[2,1]},{"size":2,"px":[10,10],"py":[1,8],"pz":[0,-1],"nx":[2,2],"ny":[3,2],"nz":[2,2]},{"size":2,"px":[4,0],"py":[5,2],"pz":[1,-1],"nx":[1,2],"ny":[2,3],"nz":[2,1]},{"size":2,"px":[1,12],"py":[1,9],"pz":[2,-1],"nx":[16,17],"ny":[3,3],"nz":[0,0]},{"size":2,"px":[12,6],"py":[5,8],"pz":[0,-1],"nx":[3,4],"ny":[7,4],"nz":[1,1]},{"size":2,"px":[14,3],"py":[11,5],"pz":[0,-1],"nx":[11,4],"ny":[0,0],"nz":[0,1]},{"size":2,"px":[6,10],"py":[6,6],"pz":[1,-1],"nx":[0,0],"ny":[1,0],"nz":[2,2]},{"size":2,"px":[3,7],"py":[0,7],"pz":[1,-1],"nx":[15,13],"ny":[8,4],"nz":[0,0]},{"size":2,"px":[18,1],"py":[15,0],"pz":[0,-1],"nx":[18,18],"ny":[18,17],"nz":[0,0]},{"size":2,"px":[5,2],"py":[4,4],"pz":[0,-1],"nx":[4,18],"ny":[4,15],"nz":[1,0]},{"size":3,"px":[3,14,13],"py":[2,7,8],"pz":[2,0,0],"nx":[10,0,2],"ny":[8,3,2],"nz":[0,-1,-1]},{"size":2,"px":[16,0],"py":[14,3],"pz":[0,-1],"nx":[18,3],"ny":[12,5],"nz":[0,2]},{"size":2,"px":[5,3],"py":[8,3],"pz":[1,2],"nx":[13,4],"ny":[10,4],"nz":[0,-1]},{"size":2,"px":[3,6],"py":[1,2],"pz":[2,1],"nx":[8,1],"ny":[4,20],"nz":[1,-1]},{"size":2,"px":[10,10],"py":[8,3],"pz":[1,-1],"nx":[12,7],"ny":[2,1],"nz":[0,1]},{"size":2,"px":[17,3],"py":[9,2],"pz":[0,2],"nx":[7,6],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[12,1],"py":[2,1],"pz":[0,-1],"nx":[4,4],"ny":[2,3],"nz":[2,2]},{"size":2,"px":[22,5],"py":[15,3],"pz":[0,2],"nx":[16,17],"ny":[14,2],"nz":[0,-1]},{"size":2,"px":[8,11],"py":[19,13],"pz":[0,-1],"nx":[0,0],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[8,11],"py":[8,1],"pz":[1,-1],"nx":[3,3],"ny":[2,5],"nz":[1,2]},{"size":3,"px":[3,8,0],"py":[7,7,5],"pz":[1,-1,-1],"nx":[11,5,1],"ny":[11,7,5],"nz":[0,1,1]},{"size":2,"px":[12,6],"py":[12,6],"pz":[0,1],"nx":[9,0],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[16,12],"py":[7,1],"pz":[0,-1],"nx":[16,7],"ny":[6,4],"nz":[0,1]},{"size":2,"px":[13,5],"py":[14,0],"pz":[0,-1],"nx":[13,10],"ny":[0,0],"nz":[0,0]},{"size":5,"px":[11,12,13,12,7],"py":[0,1,0,0,0],"pz":[0,0,0,0,1],"nx":[13,16,14,4,4],"ny":[18,23,18,5,5],"nz":[0,0,0,2,-1]},{"size":2,"px":[14,5],"py":[12,4],"pz":[0,-1],"nx":[7,7],"ny":[8,2],"nz":[1,1]},{"size":2,"px":[19,3],"py":[2,5],"pz":[0,-1],"nx":[11,23],"ny":[7,13],"nz":[1,0]},{"size":2,"px":[0,0],"py":[19,20],"pz":[0,0],"nx":[9,4],"ny":[5,2],"nz":[0,-1]},{"size":2,"px":[15,4],"py":[12,3],"pz":[0,2],"nx":[9,5],"ny":[4,5],"nz":[1,-1]},{"size":4,"px":[8,0,1,21],"py":[6,0,7,16],"pz":[1,-1,-1,-1],"nx":[11,6,11,5],"ny":[8,6,4,3],"nz":[1,1,1,2]},{"size":2,"px":[11,11],"py":[7,5],"pz":[0,-1],"nx":[9,10],"ny":[6,7],"nz":[0,0]},{"size":2,"px":[2,4],"py":[1,2],"pz":[2,1],"nx":[16,6],"ny":[0,1],"nz":[0,-1]},{"size":2,"px":[0,0],"py":[5,3],"pz":[1,2],"nx":[1,21],"ny":[23,8],"nz":[0,-1]},{"size":2,"px":[10,0],"py":[7,0],"pz":[0,-1],"nx":[4,13],"ny":[4,10],"nz":[1,0]},{"size":2,"px":[11,4],"py":[0,4],"pz":[1,-1],"nx":[4,2],"ny":[16,8],"nz":[0,1]},{"size":2,"px":[5,3],"py":[12,6],"pz":[0,1],"nx":[3,3],"ny":[4,2],"nz":[1,-1]},{"size":2,"px":[10,0],"py":[19,11],"pz":[0,-1],"nx":[9,5],"ny":[21,9],"nz":[0,1]},{"size":2,"px":[0,0],"py":[17,9],"pz":[0,1],"nx":[0,5],"ny":[0,9],"nz":[2,-1]},{"size":2,"px":[4,5],"py":[2,4],"pz":[0,-1],"nx":[4,4],"ny":[5,6],"nz":[1,1]},{"size":2,"px":[8,4],"py":[1,0],"pz":[1,2],"nx":[4,3],"ny":[3,6],"nz":[0,-1]},{"size":2,"px":[11,0],"py":[7,2],"pz":[1,-1],"nx":[5,5],"ny":[1,0],"nz":[2,2]},{"size":2,"px":[13,0],"py":[17,2],"pz":[0,-1],"nx":[3,6],"ny":[5,8],"nz":[2,1]},{"size":2,"px":[2,1],"py":[0,5],"pz":[2,-1],"nx":[4,9],"ny":[2,7],"nz":[2,1]},{"size":2,"px":[12,5],"py":[13,8],"pz":[0,-1],"nx":[23,11],"ny":[13,7],"nz":[0,1]},{"size":2,"px":[0,0],"py":[0,2],"pz":[1,0],"nx":[3,6],"ny":[11,18],"nz":[0,-1]},{"size":2,"px":[4,3],"py":[6,5],"pz":[0,-1],"nx":[1,1],"ny":[1,3],"nz":[2,1]},{"size":4,"px":[3,6,3,6],"py":[3,6,2,5],"pz":[2,1,2,1],"nx":[0,4,1,1],"ny":[0,22,17,0],"nz":[0,-1,-1,-1]},{"size":2,"px":[8,4],"py":[6,3],"pz":[1,2],"nx":[9,15],"ny":[4,8],"nz":[1,-1]},{"size":2,"px":[8,18],"py":[7,8],"pz":[1,0],"nx":[8,5],"ny":[4,0],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[4,5],"pz":[1,-1],"nx":[5,6],"ny":[0,0],"nz":[1,1]},{"size":2,"px":[13,18],"py":[23,19],"pz":[0,0],"nx":[7,13],"ny":[10,20],"nz":[1,-1]},{"size":2,"px":[10,6],"py":[2,0],"pz":[0,1],"nx":[4,1],"ny":[5,1],"nz":[1,-1]},{"size":2,"px":[1,1],"py":[5,4],"pz":[2,2],"nx":[0,20],"ny":[4,4],"nz":[2,-1]},{"size":2,"px":[5,5],"py":[1,0],"pz":[2,2],"nx":[12,6],"ny":[18,11],"nz":[0,-1]},{"size":5,"px":[2,1,3,1,5],"py":[3,3,7,4,9],"pz":[2,2,1,2,1],"nx":[9,3,8,16,10],"ny":[5,3,10,6,7],"nz":[1,-1,-1,-1,-1]},{"size":2,"px":[4,1],"py":[12,3],"pz":[0,-1],"nx":[10,1],"ny":[11,2],"nz":[0,2]},{"size":2,"px":[19,0],"py":[10,7],"pz":[0,-1],"nx":[14,7],"ny":[6,3],"nz":[0,1]},{"size":2,"px":[7,4],"py":[2,1],"pz":[1,2],"nx":[6,0],"ny":[2,18],"nz":[0,-1]},{"size":2,"px":[14,8],"py":[3,0],"pz":[0,1],"nx":[17,1],"ny":[1,4],"nz":[0,-1]},{"size":2,"px":[18,19],"py":[1,17],"pz":[0,-1],"nx":[5,11],"ny":[2,5],"nz":[2,1]},{"size":5,"px":[12,12,12,6,12],"py":[10,11,12,6,9],"pz":[0,0,0,1,0],"nx":[13,3,12,6,6],"ny":[4,1,4,2,2],"nz":[0,2,0,1,-1]},{"size":2,"px":[11,10],"py":[3,3],"pz":[0,0],"nx":[4,9],"ny":[4,17],"nz":[1,-1]},{"size":2,"px":[11,0],"py":[13,5],"pz":[0,2],"nx":[8,18],"ny":[15,15],"nz":[0,-1]},{"size":2,"px":[3,4],"py":[6,5],"pz":[1,1],"nx":[0,0],"ny":[9,4],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[1,0],"pz":[2,2],"nx":[2,15],"ny":[2,1],"nz":[2,-1]},{"size":3,"px":[2,4,2],"py":[4,9,5],"pz":[2,1,2],"nx":[2,5,14],"ny":[0,1,4],"nz":[0,-1,-1]},{"size":2,"px":[11,12],"py":[20,20],"pz":[0,0],"nx":[6,10],"ny":[9,19],"nz":[1,-1]},{"size":2,"px":[7,0],"py":[16,8],"pz":[0,-1],"nx":[2,3],"ny":[2,4],"nz":[2,1]},{"size":5,"px":[16,17,15,16,15],"py":[1,1,1,0,0],"pz":[0,0,0,0,0],"nx":[8,8,4,12,12],"ny":[8,7,2,23,23],"nz":[1,1,2,0,-1]},{"size":2,"px":[2,4],"py":[6,12],"pz":[1,-1],"nx":[8,13],"ny":[1,1],"nz":[1,0]},{"size":2,"px":[9,2],"py":[3,2],"pz":[0,-1],"nx":[3,4],"ny":[6,5],"nz":[1,1]},{"size":2,"px":[10,8],"py":[6,1],"pz":[1,-1],"nx":[11,8],"ny":[2,2],"nz":[0,0]},{"size":2,"px":[9,3],"py":[7,0],"pz":[1,-1],"nx":[19,19],"ny":[18,16],"nz":[0,0]},{"size":2,"px":[3,2],"py":[1,1],"pz":[2,2],"nx":[22,11],"ny":[4,0],"nz":[0,-1]},{"size":2,"px":[10,10],"py":[9,8],"pz":[1,1],"nx":[4,4],"ny":[10,2],"nz":[1,-1]},{"size":2,"px":[0,1],"py":[0,5],"pz":[0,-1],"nx":[10,8],"ny":[2,2],"nz":[0,0]},{"size":2,"px":[3,3],"py":[8,7],"pz":[1,1],"nx":[8,2],"ny":[8,3],"nz":[0,-1]},{"size":2,"px":[13,5],"py":[21,3],"pz":[0,-1],"nx":[13,3],"ny":[20,5],"nz":[0,2]},{"size":2,"px":[12,5],"py":[11,2],"pz":[0,-1],"nx":[1,0],"ny":[19,9],"nz":[0,1]},{"size":2,"px":[7,10],"py":[9,10],"pz":[1,1],"nx":[8,4],"ny":[10,2],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[5,9],"pz":[2,1],"nx":[2,11],"ny":[9,19],"nz":[1,-1]},{"size":2,"px":[3,5],"py":[1,2],"pz":[2,1],"nx":[8,23],"ny":[4,9],"nz":[1,-1]},{"size":2,"px":[3,4],"py":[2,4],"pz":[2,1],"nx":[5,9],"ny":[2,5],"nz":[2,-1]},{"size":2,"px":[11,11],"py":[2,3],"pz":[1,1],"nx":[19,9],"ny":[6,5],"nz":[0,-1]},{"size":2,"px":[9,4],"py":[5,10],"pz":[1,-1],"nx":[10,22],"ny":[0,16],"nz":[1,0]},{"size":3,"px":[19,9,19],"py":[3,1,2],"pz":[0,1,0],"nx":[6,3,6],"ny":[10,3,0],"nz":[1,-1,-1]},{"size":2,"px":[8,3],"py":[10,3],"pz":[1,2],"nx":[23,14],"ny":[3,18],"nz":[0,-1]},{"size":2,"px":[11,11],"py":[19,0],"pz":[0,-1],"nx":[4,16],"ny":[4,11],"nz":[1,0]},{"size":2,"px":[22,23],"py":[3,22],"pz":[0,-1],"nx":[9,3],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[7,2],"py":[12,4],"pz":[0,-1],"nx":[8,4],"ny":[10,5],"nz":[0,1]},{"size":2,"px":[12,13],"py":[5,13],"pz":[0,-1],"nx":[11,3],"ny":[2,0],"nz":[0,2]},{"size":2,"px":[3,17],"py":[0,16],"pz":[1,-1],"nx":[12,12],"ny":[5,6],"nz":[0,0]},{"size":2,"px":[4,3],"py":[1,0],"pz":[2,2],"nx":[4,3],"ny":[0,3],"nz":[0,-1]},{"size":2,"px":[10,3],"py":[12,0],"pz":[0,-1],"nx":[12,12],"ny":[13,12],"nz":[0,0]},{"size":2,"px":[13,4],"py":[11,14],"pz":[0,-1],"nx":[0,0],"ny":[4,6],"nz":[1,0]},{"size":2,"px":[8,7],"py":[7,8],"pz":[1,1],"nx":[3,0],"ny":[5,21],"nz":[2,-1]},{"size":2,"px":[1,3],"py":[4,14],"pz":[2,0],"nx":[8,8],"ny":[7,7],"nz":[1,-1]},{"size":2,"px":[13,11],"py":[20,7],"pz":[0,-1],"nx":[21,21],"ny":[20,18],"nz":[0,0]},{"size":2,"px":[2,1],"py":[11,0],"pz":[0,-1],"nx":[2,2],"ny":[15,14],"nz":[0,0]},{"size":2,"px":[10,1],"py":[8,0],"pz":[1,-1],"nx":[8,4],"ny":[7,4],"nz":[1,2]},{"size":2,"px":[17,6],"py":[13,1],"pz":[0,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[7,15],"py":[1,3],"pz":[1,0],"nx":[15,5],"ny":[1,8],"nz":[0,-1]},{"size":2,"px":[16,1],"py":[20,10],"pz":[0,-1],"nx":[6,8],"ny":[11,10],"nz":[1,1]},{"size":2,"px":[7,14],"py":[0,0],"pz":[1,0],"nx":[7,8],"ny":[7,3],"nz":[1,-1]},{"size":2,"px":[12,5],"py":[17,4],"pz":[0,-1],"nx":[12,5],"ny":[16,10],"nz":[0,1]},{"size":2,"px":[13,3],"py":[15,0],"pz":[0,-1],"nx":[12,7],"ny":[17,8],"nz":[0,1]},{"size":2,"px":[7,1],"py":[14,1],"pz":[0,-1],"nx":[4,6],"ny":[6,12],"nz":[1,0]},{"size":2,"px":[8,7],"py":[0,0],"pz":[0,0],"nx":[6,20],"ny":[5,5],"nz":[0,-1]},{"size":2,"px":[10,2],"py":[22,5],"pz":[0,-1],"nx":[4,8],"ny":[4,9],"nz":[2,1]},{"size":4,"px":[8,2,2,9],"py":[6,5,3,11],"pz":[1,-1,-1,-1],"nx":[2,7,4,3],"ny":[2,1,0,2],"nz":[2,0,1,2]},{"size":2,"px":[12,6],"py":[12,6],"pz":[0,1],"nx":[8,2],"ny":[4,1],"nz":[1,-1]},{"size":2,"px":[13,11],"py":[19,8],"pz":[0,-1],"nx":[13,13],"ny":[20,17],"nz":[0,0]},{"size":2,"px":[11,19],"py":[5,14],"pz":[0,-1],"nx":[3,4],"ny":[8,4],"nz":[1,1]},{"size":2,"px":[10,0],"py":[8,6],"pz":[1,-1],"nx":[21,21],"ny":[16,15],"nz":[0,0]},{"size":2,"px":[1,12],"py":[7,6],"pz":[1,-1],"nx":[2,7],"ny":[5,14],"nz":[2,0]},{"size":2,"px":[2,9],"py":[7,5],"pz":[1,-1],"nx":[2,5],"ny":[5,9],"nz":[2,1]},{"size":2,"px":[12,5],"py":[15,6],"pz":[0,-1],"nx":[3,12],"ny":[0,2],"nz":[2,0]},{"size":2,"px":[23,22],"py":[23,1],"pz":[0,-1],"nx":[0,0],"ny":[2,3],"nz":[2,2]},{"size":2,"px":[3,6],"py":[1,2],"pz":[2,1],"nx":[8,0],"ny":[4,3],"nz":[1,-1]},{"size":2,"px":[5,1],"py":[9,1],"pz":[0,-1],"nx":[4,2],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[0,1],"py":[0,0],"pz":[2,0],"nx":[2,3],"ny":[9,10],"nz":[0,-1]},{"size":2,"px":[6,0],"py":[16,14],"pz":[0,-1],"nx":[6,3],"ny":[23,14],"nz":[0,0]},{"size":2,"px":[3,3],"py":[2,3],"pz":[2,1],"nx":[13,3],"ny":[19,14],"nz":[0,-1]},{"size":2,"px":[11,5],"py":[8,18],"pz":[0,-1],"nx":[4,7],"ny":[1,2],"nz":[2,1]},{"size":2,"px":[4,4],"py":[5,6],"pz":[1,1],"nx":[2,2],"ny":[5,3],"nz":[2,-1]},{"size":2,"px":[7,3],"py":[13,7],"pz":[0,1],"nx":[4,3],"ny":[4,1],"nz":[1,-1]},{"size":2,"px":[0,0],"py":[5,6],"pz":[1,0],"nx":[2,1],"ny":[5,1],"nz":[1,-1]},{"size":2,"px":[7,14],"py":[3,5],"pz":[1,0],"nx":[5,0],"ny":[16,7],"nz":[0,-1]},{"size":2,"px":[11,2],"py":[18,5],"pz":[0,2],"nx":[11,4],"ny":[16,4],"nz":[0,-1]},{"size":2,"px":[6,16],"py":[19,20],"pz":[0,-1],"nx":[3,2],"ny":[10,5],"nz":[1,2]},{"size":2,"px":[5,3],"py":[3,1],"pz":[0,1],"nx":[1,3],"ny":[4,8],"nz":[0,-1]},{"size":2,"px":[12,6],"py":[13,6],"pz":[0,1],"nx":[10,1],"ny":[12,2],"nz":[0,-1]},{"size":2,"px":[8,3],"py":[6,2],"pz":[1,-1],"nx":[4,8],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[9,3],"py":[21,2],"pz":[0,-1],"nx":[8,4],"ny":[1,0],"nz":[1,2]},{"size":2,"px":[8,4],"py":[1,0],"pz":[1,-1],"nx":[8,6],"ny":[4,2],"nz":[1,1]},{"size":2,"px":[2,7],"py":[1,6],"pz":[2,-1],"nx":[7,9],"ny":[6,4],"nz":[1,1]},{"size":2,"px":[6,3],"py":[8,3],"pz":[1,2],"nx":[10,5],"ny":[19,11],"nz":[0,-1]},{"size":2,"px":[2,2],"py":[3,4],"pz":[2,2],"nx":[3,6],"ny":[4,6],"nz":[1,-1]},{"size":2,"px":[3,11],"py":[5,20],"pz":[2,0],"nx":[11,5],"ny":[21,8],"nz":[0,-1]},{"size":3,"px":[5,9,5],"py":[4,7,5],"pz":[2,0,2],"nx":[23,10,4],"ny":[23,3,22],"nz":[0,-1,-1]},{"size":4,"px":[11,9,7,1],"py":[13,8,11,10],"pz":[0,-1,-1,-1],"nx":[8,2,11,12],"ny":[4,2,4,4],"nz":[1,2,0,0]},{"size":2,"px":[0,0],"py":[7,6],"pz":[1,1],"nx":[0,4],"ny":[1,0],"nz":[2,-1]},{"size":2,"px":[19,20],"py":[0,1],"pz":[0,0],"nx":[21,1],"ny":[0,2],"nz":[0,-1]},{"size":2,"px":[8,5],"py":[11,0],"pz":[0,-1],"nx":[11,0],"ny":[12,1],"nz":[0,2]},{"size":2,"px":[11,11],"py":[1,1],"pz":[0,-1],"nx":[4,7],"ny":[5,4],"nz":[1,1]},{"size":2,"px":[5,12],"py":[4,23],"pz":[2,-1],"nx":[13,15],"ny":[5,4],"nz":[0,0]},{"size":2,"px":[12,20],"py":[4,16],"pz":[0,-1],"nx":[9,4],"ny":[2,1],"nz":[0,1]},{"size":2,"px":[12,13],"py":[2,2],"pz":[0,0],"nx":[4,16],"ny":[2,11],"nz":[2,0]},{"size":2,"px":[19,14],"py":[10,17],"pz":[0,-1],"nx":[3,8],"ny":[0,2],"nz":[2,0]},{"size":2,"px":[8,12],"py":[1,2],"pz":[1,0],"nx":[19,10],"ny":[3,1],"nz":[0,-1]},{"size":4,"px":[17,2,3,10],"py":[8,6,2,12],"pz":[0,1,2,0],"nx":[17,9,12,2],"ny":[9,22,13,5],"nz":[0,-1,-1,-1]},{"size":2,"px":[20,10],"py":[15,7],"pz":[0,1],"nx":[13,9],"ny":[7,3],"nz":[0,-1]},{"size":2,"px":[0,0],"py":[1,0],"pz":[2,2],"nx":[10,3],"ny":[9,2],"nz":[1,-1]},{"size":2,"px":[4,3],"py":[1,0],"pz":[2,2],"nx":[0,22],"ny":[14,6],"nz":[0,-1]},{"size":2,"px":[16,3],"py":[4,0],"pz":[0,2],"nx":[16,3],"ny":[2,0],"nz":[0,-1]},{"size":2,"px":[8,16],"py":[6,12],"pz":[1,0],"nx":[8,12],"ny":[4,7],"nz":[1,-1]},{"size":2,"px":[5,11],"py":[0,5],"pz":[2,1],"nx":[10,1],"ny":[5,5],"nz":[1,-1]},{"size":2,"px":[7,4],"py":[5,5],"pz":[0,-1],"nx":[3,6],"ny":[2,3],"nz":[1,0]},{"size":2,"px":[11,11],"py":[11,12],"pz":[0,0],"nx":[23,7],"ny":[20,2],"nz":[0,-1]},{"size":2,"px":[16,8],"py":[12,5],"pz":[0,1],"nx":[8,2],"ny":[2,1],"nz":[1,-1]},{"size":3,"px":[6,11,11],"py":[11,23,20],"pz":[1,0,0],"nx":[11,3,22],"ny":[21,3,16],"nz":[0,-1,-1]},{"size":2,"px":[17,15],"py":[3,2],"pz":[0,-1],"nx":[4,4],"ny":[3,2],"nz":[2,2]},{"size":2,"px":[21,21],"py":[11,10],"pz":[0,0],"nx":[11,3],"ny":[6,2],"nz":[1,-1]},{"size":2,"px":[23,21],"py":[22,10],"pz":[0,-1],"nx":[20,10],"ny":[18,10],"nz":[0,1]},{"size":2,"px":[4,2],"py":[6,3],"pz":[1,2],"nx":[3,2],"ny":[4,3],"nz":[1,-1]},{"size":2,"px":[16,0],"py":[18,11],"pz":[0,-1],"nx":[8,7],"ny":[4,4],"nz":[0,0]},{"size":2,"px":[6,21],"py":[3,16],"pz":[0,-1],"nx":[1,8],"ny":[2,14],"nz":[2,0]},{"size":2,"px":[8,1],"py":[3,0],"pz":[0,-1],"nx":[11,11],"ny":[2,1],"nz":[0,0]},{"size":3,"px":[11,11,11],"py":[9,10,8],"pz":[1,1,1],"nx":[23,1,0],"ny":[23,9,11],"nz":[0,-1,-1]},{"size":2,"px":[6,3],"py":[2,1],"pz":[1,2],"nx":[7,1],"ny":[8,2],"nz":[0,-1]},{"size":2,"px":[10,17],"py":[17,19],"pz":[0,-1],"nx":[10,4],"ny":[16,9],"nz":[0,1]},{"size":2,"px":[3,6],"py":[7,1],"pz":[1,-1],"nx":[11,0],"ny":[11,8],"nz":[0,1]},{"size":2,"px":[10,5],"py":[11,4],"pz":[1,2],"nx":[5,5],"ny":[0,0],"nz":[2,-1]},{"size":2,"px":[3,6],"py":[3,6],"pz":[2,1],"nx":[8,0],"ny":[4,16],"nz":[1,-1]},{"size":2,"px":[14,1],"py":[20,2],"pz":[0,-1],"nx":[7,7],"ny":[11,9],"nz":[1,1]},{"size":3,"px":[11,13,4],"py":[16,21,3],"pz":[0,0,2],"nx":[14,16,5],"ny":[20,14,9],"nz":[0,-1,-1]},{"size":2,"px":[7,0],"py":[1,1],"pz":[1,-1],"nx":[4,7],"ny":[2,4],"nz":[2,1]},{"size":2,"px":[23,11],"py":[9,4],"pz":[0,1],"nx":[11,3],"ny":[1,3],"nz":[0,-1]},{"size":2,"px":[11,13],"py":[23,23],"pz":[0,0],"nx":[13,13],"ny":[20,20],"nz":[0,-1]},{"size":2,"px":[10,8],"py":[5,11],"pz":[0,-1],"nx":[20,19],"ny":[18,20],"nz":[0,0]},{"size":2,"px":[19,5],"py":[22,4],"pz":[0,-1],"nx":[2,9],"ny":[3,17],"nz":[1,0]},{"size":2,"px":[15,2],"py":[13,7],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":2,"px":[14,13],"py":[17,2],"pz":[0,-1],"nx":[15,13],"ny":[19,15],"nz":[0,0]},{"size":2,"px":[12,23],"py":[8,22],"pz":[0,-1],"nx":[7,10],"ny":[5,9],"nz":[1,0]},{"size":2,"px":[2,6],"py":[21,10],"pz":[0,-1],"nx":[3,4],"ny":[3,3],"nz":[1,1]},{"size":2,"px":[15,11],"py":[5,0],"pz":[0,-1],"nx":[3,4],"ny":[17,16],"nz":[0,0]},{"size":2,"px":[3,1],"py":[18,8],"pz":[0,1],"nx":[14,4],"ny":[17,7],"nz":[0,-1]},{"size":2,"px":[15,3],"py":[18,3],"pz":[0,2],"nx":[1,22],"ny":[0,1],"nz":[0,-1]},{"size":2,"px":[13,3],"py":[9,3],"pz":[0,-1],"nx":[0,1],"ny":[9,20],"nz":[1,0]},{"size":2,"px":[1,1],"py":[1,0],"pz":[2,2],"nx":[9,23],"ny":[10,12],"nz":[1,-1]},{"size":4,"px":[9,0,9,1],"py":[8,0,0,10],"pz":[1,-1,-1,-1],"nx":[23,7,5,23],"ny":[20,7,5,19],"nz":[0,1,2,0]},{"size":2,"px":[18,18],"py":[12,12],"pz":[0,-1],"nx":[8,4],"ny":[4,2],"nz":[1,2]},{"size":3,"px":[0,4,1],"py":[3,5,3],"pz":[1,-1,-1],"nx":[16,11,8],"ny":[8,5,6],"nz":[0,0,0]},{"size":5,"px":[9,10,14,11,11],"py":[0,0,0,0,0],"pz":[0,0,0,0,-1],"nx":[8,3,4,6,2],"ny":[22,9,5,4,0],"nz":[0,1,0,0,2]},{"size":2,"px":[6,5],"py":[2,2],"pz":[1,1],"nx":[7,3],"ny":[8,7],"nz":[0,-1]},{"size":2,"px":[11,5],"py":[15,2],"pz":[0,-1],"nx":[3,10],"ny":[0,1],"nz":[2,0]},{"size":2,"px":[0,11],"py":[11,12],"pz":[1,-1],"nx":[22,22],"ny":[14,13],"nz":[0,0]},{"size":2,"px":[2,2],"py":[15,14],"pz":[0,0],"nx":[1,2],"ny":[11,8],"nz":[1,-1]},{"size":2,"px":[11,6],"py":[0,7],"pz":[1,-1],"nx":[19,5],"ny":[3,0],"nz":[0,2]},{"size":2,"px":[2,3],"py":[3,7],"pz":[2,1],"nx":[1,5],"ny":[5,0],"nz":[1,-1]},{"size":2,"px":[10,14],"py":[4,5],"pz":[0,-1],"nx":[4,18],"ny":[2,12],"nz":[2,0]},{"size":2,"px":[19,10],"py":[12,2],"pz":[0,-1],"nx":[13,4],"ny":[10,2],"nz":[0,2]},{"size":2,"px":[6,1],"py":[21,6],"pz":[0,-1],"nx":[6,5],"ny":[0,0],"nz":[1,1]}],"alpha":[-1.044179e+00,1.044179e+00,-6.003138e-01,6.003138e-01,-4.091282e-01,4.091282e-01,-4.590148e-01,4.590148e-01,-4.294004e-01,4.294004e-01,-3.360846e-01,3.360846e-01,-3.054186e-01,3.054186e-01,-2.901743e-01,2.901743e-01,-3.522417e-01,3.522417e-01,-3.195838e-01,3.195838e-01,-2.957309e-01,2.957309e-01,-2.876727e-01,2.876727e-01,-2.637460e-01,2.637460e-01,-2.607900e-01,2.607900e-01,-2.455714e-01,2.455714e-01,-2.749847e-01,2.749847e-01,-2.314217e-01,2.314217e-01,-2.540871e-01,2.540871e-01,-2.143416e-01,2.143416e-01,-2.565697e-01,2.565697e-01,-1.901272e-01,1.901272e-01,-2.259981e-01,2.259981e-01,-2.012333e-01,2.012333e-01,-2.448460e-01,2.448460e-01,-2.192845e-01,2.192845e-01,-2.005951e-01,2.005951e-01,-2.259000e-01,2.259000e-01,-1.955758e-01,1.955758e-01,-2.235332e-01,2.235332e-01,-1.704490e-01,1.704490e-01,-1.584628e-01,1.584628e-01,-2.167710e-01,2.167710e-01,-1.592909e-01,1.592909e-01,-1.967292e-01,1.967292e-01,-1.432268e-01,1.432268e-01,-2.039949e-01,2.039949e-01,-1.404068e-01,1.404068e-01,-1.788201e-01,1.788201e-01,-1.498714e-01,1.498714e-01,-1.282541e-01,1.282541e-01,-1.630182e-01,1.630182e-01,-1.398111e-01,1.398111e-01,-1.464143e-01,1.464143e-01,-1.281712e-01,1.281712e-01,-1.417014e-01,1.417014e-01,-1.779164e-01,1.779164e-01,-2.067174e-01,2.067174e-01,-1.344947e-01,1.344947e-01,-1.357351e-01,1.357351e-01,-1.683191e-01,1.683191e-01,-1.821768e-01,1.821768e-01,-2.158307e-01,2.158307e-01,-1.812857e-01,1.812857e-01,-1.635445e-01,1.635445e-01,-1.474934e-01,1.474934e-01,-1.771993e-01,1.771993e-01,-1.517620e-01,1.517620e-01,-1.283184e-01,1.283184e-01,-1.862675e-01,1.862675e-01,-1.420491e-01,1.420491e-01,-1.232165e-01,1.232165e-01,-1.472696e-01,1.472696e-01,-1.192156e-01,1.192156e-01,-1.602034e-01,1.602034e-01,-1.321473e-01,1.321473e-01,-1.358101e-01,1.358101e-01,-1.295821e-01,1.295821e-01,-1.289102e-01,1.289102e-01,-1.232520e-01,1.232520e-01,-1.332227e-01,1.332227e-01,-1.358887e-01,1.358887e-01,-1.179559e-01,1.179559e-01,-1.263694e-01,1.263694e-01,-1.444876e-01,1.444876e-01,-1.933141e-01,1.933141e-01,-1.917886e-01,1.917886e-01,-1.199760e-01,1.199760e-01,-1.359937e-01,1.359937e-01,-1.690073e-01,1.690073e-01,-1.894222e-01,1.894222e-01,-1.699422e-01,1.699422e-01,-1.340361e-01,1.340361e-01,-1.840622e-01,1.840622e-01,-1.277397e-01,1.277397e-01,-1.381610e-01,1.381610e-01,-1.282241e-01,1.282241e-01,-1.211334e-01,1.211334e-01,-1.264628e-01,1.264628e-01,-1.373010e-01,1.373010e-01,-1.363356e-01,1.363356e-01,-1.562568e-01,1.562568e-01,-1.268735e-01,1.268735e-01,-1.037859e-01,1.037859e-01,-1.394322e-01,1.394322e-01,-1.449225e-01,1.449225e-01,-1.109657e-01,1.109657e-01,-1.086931e-01,1.086931e-01,-1.379135e-01,1.379135e-01,-1.881974e-01,1.881974e-01,-1.304956e-01,1.304956e-01,-9.921777e-02,9.921777e-02,-1.398624e-01,1.398624e-01,-1.216469e-01,1.216469e-01,-1.272741e-01,1.272741e-01,-1.878236e-01,1.878236e-01,-1.336894e-01,1.336894e-01,-1.256289e-01,1.256289e-01,-1.247231e-01,1.247231e-01,-1.853400e-01,1.853400e-01,-1.087805e-01,1.087805e-01,-1.205676e-01,1.205676e-01,-1.023182e-01,1.023182e-01,-1.268422e-01,1.268422e-01,-1.422900e-01,1.422900e-01,-1.098174e-01,1.098174e-01,-1.317018e-01,1.317018e-01,-1.378142e-01,1.378142e-01,-1.274550e-01,1.274550e-01,-1.142944e-01,1.142944e-01,-1.713488e-01,1.713488e-01,-1.103035e-01,1.103035e-01,-1.045221e-01,1.045221e-01,-1.293015e-01,1.293015e-01,-9.763183e-02,9.763183e-02,-1.387213e-01,1.387213e-01,-9.031167e-02,9.031167e-02,-1.283052e-01,1.283052e-01,-1.133462e-01,1.133462e-01,-9.370681e-02,9.370681e-02,-1.079269e-01,1.079269e-01,-1.331913e-01,1.331913e-01,-8.969902e-02,8.969902e-02,-1.044560e-01,1.044560e-01,-9.387466e-02,9.387466e-02,-1.208988e-01,1.208988e-01,-1.252011e-01,1.252011e-01,-1.401277e-01,1.401277e-01,-1.461381e-01,1.461381e-01,-1.323763e-01,1.323763e-01,-9.923889e-02,9.923889e-02,-1.142899e-01,1.142899e-01,-9.110853e-02,9.110853e-02,-1.106607e-01,1.106607e-01,-1.253140e-01,1.253140e-01,-9.657895e-02,9.657895e-02,-1.030010e-01,1.030010e-01,-1.348857e-01,1.348857e-01,-1.237793e-01,1.237793e-01,-1.296943e-01,1.296943e-01,-1.323385e-01,1.323385e-01,-8.331554e-02,8.331554e-02,-8.417589e-02,8.417589e-02,-1.104431e-01,1.104431e-01,-1.170710e-01,1.170710e-01,-1.391725e-01,1.391725e-01,-1.485189e-01,1.485189e-01,-1.840393e-01,1.840393e-01,-1.238250e-01,1.238250e-01,-1.095287e-01,1.095287e-01,-1.177869e-01,1.177869e-01,-1.036409e-01,1.036409e-01,-9.802581e-02,9.802581e-02,-9.364054e-02,9.364054e-02,-9.936022e-02,9.936022e-02,-1.117201e-01,1.117201e-01,-1.081300e-01,1.081300e-01,-1.331861e-01,1.331861e-01,-1.192122e-01,1.192122e-01,-9.889761e-02,9.889761e-02,-1.173456e-01,1.173456e-01,-1.032917e-01,1.032917e-01,-9.268551e-02,9.268551e-02,-1.178563e-01,1.178563e-01,-1.215065e-01,1.215065e-01,-1.060437e-01,1.060437e-01,-1.010044e-01,1.010044e-01,-1.021683e-01,1.021683e-01,-9.974968e-02,9.974968e-02,-1.161528e-01,1.161528e-01,-8.686721e-02,8.686721e-02,-8.145259e-02,8.145259e-02,-9.937060e-02,9.937060e-02,-1.170885e-01,1.170885e-01,-7.693779e-02,7.693779e-02,-9.047233e-02,9.047233e-02,-9.168442e-02,9.168442e-02,-1.054105e-01,1.054105e-01,-9.036177e-02,9.036177e-02,-1.251949e-01,1.251949e-01,-9.523847e-02,9.523847e-02,-1.038930e-01,1.038930e-01,-1.433660e-01,1.433660e-01,-1.489830e-01,1.489830e-01,-8.393174e-02,8.393174e-02,-8.888026e-02,8.888026e-02,-9.347861e-02,9.347861e-02,-1.044838e-01,1.044838e-01,-1.102144e-01,1.102144e-01,-1.383415e-01,1.383415e-01,-1.466476e-01,1.466476e-01,-1.129741e-01,1.129741e-01,-1.310915e-01,1.310915e-01,-1.070648e-01,1.070648e-01,-7.559007e-02,7.559007e-02,-8.812082e-02,8.812082e-02,-1.234272e-01,1.234272e-01,-1.088022e-01,1.088022e-01,-8.388703e-02,8.388703e-02,-7.179593e-02,7.179593e-02,-1.008961e-01,1.008961e-01,-9.030070e-02,9.030070e-02,-8.581345e-02,8.581345e-02,-9.023431e-02,9.023431e-02,-9.807321e-02,9.807321e-02,-9.621402e-02,9.621402e-02,-1.730195e-01,1.730195e-01,-8.984631e-02,8.984631e-02,-9.556661e-02,9.556661e-02,-1.047576e-01,1.047576e-01,-7.854313e-02,7.854313e-02,-8.682118e-02,8.682118e-02,-1.159761e-01,1.159761e-01,-1.339540e-01,1.339540e-01,-1.003048e-01,1.003048e-01,-9.747544e-02,9.747544e-02,-9.501058e-02,9.501058e-02,-1.321566e-01,1.321566e-01,-9.194706e-02,9.194706e-02,-9.359276e-02,9.359276e-02,-1.015916e-01,1.015916e-01,-1.174192e-01,1.174192e-01,-1.039931e-01,1.039931e-01,-9.746733e-02,9.746733e-02,-1.286120e-01,1.286120e-01,-1.044899e-01,1.044899e-01,-1.066385e-01,1.066385e-01,-8.368626e-02,8.368626e-02,-1.271919e-01,1.271919e-01,-1.055946e-01,1.055946e-01,-8.272876e-02,8.272876e-02,-1.370564e-01,1.370564e-01,-8.539379e-02,8.539379e-02,-1.100343e-01,1.100343e-01,-8.102170e-02,8.102170e-02,-1.028728e-01,1.028728e-01,-1.305065e-01,1.305065e-01,-1.059506e-01,1.059506e-01,-1.264646e-01,1.264646e-01,-8.383843e-02,8.383843e-02,-9.357698e-02,9.357698e-02,-7.474400e-02,7.474400e-02,-7.814045e-02,7.814045e-02,-8.600970e-02,8.600970e-02,-1.206090e-01,1.206090e-01,-9.986512e-02,9.986512e-02,-8.516476e-02,8.516476e-02,-7.198783e-02,7.198783e-02,-7.838409e-02,7.838409e-02,-1.005142e-01,1.005142e-01,-9.951857e-02,9.951857e-02,-7.253998e-02,7.253998e-02,-9.913739e-02,9.913739e-02,-7.500360e-02,7.500360e-02,-9.258090e-02,9.258090e-02,-1.400287e-01,1.400287e-01,-1.044404e-01,1.044404e-01,-7.404339e-02,7.404339e-02,-7.256833e-02,7.256833e-02,-1.006995e-01,1.006995e-01,-1.426043e-01,1.426043e-01,-1.036529e-01,1.036529e-01,-1.208443e-01,1.208443e-01,-1.074245e-01,1.074245e-01,-1.141448e-01,1.141448e-01,-1.015809e-01,1.015809e-01,-1.028822e-01,1.028822e-01,-1.055682e-01,1.055682e-01,-9.468699e-02,9.468699e-02,-1.010098e-01,1.010098e-01,-1.205054e-01,1.205054e-01,-8.392956e-02,8.392956e-02,-8.052297e-02,8.052297e-02,-9.576507e-02,9.576507e-02,-9.515692e-02,9.515692e-02,-1.564745e-01,1.564745e-01,-7.357238e-02,7.357238e-02,-1.129262e-01,1.129262e-01,-1.013265e-01,1.013265e-01,-8.760761e-02,8.760761e-02,-8.714771e-02,8.714771e-02,-9.605039e-02,9.605039e-02,-9.064677e-02,9.064677e-02,-8.243857e-02,8.243857e-02,-8.495858e-02,8.495858e-02,-8.350249e-02,8.350249e-02,-7.423234e-02,7.423234e-02,-7.930799e-02,7.930799e-02,-6.620023e-02,6.620023e-02,-7.311919e-02,7.311919e-02,-1.237938e-01,1.237938e-01,-1.086814e-01,1.086814e-01,-6.379798e-02,6.379798e-02,-7.526021e-02,7.526021e-02,-8.297097e-02,8.297097e-02,-8.186337e-02,8.186337e-02,-7.627362e-02,7.627362e-02,-1.061638e-01,1.061638e-01,-8.328494e-02,8.328494e-02,-1.040895e-01,1.040895e-01,-7.649056e-02,7.649056e-02,-7.299058e-02,7.299058e-02,-9.195198e-02,9.195198e-02,-7.990880e-02,7.990880e-02,-7.429346e-02,7.429346e-02,-9.991702e-02,9.991702e-02,-9.755385e-02,9.755385e-02,-1.344138e-01,1.344138e-01,-1.707917e-01,1.707917e-01,-8.325450e-02,8.325450e-02,-8.137793e-02,8.137793e-02,-8.308659e-02,8.308659e-02,-7.440414e-02,7.440414e-02,-7.012744e-02,7.012744e-02,-8.122943e-02,8.122943e-02,-8.845462e-02,8.845462e-02,-8.803450e-02,8.803450e-02,-9.653392e-02,9.653392e-02,-8.795691e-02,8.795691e-02,-1.119045e-01,1.119045e-01,-1.068308e-01,1.068308e-01,-8.406359e-02,8.406359e-02,-1.220414e-01,1.220414e-01,-1.024235e-01,1.024235e-01,-1.252897e-01,1.252897e-01,-1.121234e-01,1.121234e-01,-9.054150e-02,9.054150e-02,-8.974435e-02,8.974435e-02,-1.351578e-01,1.351578e-01,-1.106442e-01,1.106442e-01,-8.093913e-02,8.093913e-02,-9.800762e-02,9.800762e-02,-7.012823e-02,7.012823e-02,-7.434949e-02,7.434949e-02,-8.684816e-02,8.684816e-02,-8.916388e-02,8.916388e-02,-8.773159e-02,8.773159e-02,-7.709608e-02,7.709608e-02,-7.230518e-02,7.230518e-02,-9.662156e-02,9.662156e-02,-7.957632e-02,7.957632e-02,-7.628441e-02,7.628441e-02,-8.050202e-02,8.050202e-02,-1.290593e-01,1.290593e-01,-9.246182e-02,9.246182e-02,-9.703662e-02,9.703662e-02,-7.866445e-02,7.866445e-02,-1.064783e-01,1.064783e-01,-1.012339e-01,1.012339e-01,-6.828389e-02,6.828389e-02,-1.005039e-01,1.005039e-01,-7.559687e-02,7.559687e-02,-6.359878e-02,6.359878e-02,-8.387002e-02,8.387002e-02,-7.851323e-02,7.851323e-02,-8.878569e-02,8.878569e-02,-7.767654e-02,7.767654e-02,-8.033338e-02,8.033338e-02,-9.142797e-02,9.142797e-02,-8.590585e-02,8.590585e-02,-1.052318e-01,1.052318e-01,-8.760062e-02,8.760062e-02,-9.222192e-02,9.222192e-02,-7.548828e-02,7.548828e-02,-8.003344e-02,8.003344e-02,-1.177076e-01,1.177076e-01,-1.064964e-01,1.064964e-01,-8.655553e-02,8.655553e-02,-9.418112e-02,9.418112e-02,-7.248163e-02,7.248163e-02,-7.120974e-02,7.120974e-02,-6.393114e-02,6.393114e-02,-7.997487e-02,7.997487e-02,-1.220941e-01,1.220941e-01,-9.892518e-02,9.892518e-02,-8.270271e-02,8.270271e-02,-1.069400e-01,1.069400e-01,-5.860771e-02,5.860771e-02,-9.126600e-02,9.126600e-02,-6.212559e-02,6.212559e-02,-9.397538e-02,9.397538e-02,-8.070447e-02,8.070447e-02,-8.415587e-02,8.415587e-02,-8.564455e-02,8.564455e-02,-7.791811e-02,7.791811e-02,-6.642259e-02,6.642259e-02,-8.266167e-02,8.266167e-02,-1.134986e-01,1.134986e-01,-1.045267e-01,1.045267e-01,-7.122085e-02,7.122085e-02,-7.979415e-02,7.979415e-02,-7.922347e-02,7.922347e-02,-9.003421e-02,9.003421e-02,-8.796449e-02,8.796449e-02,-7.933279e-02,7.933279e-02,-8.307947e-02,8.307947e-02,-8.946349e-02,8.946349e-02,-7.643384e-02,7.643384e-02,-7.818534e-02,7.818534e-02,-7.990991e-02,7.990991e-02,-9.885664e-02,9.885664e-02,-8.071329e-02,8.071329e-02,-6.952112e-02,6.952112e-02,-6.429706e-02,6.429706e-02,-6.307229e-02,6.307229e-02,-8.100137e-02,8.100137e-02,-7.693623e-02,7.693623e-02,-6.906625e-02,6.906625e-02,-7.390462e-02,7.390462e-02,-6.487217e-02,6.487217e-02,-1.233681e-01,1.233681e-01,-6.979273e-02,6.979273e-02,-8.358669e-02,8.358669e-02,-1.095420e-01,1.095420e-01,-8.519717e-02,8.519717e-02,-7.599857e-02,7.599857e-02,-6.042816e-02,6.042816e-02,-6.546304e-02,6.546304e-02,-1.016245e-01,1.016245e-01,-8.308787e-02,8.308787e-02,-7.385708e-02,7.385708e-02,-6.751630e-02,6.751630e-02,-9.036695e-02,9.036695e-02,-9.371335e-02,9.371335e-02,-1.116088e-01,1.116088e-01,-5.693741e-02,5.693741e-02,-6.383983e-02,6.383983e-02,-5.389843e-02,5.389843e-02,-8.383191e-02,8.383191e-02,-7.820822e-02,7.820822e-02,-7.067557e-02,7.067557e-02,-7.971948e-02,7.971948e-02,-7.360668e-02,7.360668e-02,-7.008027e-02,7.008027e-02,-8.013378e-02,8.013378e-02,-8.331605e-02,8.331605e-02,-7.145702e-02,7.145702e-02,-7.863940e-02,7.863940e-02,-6.992679e-02,6.992679e-02,-5.716495e-02,5.716495e-02,-5.306006e-02,5.306006e-02,-8.855639e-02,8.855639e-02,-7.656397e-02,7.656397e-02,-6.939272e-02,6.939272e-02,-7.523742e-02,7.523742e-02,-8.472299e-02,8.472299e-02,-8.114341e-02,8.114341e-02,-6.795517e-02,6.795517e-02,-7.890130e-02,7.890130e-02,-7.488741e-02,7.488741e-02,-9.281972e-02,9.281972e-02,-9.325498e-02,9.325498e-02,-1.401587e-01,1.401587e-01,-1.176284e-01,1.176284e-01,-8.867597e-02,8.867597e-02,-8.124232e-02,8.124232e-02,-9.441235e-02,9.441235e-02,-8.029452e-02,8.029452e-02,-8.581848e-02,8.581848e-02,-1.029819e-01,1.029819e-01,-9.569118e-02,9.569118e-02,-7.690893e-02,7.690893e-02,-9.018228e-02,9.018228e-02,-1.049209e-01,1.049209e-01,-8.969413e-02,8.969413e-02,-8.651891e-02,8.651891e-02,-8.613331e-02,8.613331e-02,-7.120468e-02,7.120468e-02,-8.743959e-02,8.743959e-02,-7.607158e-02,7.607158e-02,-1.015547e-01,1.015547e-01,-8.090879e-02,8.090879e-02,-7.114079e-02,7.114079e-02,-8.744835e-02,8.744835e-02,-6.074904e-02,6.074904e-02,-6.919871e-02,6.919871e-02,-7.607774e-02,7.607774e-02,-9.444600e-02,9.444600e-02,-7.833429e-02,7.833429e-02,-6.817555e-02,6.817555e-02,-8.997390e-02,8.997390e-02,-9.845223e-02,9.845223e-02,-7.894180e-02,7.894180e-02,-7.921373e-02,7.921373e-02,-7.448032e-02,7.448032e-02,-1.178165e-01,1.178165e-01,-8.216686e-02,8.216686e-02,-8.103286e-02,8.103286e-02,-6.981470e-02,6.981470e-02,-8.709008e-02,8.709008e-02,-8.336259e-02,8.336259e-02,-6.213589e-02,6.213589e-02,-7.068045e-02,7.068045e-02,-6.915676e-02,6.915676e-02,-7.103416e-02,7.103416e-02,-6.523849e-02,6.523849e-02,-7.634760e-02,7.634760e-02,-7.263038e-02,7.263038e-02,-7.164396e-02,7.164396e-02,-8.745559e-02,8.745559e-02,-6.960181e-02,6.960181e-02,-8.500098e-02,8.500098e-02,-6.523260e-02,6.523260e-02,-7.319714e-02,7.319714e-02,-6.268125e-02,6.268125e-02,-7.083135e-02,7.083135e-02,-7.984517e-02,7.984517e-02,-1.256265e-01,1.256265e-01,-1.065412e-01,1.065412e-01,-8.524323e-02,8.524323e-02,-9.291364e-02,9.291364e-02,-7.936567e-02,7.936567e-02,-8.607723e-02,8.607723e-02,-7.583416e-02,7.583416e-02,-7.931928e-02,7.931928e-02,-7.408357e-02,7.408357e-02,-1.034404e-01,1.034404e-01,-1.012127e-01,1.012127e-01,-7.916689e-02,7.916689e-02,-8.753651e-02,8.753651e-02,-6.090366e-02,6.090366e-02,-7.500103e-02,7.500103e-02,-1.228709e-01,1.228709e-01,-6.318201e-02,6.318201e-02,-7.585420e-02,7.585420e-02,-7.089090e-02,7.089090e-02,-1.053542e-01,1.053542e-01,-8.549521e-02,8.549521e-02,-7.906308e-02,7.906308e-02,-6.338780e-02,6.338780e-02,-8.417910e-02,8.417910e-02,-7.115511e-02,7.115511e-02,-7.693949e-02,7.693949e-02,-7.446749e-02,7.446749e-02,-1.037929e-01,1.037929e-01,-7.991005e-02,7.991005e-02,-7.119439e-02,7.119439e-02,-7.071340e-02,7.071340e-02,-8.587362e-02,8.587362e-02,-7.001236e-02,7.001236e-02,-7.567115e-02,7.567115e-02,-7.118930e-02,7.118930e-02,-6.844895e-02,6.844895e-02,-1.035118e-01,1.035118e-01,-8.156618e-02,8.156618e-02,-7.449593e-02,7.449593e-02,-8.154360e-02,8.154360e-02,-9.110878e-02,9.110878e-02,-6.222534e-02,6.222534e-02,-1.033841e-01,1.033841e-01,-6.811687e-02,6.811687e-02,-6.828443e-02,6.828443e-02,-5.769408e-02,5.769408e-02,-5.917684e-02,5.917684e-02,-8.358868e-02,8.358868e-02]}]}; diff --git a/samples/js/demos/js/videopipe.js b/samples/js/demos/js/videopipe.js deleted file mode 100644 index da9e22502..000000000 --- a/samples/js/demos/js/videopipe.js +++ /dev/null @@ -1,68 +0,0 @@ -// -// A "videopipe" abstraction on top of WebRTC. -// -// The usage of this abstraction: -// var pipe = new VideoPipe(mediastream, handlerFunction); -// handlerFunction = function(mediastream) { -// do_something -// } -// pipe.close(); -// -// The VideoPipe will set up 2 PeerConnections, connect them to each -// other, and call HandlerFunction when the stream is available in the -// second PeerConnection. -// - -function errorHandler(context) { - return function(error) { - trace('Failure in ' + context + ': ' + error.toString); - } -} - -function successHandler(context) { - return function() { - trace('Success in ' + context); - } -} - -function noAction() { -} - - -function VideoPipe(stream, handler) { - var servers = null; - var pc1 = new RTCPeerConnection(servers); - var pc2 = new RTCPeerConnection(servers); - - pc1.addStream(stream); - pc1.onicecandidate = function(event) { - if (event.candidate) { - pc2.addIceCandidate(new RTCIceCandidate(event.candidate), - noAction, errorHandler('AddIceCandidate')); - } - } - pc2.onicecandidate = function(event) { - if (event.candidate) { - pc1.addIceCandidate(new RTCIceCandidate(event.candidate), - noAction, errorHandler('AddIceCandidate')); - } - } - pc2.onaddstream = function(e) { - handler(e.stream); - } - pc1.createOffer(function(desc) { - pc1.setLocalDescription(desc); - pc2.setRemoteDescription(desc); - pc2.createAnswer(function(desc2) { - pc2.setLocalDescription(desc2); - pc1.setRemoteDescription(desc2); - }, errorHandler('pc2.createAnswer')); - }, errorHandler('pc1.createOffer')); - this.pc1 = pc1; - this.pc2 = pc2; -} - -VideoPipe.prototype.close = function() { - this.pc1.close(); - this.pc2.close(); -} diff --git a/samples/js/demos/sounds/Shamisen-C4.wav b/samples/js/demos/sounds/Shamisen-C4.wav deleted file mode 100644 index 2f9075633..000000000 Binary files a/samples/js/demos/sounds/Shamisen-C4.wav and /dev/null differ diff --git a/talk/OWNERS b/talk/OWNERS index 364851bae..570953a68 100644 --- a/talk/OWNERS +++ b/talk/OWNERS @@ -11,3 +11,8 @@ tommi@webrtc.org wu@webrtc.org per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/talk/PRESUBMIT.py b/talk/PRESUBMIT.py index b855b9505..8e1a2e394 100644 --- a/talk/PRESUBMIT.py +++ b/talk/PRESUBMIT.py @@ -25,15 +25,7 @@ # List of files that should not be committed to DO_NOT_SUBMIT_FILES = [ - "talk/app/webrtc/mediaconstraintsinterface.h", - "talk/base/linux.cc", - "talk/base/linux.h", - "talk/base/linux_unittest.cc", - "talk/main.scons", - "talk/media/base/hybridvideoengine.cc", - "talk/media/base/mediaengine.cc", "talk/media/base/mutedvideocapturer.cc", - "talk/media/base/streamparams.h", "talk/media/base/videocapturer.cc", "talk/media/base/videocapturer.h", "talk/media/base/videocapturer_unittest.cc", @@ -49,9 +41,7 @@ "talk/media/webrtc/webrtcvoiceengine.cc", "talk/media/webrtc/webrtcvoiceengine.h", "talk/media/webrtc/webrtcvoiceengine_unittest.cc", - "talk/p2p/base/session.cc", - "talk/session/media/channel.cc", - "talk/session/media/mediasession_unittest.cc"] + "talk/session/media/channel.cc"] def _LicenseHeader(input_api): """Returns the license header regexp.""" @@ -59,6 +49,7 @@ def _LicenseHeader(input_api): current_year = int(input_api.time.strftime('%Y')) allowed_years = (str(s) for s in reversed(xrange(2004, current_year + 1))) years_re = '(' + '|'.join(allowed_years) + ')' + years_re = '%s(--%s)?' % (years_re, years_re) license_header = ( r'.*? libjingle\n' r'.*? Copyright %(year)s,? Google Inc\.\n' diff --git a/talk/app/webrtc/OWNERS b/talk/app/webrtc/OWNERS index 40c5bdc14..967930cf4 100644 --- a/talk/app/webrtc/OWNERS +++ b/talk/app/webrtc/OWNERS @@ -1,6 +1,7 @@ +glaznev@webrtc.org hellner@google.com juberti@google.com mallinath@google.com perkj@google.com ronghuawu@google.com -tommi@google.com \ No newline at end of file +tommi@google.com diff --git a/talk/app/webrtc/datachannel.cc b/talk/app/webrtc/datachannel.cc index 048e89a50..af4fb244f 100644 --- a/talk/app/webrtc/datachannel.cc +++ b/talk/app/webrtc/datachannel.cc @@ -35,13 +35,57 @@ namespace webrtc { -static size_t kMaxQueuedReceivedDataPackets = 100; -static size_t kMaxQueuedSendDataPackets = 100; +static size_t kMaxQueuedReceivedDataBytes = 16 * 1024 * 1024; +static size_t kMaxQueuedSendDataBytes = 16 * 1024 * 1024; enum { MSG_CHANNELREADY, }; +DataChannel::PacketQueue::PacketQueue() : byte_count_(0) {} + +DataChannel::PacketQueue::~PacketQueue() { + Clear(); +} + +bool DataChannel::PacketQueue::Empty() const { + return packets_.empty(); +} + +DataBuffer* DataChannel::PacketQueue::Front() { + return packets_.front(); +} + +void DataChannel::PacketQueue::Pop() { + if (packets_.empty()) { + return; + } + + byte_count_ -= packets_.front()->size(); + packets_.pop_front(); +} + +void DataChannel::PacketQueue::Push(DataBuffer* packet) { + byte_count_ += packet->size(); + packets_.push_back(packet); +} + +void DataChannel::PacketQueue::Clear() { + while (!packets_.empty()) { + delete packets_.front(); + packets_.pop_front(); + } + byte_count_ = 0; +} + +void DataChannel::PacketQueue::Swap(PacketQueue* other) { + size_t other_byte_count = other->byte_count_; + other->byte_count_ = byte_count_; + byte_count_ = other_byte_count; + + other->packets_.swap(packets_); +} + talk_base::scoped_refptr DataChannel::Create( DataChannelProviderInterface* provider, cricket::DataChannelType dct, @@ -114,11 +158,7 @@ bool DataChannel::Init(const InternalDataChannelInit& config) { return true; } -DataChannel::~DataChannel() { - ClearQueuedReceivedData(); - ClearQueuedSendData(); - ClearQueuedControlData(); -} +DataChannel::~DataChannel() {} void DataChannel::RegisterObserver(DataChannelObserver* observer) { observer_ = observer; @@ -139,13 +179,7 @@ bool DataChannel::reliable() const { } uint64 DataChannel::buffered_amount() const { - uint64 buffered_amount = 0; - for (std::deque::const_iterator it = queued_send_data_.begin(); - it != queued_send_data_.end(); - ++it) { - buffered_amount += (*it)->size(); - } - return buffered_amount; + return queued_send_data_.byte_count(); } void DataChannel::Close() { @@ -163,81 +197,23 @@ bool DataChannel::Send(const DataBuffer& buffer) { } // If the queue is non-empty, we're waiting for SignalReadyToSend, // so just add to the end of the queue and keep waiting. - if (!queued_send_data_.empty()) { - return QueueSendData(buffer); - } - - cricket::SendDataResult send_result; - if (!InternalSendWithoutQueueing(buffer, &send_result)) { - if (send_result == cricket::SDR_BLOCK) { - return QueueSendData(buffer); + if (!queued_send_data_.Empty()) { + // Only SCTP DataChannel queues the outgoing data when the transport is + // blocked. + ASSERT(data_channel_type_ == cricket::DCT_SCTP); + if (!QueueSendDataMessage(buffer)) { + Close(); } - // Fail for other results. - // TODO(jiayl): We should close the data channel in this case. - return false; + return true; } - return true; -} - -void DataChannel::QueueControl(const talk_base::Buffer* buffer) { - queued_control_data_.push(buffer); -} - -bool DataChannel::SendOpenMessage(const talk_base::Buffer* raw_buffer) { - ASSERT(data_channel_type_ == cricket::DCT_SCTP && - was_ever_writable_ && - config_.id >= 0 && - !config_.negotiated); - - talk_base::scoped_ptr buffer(raw_buffer); - - cricket::SendDataParams send_params; - send_params.ssrc = config_.id; - send_params.ordered = true; - send_params.type = cricket::DMT_CONTROL; - cricket::SendDataResult send_result; - bool retval = provider_->SendData(send_params, *buffer, &send_result); - if (retval) { - LOG(LS_INFO) << "Sent OPEN message on channel " << config_.id; - // Send data as ordered before we receive any mesage from the remote peer - // to make sure the remote peer will not receive any data before it receives - // the OPEN message. - waiting_for_open_ack_ = true; - } else if (send_result == cricket::SDR_BLOCK) { - // Link is congested. Queue for later. - QueueControl(buffer.release()); - } else { - LOG(LS_ERROR) << "Failed to send OPEN message with result " - << send_result << " on channel " << config_.id; + bool success = SendDataMessage(buffer); + if (data_channel_type_ == cricket::DCT_RTP) { + return success; } - return retval; -} -bool DataChannel::SendOpenAckMessage(const talk_base::Buffer* raw_buffer) { - ASSERT(data_channel_type_ == cricket::DCT_SCTP && - was_ever_writable_ && - config_.id >= 0); - - talk_base::scoped_ptr buffer(raw_buffer); - - cricket::SendDataParams send_params; - send_params.ssrc = config_.id; - send_params.ordered = config_.ordered; - send_params.type = cricket::DMT_CONTROL; - - cricket::SendDataResult send_result; - bool retval = provider_->SendData(send_params, *buffer, &send_result); - if (retval) { - LOG(LS_INFO) << "Sent OPEN_ACK message on channel " << config_.id; - } else if (send_result == cricket::SDR_BLOCK) { - // Link is congested. Queue for later. - QueueControl(buffer.release()); - } else { - LOG(LS_ERROR) << "Failed to send OPEN_ACK message with result " - << send_result << " on channel " << config_.id; - } - return retval; + // Always return true for SCTP DataChannel per the spec. + return true; } void DataChannel::SetReceiveSsrc(uint32 receive_ssrc) { @@ -256,6 +232,27 @@ void DataChannel::RemotePeerRequestClose() { DoClose(); } +void DataChannel::SetSctpSid(int sid) { + ASSERT(config_.id < 0 && sid >= 0 && data_channel_type_ == cricket::DCT_SCTP); + if (config_.id == sid) + return; + + config_.id = sid; + provider_->AddSctpDataStream(sid); +} + +void DataChannel::OnTransportChannelCreated() { + ASSERT(data_channel_type_ == cricket::DCT_SCTP); + if (!connected_to_provider_) { + connected_to_provider_ = provider_->ConnectDataChannel(this); + } + // The sid may have been unassigned when provider_->ConnectDataChannel was + // done. So always add the streams even if connected_to_provider_ is true. + if (config_.id >= 0) { + provider_->AddSctpDataStream(config_.id); + } +} + void DataChannel::SetSendSsrc(uint32 send_ssrc) { ASSERT(data_channel_type_ == cricket::DCT_RTP); if (send_ssrc_set_) { @@ -324,13 +321,18 @@ void DataChannel::OnDataReceived(cricket::DataChannel* channel, if (was_ever_writable_ && observer_) { observer_->OnMessage(*buffer.get()); } else { - if (queued_received_data_.size() > kMaxQueuedReceivedDataPackets) { - // TODO(jiayl): We should close the data channel in this case. - LOG(LS_ERROR) - << "Queued received data exceeds the max number of packes."; - ClearQueuedReceivedData(); + if (queued_received_data_.byte_count() + payload.length() > + kMaxQueuedReceivedDataBytes) { + LOG(LS_ERROR) << "Queued received data exceeds the max buffer size."; + + queued_received_data_.Clear(); + if (data_channel_type_ != cricket::DCT_RTP) { + Close(); + } + + return; } - queued_received_data_.push(buffer.release()); + queued_received_data_.Push(buffer.release()); } } @@ -345,26 +347,34 @@ void DataChannel::OnChannelReady(bool writable) { was_ever_writable_ = true; if (data_channel_type_ == cricket::DCT_SCTP) { + talk_base::Buffer payload; + if (config_.open_handshake_role == InternalDataChannelInit::kOpener) { - talk_base::Buffer* payload = new talk_base::Buffer; - WriteDataChannelOpenMessage(label_, config_, payload); - SendOpenMessage(payload); + WriteDataChannelOpenMessage(label_, config_, &payload); + SendControlMessage(payload); } else if (config_.open_handshake_role == - InternalDataChannelInit::kAcker) { - talk_base::Buffer* payload = new talk_base::Buffer; - WriteDataChannelOpenAckMessage(payload); - SendOpenAckMessage(payload); + InternalDataChannelInit::kAcker) { + WriteDataChannelOpenAckMessage(&payload); + SendControlMessage(payload); } } UpdateState(); - ASSERT(queued_send_data_.empty()); + ASSERT(queued_send_data_.Empty()); } else if (state_ == kOpen) { - DeliverQueuedSendData(); + // TODO(jiayl): Sending OPEN message here contradicts with the pre-condition + // that the readyState is open. According to the standard, the channel + // should not become open before the OPEN message is sent. + SendQueuedControlMessages(); + + SendQueuedDataMessages(); } } void DataChannel::DoClose() { + if (state_ == kClosed) + return; + receive_ssrc_set_ = false; send_ssrc_set_ = false; SetState(kClosing); @@ -381,7 +391,7 @@ void DataChannel::UpdateState() { if (was_ever_writable_) { // TODO(jiayl): Do not transition to kOpen if we failed to send the // OPEN message. - DeliverQueuedControlData(); + SendQueuedControlMessages(); SetState(kOpen); // If we have received buffers before the channel got writable. // Deliver them now. @@ -407,6 +417,9 @@ void DataChannel::UpdateState() { } void DataChannel::SetState(DataState state) { + if (state_ == state) + return; + state_ = state; if (observer_) { observer_->OnStateChange(); @@ -430,75 +443,27 @@ void DataChannel::DeliverQueuedReceivedData() { return; } - while (!queued_received_data_.empty()) { - DataBuffer* buffer = queued_received_data_.front(); + while (!queued_received_data_.Empty()) { + talk_base::scoped_ptr buffer(queued_received_data_.Front()); observer_->OnMessage(*buffer); - queued_received_data_.pop(); - delete buffer; + queued_received_data_.Pop(); } } -void DataChannel::ClearQueuedReceivedData() { - while (!queued_received_data_.empty()) { - DataBuffer* buffer = queued_received_data_.front(); - queued_received_data_.pop(); - delete buffer; - } -} - -void DataChannel::DeliverQueuedSendData() { +void DataChannel::SendQueuedDataMessages() { ASSERT(was_ever_writable_ && state_ == kOpen); - // TODO(jiayl): Sending OPEN message here contradicts with the pre-condition - // that the readyState is open. According to the standard, the channel should - // not become open before the OPEN message is sent. - DeliverQueuedControlData(); - - while (!queued_send_data_.empty()) { - DataBuffer* buffer = queued_send_data_.front(); - cricket::SendDataResult send_result; - if (!InternalSendWithoutQueueing(*buffer, &send_result)) { - LOG(LS_WARNING) << "DeliverQueuedSendData aborted due to send_result " - << send_result; - break; - } - queued_send_data_.pop_front(); - delete buffer; - } -} - -void DataChannel::ClearQueuedControlData() { - while (!queued_control_data_.empty()) { - const talk_base::Buffer *buf = queued_control_data_.front(); - queued_control_data_.pop(); - delete buf; - } -} - -void DataChannel::DeliverQueuedControlData() { - ASSERT(was_ever_writable_); - while (!queued_control_data_.empty()) { - const talk_base::Buffer* buf = queued_control_data_.front(); - queued_control_data_.pop(); - if (config_.open_handshake_role == InternalDataChannelInit::kOpener) { - SendOpenMessage(buf); - } else { - ASSERT(config_.open_handshake_role == InternalDataChannelInit::kAcker); - SendOpenAckMessage(buf); - } - } -} + PacketQueue packet_buffer; + packet_buffer.Swap(&queued_send_data_); -void DataChannel::ClearQueuedSendData() { - while (!queued_send_data_.empty()) { - DataBuffer* buffer = queued_send_data_.front(); - queued_send_data_.pop_front(); - delete buffer; + while (!packet_buffer.Empty()) { + talk_base::scoped_ptr buffer(packet_buffer.Front()); + SendDataMessage(*buffer); + packet_buffer.Pop(); } } -bool DataChannel::InternalSendWithoutQueueing( - const DataBuffer& buffer, cricket::SendDataResult* send_result) { +bool DataChannel::SendDataMessage(const DataBuffer& buffer) { cricket::SendDataParams send_params; if (data_channel_type_ == cricket::DCT_SCTP) { @@ -518,34 +483,78 @@ bool DataChannel::InternalSendWithoutQueueing( } send_params.type = buffer.binary ? cricket::DMT_BINARY : cricket::DMT_TEXT; - return provider_->SendData(send_params, buffer.data, send_result); + cricket::SendDataResult send_result = cricket::SDR_SUCCESS; + bool success = provider_->SendData(send_params, buffer.data, &send_result); + + if (!success && data_channel_type_ == cricket::DCT_SCTP) { + if (send_result != cricket::SDR_BLOCK || !QueueSendDataMessage(buffer)) { + LOG(LS_ERROR) << "Closing the DataChannel due to a failure to send data, " + << "send_result = " << send_result; + Close(); + } + } + return success; } -bool DataChannel::QueueSendData(const DataBuffer& buffer) { - if (queued_send_data_.size() > kMaxQueuedSendDataPackets) { - LOG(LS_ERROR) << "Can't buffer any more data in the data channel."; +bool DataChannel::QueueSendDataMessage(const DataBuffer& buffer) { + if (queued_send_data_.byte_count() >= kMaxQueuedSendDataBytes) { + LOG(LS_ERROR) << "Can't buffer any more data for the data channel."; return false; } - queued_send_data_.push_back(new DataBuffer(buffer)); + queued_send_data_.Push(new DataBuffer(buffer)); return true; } -void DataChannel::SetSctpSid(int sid) { - ASSERT(config_.id < 0 && sid >= 0 && data_channel_type_ == cricket::DCT_SCTP); - config_.id = sid; - provider_->AddSctpDataStream(sid); -} +void DataChannel::SendQueuedControlMessages() { + ASSERT(was_ever_writable_); -void DataChannel::OnTransportChannelCreated() { - ASSERT(data_channel_type_ == cricket::DCT_SCTP); - if (!connected_to_provider_) { - connected_to_provider_ = provider_->ConnectDataChannel(this); + PacketQueue control_packets; + control_packets.Swap(&queued_control_data_); + + while (!control_packets.Empty()) { + talk_base::scoped_ptr buf(control_packets.Front()); + SendControlMessage(buf->data); + control_packets.Pop(); } - // The sid may have been unassigned when provider_->ConnectDataChannel was - // done. So always add the streams even if connected_to_provider_ is true. - if (config_.id >= 0) { - provider_->AddSctpDataStream(config_.id); +} + +void DataChannel::QueueControlMessage(const talk_base::Buffer& buffer) { + queued_control_data_.Push(new DataBuffer(buffer, true)); +} + +bool DataChannel::SendControlMessage(const talk_base::Buffer& buffer) { + bool is_open_message = + (config_.open_handshake_role == InternalDataChannelInit::kOpener); + + ASSERT(data_channel_type_ == cricket::DCT_SCTP && + was_ever_writable_ && + config_.id >= 0 && + (!is_open_message || !config_.negotiated)); + + cricket::SendDataParams send_params; + send_params.ssrc = config_.id; + send_params.ordered = config_.ordered || is_open_message; + send_params.type = cricket::DMT_CONTROL; + + cricket::SendDataResult send_result = cricket::SDR_SUCCESS; + bool retval = provider_->SendData(send_params, buffer, &send_result); + if (retval) { + LOG(LS_INFO) << "Sent CONTROL message on channel " << config_.id; + + if (is_open_message) { + // Send data as ordered before we receive any message from the remote peer + // to make sure the remote peer will not receive any data before it + // receives the OPEN message. + waiting_for_open_ack_ = true; + } + } else if (send_result == cricket::SDR_BLOCK) { + QueueControlMessage(buffer); + } else { + LOG(LS_ERROR) << "Closing the DataChannel due to a failure to send" + << " the CONTROL message, send_result = " << send_result; + Close(); } + return retval; } } // namespace webrtc diff --git a/talk/app/webrtc/datachannel.h b/talk/app/webrtc/datachannel.h index 9256e0e07..0510f7e88 100644 --- a/talk/app/webrtc/datachannel.h +++ b/talk/app/webrtc/datachannel.h @@ -29,7 +29,7 @@ #define TALK_APP_WEBRTC_DATACHANNEL_H_ #include -#include +#include #include "talk/app/webrtc/datachannelinterface.h" #include "talk/app/webrtc/proxy.h" @@ -149,7 +149,8 @@ class DataChannel : public DataChannelInterface, // The following methods are for SCTP only. - // Sets the SCTP sid and adds to transport layer if not set yet. + // Sets the SCTP sid and adds to transport layer if not set yet. Should only + // be called once. void SetSctpSid(int sid); // Called when the transport channel is created. void OnTransportChannelCreated(); @@ -175,23 +176,49 @@ class DataChannel : public DataChannelInterface, virtual ~DataChannel(); private: + // A packet queue which tracks the total queued bytes. Queued packets are + // owned by this class. + class PacketQueue { + public: + PacketQueue(); + ~PacketQueue(); + + size_t byte_count() const { + return byte_count_; + } + + bool Empty() const; + + DataBuffer* Front(); + + void Pop(); + + void Push(DataBuffer* packet); + + void Clear(); + + void Swap(PacketQueue* other); + + private: + std::deque packets_; + size_t byte_count_; + }; + bool Init(const InternalDataChannelInit& config); void DoClose(); void UpdateState(); void SetState(DataState state); void DisconnectFromTransport(); - void DeliverQueuedControlData(); - void QueueControl(const talk_base::Buffer* buffer); - void ClearQueuedControlData(); + void DeliverQueuedReceivedData(); - void ClearQueuedReceivedData(); - void DeliverQueuedSendData(); - void ClearQueuedSendData(); - bool InternalSendWithoutQueueing(const DataBuffer& buffer, - cricket::SendDataResult* send_result); - bool QueueSendData(const DataBuffer& buffer); - bool SendOpenMessage(const talk_base::Buffer* buffer); - bool SendOpenAckMessage(const talk_base::Buffer* buffer); + + void SendQueuedDataMessages(); + bool SendDataMessage(const DataBuffer& buffer); + bool QueueSendDataMessage(const DataBuffer& buffer); + + void SendQueuedControlMessages(); + void QueueControlMessage(const talk_base::Buffer& buffer); + bool SendControlMessage(const talk_base::Buffer& buffer); std::string label_; InternalDataChannelInit config_; @@ -208,9 +235,9 @@ class DataChannel : public DataChannelInterface, uint32 receive_ssrc_; // Control messages that always have to get sent out before any queued // data. - std::queue queued_control_data_; - std::queue queued_received_data_; - std::deque queued_send_data_; + PacketQueue queued_control_data_; + PacketQueue queued_received_data_; + PacketQueue queued_send_data_; }; class DataChannelFactory { diff --git a/talk/app/webrtc/datachannel_unittest.cc b/talk/app/webrtc/datachannel_unittest.cc index 1be24e96c..6f223fe42 100644 --- a/talk/app/webrtc/datachannel_unittest.cc +++ b/talk/app/webrtc/datachannel_unittest.cc @@ -29,14 +29,37 @@ #include "talk/app/webrtc/sctputils.h" #include "talk/app/webrtc/test/fakedatachannelprovider.h" #include "talk/base/gunit.h" -#include "testing/base/public/gmock.h" using webrtc::DataChannel; class FakeDataChannelObserver : public webrtc::DataChannelObserver { public: - MOCK_METHOD0(OnStateChange, void()); - MOCK_METHOD1(OnMessage, void(const webrtc::DataBuffer& buffer)); + FakeDataChannelObserver() + : messages_received_(0), on_state_change_count_(0) {} + + void OnStateChange() { + ++on_state_change_count_; + } + + void OnMessage(const webrtc::DataBuffer& buffer) { + ++messages_received_; + } + + size_t messages_received() const { + return messages_received_; + } + + void ResetOnStateChangeCount() { + on_state_change_count_ = 0; + } + + size_t on_state_change_count() const { + return on_state_change_count_; + } + + private: + size_t messages_received_; + size_t on_state_change_count_; }; class SctpDataChannelTest : public testing::Test { @@ -151,6 +174,16 @@ TEST_F(SctpDataChannelTest, OpenMessageSent) { static_cast(webrtc_data_channel_->id())); } +TEST_F(SctpDataChannelTest, QueuedOpenMessageSent) { + provider_.set_send_blocked(true); + SetChannelReady(); + provider_.set_send_blocked(false); + + EXPECT_EQ(cricket::DMT_CONTROL, provider_.last_send_data_params().type); + EXPECT_EQ(provider_.last_send_data_params().ssrc, + static_cast(webrtc_data_channel_->id())); +} + // Tests that the DataChannel created after transport gets ready can enter OPEN // state. TEST_F(SctpDataChannelTest, LateCreatedChannelTransitionToOpen) { @@ -233,12 +266,13 @@ TEST_F(SctpDataChannelTest, ReceiveDataWithInvalidSsrc) { SetChannelReady(); AddObserver(); - EXPECT_CALL(*(observer_.get()), OnMessage(testing::_)).Times(0); cricket::ReceiveDataParams params; params.ssrc = 0; webrtc::DataBuffer buffer("abcd"); webrtc_data_channel_->OnDataReceived(NULL, params, buffer.data); + + EXPECT_EQ(0U, observer_->messages_received()); } // Tests that the incoming messages with right ssrcs are acceted. @@ -247,13 +281,13 @@ TEST_F(SctpDataChannelTest, ReceiveDataWithValidSsrc) { SetChannelReady(); AddObserver(); - EXPECT_CALL(*(observer_.get()), OnMessage(testing::_)).Times(1); cricket::ReceiveDataParams params; params.ssrc = 1; webrtc::DataBuffer buffer("abcd"); webrtc_data_channel_->OnDataReceived(NULL, params, buffer.data); + EXPECT_EQ(1U, observer_->messages_received()); } // Tests that no CONTROL message is sent if the datachannel is negotiated and @@ -302,3 +336,77 @@ TEST_F(SctpDataChannelTest, OpenAckRoleInitialization) { webrtc::InternalDataChannelInit init2(base); EXPECT_EQ(webrtc::InternalDataChannelInit::kNone, init2.open_handshake_role); } + +// Tests that the DataChannel is closed if the sending buffer is full. +TEST_F(SctpDataChannelTest, ClosedWhenSendBufferFull) { + SetChannelReady(); + + const size_t buffer_size = 1024; + talk_base::Buffer buffer; + buffer.SetLength(buffer_size); + memset(buffer.data(), 0, buffer_size); + + webrtc::DataBuffer packet(buffer, true); + provider_.set_send_blocked(true); + + for (size_t i = 0; i < 16 * 1024 + 1; ++i) { + EXPECT_TRUE(webrtc_data_channel_->Send(packet)); + } + + EXPECT_EQ(webrtc::DataChannelInterface::kClosed, + webrtc_data_channel_->state()); +} + +// Tests that the DataChannel is closed on transport errors. +TEST_F(SctpDataChannelTest, ClosedOnTransportError) { + SetChannelReady(); + webrtc::DataBuffer buffer("abcd"); + provider_.set_transport_error(); + + EXPECT_TRUE(webrtc_data_channel_->Send(buffer)); + + EXPECT_EQ(webrtc::DataChannelInterface::kClosed, + webrtc_data_channel_->state()); +} + +// Tests that a already closed DataChannel does not fire onStateChange again. +TEST_F(SctpDataChannelTest, ClosedDataChannelDoesNotFireOnStateChange) { + AddObserver(); + webrtc_data_channel_->Close(); + // OnStateChange called for kClosing and kClosed. + EXPECT_EQ(2U, observer_->on_state_change_count()); + + observer_->ResetOnStateChangeCount(); + webrtc_data_channel_->RemotePeerRequestClose(); + EXPECT_EQ(0U, observer_->on_state_change_count()); +} + +// Tests that RemotePeerRequestClose closes the local DataChannel. +TEST_F(SctpDataChannelTest, RemotePeerRequestClose) { + AddObserver(); + webrtc_data_channel_->RemotePeerRequestClose(); + + // OnStateChange called for kClosing and kClosed. + EXPECT_EQ(2U, observer_->on_state_change_count()); + EXPECT_EQ(webrtc::DataChannelInterface::kClosed, + webrtc_data_channel_->state()); +} + +// Tests that the DataChannel is closed if the received buffer is full. +TEST_F(SctpDataChannelTest, ClosedWhenReceivedBufferFull) { + SetChannelReady(); + const size_t buffer_size = 1024; + talk_base::Buffer buffer; + buffer.SetLength(buffer_size); + memset(buffer.data(), 0, buffer_size); + + cricket::ReceiveDataParams params; + params.ssrc = 0; + + // Receiving data without having an observer will overflow the buffer. + for (size_t i = 0; i < 16 * 1024 + 1; ++i) { + webrtc_data_channel_->OnDataReceived(NULL, params, buffer); + } + EXPECT_EQ(webrtc::DataChannelInterface::kClosed, + webrtc_data_channel_->state()); +} diff --git a/talk/app/webrtc/datachannelinterface.h b/talk/app/webrtc/datachannelinterface.h index 7be8a50f5..57fe200cf 100644 --- a/talk/app/webrtc/datachannelinterface.h +++ b/talk/app/webrtc/datachannelinterface.h @@ -97,7 +97,9 @@ class DataChannelObserver { class DataChannelInterface : public talk_base::RefCountInterface { public: - enum DataState { // Keep in sync with DataChannel.java:State. + // Keep in sync with DataChannel.java:State and + // RTCDataChannel.h:RTCDataChannelState. + enum DataState { kConnecting, kOpen, // The DataChannel is ready to send data. kClosing, diff --git a/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java b/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java new file mode 100644 index 000000000..439f942f1 --- /dev/null +++ b/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java @@ -0,0 +1,459 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.webrtc; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.util.Log; + +import org.webrtc.VideoRenderer.I420Frame; + +/** + * Efficiently renders YUV frames using the GPU for CSC. + * Clients will want first to call setView() to pass GLSurfaceView + * and then for each video stream either create instance of VideoRenderer using + * createGui() call or VideoRenderer.Callbacks interface using create() call. + * Only one instance of the class can be created. + */ +public class VideoRendererGui implements GLSurfaceView.Renderer { + private static VideoRendererGui instance = null; + private static final String TAG = "VideoRendererGui"; + private GLSurfaceView surface; + // Indicates if SurfaceView.Renderer.onSurfaceCreated was called. + // If true then for every newly created yuv image renderer createTexture() + // should be called. The variable is accessed on multiple threads and + // all accesses are synchronized on yuvImageRenderers' object lock. + private boolean onSurfaceCreatedCalled; + // List of yuv renderers. + private ArrayList yuvImageRenderers; + private int program; + + private final String VERTEX_SHADER_STRING = + "varying vec2 interp_tc;\n" + + "attribute vec4 in_pos;\n" + + "attribute vec2 in_tc;\n" + + "\n" + + "void main() {\n" + + " gl_Position = in_pos;\n" + + " interp_tc = in_tc;\n" + + "}\n"; + + private final String FRAGMENT_SHADER_STRING = + "precision mediump float;\n" + + "varying vec2 interp_tc;\n" + + "\n" + + "uniform sampler2D y_tex;\n" + + "uniform sampler2D u_tex;\n" + + "uniform sampler2D v_tex;\n" + + "\n" + + "void main() {\n" + + // CSC according to http://www.fourcc.org/fccyvrgb.php + " float y = texture2D(y_tex, interp_tc).r;\n" + + " float u = texture2D(u_tex, interp_tc).r - 0.5;\n" + + " float v = texture2D(v_tex, interp_tc).r - 0.5;\n" + + " gl_FragColor = vec4(y + 1.403 * v, " + + " y - 0.344 * u - 0.714 * v, " + + " y + 1.77 * u, 1);\n" + + "}\n"; + + private VideoRendererGui(GLSurfaceView surface) { + this.surface = surface; + // Create an OpenGL ES 2.0 context. + surface.setPreserveEGLContextOnPause(true); + surface.setEGLContextClientVersion(2); + surface.setRenderer(this); + surface.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + + yuvImageRenderers = new ArrayList(); + } + + // Poor-man's assert(): die with |msg| unless |condition| is true. + private static void abortUnless(boolean condition, String msg) { + if (!condition) { + throw new RuntimeException(msg); + } + } + + // Assert that no OpenGL ES 2.0 error has been raised. + private static void checkNoGLES2Error() { + int error = GLES20.glGetError(); + abortUnless(error == GLES20.GL_NO_ERROR, "GLES20 error: " + error); + } + + // Wrap a float[] in a direct FloatBuffer using native byte order. + private static FloatBuffer directNativeFloatBuffer(float[] array) { + FloatBuffer buffer = ByteBuffer.allocateDirect(array.length * 4).order( + ByteOrder.nativeOrder()).asFloatBuffer(); + buffer.put(array); + buffer.flip(); + return buffer; + } + + // Compile & attach a |type| shader specified by |source| to |program|. + private static void addShaderTo( + int type, String source, int program) { + int[] result = new int[] { + GLES20.GL_FALSE + }; + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); + abortUnless(result[0] == GLES20.GL_TRUE, + GLES20.glGetShaderInfoLog(shader) + ", source: " + source); + GLES20.glAttachShader(program, shader); + GLES20.glDeleteShader(shader); + + checkNoGLES2Error(); + } + + /** + * Class used to display stream of YUV420 frames at particular location + * on a screen. New video frames are sent to display using renderFrame() + * call. + */ + private static class YuvImageRenderer implements VideoRenderer.Callbacks { + private GLSurfaceView surface; + private int program; + private FloatBuffer textureVertices; + private int[] yuvTextures = { -1, -1, -1 }; + + // Render frame queue - accessed by two threads. renderFrame() call does + // an offer (writing I420Frame to render) and early-returns (recording + // a dropped frame) if that queue is full. draw() call does a peek(), + // copies frame to texture and then removes it from a queue using poll(). + LinkedBlockingQueue frameToRenderQueue; + // Local copy of incoming video frame. + private I420Frame frameToRender; + // Flag if renderFrame() was ever called + boolean seenFrame; + // Total number of video frames received in renderFrame() call. + private int framesReceived; + // Number of video frames dropped by renderFrame() because previous + // frame has not been rendered yet. + private int framesDropped; + // Number of rendered video frames. + private int framesRendered; + // Time in ns when the first video frame was rendered. + private long startTimeNs = -1; + // Time in ns spent in draw() function. + private long drawTimeNs; + // Time in ns spent in renderFrame() function - including copying frame + // data to rendering planes + private long copyTimeNs; + + // Texture Coordinates mapping the entire texture. + private final FloatBuffer textureCoords = directNativeFloatBuffer( + new float[] { + 0, 0, 0, 1, 1, 0, 1, 1 + }); + + private YuvImageRenderer( + GLSurfaceView surface, + int x, int y, int width, int height) { + Log.v(TAG, "YuvImageRenderer.Create"); + this.surface = surface; + frameToRenderQueue = new LinkedBlockingQueue(1); + // Create texture vertices. + float xLeft = (x - 50) / 50.0f; + float yTop = (50 - y) / 50.0f; + float xRight = Math.min(1.0f, (x + width - 50) / 50.0f); + float yBottom = Math.max(-1.0f, (50 - y - height) / 50.0f); + float textureVeticesFloat[] = new float[] { + xLeft, yTop, + xLeft, yBottom, + xRight, yTop, + xRight, yBottom + }; + textureVertices = directNativeFloatBuffer(textureVeticesFloat); + } + + private void createTextures(int program) { + Log.v(TAG, " YuvImageRenderer.createTextures"); + this.program = program; + + // Generate 3 texture ids for Y/U/V and place them into |textures|. + GLES20.glGenTextures(3, yuvTextures, 0); + for (int i = 0; i < 3; i++) { + GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, + 128, 128, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, null); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + } + checkNoGLES2Error(); + } + + private void draw() { + long now = System.nanoTime(); + if (!seenFrame) { + // No frame received yet - nothing to render. + return; + } + I420Frame frameFromQueue; + synchronized (frameToRenderQueue) { + frameFromQueue = frameToRenderQueue.peek(); + if (frameFromQueue != null && startTimeNs == -1) { + startTimeNs = now; + } + for (int i = 0; i < 3; ++i) { + int w = (i == 0) ? frameToRender.width : frameToRender.width / 2; + int h = (i == 0) ? frameToRender.height : frameToRender.height / 2; + GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); + if (frameFromQueue != null) { + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, + w, h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, + frameFromQueue.yuvPlanes[i]); + } + } + if (frameFromQueue != null) { + frameToRenderQueue.poll(); + } + } + int posLocation = GLES20.glGetAttribLocation(program, "in_pos"); + GLES20.glEnableVertexAttribArray(posLocation); + GLES20.glVertexAttribPointer( + posLocation, 2, GLES20.GL_FLOAT, false, 0, textureVertices); + + int texLocation = GLES20.glGetAttribLocation(program, "in_tc"); + GLES20.glEnableVertexAttribArray(texLocation); + GLES20.glVertexAttribPointer( + texLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + GLES20.glDisableVertexAttribArray(posLocation); + GLES20.glDisableVertexAttribArray(texLocation); + + checkNoGLES2Error(); + + if (frameFromQueue != null) { + framesRendered++; + drawTimeNs += (System.nanoTime() - now); + if ((framesRendered % 150) == 0) { + logStatistics(); + } + } + } + + private void logStatistics() { + long timeSinceFirstFrameNs = System.nanoTime() - startTimeNs; + Log.v(TAG, "Frames received: " + framesReceived + ". Dropped: " + + framesDropped + ". Rendered: " + framesRendered); + if (framesReceived > 0 && framesRendered > 0) { + Log.v(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) + + " ms. FPS: " + (float)framesRendered * 1e9 / timeSinceFirstFrameNs); + Log.v(TAG, "Draw time: " + + (int) (drawTimeNs / (1000 * framesRendered)) + " us. Copy time: " + + (int) (copyTimeNs / (1000 * framesReceived)) + " us"); + } + } + + @Override + public void setSize(final int width, final int height) { + Log.v(TAG, "YuvImageRenderer.setSize: " + width + " x " + height); + int[] strides = { width, width / 2, width / 2 }; + // Frame re-allocation need to be synchronized with copying + // frame to textures in draw() function to avoid re-allocating + // the frame while it is being copied. + synchronized (frameToRenderQueue) { + // Clear rendering queue + frameToRenderQueue.poll(); + // Re-allocate / allocate the frame + frameToRender = new I420Frame(width, height, strides, null); + } + } + + @Override + public synchronized void renderFrame(I420Frame frame) { + long now = System.nanoTime(); + framesReceived++; + // Check input frame parameters. + if (!(frame.yuvStrides[0] == frame.width && + frame.yuvStrides[1] == frame.width / 2 && + frame.yuvStrides[2] == frame.width / 2)) { + Log.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " + + frame.yuvStrides[1] + ", " + frame.yuvStrides[2]); + return; + } + // Skip rendering of this frame if setSize() was not called. + if (frameToRender == null) { + framesDropped++; + return; + } + // Check incoming frame dimensions + if (frame.width != frameToRender.width || + frame.height != frameToRender.height) { + throw new RuntimeException("Wrong frame size " + + frame.width + " x " + frame.height); + } + + if (frameToRenderQueue.size() > 0) { + // Skip rendering of this frame if previous frame was not rendered yet. + framesDropped++; + return; + } + frameToRender.copyFrom(frame); + copyTimeNs += (System.nanoTime() - now); + frameToRenderQueue.offer(frameToRender); + seenFrame = true; + surface.requestRender(); + } + } + + /** Passes GLSurfaceView to video renderer. */ + public static void setView(GLSurfaceView surface) { + Log.v(TAG, "VideoRendererGui.setView"); + instance = new VideoRendererGui(surface); + } + + /** + * Creates VideoRenderer with top left corner at (x, y) and resolution + * (width, height). All parameters are in percentage of screen resolution. + */ + public static VideoRenderer createGui( + int x, int y, int width, int height) throws Exception { + YuvImageRenderer javaGuiRenderer = create(x, y, width, height); + return new VideoRenderer(javaGuiRenderer); + } + + /** + * Creates VideoRenderer.Callbacks with top left corner at (x, y) and + * resolution (width, height). All parameters are in percentage of + * screen resolution. + */ + public static YuvImageRenderer create( + int x, int y, int width, int height) { + // Check display region parameters. + if (x < 0 || x > 100 || y < 0 || y > 100 || + width < 0 || width > 100 || height < 0 || height > 100 || + x + width > 100 || y + height > 100) { + throw new RuntimeException("Incorrect window parameters."); + } + + if (instance == null) { + throw new RuntimeException( + "Attempt to create yuv renderer before setting GLSurfaceView"); + } + final YuvImageRenderer yuvImageRenderer = new YuvImageRenderer( + instance.surface, x, y, width, height); + synchronized (instance.yuvImageRenderers) { + if (instance.onSurfaceCreatedCalled) { + // onSurfaceCreated has already been called for VideoRendererGui - + // need to create texture for new image and add image to the + // rendering list. + final CountDownLatch countDownLatch = new CountDownLatch(1); + instance.surface.queueEvent(new Runnable() { + public void run() { + yuvImageRenderer.createTextures(instance.program); + countDownLatch.countDown(); + } + }); + // Wait for task completion. + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + // Add yuv renderer to rendering list. + instance.yuvImageRenderers.add(yuvImageRenderer); + } + return yuvImageRenderer; + } + + @Override + public void onSurfaceCreated(GL10 unused, EGLConfig config) { + Log.v(TAG, "VideoRendererGui.onSurfaceCreated"); + + // Create program. + program = GLES20.glCreateProgram(); + addShaderTo(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_STRING, program); + addShaderTo(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_STRING, program); + + GLES20.glLinkProgram(program); + int[] result = new int[] { + GLES20.GL_FALSE + }; + result[0] = GLES20.GL_FALSE; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, result, 0); + abortUnless(result[0] == GLES20.GL_TRUE, + GLES20.glGetProgramInfoLog(program)); + GLES20.glUseProgram(program); + + GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "y_tex"), 0); + GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "u_tex"), 1); + GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "v_tex"), 2); + + synchronized (yuvImageRenderers) { + // Create textures for all images. + for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { + yuvImageRenderer.createTextures(program); + } + onSurfaceCreatedCalled = true; + } + checkNoGLES2Error(); + GLES20.glClearColor(0.0f, 0.0f, 0.3f, 1.0f); + } + + @Override + public void onSurfaceChanged(GL10 unused, int width, int height) { + Log.v(TAG, "VideoRendererGui.onSurfaceChanged: " + + width + " x " + height + " "); + GLES20.glViewport(0, 0, width, height); + } + + @Override + public void onDrawFrame(GL10 unused) { + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + synchronized (yuvImageRenderers) { + for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { + yuvImageRenderer.draw(); + } + } + } + +} diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc index 898b04c06..eed33bd3e 100644 --- a/talk/app/webrtc/java/jni/peerconnection_jni.cc +++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc @@ -76,7 +76,8 @@ #include "talk/media/webrtc/webrtcvideocapturer.h" #include "talk/media/webrtc/webrtcvideoencoderfactory.h" #include "third_party/icu/source/common/unicode/unistr.h" -#include "third_party/libyuv/include/libyuv/convert.h" +#include "third_party/libyuv/include/libyuv/convert_from.h" +#include "third_party/libyuv/include/libyuv/video_common.h" #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" #include "webrtc/system_wrappers/interface/compile_assert.h" #include "webrtc/system_wrappers/interface/trace.h" @@ -1133,6 +1134,28 @@ class JavaVideoRendererWrapper : public VideoRendererInterface { // into its own .h/.cc pair, if/when the JNI helper stuff above is extracted // from this file. +//#define TRACK_BUFFER_TIMING +#ifdef TRACK_BUFFER_TIMING +#include +#define TAG "MediaCodecVideoEncoder" +#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__) +#else +#define ALOGV(...) +#endif + +// Color formats supported by encoder - should mirror supportedColorList +// from MediaCodecVideoEncoder.java +enum COLOR_FORMATTYPE { + COLOR_FormatYUV420Planar = 0x13, + COLOR_FormatYUV420SemiPlanar = 0x15, + COLOR_QCOM_FormatYUV420SemiPlanar = 0x7FA30C00, + // NV12 color format supported by QCOM codec, but not declared in MediaCodec - + // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h + // This format is presumably similar to COLOR_FormatYUV420SemiPlanar, + // but requires some (16, 32?) byte alignment. + COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04 +}; + // Arbitrary interval to poll the codec for new outputs. enum { kMediaCodecPollMs = 10 }; @@ -1181,7 +1204,7 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, // If width==0 then this is assumed to be a re-initialization and the // previously-current values are reused instead of the passed parameters // (makes it easier to reason about thread-safety). - int32_t InitEncodeOnCodecThread(int width, int height, int kbps); + int32_t InitEncodeOnCodecThread(int width, int height, int kbps, int fps); int32_t EncodeOnCodecThread( const webrtc::I420VideoFrame& input_image, const std::vector* frame_types); @@ -1221,6 +1244,7 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, jmethodID j_set_rates_method_; jmethodID j_dequeue_output_buffer_method_; jmethodID j_release_output_buffer_method_; + jfieldID j_color_format_field_; jfieldID j_info_index_field_; jfieldID j_info_buffer_field_; jfieldID j_info_is_key_frame_field_; @@ -1230,9 +1254,16 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, // Touched only on codec_thread_ so no explicit synchronization necessary. int width_; // Frame width in pixels. int height_; // Frame height in pixels. + enum libyuv::FourCC encoder_fourcc_; // Encoder color space format. int last_set_bitrate_kbps_; // Last-requested bitrate in kbps. - // Frame size in bytes fed to MediaCodec (stride==width, sliceHeight==height). - int nv12_size_; + int last_set_fps_; // Last-requested frame rate. + int frames_received_; // Number of frames received by encoder. + int frames_dropped_; // Number of frames dropped by encoder. + int frames_in_queue_; // Number of frames in encoder queue. + int64_t last_input_timestamp_ms_; // Timestamp of last received yuv frame. + int64_t last_output_timestamp_ms_; // Timestamp of last encoded frame. + // Frame size in bytes fed to MediaCodec. + int yuv_size_; // True only when between a callback_->Encoded() call return a positive value // and the next Encode() call being ignored. bool drop_next_input_frame_; @@ -1240,8 +1271,6 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, std::vector input_buffers_; }; -enum { MSG_SET_RATES, MSG_POLL_FOR_READY_OUTPUTS, }; - MediaCodecVideoEncoder::~MediaCodecVideoEncoder() { // We depend on ResetParameters() to ensure no more callbacks to us after we // are deleted, so assert it here. @@ -1279,7 +1308,7 @@ MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni) j_init_encode_method_ = GetMethodID(jni, *j_media_codec_video_encoder_class_, "initEncode", - "(III)[Ljava/nio/ByteBuffer;"); + "(IIII)[Ljava/nio/ByteBuffer;"); j_dequeue_input_buffer_method_ = GetMethodID( jni, *j_media_codec_video_encoder_class_, "dequeueInputBuffer", "()I"); j_encode_method_ = GetMethodID( @@ -1296,6 +1325,8 @@ MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni) j_release_output_buffer_method_ = GetMethodID( jni, *j_media_codec_video_encoder_class_, "releaseOutputBuffer", "(I)Z"); + j_color_format_field_ = + GetFieldID(jni, *j_media_codec_video_encoder_class_, "colorFormat", "I"); j_info_index_field_ = GetFieldID(jni, j_output_buffer_info_class, "index", "I"); j_info_buffer_field_ = GetFieldID( @@ -1319,7 +1350,8 @@ int32_t MediaCodecVideoEncoder::InitEncode( this, codec_settings->width, codec_settings->height, - codec_settings->startBitrate)); + codec_settings->startBitrate, + codec_settings->maxFramerate)); } int32_t MediaCodecVideoEncoder::Encode( @@ -1380,10 +1412,11 @@ void MediaCodecVideoEncoder::CheckOnCodecThread() { } void MediaCodecVideoEncoder::ResetCodec() { + LOG(LS_ERROR) << "ResetCodec"; if (Release() != WEBRTC_VIDEO_CODEC_OK || codec_thread_->Invoke(Bind( - &MediaCodecVideoEncoder::InitEncodeOnCodecThread, this, 0, 0, 0)) != - WEBRTC_VIDEO_CODEC_OK) { + &MediaCodecVideoEncoder::InitEncodeOnCodecThread, this, 0, 0, 0, 0)) + != WEBRTC_VIDEO_CODEC_OK) { // TODO(fischman): wouldn't it be nice if there was a way to gracefully // degrade to a SW encoder at this point? There isn't one AFAICT :( // https://code.google.com/p/webrtc/issues/detail?id=2920 @@ -1391,42 +1424,65 @@ void MediaCodecVideoEncoder::ResetCodec() { } int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( - int width, int height, int kbps) { + int width, int height, int kbps, int fps) { CheckOnCodecThread(); JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); + LOG(LS_INFO) << "InitEncodeOnCodecThread " << width << " x " << height; if (width == 0) { width = width_; height = height_; kbps = last_set_bitrate_kbps_; + fps = last_set_fps_; } width_ = width; height_ = height; last_set_bitrate_kbps_ = kbps; - nv12_size_ = width_ * height_ * 3 / 2; + last_set_fps_ = fps; + yuv_size_ = width_ * height_ * 3 / 2; + frames_received_ = 0; + frames_dropped_ = 0; + frames_in_queue_ = 0; + last_input_timestamp_ms_ = -1; + last_output_timestamp_ms_ = -1; // We enforce no extra stride/padding in the format creation step. jobjectArray input_buffers = reinterpret_cast( jni->CallObjectMethod(*j_media_codec_video_encoder_, j_init_encode_method_, width_, height_, - kbps)); + kbps, + fps)); CHECK_EXCEPTION(jni, ""); if (IsNull(jni, input_buffers)) return WEBRTC_VIDEO_CODEC_ERROR; + switch (GetIntField(jni, *j_media_codec_video_encoder_, + j_color_format_field_)) { + case COLOR_FormatYUV420Planar: + encoder_fourcc_ = libyuv::FOURCC_YU12; + break; + case COLOR_FormatYUV420SemiPlanar: + case COLOR_QCOM_FormatYUV420SemiPlanar: + case COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m: + encoder_fourcc_ = libyuv::FOURCC_NV12; + break; + default: + LOG(LS_ERROR) << "Wrong color format."; + return WEBRTC_VIDEO_CODEC_ERROR; + } size_t num_input_buffers = jni->GetArrayLength(input_buffers); CHECK(input_buffers_.empty(), "Unexpected double InitEncode without Release"); input_buffers_.resize(num_input_buffers); for (size_t i = 0; i < num_input_buffers; ++i) { input_buffers_[i] = jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i)); - int64 nv12_buffer_capacity = + int64 yuv_buffer_capacity = jni->GetDirectBufferCapacity(input_buffers_[i]); CHECK_EXCEPTION(jni, ""); - CHECK(nv12_buffer_capacity >= nv12_size_, "Insufficient capacity"); + CHECK(yuv_buffer_capacity >= yuv_size_, "Insufficient capacity"); } CHECK_EXCEPTION(jni, ""); @@ -1441,6 +1497,7 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); + frames_received_++; if (!DeliverPendingOutputs(jni)) { ResetCodec(); // Continue as if everything's fine. @@ -1457,42 +1514,58 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( CHECK(frame.width() == width_, "Unexpected resolution change"); CHECK(frame.height() == height_, "Unexpected resolution change"); + // Check if we accumulated too many frames in encoder input buffers + // so the encoder latency exceeds 100ms and drop frame if so. + if (frames_in_queue_ > 0 && last_input_timestamp_ms_ > 0 && + last_output_timestamp_ms_ > 0) { + int encoder_latency_ms = last_input_timestamp_ms_ - + last_output_timestamp_ms_; + if (encoder_latency_ms > 100) { + ALOGV("Drop frame - encoder is behind by %d ms. Q size: %d", + encoder_latency_ms, frames_in_queue_); + frames_dropped_++; + return WEBRTC_VIDEO_CODEC_OK; + } + } + int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_encoder_, j_dequeue_input_buffer_method_); CHECK_EXCEPTION(jni, ""); - if (j_input_buffer_index == -1) + if (j_input_buffer_index == -1) { + // Video codec falls behind - no input buffer available. + ALOGV("Drop frame - no input buffers available"); + frames_dropped_++; return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887. + } if (j_input_buffer_index == -2) { ResetCodec(); return WEBRTC_VIDEO_CODEC_ERROR; } + ALOGV("Frame # %d. Buffer # %d. TS: %lld.", + frames_received_, j_input_buffer_index, frame.render_time_ms()); + jobject j_input_buffer = input_buffers_[j_input_buffer_index]; - uint8* nv12_buffer = + uint8* yuv_buffer = reinterpret_cast(jni->GetDirectBufferAddress(j_input_buffer)); CHECK_EXCEPTION(jni, ""); - CHECK(nv12_buffer, "Indirect buffer??"); - CHECK(!libyuv::I420ToNV12( - frame.buffer(webrtc::kYPlane), - frame.stride(webrtc::kYPlane), - frame.buffer(webrtc::kUPlane), - frame.stride(webrtc::kUPlane), - frame.buffer(webrtc::kVPlane), - frame.stride(webrtc::kVPlane), - nv12_buffer, - frame.width(), - nv12_buffer + frame.stride(webrtc::kYPlane) * frame.height(), - frame.width(), - frame.width(), - frame.height()), - "I420ToNV12 failed"); + CHECK(yuv_buffer, "Indirect buffer??"); + CHECK(!libyuv::ConvertFromI420( + frame.buffer(webrtc::kYPlane), frame.stride(webrtc::kYPlane), + frame.buffer(webrtc::kUPlane), frame.stride(webrtc::kUPlane), + frame.buffer(webrtc::kVPlane), frame.stride(webrtc::kVPlane), + yuv_buffer, width_, + width_, height_, + encoder_fourcc_), + "ConvertFromI420 failed"); jlong timestamp_us = frame.render_time_ms() * 1000; - int64_t start = talk_base::Time(); + last_input_timestamp_ms_ = frame.render_time_ms(); + frames_in_queue_++; bool encode_status = jni->CallBooleanMethod(*j_media_codec_video_encoder_, j_encode_method_, key_frame, j_input_buffer_index, - nv12_size_, + yuv_size_, timestamp_us); CHECK_EXCEPTION(jni, ""); if (!encode_status || !DeliverPendingOutputs(jni)) { @@ -1515,6 +1588,8 @@ int32_t MediaCodecVideoEncoder::RegisterEncodeCompleteCallbackOnCodecThread( int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() { CheckOnCodecThread(); JNIEnv* jni = AttachCurrentThreadIfNeeded(); + LOG(LS_INFO) << "Frames received: " << frames_received_ << + ". Frames dropped: " << frames_dropped_; ScopedLocalRefFrame local_ref_frame(jni); for (size_t i = 0; i < input_buffers_.size(); ++i) jni->DeleteGlobalRef(input_buffers_[i]); @@ -1528,9 +1603,14 @@ int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() { int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate, uint32_t frame_rate) { CheckOnCodecThread(); + if (last_set_bitrate_kbps_ == new_bit_rate && + last_set_fps_ == frame_rate) { + return WEBRTC_VIDEO_CODEC_OK; + } JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); last_set_bitrate_kbps_ = new_bit_rate; + last_set_fps_ = frame_rate; bool ret = jni->CallBooleanMethod(*j_media_codec_video_encoder_, j_set_rates_method_, new_bit_rate, @@ -1547,7 +1627,7 @@ void MediaCodecVideoEncoder::ResetParameters(JNIEnv* jni) { talk_base::MessageQueueManager::Clear(this); width_ = 0; height_ = 0; - nv12_size_ = 0; + yuv_size_ = 0; drop_next_input_frame_ = false; CHECK(input_buffers_.empty(), "ResetParameters called while holding input_buffers_!"); @@ -1596,6 +1676,11 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { jlong capture_time_ms = GetOutputBufferInfoPresentationTimestampUs(jni, j_output_buffer_info) / 1000; + last_output_timestamp_ms_ = capture_time_ms; + frames_in_queue_--; + ALOGV("Got output buffer # %d. TS: %lld. Latency: %lld", + output_buffer_index, last_output_timestamp_ms_, + last_input_timestamp_ms_ - last_output_timestamp_ms_); int32_t callback_status = 0; if (callback_) { @@ -1903,11 +1988,14 @@ JOW(jlong, PeerConnectionFactory_nativeCreateObserver)( #ifdef ANDROID JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)( - JNIEnv* jni, jclass, jobject context) { + JNIEnv* jni, jclass, jobject context, + jboolean initialize_audio, jboolean initialize_video) { CHECK(g_jvm, "JNI_OnLoad failed to run?"); bool failure = false; - failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm); - failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context); + if (initialize_video) + failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm, context); + if (initialize_audio) + failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context); return !failure; } #endif // ANDROID @@ -1939,6 +2027,12 @@ class OwnedFactoryAndThreads { JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnectionFactory)( JNIEnv* jni, jclass) { + // talk/ assumes pretty widely that the current Thread is ThreadManager'd, but + // ThreadManager only WrapCurrentThread()s the thread where it is first + // created. Since the semantics around when auto-wrapping happens in + // talk/base/ are convoluted, we simply wrap here to avoid having to think + // about ramifications of auto-wrapping there. + talk_base::ThreadManager::Instance()->WrapCurrentThread(); webrtc::Trace::CreateTrace(); Thread* worker_thread = new Thread(); worker_thread->SetName("worker_thread", NULL); @@ -2076,7 +2170,7 @@ JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)( PCOJava* observer = reinterpret_cast(observer_p); observer->SetConstraints(new ConstraintsWrapper(jni, j_constraints)); talk_base::scoped_refptr pc(f->CreatePeerConnection( - servers, observer->constraints(), NULL, observer)); + servers, observer->constraints(), NULL, NULL, observer)); return (jlong)pc.release(); } diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java index dff334b37..971c427c4 100644 --- a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java +++ b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java @@ -32,6 +32,7 @@ import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; +import android.media.MediaCodecInfo.CodecCapabilities; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -51,28 +52,49 @@ class MediaCodecVideoEncoder { private static final String TAG = "MediaCodecVideoEncoder"; private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. + private Thread mediaCodecThread; private MediaCodec mediaCodec; private ByteBuffer[] outputBuffers; private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; - private Thread mediaCodecThread; + // List of supported HW VP8 codecs. + private static final String[] supportedHwCodecPrefixes = + {"OMX.qcom.", "OMX.Nvidia." }; + // Bitrate mode + private static final int VIDEO_ControlRateConstant = 2; + // NV12 color format supported by QCOM codec, but not declared in MediaCodec - + // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h + private static final int + COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; + // Allowable color formats supported by codec - in order of preference. + private static final int[] supportedColorList = { + CodecCapabilities.COLOR_FormatYUV420Planar, + CodecCapabilities.COLOR_FormatYUV420SemiPlanar, + CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, + COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m + }; + private int colorFormat; private MediaCodecVideoEncoder() {} - private static boolean isPlatformSupported() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - return false; // MediaCodec.setParameters is missing. - - if (!Build.MODEL.equals("Nexus 5")) { - // TODO(fischman): Currently the N5 is the only >=KK device containing a - // HW VP8 encoder, so don't bother with any others. When this list grows, - // update the KEY_COLOR_FORMAT logic below. - return false; + // Helper struct for findVp8HwEncoder() below. + private static class EncoderProperties { + EncoderProperties(String codecName, int colorFormat) { + this.codecName = codecName; + this.colorFormat = colorFormat; } + public final String codecName; // OpenMax component name for VP8 codec. + public final int colorFormat; // Color format supported by codec. + } + + private static EncoderProperties findVp8HwEncoder() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + return null; // MediaCodec.setParameters is missing. for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); - if (!info.isEncoder()) + if (!info.isEncoder()) { continue; + } String name = null; for (String mimeType : info.getSupportedTypes()) { if (mimeType.equals(VP8_MIME_TYPE)) { @@ -80,25 +102,48 @@ private static boolean isPlatformSupported() { break; } } - if (name == null) + if (name == null) { continue; // No VP8 support in this codec; try the next one. - if (name.startsWith("OMX.google.") || name.startsWith("OMX.SEC.")) { - // SW encoder is highest priority VP8 codec; unlikely we can get HW. - // "OMX.google." is known-software, while "OMX.SEC." is sometimes SW & - // sometimes HW, although not VP8 HW in any known device, so treat as SW - // here (b/9735008 #20). - return false; } - return true; // Yay, passed the gauntlet of pre-requisites! + Log.d(TAG, "Found candidate encoder " + name); + CodecCapabilities capabilities = + info.getCapabilitiesForType(VP8_MIME_TYPE); + for (int colorFormat : capabilities.colorFormats) { + Log.d(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); + } + + // Check if this is supported HW encoder + for (String hwCodecPrefix : supportedHwCodecPrefixes) { + if (!name.startsWith(hwCodecPrefix)) { + continue; + } + // Check if codec supports either yuv420 or nv12 + for (int supportedColorFormat : supportedColorList) { + for (int codecColorFormat : capabilities.colorFormats) { + if (codecColorFormat == supportedColorFormat) { + // Found supported HW VP8 encoder + Log.d(TAG, "Found target encoder " + name + + ". Color: 0x" + Integer.toHexString(codecColorFormat)); + return new EncoderProperties(name, codecColorFormat); + } + } + } + } } - return false; // No VP8 encoder. + return null; // No HW VP8 encoder. + } + + private static boolean isPlatformSupported() { + return findVp8HwEncoder() != null; } private static int bitRate(int kbps) { // webrtc "kilo" means 1000, not 1024. Apparently. // (and the price for overshooting is frame-dropping as webrtc enforces its // bandwidth estimation, which is unpleasant). - return kbps * 1000; + // Since the HW encoder in the N5 overshoots, we clamp to a bit less than + // the requested rate. Sad but true. Bug 3194. + return kbps * 950; } private void checkOnMediaCodecThread() { @@ -110,33 +155,41 @@ private void checkOnMediaCodecThread() { } // Return the array of input buffers, or null on failure. - private ByteBuffer[] initEncode(int width, int height, int kbps) { + private ByteBuffer[] initEncode(int width, int height, int kbps, int fps) { + Log.d(TAG, "initEncode: " + width + " x " + height + + ". @ " + kbps + " kbps. Fps: " + fps + + ". Color: 0x" + Integer.toHexString(colorFormat)); if (mediaCodecThread != null) { throw new RuntimeException("Forgot to release()?"); } + EncoderProperties properties = findVp8HwEncoder(); + if (properties == null) { + throw new RuntimeException("Can not find HW VP8 encoder"); + } mediaCodecThread = Thread.currentThread(); try { MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate(kbps)); - // Arbitrary choices. - format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); - format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 450); - // TODO(fischman): when there is more than just the N5 with a VP8 HW - // encoder, negotiate input colorformats with the codec. For now - // hard-code qcom's supported value. See isPlatformSupported above. - format.setInteger( - MediaFormat.KEY_COLOR_FORMAT, - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); - mediaCodec = MediaCodec.createEncoderByType(VP8_MIME_TYPE); + format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); + // Default WebRTC settings + format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 100); + Log.d(TAG, " Format: " + format); + mediaCodec = MediaCodec.createByCodecName(properties.codecName); if (mediaCodec == null) { return null; } mediaCodec.configure( format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start(); + colorFormat = properties.colorFormat; outputBuffers = mediaCodec.getOutputBuffers(); - return mediaCodec.getInputBuffers(); + ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); + Log.d(TAG, "Input buffers: " + inputBuffers.length + + ". Output buffers: " + outputBuffers.length); + return inputBuffers; } catch (IllegalStateException e) { Log.e(TAG, "initEncode failed", e); return null; @@ -153,6 +206,7 @@ private boolean encode( // indicate this in queueInputBuffer() below and guarantee _this_ frame // be encoded as a key frame, but sadly that flag is ignored. Instead, // we request a key frame "soon". + Log.d(TAG, "Sync frame request"); Bundle b = new Bundle(); b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); mediaCodec.setParameters(b); @@ -168,6 +222,7 @@ private boolean encode( } private void release() { + Log.d(TAG, "release"); checkOnMediaCodecThread(); try { mediaCodec.stop(); @@ -180,13 +235,14 @@ private void release() { } private boolean setRates(int kbps, int frameRateIgnored) { + // frameRate argument is ignored - HW encoder is supposed to use + // video frame timestamps for bit allocation. checkOnMediaCodecThread(); + Log.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored); try { Bundle params = new Bundle(); params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitRate(kbps)); mediaCodec.setParameters(params); - // Sure would be nice to honor the frameRate argument to this function, - // but MediaCodec doesn't expose that particular knob. b/12977358 return true; } catch (IllegalStateException e) { Log.e(TAG, "setRates failed", e); @@ -239,6 +295,9 @@ private OutputBufferInfo dequeueOutputBuffer() { outputBuffer.limit(info.offset + info.size); boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + if (isKeyFrame) { + Log.d(TAG, "Sync frame generated"); + } return new OutputBufferInfo( result, outputBuffer.slice(), isKeyFrame, info.presentationTimeUs); } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { diff --git a/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java b/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java index 0d047800b..441f37ba0 100644 --- a/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java +++ b/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java @@ -43,7 +43,11 @@ public class PeerConnectionFactory { // |context| is an android.content.Context object, but we keep it untyped here // to allow building on non-Android platforms. - public static native boolean initializeAndroidGlobals(Object context); + // Callers may specify either |initializeAudio| or |initializeVideo| as false + // to skip initializing the respective engine (and avoid the need for the + // respective permissions). + public static native boolean initializeAndroidGlobals( + Object context, boolean initializeAudio, boolean initializeVideo); public PeerConnectionFactory() { nativeFactory = nativeCreatePeerConnectionFactory(); diff --git a/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java b/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java index fddb50345..240e996bd 100644 --- a/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java +++ b/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java @@ -175,6 +175,12 @@ public synchronized void expectIceConnectionChange( @Override public synchronized void onIceConnectionChange( IceConnectionState newState) { + // TODO(bemasc): remove once delivery of ICECompleted is reliable + // (https://code.google.com/p/webrtc/issues/detail?id=3021). + if (newState.equals(IceConnectionState.COMPLETED)) { + return; + } + assertEquals(expectedIceConnectionChanges.removeFirst(), newState); } @@ -519,7 +525,7 @@ public void testCompleteSessionOnNonMainThread() throws Exception { private void doTest() throws Exception { CountDownLatch testDone = new CountDownLatch(1); System.gc(); // Encourage any GC-related threads to start up. - //TreeSet threadsBeforeTest = allThreads(); + TreeSet threadsBeforeTest = allThreads(); PeerConnectionFactory factory = new PeerConnectionFactory(); // Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging. @@ -646,8 +652,11 @@ private void doTest() throws Exception { IceConnectionState.CHECKING); offeringExpectations.expectIceConnectionChange( IceConnectionState.CONNECTED); - offeringExpectations.expectIceConnectionChange( - IceConnectionState.COMPLETED); + // TODO(bemasc): uncomment once delivery of ICECompleted is reliable + // (https://code.google.com/p/webrtc/issues/detail?id=3021). + // + // offeringExpectations.expectIceConnectionChange( + // IceConnectionState.COMPLETED); answeringExpectations.expectIceConnectionChange( IceConnectionState.CHECKING); answeringExpectations.expectIceConnectionChange( @@ -733,11 +742,8 @@ private void doTest() throws Exception { factory.dispose(); System.gc(); - // TODO(ldixon): the usrsctp threads are not cleaned up (issue 2749) and - // caused the assert to fail. We should reenable the assert once issue 2749 - // is fixed. - //TreeSet threadsAfterTest = allThreads(); - //assertEquals(threadsBeforeTest, threadsAfterTest); + TreeSet threadsAfterTest = allThreads(); + assertEquals(threadsBeforeTest, threadsAfterTest); Thread.sleep(100); } diff --git a/talk/app/webrtc/mediaconstraintsinterface.cc b/talk/app/webrtc/mediaconstraintsinterface.cc index c4f9306c0..0ecadd691 100644 --- a/talk/app/webrtc/mediaconstraintsinterface.cc +++ b/talk/app/webrtc/mediaconstraintsinterface.cc @@ -106,6 +106,10 @@ const char MediaConstraintsInterface::kCpuUnderuseThreshold[] = "googCpuUnderuseThreshold"; const char MediaConstraintsInterface::kCpuOveruseThreshold[] = "googCpuOveruseThreshold"; +const char MediaConstraintsInterface::kCpuUnderuseEncodeRsdThreshold[] = + "googCpuUnderuseEncodeRsdThreshold"; +const char MediaConstraintsInterface::kCpuOveruseEncodeRsdThreshold[] = + "googCpuOveruseEncodeRsdThreshold"; const char MediaConstraintsInterface::kCpuOveruseEncodeUsage[] = "googCpuOveruseEncodeUsage"; const char MediaConstraintsInterface::kHighStartBitrate[] = @@ -114,6 +118,9 @@ const char MediaConstraintsInterface::kHighBitrate[] = "googHighBitrate"; const char MediaConstraintsInterface::kVeryHighBitrate[] = "googVeryHighBitrate"; +const char MediaConstraintsInterface::kPayloadPadding[] = "googPayloadPadding"; +const char MediaConstraintsInterface::kOpusFec[] = "googOpusFec"; + // Set |value| to the value associated with the first appearance of |key|, or // return false if |key| is not found. diff --git a/talk/app/webrtc/mediaconstraintsinterface.h b/talk/app/webrtc/mediaconstraintsinterface.h index 4fd8b219b..2ac251623 100644 --- a/talk/app/webrtc/mediaconstraintsinterface.h +++ b/talk/app/webrtc/mediaconstraintsinterface.h @@ -60,7 +60,6 @@ class MediaConstraintsInterface { virtual const Constraints& GetMandatory() const = 0; virtual const Constraints& GetOptional() const = 0; - // Constraint keys used by a local video source. // Specified by draft-alvestrand-constraints-resolution-00b static const char kMinAspectRatio[]; // minAspectRatio @@ -123,10 +122,19 @@ class MediaConstraintsInterface { static const char kCpuOveruseDetection[]; // googCpuOveruseDetection static const char kCpuUnderuseThreshold[]; // googCpuUnderuseThreshold static const char kCpuOveruseThreshold[]; // googCpuOveruseThreshold + // Low cpu adaptation threshold for relative standard deviation of encode + // time. + static const char kCpuUnderuseEncodeRsdThreshold[]; + // High cpu adaptation threshold for relative standard deviation of encode + // time. + static const char kCpuOveruseEncodeRsdThreshold[]; static const char kCpuOveruseEncodeUsage[]; // googCpuOveruseEncodeUsage static const char kHighStartBitrate[]; // googHighStartBitrate static const char kHighBitrate[]; // googHighBitrate static const char kVeryHighBitrate[]; // googVeryHighBitrate + static const char kPayloadPadding[]; // googPayloadPadding + // kOpusFec controls whether we ask the other side to turn on FEC for Opus. + static const char kOpusFec[]; // googOpusFec // The prefix of internal-only constraints whose JS set values should be // stripped by Chrome before passed down to Libjingle. diff --git a/talk/app/webrtc/mediastream_unittest.cc b/talk/app/webrtc/mediastream_unittest.cc index bb2d50e6a..113242faf 100644 --- a/talk/app/webrtc/mediastream_unittest.cc +++ b/talk/app/webrtc/mediastream_unittest.cc @@ -33,7 +33,8 @@ #include "talk/base/refcount.h" #include "talk/base/scoped_ptr.h" #include "talk/base/gunit.h" -#include "testing/base/public/gmock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" static const char kStreamLabel1[] = "local_stream_1"; static const char kVideoTrackId[] = "dummy_video_cam_1"; diff --git a/talk/app/webrtc/mediastreamhandler_unittest.cc b/talk/app/webrtc/mediastreamhandler_unittest.cc index 6eedb7e89..9a53f3556 100644 --- a/talk/app/webrtc/mediastreamhandler_unittest.cc +++ b/talk/app/webrtc/mediastreamhandler_unittest.cc @@ -38,7 +38,8 @@ #include "talk/base/gunit.h" #include "talk/media/base/fakevideocapturer.h" #include "talk/media/base/mediachannel.h" -#include "testing/base/public/gmock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" using ::testing::_; using ::testing::Exactly; diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc index e1b33f102..99f627a1d 100644 --- a/talk/app/webrtc/mediastreamsignaling.cc +++ b/talk/app/webrtc/mediastreamsignaling.cc @@ -198,14 +198,8 @@ void MediaStreamSignaling::TearDown() { bool MediaStreamSignaling::IsSctpSidAvailable(int sid) const { if (sid < 0 || sid > static_cast(cricket::kMaxSctpSid)) return false; - for (SctpDataChannels::const_iterator iter = sctp_data_channels_.begin(); - iter != sctp_data_channels_.end(); - ++iter) { - if ((*iter)->id() == sid) { - return false; - } - } - return true; + + return FindDataChannelBySid(sid) < 0; } // Gets the first unused odd/even id based on the DTLS role. If |role| is @@ -280,6 +274,23 @@ bool MediaStreamSignaling::AddDataChannelFromOpenMessage( return true; } +void MediaStreamSignaling::RemoveSctpDataChannel(int sid) { + for (SctpDataChannels::iterator iter = sctp_data_channels_.begin(); + iter != sctp_data_channels_.end(); + ++iter) { + if ((*iter)->id() == sid) { + sctp_data_channels_.erase(iter); + + if (talk_base::IsEven(sid) && sid <= last_allocated_sctp_even_sid_) { + last_allocated_sctp_even_sid_ = sid - 2; + } else if (talk_base::IsOdd(sid) && sid <= last_allocated_sctp_odd_sid_) { + last_allocated_sctp_odd_sid_ = sid - 2; + } + return; + } + } +} + bool MediaStreamSignaling::AddLocalStream(MediaStreamInterface* local_stream) { if (local_streams_->find(local_stream->label()) != NULL) { LOG(LS_WARNING) << "MediaStream with label " << local_stream->label() @@ -487,12 +498,19 @@ void MediaStreamSignaling::OnVideoChannelClose() { } void MediaStreamSignaling::OnDataChannelClose() { - RtpDataChannels::iterator it1 = rtp_data_channels_.begin(); - for (; it1 != rtp_data_channels_.end(); ++it1) { + // Use a temporary copy of the RTP/SCTP DataChannel list because the + // DataChannel may callback to us and try to modify the list. + RtpDataChannels temp_rtp_dcs; + temp_rtp_dcs.swap(rtp_data_channels_); + RtpDataChannels::iterator it1 = temp_rtp_dcs.begin(); + for (; it1 != temp_rtp_dcs.end(); ++it1) { it1->second->OnDataEngineClose(); } - SctpDataChannels::iterator it2 = sctp_data_channels_.begin(); - for (; it2 != sctp_data_channels_.end(); ++it2) { + + SctpDataChannels temp_sctp_dcs; + temp_sctp_dcs.swap(sctp_data_channels_); + SctpDataChannels::iterator it2 = temp_sctp_dcs.begin(); + for (; it2 != temp_sctp_dcs.end(); ++it2) { (*it2)->OnDataEngineClose(); } } @@ -953,6 +971,17 @@ void MediaStreamSignaling::OnDtlsRoleReadyForSctp(talk_base::SSLRole role) { } } + +void MediaStreamSignaling::OnRemoteSctpDataChannelClosed(uint32 sid) { + int index = FindDataChannelBySid(sid); + if (index < 0) { + LOG(LS_WARNING) << "Unexpected sid " << sid + << " of the remotely closed DataChannel."; + return; + } + sctp_data_channels_[index]->Close(); +} + const MediaStreamSignaling::TrackInfo* MediaStreamSignaling::FindTrackInfo( const MediaStreamSignaling::TrackInfos& infos, @@ -967,4 +996,13 @@ MediaStreamSignaling::FindTrackInfo( return NULL; } +int MediaStreamSignaling::FindDataChannelBySid(int sid) const { + for (size_t i = 0; i < sctp_data_channels_.size(); ++i) { + if (sctp_data_channels_[i]->id() == sid) { + return static_cast(i); + } + } + return -1; +} + } // namespace webrtc diff --git a/talk/app/webrtc/mediastreamsignaling.h b/talk/app/webrtc/mediastreamsignaling.h index dc83b47da..737816616 100644 --- a/talk/app/webrtc/mediastreamsignaling.h +++ b/talk/app/webrtc/mediastreamsignaling.h @@ -37,6 +37,7 @@ #include "talk/app/webrtc/peerconnectioninterface.h" #include "talk/app/webrtc/streamcollection.h" #include "talk/base/scoped_ref_ptr.h" +#include "talk/base/sigslot.h" #include "talk/session/media/mediasession.h" namespace talk_base { @@ -157,7 +158,7 @@ class MediaStreamSignalingObserver { // DataChannel label or SSRC. The DataChannel SSRC is updated with SSRC=0. // The DataChannel change state to kClosed. -class MediaStreamSignaling { +class MediaStreamSignaling : public sigslot::has_slots<> { public: MediaStreamSignaling(talk_base::Thread* signaling_thread, MediaStreamSignalingObserver* stream_observer, @@ -197,6 +198,7 @@ class MediaStreamSignaling { // After we receive an OPEN message, create a data channel and add it. bool AddDataChannelFromOpenMessage(const cricket::ReceiveDataParams& params, const talk_base::Buffer& payload); + void RemoveSctpDataChannel(int sid); // Returns a MediaSessionOptions struct with options decided by |constraints|, // the local MediaStreams and DataChannels. @@ -248,6 +250,7 @@ class MediaStreamSignaling { } void OnDataTransportCreatedForSctp(); void OnDtlsRoleReadyForSctp(talk_base::SSLRole role); + void OnRemoteSctpDataChannelClosed(uint32 sid); private: struct RemotePeerInfo { @@ -368,6 +371,10 @@ class MediaStreamSignaling { const std::string& stream_label, const std::string track_id) const; + // Returns the index of the specified SCTP DataChannel in sctp_data_channels_, + // or -1 if not found. + int FindDataChannelBySid(int sid) const; + RemotePeerInfo remote_info_; talk_base::Thread* signaling_thread_; DataChannelFactory* data_channel_factory_; @@ -388,6 +395,7 @@ class MediaStreamSignaling { typedef std::map > RtpDataChannels; typedef std::vector > SctpDataChannels; + RtpDataChannels rtp_data_channels_; SctpDataChannels sctp_data_channels_; }; diff --git a/talk/app/webrtc/mediastreamsignaling_unittest.cc b/talk/app/webrtc/mediastreamsignaling_unittest.cc index 03452738b..150058eea 100644 --- a/talk/app/webrtc/mediastreamsignaling_unittest.cc +++ b/talk/app/webrtc/mediastreamsignaling_unittest.cc @@ -1141,6 +1141,47 @@ TEST_F(MediaStreamSignalingTest, SctpIdAllocationNoReuse) { EXPECT_NE(old_id, new_id); } +// Verifies that SCTP ids of removed DataChannels can be reused. +TEST_F(MediaStreamSignalingTest, SctpIdReusedForRemovedDataChannel) { + int odd_id = 1; + int even_id = 0; + AddDataChannel(cricket::DCT_SCTP, "a", odd_id); + AddDataChannel(cricket::DCT_SCTP, "a", even_id); + + int allocated_id = -1; + ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_SERVER, + &allocated_id)); + EXPECT_EQ(odd_id + 2, allocated_id); + AddDataChannel(cricket::DCT_SCTP, "a", allocated_id); + + ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_CLIENT, + &allocated_id)); + EXPECT_EQ(even_id + 2, allocated_id); + AddDataChannel(cricket::DCT_SCTP, "a", allocated_id); + + signaling_->RemoveSctpDataChannel(odd_id); + signaling_->RemoveSctpDataChannel(even_id); + + // Verifies that removed DataChannel ids are reused. + ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_SERVER, + &allocated_id)); + EXPECT_EQ(odd_id, allocated_id); + + ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_CLIENT, + &allocated_id)); + EXPECT_EQ(even_id, allocated_id); + + // Verifies that used higher DataChannel ids are not reused. + ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_SERVER, + &allocated_id)); + EXPECT_NE(odd_id + 2, allocated_id); + + ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_CLIENT, + &allocated_id)); + EXPECT_NE(even_id + 2, allocated_id); + +} + // Verifies that duplicated label is not allowed for RTP data channel. TEST_F(MediaStreamSignalingTest, RtpDuplicatedLabelNotAllowed) { AddDataChannel(cricket::DCT_RTP, "a", -1); @@ -1193,3 +1234,22 @@ TEST_F(MediaStreamSignalingTest, DuplicatedLabelFromOpenMessageAllowed) { params.ssrc = config.id; EXPECT_TRUE(signaling_->AddDataChannelFromOpenMessage(params, payload)); } + +// Verifies that a DataChannel closed remotely is closed locally. +TEST_F(MediaStreamSignalingTest, + SctpDataChannelClosedLocallyWhenClosedRemotely) { + webrtc::InternalDataChannelInit config; + config.id = 0; + + talk_base::scoped_refptr data_channel = + webrtc::DataChannel::Create( + data_channel_provider_.get(), cricket::DCT_SCTP, "a", config); + ASSERT_TRUE(data_channel.get() != NULL); + EXPECT_EQ(webrtc::DataChannelInterface::kConnecting, + data_channel->state()); + + EXPECT_TRUE(signaling_->AddDataChannel(data_channel.get())); + + signaling_->OnRemoteSctpDataChannelClosed(config.id); + EXPECT_EQ(webrtc::DataChannelInterface::kClosed, data_channel->state()); +} diff --git a/talk/app/webrtc/objc/README b/talk/app/webrtc/objc/README index 777874471..692fbbc56 100644 --- a/talk/app/webrtc/objc/README +++ b/talk/app/webrtc/objc/README @@ -45,6 +45,10 @@ Example of building & using the unittest & app: ninja -C out_mac/Debug libjingle_peerconnection_objc_test && \ ./out_mac/Debug/libjingle_peerconnection_objc_test.app/Contents/MacOS/libjingle_peerconnection_objc_test +- To build & launch the sample app on OSX: + wrmac && gclient runhooks && ninja -C out_mac/Debug AppRTCDemo && \ + ./out_mac/Debug/AppRTCDemo.app/Contents/MacOS/AppRTCDemo + - To build & launch the sample app on the iOS simulator: wrsim && gclient runhooks && ninja -C out_sim/Debug iossim AppRTCDemo && \ ./out_sim/Debug/iossim out_sim/Debug/AppRTCDemo.app @@ -66,14 +70,11 @@ Example of building & using the unittest & app: the Info.plist file to ensure that the Bundle Identifier matches your phone provisioning profile, or use a development wildcard provisioning profile.) +- Alternately, use ios-deploy: + ios-deploy -d -b out_ios/Debug-iphoneos/AppRTCDemo.app - Once installed: - Tap AppRTCDemo on the iOS device's home screen (might have to scroll to find it). - In desktop chrome, navigate to http://apprtc.appspot.com and note the r= room number in the resulting URL; enter that number into the text field on the phone. - - Alternatively, background the app and launch Safari. In Safari, - open the url apprtc://apprtc.appspot.com/?r= where is - the room name. Other options are to put the link in an email/chat - and send it to yourself. Clicking on it will launch AppRTCDemo - and navigate to the room. diff --git a/talk/app/webrtc/objc/RTCDataChannel+Internal.h b/talk/app/webrtc/objc/RTCDataChannel+Internal.h new file mode 100644 index 000000000..a55089193 --- /dev/null +++ b/talk/app/webrtc/objc/RTCDataChannel+Internal.h @@ -0,0 +1,55 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "RTCDataChannel.h" + +#include "talk/app/webrtc/datachannelinterface.h" +#include "talk/base/scoped_ref_ptr.h" + +@interface RTCDataBuffer (Internal) + +@property(nonatomic, readonly) const webrtc::DataBuffer* dataBuffer; + +- (instancetype)initWithDataBuffer:(const webrtc::DataBuffer&)buffer; + +@end + +@interface RTCDataChannelInit (Internal) + +@property(nonatomic, readonly) const webrtc::DataChannelInit* dataChannelInit; + +@end + +@interface RTCDataChannel (Internal) + +@property(nonatomic, readonly) + talk_base::scoped_refptr dataChannel; + +- (instancetype)initWithDataChannel: + (talk_base::scoped_refptr)dataChannel; + +@end diff --git a/talk/app/webrtc/objc/RTCDataChannel.mm b/talk/app/webrtc/objc/RTCDataChannel.mm new file mode 100644 index 000000000..083794037 --- /dev/null +++ b/talk/app/webrtc/objc/RTCDataChannel.mm @@ -0,0 +1,273 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "RTCDataChannel+Internal.h" + +#include "talk/app/webrtc/datachannelinterface.h" + +namespace webrtc { + +class RTCDataChannelObserver : public DataChannelObserver { + public: + RTCDataChannelObserver(RTCDataChannel* channel) { _channel = channel; } + + virtual void OnStateChange() OVERRIDE { + [_channel.delegate channelDidChangeState:_channel]; + } + + virtual void OnMessage(const DataBuffer& buffer) OVERRIDE { + if (!_channel.delegate) { + return; + } + RTCDataBuffer* dataBuffer = + [[RTCDataBuffer alloc] initWithDataBuffer:buffer]; + [_channel.delegate channel:_channel didReceiveMessageWithBuffer:dataBuffer]; + } + + private: + __weak RTCDataChannel* _channel; +}; +} + +// TODO(tkchin): move to shared location +NSString* NSStringFromStdString(const std::string& stdString) { + // std::string may contain null termination character so we construct + // using length. + return [[NSString alloc] initWithBytes:stdString.data() + length:stdString.length() + encoding:NSUTF8StringEncoding]; +} + +std::string StdStringFromNSString(NSString* nsString) { + NSData* charData = [nsString dataUsingEncoding:NSUTF8StringEncoding]; + return std::string(reinterpret_cast([charData bytes]), + [charData length]); +} + +@implementation RTCDataChannelInit { + webrtc::DataChannelInit _dataChannelInit; +} + +- (BOOL)isOrdered { + return _dataChannelInit.ordered; +} + +- (void)setIsOrdered:(BOOL)isOrdered { + _dataChannelInit.ordered = isOrdered; +} + +- (NSInteger)maxRetransmitTime { + return _dataChannelInit.maxRetransmitTime; +} + +- (void)setMaxRetransmitTime:(NSInteger)maxRetransmitTime { + _dataChannelInit.maxRetransmitTime = maxRetransmitTime; +} + +- (NSInteger)maxRetransmits { + return _dataChannelInit.maxRetransmits; +} + +- (void)setMaxRetransmits:(NSInteger)maxRetransmits { + _dataChannelInit.maxRetransmits = maxRetransmits; +} + +- (NSString*)protocol { + return NSStringFromStdString(_dataChannelInit.protocol); +} + +- (void)setProtocol:(NSString*)protocol { + _dataChannelInit.protocol = StdStringFromNSString(protocol); +} + +- (BOOL)isNegotiated { + return _dataChannelInit.negotiated; +} + +- (void)setIsNegotiated:(BOOL)isNegotiated { + _dataChannelInit.negotiated = isNegotiated; +} + +- (NSInteger)streamId { + return _dataChannelInit.id; +} + +- (void)setStreamId:(NSInteger)streamId { + _dataChannelInit.id = streamId; +} + +@end + +@implementation RTCDataChannelInit (Internal) + +- (const webrtc::DataChannelInit*)dataChannelInit { + return &_dataChannelInit; +} + +@end + +@implementation RTCDataBuffer { + talk_base::scoped_ptr _dataBuffer; +} + +- (instancetype)initWithData:(NSData*)data isBinary:(BOOL)isBinary { + NSAssert(data, @"data cannot be nil"); + if (self = [super init]) { + talk_base::Buffer buffer([data bytes], [data length]); + _dataBuffer.reset(new webrtc::DataBuffer(buffer, isBinary)); + } + return self; +} + +- (NSData*)data { + return [NSData dataWithBytes:_dataBuffer->data.data() + length:_dataBuffer->data.length()]; +} + +- (BOOL)isBinary { + return _dataBuffer->binary; +} + +@end + +@implementation RTCDataBuffer (Internal) + +- (instancetype)initWithDataBuffer:(const webrtc::DataBuffer&)buffer { + if (self = [super init]) { + _dataBuffer.reset(new webrtc::DataBuffer(buffer)); + } + return self; +} + +- (const webrtc::DataBuffer*)dataBuffer { + return _dataBuffer.get(); +} + +@end + +@implementation RTCDataChannel { + talk_base::scoped_refptr _dataChannel; + talk_base::scoped_ptr _observer; + BOOL _isObserverRegistered; +} + +- (NSString*)label { + return NSStringFromStdString(_dataChannel->label()); +} + +- (BOOL)isReliable { + return _dataChannel->reliable(); +} + +- (BOOL)isOrdered { + return _dataChannel->ordered(); +} + +- (NSUInteger)maxRetransmitTimeMs { + return _dataChannel->maxRetransmitTime(); +} + +- (NSUInteger)maxRetransmits { + return _dataChannel->maxRetransmits(); +} + +- (NSString*)protocol { + return NSStringFromStdString(_dataChannel->protocol()); +} + +- (BOOL)isNegotiated { + return _dataChannel->negotiated(); +} + +- (NSInteger)streamId { + return _dataChannel->id(); +} + +- (RTCDataChannelState)state { + switch (_dataChannel->state()) { + case webrtc::DataChannelInterface::DataState::kConnecting: + return kRTCDataChannelStateConnecting; + case webrtc::DataChannelInterface::DataState::kOpen: + return kRTCDataChannelStateOpen; + case webrtc::DataChannelInterface::DataState::kClosing: + return kRTCDataChannelStateClosing; + case webrtc::DataChannelInterface::DataState::kClosed: + return kRTCDataChannelStateClosed; + } +} + +- (NSUInteger)bufferedAmount { + return _dataChannel->buffered_amount(); +} + +- (void)setDelegate:(id)delegate { + if (_delegate == delegate) { + return; + } + if (_isObserverRegistered) { + _dataChannel->UnregisterObserver(); + _isObserverRegistered = NO; + } + _delegate = delegate; + if (_delegate) { + _dataChannel->RegisterObserver(_observer.get()); + _isObserverRegistered = YES; + } +} + +- (void)close { + _dataChannel->Close(); +} + +- (BOOL)sendData:(RTCDataBuffer*)data { + return _dataChannel->Send(*data.dataBuffer); +} + +@end + +@implementation RTCDataChannel (Internal) + +- (instancetype)initWithDataChannel: + (talk_base::scoped_refptr) + dataChannel { + NSAssert(dataChannel != NULL, @"dataChannel cannot be NULL"); + if (self = [super init]) { + _dataChannel = dataChannel; + _observer.reset(new webrtc::RTCDataChannelObserver(self)); + } + return self; +} + +- (talk_base::scoped_refptr)dataChannel { + return _dataChannel; +} + +@end diff --git a/talk/app/webrtc/objc/RTCEAGLVideoView+Internal.h b/talk/app/webrtc/objc/RTCEAGLVideoView+Internal.h new file mode 100644 index 000000000..10df2e335 --- /dev/null +++ b/talk/app/webrtc/objc/RTCEAGLVideoView+Internal.h @@ -0,0 +1,36 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#import "RTCEAGLVideoView.h" +#import "RTCVideoRenderer.h" + +// TODO(tkchin): Move declaration to implementation file. Exposed here in order +// to support deprecated methods in RTCVideoRenderer. +@interface RTCEAGLVideoView (Internal) +@end diff --git a/talk/app/webrtc/objc/RTCEAGLVideoView.m b/talk/app/webrtc/objc/RTCEAGLVideoView.m new file mode 100644 index 000000000..5365d9821 --- /dev/null +++ b/talk/app/webrtc/objc/RTCEAGLVideoView.m @@ -0,0 +1,244 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "RTCEAGLVideoView+Internal.h" + +#import + +#import "RTCOpenGLVideoRenderer.h" +#import "RTCVideoRenderer.h" +#import "RTCVideoTrack.h" + +// RTCDisplayLinkTimer wraps a CADisplayLink and is set to fire every two screen +// refreshes, which should be 30fps. We wrap the display link in order to avoid +// a retain cycle since CADisplayLink takes a strong reference onto its target. +// The timer is paused by default. +@interface RTCDisplayLinkTimer : NSObject + +@property(nonatomic) BOOL isPaused; + +- (instancetype)initWithTimerHandler:(void (^)(void))timerHandler; +- (void)invalidate; + +@end + +@implementation RTCDisplayLinkTimer { + CADisplayLink* _displayLink; + void (^_timerHandler)(void); +} + +- (instancetype)initWithTimerHandler:(void (^)(void))timerHandler { + NSParameterAssert(timerHandler); + if (self = [super init]) { + _timerHandler = timerHandler; + _displayLink = + [CADisplayLink displayLinkWithTarget:self + selector:@selector(displayLinkDidFire:)]; + _displayLink.paused = YES; + // Set to half of screen refresh, which should be 30fps. + [_displayLink setFrameInterval:2]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] + forMode:NSRunLoopCommonModes]; + } + return self; +} + +- (void)dealloc { + [self invalidate]; +} + +- (BOOL)isPaused { + return _displayLink.paused; +} + +- (void)setIsPaused:(BOOL)isPaused { + _displayLink.paused = isPaused; +} + +- (void)invalidate { + [_displayLink invalidate]; +} + +- (void)displayLinkDidFire:(CADisplayLink*)displayLink { + _timerHandler(); +} + +@end + +@interface RTCEAGLVideoView () +// |i420Frame| is set when we receive a frame from a worker thread and is read +// from the display link callback so atomicity is required. +@property(atomic, strong) RTCI420Frame* i420Frame; +@property(nonatomic, readonly) GLKView* glkView; +@property(nonatomic, readonly) RTCOpenGLVideoRenderer* glRenderer; +@end + +@implementation RTCEAGLVideoView { + RTCDisplayLinkTimer* _timer; + GLKView* _glkView; + RTCOpenGLVideoRenderer* _glRenderer; + RTCVideoRenderer* _videoRenderer; +} + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + EAGLContext* glContext = + [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext]; + + // GLKView manages a framebuffer for us. + _glkView = [[GLKView alloc] initWithFrame:CGRectZero + context:glContext]; + _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; + _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone; + _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone; + _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; + _glkView.delegate = self; + _glkView.layer.masksToBounds = YES; + [self addSubview:_glkView]; + + // Listen to application state in order to clean up OpenGL before app goes + // away. + NSNotificationCenter* notificationCenter = + [NSNotificationCenter defaultCenter]; + [notificationCenter addObserver:self + selector:@selector(willResignActive) + name:UIApplicationWillResignActiveNotification + object:nil]; + [notificationCenter addObserver:self + selector:@selector(didBecomeActive) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + + // Frames are received on a separate thread, so we poll for current frame + // using a refresh rate proportional to screen refresh frequency. This + // occurs on the main thread. + __weak RTCEAGLVideoView* weakSelf = self; + _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ + RTCEAGLVideoView* strongSelf = weakSelf; + // Don't render if frame hasn't changed. + if (strongSelf.glRenderer.lastDrawnFrame == strongSelf.i420Frame) { + return; + } + // This tells the GLKView that it's dirty, which will then call the + // GLKViewDelegate method implemented below. + [strongSelf.glkView setNeedsDisplay]; + }]; + _videoRenderer = [[RTCVideoRenderer alloc] initWithDelegate:self]; + [self setupGL]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + UIApplicationState appState = + [UIApplication sharedApplication].applicationState; + if (appState == UIApplicationStateActive) { + [self teardownGL]; + } + [_timer invalidate]; +} + +- (void)setVideoTrack:(RTCVideoTrack*)videoTrack { + if (_videoTrack == videoTrack) { + return; + } + [_videoTrack removeRenderer:_videoRenderer]; + _videoTrack = videoTrack; + [_videoTrack addRenderer:_videoRenderer]; + // TODO(tkchin): potentially handle changes in track state - e.g. render + // black if track fails. +} + +#pragma mark - UIView + +- (void)layoutSubviews { + [super layoutSubviews]; + _glkView.frame = self.bounds; +} + +#pragma mark - GLKViewDelegate + +// This method is called when the GLKView's content is dirty and needs to be +// redrawn. This occurs on main thread. +- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { + if (self.i420Frame) { + // The renderer will draw the frame to the framebuffer corresponding to the + // one used by |view|. + [_glRenderer drawFrame:self.i420Frame]; + } +} + +#pragma mark - Private + +- (void)setupGL { + [_glRenderer setupGL]; + _timer.isPaused = NO; +} + +- (void)teardownGL { + _timer.isPaused = YES; + [_glkView deleteDrawable]; + [_glRenderer teardownGL]; +} + +- (void)didBecomeActive { + [self setupGL]; +} + +- (void)willResignActive { + [self teardownGL]; +} + +@end + +@implementation RTCEAGLVideoView (Internal) + +#pragma mark - RTCVideoRendererDelegate + +// These methods are called when the video track has frame information to +// provide. This occurs on non-main thread. +- (void)renderer:(RTCVideoRenderer*)renderer + didSetSize:(CGSize)size { + __weak RTCEAGLVideoView* weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + RTCEAGLVideoView* strongSelf = weakSelf; + [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size]; + }); +} + +- (void)renderer:(RTCVideoRenderer*)renderer + didReceiveFrame:(RTCI420Frame*)frame { + self.i420Frame = frame; +} + +@end diff --git a/talk/app/webrtc/objc/RTCEnumConverter.h b/talk/app/webrtc/objc/RTCEnumConverter.h index 0e83719d5..d33709d30 100644 --- a/talk/app/webrtc/objc/RTCEnumConverter.h +++ b/talk/app/webrtc/objc/RTCEnumConverter.h @@ -42,6 +42,9 @@ + (RTCSignalingState)convertSignalingStateToObjC: (webrtc::PeerConnectionInterface::SignalingState)nativeState; ++ (webrtc::PeerConnectionInterface::StatsOutputLevel) + convertStatsOutputLevelToNative:(RTCStatsOutputLevel)statsOutputLevel; + + (RTCSourceState)convertSourceStateToObjC: (webrtc::MediaSourceInterface::SourceState)nativeState; diff --git a/talk/app/webrtc/objc/RTCEnumConverter.mm b/talk/app/webrtc/objc/RTCEnumConverter.mm index 7c81c8d85..9d019419c 100644 --- a/talk/app/webrtc/objc/RTCEnumConverter.mm +++ b/talk/app/webrtc/objc/RTCEnumConverter.mm @@ -81,6 +81,16 @@ + (RTCSignalingState)convertSignalingStateToObjC: } } ++ (webrtc::PeerConnectionInterface::StatsOutputLevel) + convertStatsOutputLevelToNative:(RTCStatsOutputLevel)statsOutputLevel { + switch (statsOutputLevel) { + case RTCStatsOutputLevelStandard: + return webrtc::PeerConnectionInterface::kStatsOutputLevelStandard; + case RTCStatsOutputLevelDebug: + return webrtc::PeerConnectionInterface::kStatsOutputLevelDebug; + } +} + + (RTCSourceState)convertSourceStateToObjC: (webrtc::MediaSourceInterface::SourceState)nativeState { switch (nativeState) { diff --git a/talk/media/devices/iosdeviceinfo.cc b/talk/app/webrtc/objc/RTCI420Frame+Internal.h similarity index 83% rename from talk/media/devices/iosdeviceinfo.cc rename to talk/app/webrtc/objc/RTCI420Frame+Internal.h index 8b65c1428..622c0b31e 100644 --- a/talk/media/devices/iosdeviceinfo.cc +++ b/talk/app/webrtc/objc/RTCI420Frame+Internal.h @@ -1,6 +1,6 @@ /* * libjingle - * Copyright 2012 Google Inc. + * Copyright 2014, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -25,16 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/media/devices/deviceinfo.h" +#import "RTCI420Frame.h" -namespace cricket { +#include "talk/media/base/videoframe.h" -bool GetUsbId(const Device& device, std::string* usb_id) { - return false; -} +@interface RTCI420Frame (Internal) -bool GetUsbVersion(const Device& device, std::string* usb_version) { - return false; -} +- (instancetype)initWithVideoFrame:(const cricket::VideoFrame*)videoFrame; -} // namespace cricket +@end diff --git a/talk/app/webrtc/objc/RTCI420Frame.mm b/talk/app/webrtc/objc/RTCI420Frame.mm index df84fc15e..eff3102e2 100644 --- a/talk/app/webrtc/objc/RTCI420Frame.mm +++ b/talk/app/webrtc/objc/RTCI420Frame.mm @@ -27,8 +27,68 @@ #import "RTCI420Frame.h" -@implementation RTCI420Frame +#include "talk/base/scoped_ptr.h" +#include "talk/media/base/videoframe.h" -// TODO(hughv): Should this just be a cricket::VideoFrame wrapper object? +@implementation RTCI420Frame { + talk_base::scoped_ptr _videoFrame; +} + +- (NSUInteger)width { + return _videoFrame->GetWidth(); +} + +- (NSUInteger)height { + return _videoFrame->GetHeight(); +} + +- (NSUInteger)chromaWidth { + return _videoFrame->GetChromaWidth(); +} + +- (NSUInteger)chromaHeight { + return _videoFrame->GetChromaHeight(); +} + +- (NSUInteger)chromaSize { + return _videoFrame->GetChromaSize(); +} + +- (const uint8_t*)yPlane { + return _videoFrame->GetYPlane(); +} + +- (const uint8_t*)uPlane { + return _videoFrame->GetUPlane(); +} + +- (const uint8_t*)vPlane { + return _videoFrame->GetVPlane(); +} + +- (NSInteger)yPitch { + return _videoFrame->GetYPitch(); +} + +- (NSInteger)uPitch { + return _videoFrame->GetUPitch(); +} + +- (NSInteger)vPitch { + return _videoFrame->GetVPitch(); +} + +@end + +@implementation RTCI420Frame (Internal) + +- (instancetype)initWithVideoFrame:(cricket::VideoFrame*)videoFrame { + if (self = [super init]) { + // Keep a shallow copy of the video frame. The underlying frame buffer is + // not copied. + _videoFrame.reset(videoFrame->Copy()); + } + return self; +} @end diff --git a/talk/app/webrtc/objc/RTCMediaStreamTrack.mm b/talk/app/webrtc/objc/RTCMediaStreamTrack.mm index 0c7fc5c0d..593131200 100644 --- a/talk/app/webrtc/objc/RTCMediaStreamTrack.mm +++ b/talk/app/webrtc/objc/RTCMediaStreamTrack.mm @@ -32,8 +32,24 @@ #import "RTCMediaStreamTrack+Internal.h" #import "RTCEnumConverter.h" +namespace webrtc { + +class RTCMediaStreamTrackObserver : public ObserverInterface { + public: + RTCMediaStreamTrackObserver(RTCMediaStreamTrack* track) { _track = track; } + + virtual void OnChanged() OVERRIDE { + [_track.delegate mediaStreamTrackDidChange:_track]; + } + + private: + __weak RTCMediaStreamTrack* _track; +}; +} + @implementation RTCMediaStreamTrack { talk_base::scoped_refptr _mediaTrack; + talk_base::scoped_ptr _observer; } @synthesize label; @@ -91,13 +107,19 @@ - (id)initWithMediaTrack: self = nil; return nil; } - if ((self = [super init])) { + if (self = [super init]) { _mediaTrack = mediaTrack; label = @(mediaTrack->id().c_str()); + _observer.reset(new webrtc::RTCMediaStreamTrackObserver(self)); + _mediaTrack->RegisterObserver(_observer.get()); } return self; } +- (void)dealloc { + _mediaTrack->UnregisterObserver(_observer.get()); +} + - (talk_base::scoped_refptr)mediaTrack { return _mediaTrack; } diff --git a/talk/app/webrtc/objc/RTCNSGLVideoView.m b/talk/app/webrtc/objc/RTCNSGLVideoView.m new file mode 100644 index 000000000..39f3678bf --- /dev/null +++ b/talk/app/webrtc/objc/RTCNSGLVideoView.m @@ -0,0 +1,187 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "RTCNSGLVideoView.h" + +#import +#import +#import "RTCOpenGLVideoRenderer.h" +#import "RTCVideoRenderer.h" + +@interface RTCNSGLVideoView () +// |i420Frame| is set when we receive a frame from a worker thread and is read +// from the display link callback so atomicity is required. +@property(atomic, strong) RTCI420Frame* i420Frame; +@property(atomic, strong) RTCOpenGLVideoRenderer* glRenderer; +- (void)drawFrame; +@end + +static CVReturn OnDisplayLinkFired(CVDisplayLinkRef displayLink, + const CVTimeStamp* now, + const CVTimeStamp* outputTime, + CVOptionFlags flagsIn, + CVOptionFlags* flagsOut, + void* displayLinkContext) { + RTCNSGLVideoView* view = (__bridge RTCNSGLVideoView*)displayLinkContext; + [view drawFrame]; + return kCVReturnSuccess; +} + +@implementation RTCNSGLVideoView { + CVDisplayLinkRef _displayLink; + RTCVideoRenderer* _videoRenderer; +} + +- (instancetype)initWithFrame:(NSRect)frame + pixelFormat:(NSOpenGLPixelFormat*)format { + if (self = [super initWithFrame:frame pixelFormat:format]) { + _videoRenderer = [[RTCVideoRenderer alloc] initWithDelegate:self]; + } + return self; +} + +- (void)dealloc { + [self teardownDisplayLink]; +} + +- (void)drawRect:(NSRect)rect { + [self drawFrame]; +} + +- (void)reshape { + [super reshape]; + NSRect frame = [self frame]; + CGLLockContext([[self openGLContext] CGLContextObj]); + glViewport(0, 0, frame.size.width, frame.size.height); + CGLUnlockContext([[self openGLContext] CGLContextObj]); +} + +- (void)lockFocus { + NSOpenGLContext* context = [self openGLContext]; + [super lockFocus]; + if ([context view] != self) { + [context setView:self]; + } + [context makeCurrentContext]; +} + +- (void)prepareOpenGL { + [super prepareOpenGL]; + if (!self.glRenderer) { + self.glRenderer = + [[RTCOpenGLVideoRenderer alloc] initWithContext:[self openGLContext]]; + } + [self.glRenderer setupGL]; + [self setupDisplayLink]; +} + +- (void)clearGLContext { + [self.glRenderer teardownGL]; + self.glRenderer = nil; + [super clearGLContext]; +} + +- (void)setVideoTrack:(RTCVideoTrack*)videoTrack { + if (_videoTrack == videoTrack) { + return; + } + if (_videoTrack) { + [_videoTrack removeRenderer:_videoRenderer]; + CVDisplayLinkStop(_displayLink); + } + _videoTrack = videoTrack; + if (_videoTrack) { + [_videoTrack addRenderer:_videoRenderer]; + CVDisplayLinkStart(_displayLink); + } +} + +#pragma mark - RTCVideoRendererDelegate + +// These methods are called when the video track has frame information to +// provide. This occurs on non-main thread. +- (void)renderer:(RTCVideoRenderer*)renderer + didSetSize:(CGSize)size { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate videoView:self didChangeVideoSize:size]; + }); +} + +- (void)renderer:(RTCVideoRenderer*)renderer + didReceiveFrame:(RTCI420Frame*)frame { + self.i420Frame = frame; +} + +#pragma mark - Private + +- (void)drawFrame { + RTCI420Frame* i420Frame = self.i420Frame; + if (i420Frame && self.glRenderer.lastDrawnFrame != i420Frame) { + // This method may be called from CVDisplayLink callback which isn't on the + // main thread so we have to lock the GL context before drawing. + CGLLockContext([[self openGLContext] CGLContextObj]); + [self.glRenderer drawFrame:i420Frame]; + CGLUnlockContext([[self openGLContext] CGLContextObj]); + } +} + +- (void)setupDisplayLink { + if (_displayLink) { + return; + } + // Synchronize buffer swaps with vertical refresh rate. + GLint swapInt = 1; + [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; + + // Create display link. + CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); + CVDisplayLinkSetOutputCallback(_displayLink, + &OnDisplayLinkFired, + (__bridge void*)self); + // Set the display link for the current renderer. + CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; + CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; + CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( + _displayLink, cglContext, cglPixelFormat); + if (_videoTrack) { + CVDisplayLinkStart(_displayLink); + } +} + +- (void)teardownDisplayLink { + if (!_displayLink) { + return; + } + CVDisplayLinkRelease(_displayLink); + _displayLink = NULL; +} + +@end diff --git a/talk/app/webrtc/objc/RTCOpenGLVideoRenderer.mm b/talk/app/webrtc/objc/RTCOpenGLVideoRenderer.mm new file mode 100644 index 000000000..9ee0216cb --- /dev/null +++ b/talk/app/webrtc/objc/RTCOpenGLVideoRenderer.mm @@ -0,0 +1,457 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "RTCOpenGLVideoRenderer.h" + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import "RTCI420Frame.h" + +// TODO(tkchin): check and log openGL errors. Methods here return BOOLs in +// anticipation of that happening in the future. + +#if TARGET_OS_IPHONE +#define RTC_PIXEL_FORMAT GL_LUMINANCE +#define SHADER_VERSION +#define VERTEX_SHADER_IN "attribute" +#define VERTEX_SHADER_OUT "varying" +#define FRAGMENT_SHADER_IN "varying" +#define FRAGMENT_SHADER_OUT +#define FRAGMENT_SHADER_COLOR "gl_FragColor" +#define FRAGMENT_SHADER_TEXTURE "texture2D" +#else +#define RTC_PIXEL_FORMAT GL_RED +#define SHADER_VERSION "#version 150\n" +#define VERTEX_SHADER_IN "in" +#define VERTEX_SHADER_OUT "out" +#define FRAGMENT_SHADER_IN "in" +#define FRAGMENT_SHADER_OUT "out vec4 fragColor;\n" +#define FRAGMENT_SHADER_COLOR "fragColor" +#define FRAGMENT_SHADER_TEXTURE "texture" +#endif + +// Vertex shader doesn't do anything except pass coordinates through. +static const char kVertexShaderSource[] = + SHADER_VERSION + VERTEX_SHADER_IN " vec2 position;\n" + VERTEX_SHADER_IN " vec2 texcoord;\n" + VERTEX_SHADER_OUT " vec2 v_texcoord;\n" + "void main() {\n" + " gl_Position = vec4(position.x, position.y, 0.0, 1.0);\n" + " v_texcoord = texcoord;\n" + "}\n"; + +// Fragment shader converts YUV values from input textures into a final RGB +// pixel. The conversion formula is from http://www.fourcc.org/fccyvrgb.php. +static const char kFragmentShaderSource[] = + SHADER_VERSION + "precision highp float;" + FRAGMENT_SHADER_IN " vec2 v_texcoord;\n" + "uniform lowp sampler2D s_textureY;\n" + "uniform lowp sampler2D s_textureU;\n" + "uniform lowp sampler2D s_textureV;\n" + FRAGMENT_SHADER_OUT + "void main() {\n" + " float y, u, v, r, g, b;\n" + " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n" + " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n" + " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n" + " u = u - 0.5;\n" + " v = v - 0.5;\n" + " r = y + 1.403 * v;\n" + " g = y - 0.344 * u - 0.714 * v;\n" + " b = y + 1.770 * u;\n" + " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n" + " }\n"; + +// Compiles a shader of the given |type| with GLSL source |source| and returns +// the shader handle or 0 on error. +GLuint CreateShader(GLenum type, const GLchar* source) { + GLuint shader = glCreateShader(type); + if (!shader) { + return 0; + } + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + GLint compileStatus = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); + if (compileStatus == GL_FALSE) { + glDeleteShader(shader); + shader = 0; + } + return shader; +} + +// Links a shader program with the given vertex and fragment shaders and +// returns the program handle or 0 on error. +GLuint CreateProgram(GLuint vertexShader, GLuint fragmentShader) { + if (vertexShader == 0 || fragmentShader == 0) { + return 0; + } + GLuint program = glCreateProgram(); + if (!program) { + return 0; + } + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus == GL_FALSE) { + glDeleteProgram(program); + program = 0; + } + return program; +} + +// When modelview and projection matrices are identity (default) the world is +// contained in the square around origin with unit size 2. Drawing to these +// coordinates is equivalent to drawing to the entire screen. The texture is +// stretched over that square using texture coordinates (u, v) that range +// from (0, 0) to (1, 1) inclusive. Texture coordinates are flipped vertically +// here because the incoming frame has origin in upper left hand corner but +// OpenGL expects origin in bottom left corner. +const GLfloat gVertices[] = { + // X, Y, U, V. + -1, -1, 0, 1, // Bottom left. + 1, -1, 1, 1, // Bottom right. + 1, 1, 1, 0, // Top right. + -1, 1, 0, 0, // Top left. +}; + +// |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets +// of 3 textures are used here, one for each of the Y, U and V planes. Having +// two sets alleviates CPU blockage in the event that the GPU is asked to render +// to a texture that is already in use. +static const GLsizei kNumTextureSets = 2; +static const GLsizei kNumTextures = 3 * kNumTextureSets; + +@implementation RTCOpenGLVideoRenderer { +#if TARGET_OS_IPHONE + EAGLContext* _context; +#else + NSOpenGLContext* _context; +#endif + BOOL _isInitialized; + NSUInteger _currentTextureSet; + // Handles for OpenGL constructs. + GLuint _textures[kNumTextures]; + GLuint _program; +#if !TARGET_OS_IPHONE + GLuint _vertexArray; +#endif + GLuint _vertexBuffer; + GLint _position; + GLint _texcoord; + GLint _ySampler; + GLint _uSampler; + GLint _vSampler; +} + ++ (void)initialize { + // Disable dithering for performance. + glDisable(GL_DITHER); +} + +#if TARGET_OS_IPHONE +- (instancetype)initWithContext:(EAGLContext*)context { +#else +- (instancetype)initWithContext:(NSOpenGLContext*)context { +#endif + NSAssert(context != nil, @"context cannot be nil"); + if (self = [super init]) { + _context = context; + } + return self; +} + +- (BOOL)drawFrame:(RTCI420Frame*)frame { + if (!_isInitialized) { + return NO; + } + if (_lastDrawnFrame == frame) { + return NO; + } + [self ensureGLContext]; + if (![self updateTextureSizesForFrame:frame] || + ![self updateTextureDataForFrame:frame]) { + return NO; + } + glClear(GL_COLOR_BUFFER_BIT); +#if !TARGET_OS_IPHONE + glBindVertexArray(_vertexArray); +#endif + glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); +#if !TARGET_OS_IPHONE + [_context flushBuffer]; +#endif + _lastDrawnFrame = frame; + return YES; +} + +- (void)setupGL { + if (_isInitialized) { + return; + } + [self ensureGLContext]; + if (![self setupProgram]) { + return; + } + if (![self setupTextures]) { + return; + } + if (![self setupVertices]) { + return; + } + glUseProgram(_program); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glClearColor(0, 0, 0, 1); + _isInitialized = YES; +} + +- (void)teardownGL { + if (!_isInitialized) { + return; + } + [self ensureGLContext]; + glDeleteProgram(_program); + _program = 0; + glDeleteTextures(kNumTextures, _textures); + glDeleteBuffers(1, &_vertexBuffer); + _vertexBuffer = 0; +#if !TARGET_OS_IPHONE + glDeleteVertexArrays(1, &_vertexArray); +#endif + _isInitialized = NO; +} + +#pragma mark - Private + +- (void)ensureGLContext { + NSAssert(_context, @"context shouldn't be nil"); +#if TARGET_OS_IPHONE + if ([EAGLContext currentContext] != _context) { + [EAGLContext setCurrentContext:_context]; + } +#else + if ([NSOpenGLContext currentContext] != _context) { + [_context makeCurrentContext]; + } +#endif +} + +- (BOOL)setupProgram { + NSAssert(!_program, @"program already set up"); + GLuint vertexShader = CreateShader(GL_VERTEX_SHADER, kVertexShaderSource); + NSAssert(vertexShader, @"failed to create vertex shader"); + GLuint fragmentShader = + CreateShader(GL_FRAGMENT_SHADER, kFragmentShaderSource); + NSAssert(fragmentShader, @"failed to create fragment shader"); + _program = CreateProgram(vertexShader, fragmentShader); + // Shaders are created only to generate program. + if (vertexShader) { + glDeleteShader(vertexShader); + } + if (fragmentShader) { + glDeleteShader(fragmentShader); + } + if (!_program) { + return NO; + } + _position = glGetAttribLocation(_program, "position"); + _texcoord = glGetAttribLocation(_program, "texcoord"); + _ySampler = glGetUniformLocation(_program, "s_textureY"); + _uSampler = glGetUniformLocation(_program, "s_textureU"); + _vSampler = glGetUniformLocation(_program, "s_textureV"); + if (_position < 0 || _texcoord < 0 || _ySampler < 0 || _uSampler < 0 || + _vSampler < 0) { + return NO; + } + return YES; +} + +- (BOOL)setupTextures { + glGenTextures(kNumTextures, _textures); + // Set parameters for each of the textures we created. + for (GLsizei i = 0; i < kNumTextures; i++) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, _textures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + return YES; +} + +- (BOOL)updateTextureSizesForFrame:(RTCI420Frame*)frame { + if (frame.height == _lastDrawnFrame.height && + frame.width == _lastDrawnFrame.width && + frame.chromaWidth == _lastDrawnFrame.chromaWidth && + frame.chromaHeight == _lastDrawnFrame.chromaHeight) { + return YES; + } + GLsizei lumaWidth = frame.width; + GLsizei lumaHeight = frame.height; + GLsizei chromaWidth = frame.chromaWidth; + GLsizei chromaHeight = frame.chromaHeight; + for (GLint i = 0; i < kNumTextureSets; i++) { + glActiveTexture(GL_TEXTURE0 + i * 3); + glTexImage2D(GL_TEXTURE_2D, + 0, + RTC_PIXEL_FORMAT, + lumaWidth, + lumaHeight, + 0, + RTC_PIXEL_FORMAT, + GL_UNSIGNED_BYTE, + 0); + glActiveTexture(GL_TEXTURE0 + i * 3 + 1); + glTexImage2D(GL_TEXTURE_2D, + 0, + RTC_PIXEL_FORMAT, + chromaWidth, + chromaHeight, + 0, + RTC_PIXEL_FORMAT, + GL_UNSIGNED_BYTE, + 0); + glActiveTexture(GL_TEXTURE0 + i * 3 + 2); + glTexImage2D(GL_TEXTURE_2D, + 0, + RTC_PIXEL_FORMAT, + chromaWidth, + chromaHeight, + 0, + RTC_PIXEL_FORMAT, + GL_UNSIGNED_BYTE, + 0); + } + return YES; +} + +- (BOOL)updateTextureDataForFrame:(RTCI420Frame*)frame { + NSUInteger textureOffset = _currentTextureSet * 3; + NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset"); + NSParameterAssert(frame.yPitch == frame.width); + NSParameterAssert(frame.uPitch == frame.chromaWidth); + NSParameterAssert(frame.vPitch == frame.chromaWidth); + + glActiveTexture(GL_TEXTURE0 + textureOffset); + // When setting texture sampler uniforms, the texture index is used not + // the texture handle. + glUniform1i(_ySampler, textureOffset); + glTexImage2D(GL_TEXTURE_2D, + 0, + RTC_PIXEL_FORMAT, + frame.width, + frame.height, + 0, + RTC_PIXEL_FORMAT, + GL_UNSIGNED_BYTE, + frame.yPlane); + + glActiveTexture(GL_TEXTURE0 + textureOffset + 1); + glUniform1i(_uSampler, textureOffset + 1); + glTexImage2D(GL_TEXTURE_2D, + 0, + RTC_PIXEL_FORMAT, + frame.chromaWidth, + frame.chromaHeight, + 0, + RTC_PIXEL_FORMAT, + GL_UNSIGNED_BYTE, + frame.uPlane); + + glActiveTexture(GL_TEXTURE0 + textureOffset + 2); + glUniform1i(_vSampler, textureOffset + 2); + glTexImage2D(GL_TEXTURE_2D, + 0, + RTC_PIXEL_FORMAT, + frame.chromaWidth, + frame.chromaHeight, + 0, + RTC_PIXEL_FORMAT, + GL_UNSIGNED_BYTE, + frame.vPlane); + + _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets; + return YES; +} + +- (BOOL)setupVertices { +#if !TARGET_OS_IPHONE + NSAssert(!_vertexArray, @"vertex array already set up"); + glGenVertexArrays(1, &_vertexArray); + if (!_vertexArray) { + return NO; + } + glBindVertexArray(_vertexArray); +#endif + NSAssert(!_vertexBuffer, @"vertex buffer already set up"); + glGenBuffers(1, &_vertexBuffer); + if (!_vertexBuffer) { +#if !TARGET_OS_IPHONE + glDeleteVertexArrays(1, &_vertexArray); + _vertexArray = 0; +#endif + return NO; + } + glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_DYNAMIC_DRAW); + + // Read position attribute from |gVertices| with size of 2 and stride of 4 + // beginning at the start of the array. The last argument indicates offset + // of data within |gVertices| as supplied to the vertex buffer. + glVertexAttribPointer( + _position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0); + glEnableVertexAttribArray(_position); + + // Read texcoord attribute from |gVertices| with size of 2 and stride of 4 + // beginning at the first texcoord in the array. The last argument indicates + // offset of data within |gVertices| as supplied to the vertex buffer. + glVertexAttribPointer(_texcoord, + 2, + GL_FLOAT, + GL_FALSE, + 4 * sizeof(GLfloat), + (void*)(2 * sizeof(GLfloat))); + glEnableVertexAttribArray(_texcoord); + + return YES; +} + +@end diff --git a/talk/app/webrtc/objc/RTCPair.m b/talk/app/webrtc/objc/RTCPair.m index 226484522..2b289f5cc 100644 --- a/talk/app/webrtc/objc/RTCPair.m +++ b/talk/app/webrtc/objc/RTCPair.m @@ -40,4 +40,8 @@ - (id)initWithKey:(NSString*)key value:(NSString*)value { return self; } +- (NSString*)description { + return [NSString stringWithFormat:@"%@: %@", _key, _value]; +} + @end diff --git a/talk/app/webrtc/objc/RTCPeerConnection+Internal.h b/talk/app/webrtc/objc/RTCPeerConnection+Internal.h index d1b4639f8..ad1c334a2 100644 --- a/talk/app/webrtc/objc/RTCPeerConnection+Internal.h +++ b/talk/app/webrtc/objc/RTCPeerConnection+Internal.h @@ -28,7 +28,6 @@ #import "RTCPeerConnection.h" #import "RTCPeerConnectionDelegate.h" -#import "RTCPeerConnectionObserver.h" #include "talk/app/webrtc/peerconnectioninterface.h" @@ -37,8 +36,8 @@ @property(nonatomic, assign, readonly) talk_base::scoped_refptr peerConnection; -- (id)initWithPeerConnection:( - talk_base::scoped_refptr)peerConnection - observer:(webrtc::RTCPeerConnectionObserver *)observer; +- (instancetype)initWithFactory:(webrtc::PeerConnectionFactoryInterface*)factory + iceServers:(const webrtc::PeerConnectionInterface::IceServers&)iceServers + constraints:(const webrtc::MediaConstraintsInterface*)constraints; @end diff --git a/talk/app/webrtc/objc/RTCPeerConnection.mm b/talk/app/webrtc/objc/RTCPeerConnection.mm index a7e31a1d7..738fb313f 100644 --- a/talk/app/webrtc/objc/RTCPeerConnection.mm +++ b/talk/app/webrtc/objc/RTCPeerConnection.mm @@ -31,14 +31,19 @@ #import "RTCPeerConnection+Internal.h" +#import "RTCDataChannel+Internal.h" #import "RTCEnumConverter.h" #import "RTCICECandidate+Internal.h" #import "RTCICEServer+Internal.h" #import "RTCMediaConstraints+Internal.h" #import "RTCMediaStream+Internal.h" +#import "RTCMediaStreamTrack+Internal.h" +#import "RTCPeerConnectionObserver.h" #import "RTCSessionDescription+Internal.h" -#import "RTCSessionDescriptonDelegate.h" +#import "RTCSessionDescriptionDelegate.h" #import "RTCSessionDescription.h" +#import "RTCStatsDelegate.h" +#import "RTCStatsReport+Internal.h" #include "talk/app/webrtc/jsep.h" @@ -50,8 +55,9 @@ class RTCCreateSessionDescriptionObserver : public CreateSessionDescriptionObserver { public: - RTCCreateSessionDescriptionObserver(id delegate, - RTCPeerConnection* peerConnection) { + RTCCreateSessionDescriptionObserver( + id delegate, + RTCPeerConnection* peerConnection) { _delegate = delegate; _peerConnection = peerConnection; } @@ -76,13 +82,13 @@ virtual void OnFailure(const std::string& error) OVERRIDE { } private: - id _delegate; + id _delegate; RTCPeerConnection* _peerConnection; }; class RTCSetSessionDescriptionObserver : public SetSessionDescriptionObserver { public: - RTCSetSessionDescriptionObserver(id delegate, + RTCSetSessionDescriptionObserver(id delegate, RTCPeerConnection* peerConnection) { _delegate = delegate; _peerConnection = peerConnection; @@ -104,7 +110,31 @@ virtual void OnFailure(const std::string& error) OVERRIDE { } private: - id _delegate; + id _delegate; + RTCPeerConnection* _peerConnection; +}; + +class RTCStatsObserver : public StatsObserver { + public: + RTCStatsObserver(id delegate, + RTCPeerConnection* peerConnection) { + _delegate = delegate; + _peerConnection = peerConnection; + } + + virtual void OnComplete(const std::vector& reports) OVERRIDE { + NSMutableArray* stats = [NSMutableArray arrayWithCapacity:reports.size()]; + std::vector::const_iterator it = reports.begin(); + for (; it != reports.end(); ++it) { + RTCStatsReport* statsReport = + [[RTCStatsReport alloc] initWithStatsReport:*it]; + [stats addObject:statsReport]; + } + [_delegate peerConnection:_peerConnection didGetStats:stats]; + } + + private: + id _delegate; RTCPeerConnection* _peerConnection; }; } @@ -132,7 +162,16 @@ - (BOOL)addStream:(RTCMediaStream*)stream return YES; } -- (void)createAnswerWithDelegate:(id)delegate +- (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label + config:(RTCDataChannelInit*)config { + std::string labelString([label UTF8String]); + talk_base::scoped_refptr dataChannel = + self.peerConnection->CreateDataChannel(labelString, + config.dataChannelInit); + return [[RTCDataChannel alloc] initWithDataChannel:dataChannel]; +} + +- (void)createAnswerWithDelegate:(id)delegate constraints:(RTCMediaConstraints*)constraints { talk_base::scoped_refptr observer(new talk_base::RefCountedObject< @@ -140,7 +179,7 @@ - (void)createAnswerWithDelegate:(id)delegate self.peerConnection->CreateAnswer(observer, constraints.constraints); } -- (void)createOfferWithDelegate:(id)delegate +- (void)createOfferWithDelegate:(id)delegate constraints:(RTCMediaConstraints*)constraints { talk_base::scoped_refptr observer(new talk_base::RefCountedObject< @@ -154,7 +193,7 @@ - (void)removeStream:(RTCMediaStream*)stream { } - (void)setLocalDescriptionWithDelegate: - (id)delegate + (id)delegate sessionDescription:(RTCSessionDescription*)sdp { talk_base::scoped_refptr observer( new talk_base::RefCountedObject( @@ -163,7 +202,7 @@ - (void)setLocalDescriptionWithDelegate: } - (void)setRemoteDescriptionWithDelegate: - (id)delegate + (id)delegate sessionDescription:(RTCSessionDescription*)sdp { talk_base::scoped_refptr observer( new talk_base::RefCountedObject( @@ -219,23 +258,31 @@ - (void)close { self.peerConnection->Close(); } +- (BOOL)getStatsWithDelegate:(id)delegate + mediaStreamTrack:(RTCMediaStreamTrack*)mediaStreamTrack + statsOutputLevel:(RTCStatsOutputLevel)statsOutputLevel { + talk_base::scoped_refptr observer( + new talk_base::RefCountedObject(delegate, + self)); + webrtc::PeerConnectionInterface::StatsOutputLevel nativeOutputLevel = + [RTCEnumConverter convertStatsOutputLevelToNative:statsOutputLevel]; + return self.peerConnection->GetStats( + observer, mediaStreamTrack.mediaTrack, nativeOutputLevel); +} + @end @implementation RTCPeerConnection (Internal) -- (id)initWithPeerConnection: - (talk_base::scoped_refptr) - peerConnection - observer:(webrtc::RTCPeerConnectionObserver*)observer { - if (!peerConnection || !observer) { - NSAssert(NO, @"nil arguments not allowed"); - self = nil; - return nil; - } - if ((self = [super init])) { - _peerConnection = peerConnection; +- (instancetype)initWithFactory:(webrtc::PeerConnectionFactoryInterface*)factory + iceServers:(const webrtc::PeerConnectionInterface::IceServers&)iceServers + constraints:(const webrtc::MediaConstraintsInterface*)constraints { + NSParameterAssert(factory != NULL); + if (self = [super init]) { + _observer.reset(new webrtc::RTCPeerConnectionObserver(self)); + _peerConnection = factory->CreatePeerConnection( + iceServers, constraints, NULL, NULL, _observer.get()); _localStreams = [[NSMutableArray alloc] init]; - _observer.reset(observer); } return self; } diff --git a/talk/app/webrtc/objc/RTCPeerConnectionFactory.mm b/talk/app/webrtc/objc/RTCPeerConnectionFactory.mm index 81af8e11f..8ada166d6 100644 --- a/talk/app/webrtc/objc/RTCPeerConnectionFactory.mm +++ b/talk/app/webrtc/objc/RTCPeerConnectionFactory.mm @@ -41,7 +41,6 @@ #import "RTCMediaStreamTrack+Internal.h" #import "RTCPeerConnection+Internal.h" #import "RTCPeerConnectionDelegate.h" -#import "RTCPeerConnectionObserver.h" #import "RTCVideoCapturer+Internal.h" #import "RTCVideoSource+Internal.h" #import "RTCVideoTrack+Internal.h" @@ -94,18 +93,11 @@ - (id)init { for (RTCICEServer* server in servers) { iceServers.push_back(server.iceServer); } - webrtc::RTCPeerConnectionObserver* observer = - new webrtc::RTCPeerConnectionObserver(delegate); - webrtc::DTLSIdentityServiceInterface* dummy_dtls_identity_service = NULL; - talk_base::scoped_refptr peerConnection = - self.nativeFactory->CreatePeerConnection(iceServers, - constraints.constraints, - dummy_dtls_identity_service, - observer); RTCPeerConnection* pc = - [[RTCPeerConnection alloc] initWithPeerConnection:peerConnection - observer:observer]; - observer->SetPeerConnection(pc); + [[RTCPeerConnection alloc] initWithFactory:self.nativeFactory.get() + iceServers:iceServers + constraints:constraints.constraints]; + pc.delegate = delegate; return pc; } diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.h b/talk/app/webrtc/objc/RTCPeerConnectionObserver.h index 9edcc82f0..f66b5672e 100644 --- a/talk/app/webrtc/objc/RTCPeerConnectionObserver.h +++ b/talk/app/webrtc/objc/RTCPeerConnectionObserver.h @@ -38,12 +38,9 @@ namespace webrtc { class RTCPeerConnectionObserver : public PeerConnectionObserver { public: - explicit RTCPeerConnectionObserver(id delegate); + RTCPeerConnectionObserver(RTCPeerConnection* peerConnection); virtual ~RTCPeerConnectionObserver(); - // |peerConnection| owns |this|, so outlives it. - void SetPeerConnection(RTCPeerConnection *peerConnection); - virtual void OnError() OVERRIDE; // Triggered when the SignalingState changed. @@ -74,10 +71,7 @@ class RTCPeerConnectionObserver : public PeerConnectionObserver { virtual void OnIceCandidate(const IceCandidateInterface* candidate) OVERRIDE; private: - id _delegate; - // __unsafe_unretained is in fact safe because |_peerConnection| owns |this|; - // see comment on SetPeerConnection() above. - __unsafe_unretained RTCPeerConnection *_peerConnection; + __weak RTCPeerConnection* _peerConnection; }; } // namespace webrtc diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm index 5fb098f44..061ccf0a9 100644 --- a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm +++ b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm @@ -31,6 +31,7 @@ #import "RTCPeerConnectionObserver.h" +#import "RTCDataChannel+Internal.h" #import "RTCICECandidate+Internal.h" #import "RTCMediaStream+Internal.h" #import "RTCEnumConverter.h" @@ -38,68 +39,74 @@ namespace webrtc { RTCPeerConnectionObserver::RTCPeerConnectionObserver( - id delegate) { - _delegate = delegate; -} - -RTCPeerConnectionObserver::~RTCPeerConnectionObserver() {} - -void RTCPeerConnectionObserver::SetPeerConnection( RTCPeerConnection* peerConnection) { _peerConnection = peerConnection; } +RTCPeerConnectionObserver::~RTCPeerConnectionObserver() { +} + void RTCPeerConnectionObserver::OnError() { - [_delegate peerConnectionOnError:_peerConnection]; + [_peerConnection.delegate peerConnectionOnError:_peerConnection]; } void RTCPeerConnectionObserver::OnSignalingChange( PeerConnectionInterface::SignalingState new_state) { - [_delegate peerConnection:_peerConnection - signalingStateChanged:[RTCEnumConverter - convertSignalingStateToObjC:new_state]]; + RTCSignalingState state = + [RTCEnumConverter convertSignalingStateToObjC:new_state]; + [_peerConnection.delegate peerConnection:_peerConnection + signalingStateChanged:state]; } void RTCPeerConnectionObserver::OnAddStream(MediaStreamInterface* stream) { RTCMediaStream* mediaStream = [[RTCMediaStream alloc] initWithMediaStream:stream]; - [_delegate peerConnection:_peerConnection addedStream:mediaStream]; + [_peerConnection.delegate peerConnection:_peerConnection + addedStream:mediaStream]; } void RTCPeerConnectionObserver::OnRemoveStream(MediaStreamInterface* stream) { RTCMediaStream* mediaStream = [[RTCMediaStream alloc] initWithMediaStream:stream]; - [_delegate peerConnection:_peerConnection removedStream:mediaStream]; + [_peerConnection.delegate peerConnection:_peerConnection + removedStream:mediaStream]; } void RTCPeerConnectionObserver::OnDataChannel( DataChannelInterface* data_channel) { - // TODO(hughv): Implement for future version. + RTCDataChannel* dataChannel = + [[RTCDataChannel alloc] initWithDataChannel:data_channel]; + [_peerConnection.delegate peerConnection:_peerConnection + didOpenDataChannel:dataChannel]; } void RTCPeerConnectionObserver::OnRenegotiationNeeded() { - [_delegate peerConnectionOnRenegotiationNeeded:_peerConnection]; + id delegate = _peerConnection.delegate; + [delegate peerConnectionOnRenegotiationNeeded:_peerConnection]; } void RTCPeerConnectionObserver::OnIceConnectionChange( PeerConnectionInterface::IceConnectionState new_state) { - [_delegate peerConnection:_peerConnection - iceConnectionChanged:[RTCEnumConverter - convertIceConnectionStateToObjC:new_state]]; + RTCICEConnectionState state = + [RTCEnumConverter convertIceConnectionStateToObjC:new_state]; + [_peerConnection.delegate peerConnection:_peerConnection + iceConnectionChanged:state]; } void RTCPeerConnectionObserver::OnIceGatheringChange( PeerConnectionInterface::IceGatheringState new_state) { - [_delegate peerConnection:_peerConnection - iceGatheringChanged:[RTCEnumConverter - convertIceGatheringStateToObjC:new_state]]; + RTCICEGatheringState state = + [RTCEnumConverter convertIceGatheringStateToObjC:new_state]; + [_peerConnection.delegate peerConnection:_peerConnection + iceGatheringChanged:state]; } void RTCPeerConnectionObserver::OnIceCandidate( const IceCandidateInterface* candidate) { RTCICECandidate* iceCandidate = [[RTCICECandidate alloc] initWithCandidate:candidate]; - [_delegate peerConnection:_peerConnection gotICECandidate:iceCandidate]; + [_peerConnection.delegate peerConnection:_peerConnection + gotICECandidate:iceCandidate]; } } // namespace webrtc diff --git a/talk/app/webrtc/objc/RTCStatsReport+Internal.h b/talk/app/webrtc/objc/RTCStatsReport+Internal.h new file mode 100644 index 000000000..b17b01a2f --- /dev/null +++ b/talk/app/webrtc/objc/RTCStatsReport+Internal.h @@ -0,0 +1,36 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "RTCStatsReport.h" + +#include "talk/app/webrtc/statstypes.h" + +@interface RTCStatsReport (Internal) + +- (instancetype)initWithStatsReport:(const webrtc::StatsReport&)statsReport; + +@end diff --git a/talk/app/webrtc/objc/RTCStatsReport.mm b/talk/app/webrtc/objc/RTCStatsReport.mm new file mode 100644 index 000000000..8da4b469a --- /dev/null +++ b/talk/app/webrtc/objc/RTCStatsReport.mm @@ -0,0 +1,69 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "RTCStatsReport+Internal.h" + +#import "RTCPair.h" + +@implementation RTCStatsReport + +- (NSString*)description { + NSString* format = @"id: %@, type: %@, timestamp: %f, values: %@"; + return [NSString stringWithFormat:format, + self.reportId, + self.type, + self.timestamp, + self.values]; +} + +@end + +@implementation RTCStatsReport (Internal) + +- (instancetype)initWithStatsReport:(const webrtc::StatsReport&)statsReport { + if (self = [super init]) { + _reportId = @(statsReport.id.c_str()); + _type = @(statsReport.type.c_str()); + _timestamp = statsReport.timestamp; + NSMutableArray* values = + [NSMutableArray arrayWithCapacity:statsReport.values.size()]; + webrtc::StatsReport::Values::const_iterator it = statsReport.values.begin(); + for (; it != statsReport.values.end(); ++it) { + RTCPair* pair = [[RTCPair alloc] initWithKey:@(it->name.c_str()) + value:@(it->value.c_str())]; + [values addObject:pair]; + } + _values = values; + } + return self; +} + +@end diff --git a/talk/app/webrtc/objc/RTCVideoRenderer+Internal.h b/talk/app/webrtc/objc/RTCVideoRenderer+Internal.h index 6672cfabf..22e445ccc 100644 --- a/talk/app/webrtc/objc/RTCVideoRenderer+Internal.h +++ b/talk/app/webrtc/objc/RTCVideoRenderer+Internal.h @@ -31,8 +31,6 @@ @interface RTCVideoRenderer (Internal) -// TODO(hughv): Use smart pointer. -@property(nonatomic, assign, readonly) - webrtc::VideoRendererInterface *videoRenderer; +@property(nonatomic, readonly) webrtc::VideoRendererInterface* videoRenderer; @end diff --git a/talk/app/webrtc/objc/RTCVideoRenderer.mm b/talk/app/webrtc/objc/RTCVideoRenderer.mm index be3d2051b..07041819f 100644 --- a/talk/app/webrtc/objc/RTCVideoRenderer.mm +++ b/talk/app/webrtc/objc/RTCVideoRenderer.mm @@ -32,173 +32,71 @@ #import "RTCVideoRenderer+Internal.h" #if TARGET_OS_IPHONE -#import - -#import "RTCI420Frame.h" -#import "RTCVideoRendererDelegate.h" - -#import "webrtc/modules/video_render/ios/video_render_ios_impl.h" -#import "webrtc/modules/video_render/ios/video_render_ios_view.h" +#import "RTCEAGLVideoView+Internal.h" +#endif +#import "RTCI420Frame+Internal.h" -#include "common_video/interface/i420_video_frame.h" -#include "talk/app/webrtc/mediastreaminterface.h" -#include "talk/media/base/videoframe.h" -#include "webrtc/modules/video_render/include/video_render_defines.h" +namespace webrtc { -// An adapter presenting VideoRendererInterface's API and delegating to -// a VideoRenderCallback. Suitable for feeding to -// VideoTrackInterface::AddRenderer(). -class CallbackConverter : public webrtc::VideoRendererInterface { +class RTCVideoRendererAdapter : public VideoRendererInterface { public: - CallbackConverter(webrtc::VideoRenderCallback* callback, - const uint32_t streamId) - : callback_(callback), streamId_(streamId) {} - - virtual void SetSize(int width, int height) {}; - virtual void RenderFrame(const cricket::VideoFrame* frame) { - // Make this into an I420VideoFrame. - size_t width = frame->GetWidth(); - size_t height = frame->GetHeight(); - - size_t y_plane_size = width * height; - size_t uv_plane_size = frame->GetChromaSize(); - - webrtc::I420VideoFrame i420Frame; - i420Frame.CreateFrame(y_plane_size, - frame->GetYPlane(), - uv_plane_size, - frame->GetUPlane(), - uv_plane_size, - frame->GetVPlane(), - width, - height, - frame->GetYPitch(), - frame->GetUPitch(), - frame->GetVPitch()); + RTCVideoRendererAdapter(RTCVideoRenderer* renderer) { _renderer = renderer; } - i420Frame.set_render_time_ms(frame->GetTimeStamp() / 1000000); + virtual void SetSize(int width, int height) OVERRIDE { + [_renderer.delegate renderer:_renderer + didSetSize:CGSizeMake(width, height)]; + } - callback_->RenderFrame(streamId_, i420Frame); + virtual void RenderFrame(const cricket::VideoFrame* frame) OVERRIDE { + if (!_renderer.delegate) { + return; + } + RTCI420Frame* i420Frame = [[RTCI420Frame alloc] initWithVideoFrame:frame]; + [_renderer.delegate renderer:_renderer didReceiveFrame:i420Frame]; } private: - webrtc::VideoRenderCallback* callback_; - const uint32_t streamId_; + __weak RTCVideoRenderer* _renderer; }; +} @implementation RTCVideoRenderer { - VideoRenderIosView* _renderView; - UIActivityIndicatorView* _activityIndicator; - CallbackConverter* _converter; - talk_base::scoped_ptr _iosRenderer; + talk_base::scoped_ptr _adapter; +#if TARGET_OS_IPHONE + RTCEAGLVideoView* _videoView; +#endif } -@synthesize delegate = _delegate; - -- (id)initWithDelegate:(id)delegate { - // TODO(hughv): Create video renderer. - [self doesNotRecognizeSelector:_cmd]; +- (instancetype)initWithDelegate:(id)delegate { + if (self = [super init]) { + _delegate = delegate; + _adapter.reset(new webrtc::RTCVideoRendererAdapter(self)); + } return self; } -- (id)initWithView:(UIView*)view { - if ((self = [super init])) { - CGRect frame = - CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height); - _renderView = [[VideoRenderIosView alloc] initWithFrame:frame]; - _iosRenderer.reset( - new webrtc::VideoRenderIosImpl(0, (__bridge void*)_renderView, NO)); - if (_iosRenderer->Init() == -1) { - self = nil; - } else { - webrtc::VideoRenderCallback* callback = - _iosRenderer->AddIncomingRenderStream(0, 1, 0, 0, 1, 1); - _converter = new CallbackConverter(callback, 0); - _iosRenderer->StartRender(); - [view addSubview:_renderView]; - _renderView.autoresizingMask = - UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - _renderView.translatesAutoresizingMaskIntoConstraints = YES; - - _activityIndicator = [[UIActivityIndicatorView alloc] - initWithActivityIndicatorStyle: - UIActivityIndicatorViewStyleWhiteLarge]; - _activityIndicator.frame = view.bounds; - _activityIndicator.hidesWhenStopped = YES; - [view addSubview:_activityIndicator]; - _activityIndicator.autoresizingMask = - UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _activityIndicator.translatesAutoresizingMaskIntoConstraints = YES; - [_activityIndicator startAnimating]; - } +#if TARGET_OS_IPHONE +// TODO(tkchin): remove shim for deprecated method. +- (instancetype)initWithView:(UIView*)view { + if (self = [super init]) { + _videoView = [[RTCEAGLVideoView alloc] initWithFrame:view.bounds]; + _videoView.autoresizingMask = + UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + _videoView.translatesAutoresizingMaskIntoConstraints = YES; + [view addSubview:_videoView]; + self.delegate = _videoView; + _adapter.reset(new webrtc::RTCVideoRendererAdapter(self)); } return self; } - -- (void)start { - [_activityIndicator stopAnimating]; - [_activityIndicator removeFromSuperview]; - _iosRenderer->StartRender(); -} - -- (void)stop { - [_activityIndicator stopAnimating]; - [_activityIndicator removeFromSuperview]; - _iosRenderer->StopRender(); -} +#endif @end @implementation RTCVideoRenderer (Internal) - (webrtc::VideoRendererInterface*)videoRenderer { - return _converter; + return _adapter.get(); } @end - -#else // TARGET_OS_IPHONE - -// TODO(fischman): implement an OS/X RTCVideoRenderer (and add to -// RTCPeerConnectionTest!). - -#import "RTCI420Frame.h" -#import "RTCVideoRendererDelegate.h" -@implementation RTCVideoRenderer -@synthesize delegate = _delegate; -+ (RTCVideoRenderer*)videoRendererWithFrame:(CGRect)frame { - // TODO(hughv): Implement. - return nil; -} -- (id)initWithDelegate:(id)delegate { - if ((self = [super init])) { - _delegate = delegate; - // TODO(hughv): Create video renderer. - } - return self; -} -- (id)initWithView:(UIView*)view { - return nil; -} -- (void)setTransform:(CGAffineTransform)transform { -} -- (void)start { -} -- (void)stop { -} - -@end -@implementation RTCVideoRenderer (Internal) -- (id)initWithVideoRenderer:(webrtc::VideoRendererInterface*)videoRenderer { - if ((self = [super init])) { - // TODO(hughv): Implement. - } - return self; -} -- (webrtc::VideoRendererInterface*)videoRenderer { - // TODO(hughv): Implement. - return NULL; -} -@end - -#endif // TARGET_OS_IPHONE diff --git a/talk/app/webrtc/objc/public/RTCDataChannel.h b/talk/app/webrtc/objc/public/RTCDataChannel.h new file mode 100644 index 000000000..762bd662e --- /dev/null +++ b/talk/app/webrtc/objc/public/RTCDataChannel.h @@ -0,0 +1,112 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +// ObjectiveC wrapper for a DataChannelInit object. +@interface RTCDataChannelInit : NSObject + +// Set to YES if ordered delivery is required +@property(nonatomic) BOOL isOrdered; +// Max period in milliseconds in which retransmissions will be sent. After this +// time, no more retransmissions will be sent. -1 if unset. +@property(nonatomic) NSInteger maxRetransmitTimeMs; +// The max number of retransmissions. -1 if unset. +@property(nonatomic) NSInteger maxRetransmits; +// Set to YES if the channel has been externally negotiated and we do not send +// an in-band signalling in the form of an "open" message +@property(nonatomic) BOOL isNegotiated; +// The stream id, or SID, for SCTP data channels. -1 if unset. +@property(nonatomic) NSInteger streamId; +// Set by the application and opaque to the WebRTC implementation. +@property(nonatomic) NSString* protocol; + +@end + +// ObjectiveC wrapper for a DataBuffer object. +@interface RTCDataBuffer : NSObject + +@property(nonatomic, readonly) NSData* data; +@property(nonatomic, readonly) BOOL isBinary; + +- (instancetype)initWithData:(NSData*)data isBinary:(BOOL)isBinary; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +@end + +// Keep in sync with webrtc::DataChannelInterface::DataState +typedef enum { + kRTCDataChannelStateConnecting, + kRTCDataChannelStateOpen, + kRTCDataChannelStateClosing, + kRTCDataChannelStateClosed +} RTCDataChannelState; + +@class RTCDataChannel; +// Protocol for receving data channel state and message events. +@protocol RTCDataChannelDelegate + +// Called when the data channel state has changed. +- (void)channelDidChangeState:(RTCDataChannel*)channel; + +// Called when a data buffer was successfully received. +- (void)channel:(RTCDataChannel*)channel + didReceiveMessageWithBuffer:(RTCDataBuffer*)buffer; + +@end + +// ObjectiveC wrapper for a DataChannel object. +// See talk/app/webrtc/datachannelinterface.h +@interface RTCDataChannel : NSObject + +@property(nonatomic, readonly) NSString* label; +@property(nonatomic, readonly) BOOL isReliable; +@property(nonatomic, readonly) BOOL isOrdered; +@property(nonatomic, readonly) NSUInteger maxRetransmitTime; +@property(nonatomic, readonly) NSUInteger maxRetransmits; +@property(nonatomic, readonly) NSString* protocol; +@property(nonatomic, readonly) BOOL isNegotiated; +@property(nonatomic, readonly) NSInteger streamId; +@property(nonatomic, readonly) RTCDataChannelState state; +@property(nonatomic, readonly) NSUInteger bufferedAmount; +@property(nonatomic, weak) id delegate; + +- (void)close; +- (BOOL)sendData:(RTCDataBuffer*)data; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +@end diff --git a/talk/app/webrtc/objc/public/RTCEAGLVideoView.h b/talk/app/webrtc/objc/public/RTCEAGLVideoView.h new file mode 100644 index 000000000..c38799e86 --- /dev/null +++ b/talk/app/webrtc/objc/public/RTCEAGLVideoView.h @@ -0,0 +1,47 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import + +#import "RTCVideoRenderer.h" + +@class RTCEAGLVideoView; +@protocol RTCEAGLVideoViewDelegate + +- (void)videoView:(RTCEAGLVideoView*)videoView didChangeVideoSize:(CGSize)size; + +@end + +@class RTCVideoTrack; +// RTCEAGLVideoView renders |videoTrack| onto itself using OpenGLES. +@interface RTCEAGLVideoView : UIView + +@property(nonatomic, strong) RTCVideoTrack* videoTrack; +@property(nonatomic, weak) id delegate; + +@end diff --git a/talk/app/webrtc/objc/public/RTCI420Frame.h b/talk/app/webrtc/objc/public/RTCI420Frame.h index bf58085da..737968c12 100644 --- a/talk/app/webrtc/objc/public/RTCI420Frame.h +++ b/talk/app/webrtc/objc/public/RTCI420Frame.h @@ -30,7 +30,24 @@ // RTCI420Frame is an ObjectiveC version of cricket::VideoFrame. @interface RTCI420Frame : NSObject -// TODO(hughv): Implement this when iOS VP8 is ready. +@property(nonatomic, readonly) NSUInteger width; +@property(nonatomic, readonly) NSUInteger height; +@property(nonatomic, readonly) NSUInteger chromaWidth; +@property(nonatomic, readonly) NSUInteger chromaHeight; +@property(nonatomic, readonly) NSUInteger chromaSize; +// These can return NULL if the object is not backed by a buffer. +@property(nonatomic, readonly) const uint8_t* yPlane; +@property(nonatomic, readonly) const uint8_t* uPlane; +@property(nonatomic, readonly) const uint8_t* vPlane; +@property(nonatomic, readonly) NSInteger yPitch; +@property(nonatomic, readonly) NSInteger uPitch; +@property(nonatomic, readonly) NSInteger vPitch; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ @end diff --git a/talk/app/webrtc/objc/public/RTCMediaStreamTrack.h b/talk/app/webrtc/objc/public/RTCMediaStreamTrack.h index f8f9369b5..1b9339d76 100644 --- a/talk/app/webrtc/objc/public/RTCMediaStreamTrack.h +++ b/talk/app/webrtc/objc/public/RTCMediaStreamTrack.h @@ -29,13 +29,21 @@ #import "RTCTypes.h" +@class RTCMediaStreamTrack; +@protocol RTCMediaStreamTrackDelegate + +- (void)mediaStreamTrackDidChange:(RTCMediaStreamTrack*)mediaStreamTrack; + +@end + // RTCMediaStreamTrack implements the interface common to RTCAudioTrack and // RTCVideoTrack. Do not create an instance of this class, rather create one // of the derived classes. @interface RTCMediaStreamTrack : NSObject -@property(nonatomic, assign, readonly) NSString *kind; -@property(nonatomic, assign, readonly) NSString *label; +@property(nonatomic, readonly) NSString* kind; +@property(nonatomic, readonly) NSString* label; +@property(nonatomic, weak) id delegate; - (BOOL)isEnabled; - (BOOL)setEnabled:(BOOL)enabled; diff --git a/talk/app/webrtc/objc/public/RTCNSGLVideoView.h b/talk/app/webrtc/objc/public/RTCNSGLVideoView.h new file mode 100644 index 000000000..fd757cb43 --- /dev/null +++ b/talk/app/webrtc/objc/public/RTCNSGLVideoView.h @@ -0,0 +1,48 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if TARGET_OS_IPHONE +#error "This file targets OSX." +#endif + +#import + +#import "RTCVideoTrack.h" + +@class RTCNSGLVideoView; +@protocol RTCNSGLVideoViewDelegate + +- (void)videoView:(RTCNSGLVideoView*)videoView didChangeVideoSize:(CGSize)size; + +@end + +@interface RTCNSGLVideoView : NSOpenGLView + +@property(nonatomic, strong) RTCVideoTrack* videoTrack; +@property(nonatomic, weak) id delegate; + +@end diff --git a/talk/app/webrtc/objc/public/RTCOpenGLVideoRenderer.h b/talk/app/webrtc/objc/public/RTCOpenGLVideoRenderer.h new file mode 100644 index 000000000..d6744b23f --- /dev/null +++ b/talk/app/webrtc/objc/public/RTCOpenGLVideoRenderer.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +@class RTCI420Frame; + +// RTCOpenGLVideoRenderer issues appropriate OpenGL commands to draw a frame to +// the currently bound framebuffer. Supports OpenGL 3.2 and OpenGLES 2.0. OpenGL +// framebuffer creation and management should be handled elsewhere using the +// same context used to initialize this class. +@interface RTCOpenGLVideoRenderer : NSObject + +// The last successfully drawn frame. Used to avoid drawing frames unnecessarily +// hence saving battery life by reducing load. +@property(nonatomic, readonly) RTCI420Frame* lastDrawnFrame; + +#if TARGET_OS_IPHONE +- (instancetype)initWithContext:(EAGLContext*)context; +#else +- (instancetype)initWithContext:(NSOpenGLContext*)context; +#endif + +// Draws |frame| onto the currently bound OpenGL framebuffer. |setupGL| must be +// called before this function will succeed. +- (BOOL)drawFrame:(RTCI420Frame*)frame; + +// The following methods are used to manage OpenGL resources. On iOS +// applications should release resources when placed in background for use in +// the foreground application. In fact, attempting to call OpenGLES commands +// while in background will result in application termination. + +// Sets up the OpenGL state needed for rendering. +- (void)setupGL; +// Tears down the OpenGL state created by |setupGL|. +- (void)teardownGL; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +@end diff --git a/talk/app/webrtc/objc/public/RTCPeerConnection.h b/talk/app/webrtc/objc/public/RTCPeerConnection.h index c66bac8b4..32a98306e 100644 --- a/talk/app/webrtc/objc/public/RTCPeerConnection.h +++ b/talk/app/webrtc/objc/public/RTCPeerConnection.h @@ -27,12 +27,16 @@ #import "RTCPeerConnectionDelegate.h" +@class RTCDataChannel; +@class RTCDataChannelInit; @class RTCICECandidate; @class RTCICEServers; @class RTCMediaConstraints; @class RTCMediaStream; +@class RTCMediaStreamTrack; @class RTCSessionDescription; -@protocol RTCSessionDescriptonDelegate; +@protocol RTCSessionDescriptionDelegate; +@protocol RTCStatsDelegate; // RTCPeerConnection is an ObjectiveC friendly wrapper around a PeerConnection // object. See the documentation in talk/app/webrtc/peerconnectioninterface.h. @@ -41,6 +45,8 @@ // http://www.w3.org/TR/mediacapture-streams/ @interface RTCPeerConnection : NSObject +@property(nonatomic, weak) id delegate; + // Accessor methods to active local streams. @property(nonatomic, strong, readonly) NSArray *localStreams; @@ -66,26 +72,30 @@ // remote peer is notified. - (void)removeStream:(RTCMediaStream *)stream; +// Create a data channel. +- (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label + config:(RTCDataChannelInit*)config; + // Create a new offer. -// Success or failure will be reported via RTCSessionDescriptonDelegate. -- (void)createOfferWithDelegate:(id)delegate +// Success or failure will be reported via RTCSessionDescriptionDelegate. +- (void)createOfferWithDelegate:(id)delegate constraints:(RTCMediaConstraints *)constraints; // Create an answer to an offer. -// Success or failure will be reported via RTCSessionDescriptonDelegate. -- (void)createAnswerWithDelegate:(id)delegate +// Success or failure will be reported via RTCSessionDescriptionDelegate. +- (void)createAnswerWithDelegate:(id)delegate constraints:(RTCMediaConstraints *)constraints; // Sets the local session description. -// Success or failure will be reported via RTCSessionDescriptonDelegate. +// Success or failure will be reported via RTCSessionDescriptionDelegate. - (void) - setLocalDescriptionWithDelegate:(id)delegate + setLocalDescriptionWithDelegate:(id)delegate sessionDescription:(RTCSessionDescription *)sdp; // Sets the remote session description. -// Success or failure will be reported via RTCSessionDescriptonDelegate. +// Success or failure will be reported via RTCSessionDescriptionDelegate. - (void) - setRemoteDescriptionWithDelegate:(id)delegate + setRemoteDescriptionWithDelegate:(id)delegate sessionDescription:(RTCSessionDescription *)sdp; // Restarts or updates the ICE Agent process of gathering local candidates @@ -99,7 +109,12 @@ // Terminates all media and closes the transport. - (void)close; -// TODO(hughv): Implement GetStats. +// Gets statistics for the media track. If |mediaStreamTrack| is nil statistics +// are gathered for all tracks. +// Statistics information will be reported via RTCStatsDelegate. +- (BOOL)getStatsWithDelegate:(id)delegate + mediaStreamTrack:(RTCMediaStreamTrack*)mediaStreamTrack + statsOutputLevel:(RTCStatsOutputLevel)statsOutputLevel; #ifndef DOXYGEN_SHOULD_SKIP_THIS // Disallow init and don't add to documentation diff --git a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h index 9695be8e0..4b177d504 100644 --- a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h +++ b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h @@ -29,6 +29,7 @@ #import "RTCTypes.h" +@class RTCDataChannel; @class RTCICECandidate; @class RTCMediaStream; @class RTCPeerConnection; @@ -67,4 +68,8 @@ - (void)peerConnection:(RTCPeerConnection *)peerConnection gotICECandidate:(RTCICECandidate *)candidate; +// New data channel has been opened. +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didOpenDataChannel:(RTCDataChannel*)dataChannel; + @end diff --git a/talk/app/webrtc/objc/public/RTCSessionDescriptonDelegate.h b/talk/app/webrtc/objc/public/RTCSessionDescriptionDelegate.h similarity index 91% rename from talk/app/webrtc/objc/public/RTCSessionDescriptonDelegate.h rename to talk/app/webrtc/objc/public/RTCSessionDescriptionDelegate.h index 409aaee5d..ecdc581f5 100644 --- a/talk/app/webrtc/objc/public/RTCSessionDescriptonDelegate.h +++ b/talk/app/webrtc/objc/public/RTCSessionDescriptionDelegate.h @@ -33,9 +33,9 @@ extern NSString* const kRTCSessionDescriptionDelegateErrorDomain; extern int const kRTCSessionDescriptionDelegateErrorCode; -// RTCSessionDescriptonDelegate is a protocol for listening to callback messages -// when RTCSessionDescriptions are created or set. -@protocol RTCSessionDescriptonDelegate +// RTCSessionDescriptionDelegate is a protocol for listening to callback +// messages when RTCSessionDescriptions are created or set. +@protocol RTCSessionDescriptionDelegate // Called when creating a session. - (void)peerConnection:(RTCPeerConnection *)peerConnection diff --git a/talk/app/webrtc/objc/public/RTCVideoRendererDelegate.h b/talk/app/webrtc/objc/public/RTCStatsDelegate.h similarity index 75% rename from talk/app/webrtc/objc/public/RTCVideoRendererDelegate.h rename to talk/app/webrtc/objc/public/RTCStatsDelegate.h index af72bdeb9..3de3dfe96 100644 --- a/talk/app/webrtc/objc/public/RTCVideoRendererDelegate.h +++ b/talk/app/webrtc/objc/public/RTCStatsDelegate.h @@ -1,6 +1,6 @@ /* * libjingle - * Copyright 2013, Google Inc. + * Copyright 2014, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -27,18 +27,13 @@ #import -@class RTCI420Frame; -@class RTCVideoRenderer; +@class RTCPeerConnection; -// RTCVideoRendererDelegate is a protocol for an object that must be -// implemented to get messages when rendering. -@protocol RTCVideoRendererDelegate +// RTCSessionDescriptionDelegate is a protocol for receiving statistic +// reports from RTCPeerConnection. +@protocol RTCStatsDelegate -// The size of the frame. -- (void)videoRenderer:(RTCVideoRenderer *)videoRenderer setSize:(CGSize)size; - -// The frame to be displayed. -- (void)videoRenderer:(RTCVideoRenderer *)videoRenderer - renderFrame:(RTCI420Frame *)frame; +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didGetStats:(NSArray*)stats; // NSArray of RTCStatsReport*. @end diff --git a/talk/app/webrtc/objc/public/RTCStatsReport.h b/talk/app/webrtc/objc/public/RTCStatsReport.h new file mode 100644 index 000000000..784ce6741 --- /dev/null +++ b/talk/app/webrtc/objc/public/RTCStatsReport.h @@ -0,0 +1,45 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +// ObjectiveC friendly wrapper around a StatsReport object. +// See talk/app/webrtc/statsypes.h +@interface RTCStatsReport : NSObject + +@property(nonatomic, readonly) NSString* reportId; +@property(nonatomic, readonly) NSString* type; +@property(nonatomic, readonly) CFTimeInterval timestamp; +@property(nonatomic, readonly) NSArray* values; // NSArray of RTCPair*. + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +@end diff --git a/talk/app/webrtc/objc/public/RTCTypes.h b/talk/app/webrtc/objc/public/RTCTypes.h index 8ff8bf485..2a55200a6 100644 --- a/talk/app/webrtc/objc/public/RTCTypes.h +++ b/talk/app/webrtc/objc/public/RTCTypes.h @@ -55,6 +55,12 @@ typedef enum { RTCSignalingClosed, } RTCSignalingState; +// RTCStatsOutputLevel correspond to webrtc::StatsOutputLevel +typedef enum { + RTCStatsOutputLevelStandard, + RTCStatsOutputLevelDebug, +} RTCStatsOutputLevel; + // RTCSourceState corresponds to the states in webrtc::SourceState. typedef enum { RTCSourceStateInitializing, diff --git a/talk/app/webrtc/objc/public/RTCVideoRenderer.h b/talk/app/webrtc/objc/public/RTCVideoRenderer.h index d6b6a6532..f78746c2f 100644 --- a/talk/app/webrtc/objc/public/RTCVideoRenderer.h +++ b/talk/app/webrtc/objc/public/RTCVideoRenderer.h @@ -26,27 +26,40 @@ */ #import -#import +#if TARGET_OS_IPHONE +#import +#endif -@protocol RTCVideoRendererDelegate; -struct CGRect; -@class UIView; +@class RTCI420Frame; +@class RTCVideoRenderer; + +// RTCVideoRendererDelegate is a protocol for an object that must be +// implemented to get messages when rendering. +@protocol RTCVideoRendererDelegate + +// The size of the frame. +- (void)renderer:(RTCVideoRenderer*)renderer didSetSize:(CGSize)size; + +// The frame to be displayed. +- (void)renderer:(RTCVideoRenderer*)renderer + didReceiveFrame:(RTCI420Frame*)frame; + +@end // Interface for rendering VideoFrames from a VideoTrack @interface RTCVideoRenderer : NSObject -@property(nonatomic, strong) id delegate; - -- (id)initWithView:(UIView*)view; +@property(nonatomic, weak) id delegate; // Initialize the renderer. Requires a delegate which does the actual drawing // of frames. -- (id)initWithDelegate:(id)delegate; +- (instancetype)initWithDelegate:(id)delegate; -// Starts rendering. -- (void)start; -// Stops rendering. It can be restarted again using the 'start' method above. -- (void)stop; +#if TARGET_OS_IPHONE +// DEPRECATED. See https://code.google.com/p/webrtc/issues/detail?id=3341 for +// details. +- (instancetype)initWithView:(UIView*)view; +#endif #ifndef DOXYGEN_SHOULD_SKIP_THIS // Disallow init and don't add to documentation diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h index db97816fc..8bbf98251 100644 --- a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h +++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h @@ -27,11 +27,14 @@ #import +#import "RTCDataChannel.h" #import "RTCPeerConnectionDelegate.h" // Observer of PeerConnection events, used by RTCPeerConnectionTest to check // expectations. -@interface RTCPeerConnectionSyncObserver : NSObject +@interface RTCPeerConnectionSyncObserver + : NSObject +@property(nonatomic) RTCDataChannel* dataChannel; // TODO(hughv): Add support for RTCVideoRendererDelegate when Video is enabled. // Transfer received ICE candidates to the caller. @@ -46,6 +49,9 @@ - (void)expectICECandidates:(int)count; - (void)expectICEConnectionChange:(RTCICEConnectionState)state; - (void)expectICEGatheringChange:(RTCICEGatheringState)state; +- (void)expectDataChannel:(NSString*)label; +- (void)expectStateChange:(RTCDataChannelState)state; +- (void)expectMessage:(NSData*)message isBinary:(BOOL)isBinary; // Wait until all registered expectations above have been observed. - (void)waitForAllExpectationsToBeSatisfied; diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m index 65684033c..c3f898a29 100644 --- a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m +++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m @@ -42,6 +42,9 @@ @implementation RTCPeerConnectionSyncObserver { NSMutableArray* _receivedICECandidates; NSMutableArray* _expectedICEConnectionChanges; NSMutableArray* _expectedICEGatheringChanges; + NSMutableArray* _expectedDataChannels; + NSMutableArray* _expectedStateChanges; + NSMutableArray* _expectedMessages; } - (id)init { @@ -54,6 +57,9 @@ - (id)init { _receivedICECandidates = [NSMutableArray array]; _expectedICEConnectionChanges = [NSMutableArray array]; _expectedICEGatheringChanges = [NSMutableArray array]; + _expectedDataChannels = [NSMutableArray array]; + _expectedMessages = [NSMutableArray array]; + _expectedStateChanges = [NSMutableArray array]; } return self; } @@ -78,7 +84,10 @@ - (BOOL)areAllExpectationsSatisfied { [_expectedICEConnectionChanges count] == 0 && [_expectedICEGatheringChanges count] == 0 && [_expectedAddStreamLabels count] == 0 && - [_expectedRemoveStreamLabels count] == 0; + [_expectedRemoveStreamLabels count] == 0 && + [_expectedDataChannels count] == 0 && + [_expectedStateChanges count] == 0 && + [_expectedMessages count] == 0; // TODO(hughv): Test video state here too. } @@ -116,6 +125,20 @@ - (void)expectICEGatheringChange:(RTCICEGatheringState)state { [_expectedICEGatheringChanges addObject:@((int)state)]; } +- (void)expectDataChannel:(NSString*)label { + [_expectedDataChannels addObject:label]; +} + +- (void)expectStateChange:(RTCDataChannelState)state { + [_expectedStateChanges addObject:@(state)]; +} + +- (void)expectMessage:(NSData*)message isBinary:(BOOL)isBinary { + RTCDataBuffer* buffer = [[RTCDataBuffer alloc] initWithData:message + isBinary:isBinary]; + [_expectedMessages addObject:buffer]; +} + - (void)waitForAllExpectationsToBeSatisfied { // TODO (fischman): Revisit. Keeping in sync with the Java version, but // polling is not optimal. @@ -191,4 +214,37 @@ - (void)peerConnection:(RTCPeerConnection*)peerConnection NSAssert(expectedState == (int)newState, @"Empty expectation array"); } +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didOpenDataChannel:(RTCDataChannel*)dataChannel { + NSString* expectedLabel = + [self popFirstElementAsNSString:_expectedDataChannels]; + NSAssert([expectedLabel isEqual:dataChannel.label], + @"Data channel not expected"); + self.dataChannel = dataChannel; + dataChannel.delegate = self; + NSAssert(kRTCDataChannelStateConnecting == dataChannel.state, + @"Unexpected state"); +} + +#pragma mark - RTCDataChannelDelegate + +- (void)channelDidChangeState:(RTCDataChannel*)channel { + NSAssert([_expectedStateChanges count] > 0, + @"Unexpected state change"); + int expectedState = [self popFirstElementAsInt:_expectedStateChanges]; + NSAssert(expectedState == channel.state, @"Channel state should match"); +} + +- (void)channel:(RTCDataChannel*)channel + didReceiveMessageWithBuffer:(RTCDataBuffer*)buffer { + NSAssert([_expectedMessages count] > 0, + @"Unexpected message received"); + RTCDataBuffer* expectedBuffer = [_expectedMessages objectAtIndex:0]; + NSAssert(expectedBuffer.isBinary == buffer.isBinary, + @"Buffer isBinary should match"); + NSAssert([expectedBuffer.data isEqual:buffer.data], + @"Buffer data should match"); + [_expectedMessages removeObjectAtIndex:0]; +} + @end diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm b/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm index b46df7f41..7a178f39f 100644 --- a/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm +++ b/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm @@ -30,6 +30,7 @@ #import "RTCICEServer.h" #import "RTCMediaConstraints.h" #import "RTCMediaStream.h" +#import "RTCPair.h" #import "RTCPeerConnection.h" #import "RTCPeerConnectionFactory.h" #import "RTCPeerConnectionSyncObserver.h" @@ -94,12 +95,20 @@ - (RTCMediaStream*)addTracksToPeerConnection:(RTCPeerConnection*)pc } - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory { + NSArray* mandatory = @[ + [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"], + [[RTCPair alloc] initWithKey:@"internalSctpDataChannels" value:@"true"], + ]; RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] init]; + RTCMediaConstraints* pcConstraints = + [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory + optionalConstraints:nil]; + RTCPeerConnectionSyncObserver* offeringExpectations = [[RTCPeerConnectionSyncObserver alloc] init]; RTCPeerConnection* pcOffer = [factory peerConnectionWithICEServers:nil - constraints:constraints + constraints:pcConstraints delegate:offeringExpectations]; RTCPeerConnectionSyncObserver* answeringExpectations = @@ -107,7 +116,7 @@ - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory { RTCPeerConnection* pcAnswer = [factory peerConnectionWithICEServers:nil - constraints:constraints + constraints:pcConstraints delegate:answeringExpectations]; // TODO(hughv): Create video capturer RTCVideoCapturer* capturer = nil; @@ -123,6 +132,14 @@ - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory { streamLabel:@"oLMS" videoTrackID:@"oLMSv0" audioTrackID:@"oLMSa0"]; + + RTCDataChannel* offerDC = + [pcOffer createDataChannelWithLabel:@"offerDC" + config:[[RTCDataChannelInit alloc] init]]; + EXPECT_TRUE([offerDC.label isEqual:@"offerDC"]); + offerDC.delegate = offeringExpectations; + offeringExpectations.dataChannel = offerDC; + RTCSessionDescriptionSyncObserver* sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init]; [pcOffer createOfferWithDelegate:sdpObserver constraints:constraints]; @@ -181,6 +198,10 @@ - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory { [answeringExpectations expectICEConnectionChange:RTCICEConnectionChecking]; [answeringExpectations expectICEConnectionChange:RTCICEConnectionConnected]; + [offeringExpectations expectStateChange:kRTCDataChannelStateOpen]; + [answeringExpectations expectDataChannel:@"offerDC"]; + [answeringExpectations expectStateChange:kRTCDataChannelStateOpen]; + [offeringExpectations expectICEGatheringChange:RTCICEGatheringComplete]; [answeringExpectations expectICEGatheringChange:RTCICEGatheringComplete]; @@ -209,6 +230,42 @@ - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory { [offeringExpectations waitForAllExpectationsToBeSatisfied]; [answeringExpectations waitForAllExpectationsToBeSatisfied]; + EXPECT_EQ(pcOffer.signalingState, RTCSignalingStable); + EXPECT_EQ(pcAnswer.signalingState, RTCSignalingStable); + + // Test send and receive UTF-8 text + NSString* text = @"你好"; + NSData* textData = [text dataUsingEncoding:NSUTF8StringEncoding]; + RTCDataBuffer* buffer = + [[RTCDataBuffer alloc] initWithData:textData isBinary:NO]; + [answeringExpectations expectMessage:[textData copy] isBinary:NO]; + EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]); + [answeringExpectations waitForAllExpectationsToBeSatisfied]; + + // Test send and receive binary data + const size_t byteLength = 5; + char bytes[byteLength] = {1, 2, 3, 4, 5}; + NSData* byteData = [NSData dataWithBytes:bytes length:byteLength]; + buffer = [[RTCDataBuffer alloc] initWithData:byteData isBinary:YES]; + [answeringExpectations expectMessage:[byteData copy] isBinary:YES]; + EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]); + [answeringExpectations waitForAllExpectationsToBeSatisfied]; + + [offeringExpectations expectStateChange:kRTCDataChannelStateClosing]; + [answeringExpectations expectStateChange:kRTCDataChannelStateClosing]; + [offeringExpectations expectStateChange:kRTCDataChannelStateClosed]; + [answeringExpectations expectStateChange:kRTCDataChannelStateClosed]; + + [answeringExpectations.dataChannel close]; + [offeringExpectations.dataChannel close]; + + [offeringExpectations waitForAllExpectationsToBeSatisfied]; + [answeringExpectations waitForAllExpectationsToBeSatisfied]; + // Don't need to listen to further state changes. + // TODO(tkchin): figure out why Closed->Closing without this. + offeringExpectations.dataChannel.delegate = nil; + answeringExpectations.dataChannel.delegate = nil; + // Let the audio feedback run for 2s to allow human testing and to ensure // things stabilize. TODO(fischman): replace seconds with # of video frames, // when we have video flowing. diff --git a/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h index 18d790288..8631f0170 100644 --- a/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h +++ b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h @@ -27,14 +27,14 @@ #import -#import "RTCSessionDescriptonDelegate.h" +#import "RTCSessionDescriptionDelegate.h" @class RTCSessionDescription; // Observer of SDP-related events, used by RTCPeerConnectionTest to check // expectations. @interface RTCSessionDescriptionSyncObserver : NSObject< - RTCSessionDescriptonDelegate> + RTCSessionDescriptionDelegate> // Error string. May be nil. @property(atomic, copy) NSString *error; diff --git a/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m index 75a4671b1..6d782536c 100644 --- a/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m +++ b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m @@ -71,7 +71,7 @@ - (void)wait { [self.condition unlock]; } -#pragma mark - RTCSessionDescriptonDelegate methods +#pragma mark - RTCSessionDescriptionDelegate methods - (void)peerConnection:(RTCPeerConnection*)peerConnection didCreateSessionDescription:(RTCSessionDescription*)sdp error:(NSError*)error { diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc index 502451a17..0839977f1 100644 --- a/talk/app/webrtc/peerconnection.cc +++ b/talk/app/webrtc/peerconnection.cc @@ -37,6 +37,7 @@ #include "talk/app/webrtc/streamcollection.h" #include "talk/base/logging.h" #include "talk/base/stringencode.h" +#include "talk/p2p/client/basicportallocator.h" #include "talk/session/media/channelmanager.h" namespace { @@ -266,13 +267,6 @@ bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration, server.password, turn_transport_type, secure)); - // STUN functionality is part of TURN. - // Note: If there is only TURNS is supplied as part of configuration, - // we will have problem in fetching server reflexive candidate, as - // currently we don't have support of TCP/TLS in stunport.cc. - // In that case we should fetch server reflexive addess from - // TURN allocate response. - stun_config->push_back(StunConfiguration(address, port)); break; } case INVALID: @@ -307,6 +301,7 @@ namespace webrtc { PeerConnection::PeerConnection(PeerConnectionFactory* factory) : factory_(factory), observer_(NULL), + uma_observer_(NULL), signaling_state_(kStable), ice_state_(kIceNew), ice_connection_state_(kIceConnectionNew), @@ -321,22 +316,23 @@ PeerConnection::~PeerConnection() { } bool PeerConnection::Initialize( - const PeerConnectionInterface::IceServers& configuration, + const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, PeerConnectionObserver* observer) { std::vector stun_config; std::vector turn_config; - if (!ParseIceServers(configuration, &stun_config, &turn_config)) { + if (!ParseIceServers(configuration.servers, &stun_config, &turn_config)) { return false; } - return DoInitialize(stun_config, turn_config, constraints, + return DoInitialize(configuration.type, stun_config, turn_config, constraints, allocator_factory, dtls_identity_service, observer); } bool PeerConnection::DoInitialize( + IceTransportsType type, const StunConfigurations& stun_config, const TurnConfigurations& turn_config, const MediaConstraintsInterface* constraints, @@ -381,7 +377,7 @@ bool PeerConnection::DoInitialize( // Initialize the WebRtcSession. It creates transport channels etc. if (!session_->Initialize(factory_->options(), constraints, - dtls_identity_service)) + dtls_identity_service, type)) return false; // Register PeerConnection as receiver of local ice candidates. @@ -486,6 +482,8 @@ talk_base::scoped_refptr PeerConnection::CreateDataChannel( const std::string& label, const DataChannelInit* config) { + bool first_datachannel = !mediastream_signaling_->HasDataChannels(); + talk_base::scoped_ptr internal_config; if (config) { internal_config.reset(new InternalDataChannelInit(*config)); @@ -495,7 +493,11 @@ PeerConnection::CreateDataChannel( if (!channel.get()) return NULL; - observer_->OnRenegotiationNeeded(); + // Trigger the onRenegotiationNeeded event for every new RTP DataChannel, or + // the first SCTP DataChannel. + if (session_->data_channel_type() == cricket::DCT_RTP || first_datachannel) { + observer_->OnRenegotiationNeeded(); + } return DataChannelProxy::Create(signaling_thread(), channel.get()); } @@ -575,16 +577,67 @@ void PeerConnection::PostSetSessionDescriptionFailure( bool PeerConnection::UpdateIce(const IceServers& configuration, const MediaConstraintsInterface* constraints) { - // TODO(ronghuawu): Implement UpdateIce. - LOG(LS_ERROR) << "UpdateIce is not implemented."; return false; } +bool PeerConnection::UpdateIce(const RTCConfiguration& config) { + if (port_allocator_) { + std::vector stuns; + std::vector turns; + if (!ParseIceServers(config.servers, &stuns, &turns)) { + return false; + } + + std::vector stun_hosts; + typedef std::vector::const_iterator StunIt; + for (StunIt stun_it = stuns.begin(); stun_it != stuns.end(); ++stun_it) { + stun_hosts.push_back(stun_it->server); + } + + talk_base::SocketAddress stun_addr; + if (!stun_hosts.empty()) { + stun_addr = stun_hosts.front(); + LOG(LS_INFO) << "UpdateIce: StunServer Address: " << stun_addr.ToString(); + } + + for (size_t i = 0; i < turns.size(); ++i) { + cricket::RelayCredentials credentials(turns[i].username, + turns[i].password); + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::ProtocolType protocol; + if (cricket::StringToProto(turns[i].transport_type.c_str(), &protocol)) { + relay_server.ports.push_back(cricket::ProtocolAddress( + turns[i].server, protocol, turns[i].secure)); + relay_server.credentials = credentials; + LOG(LS_INFO) << "UpdateIce: TurnServer Address: " + << turns[i].server.ToString(); + } else { + LOG(LS_WARNING) << "Ignoring TURN server " << turns[i].server << ". " + << "Reason= Incorrect " << turns[i].transport_type + << " transport parameter."; + } + } + } + return session_->UpdateIce(config.type); +} + bool PeerConnection::AddIceCandidate( const IceCandidateInterface* ice_candidate) { return session_->ProcessIceMessage(ice_candidate); } +void PeerConnection::RegisterUMAObserver(UMAObserver* observer) { + uma_observer_ = observer; + // Send information about IPv4/IPv6 status. + if (uma_observer_ && port_allocator_) { + if (port_allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_IPV6) { + uma_observer_->IncrementCounter(kPeerConnection_IPv6); + } else { + uma_observer_->IncrementCounter(kPeerConnection_IPv4); + } + } +} + const SessionDescriptionInterface* PeerConnection::local_description() const { return session_->local_description(); } diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h index cd5e2a9bc..4a428efee 100644 --- a/talk/app/webrtc/peerconnection.h +++ b/talk/app/webrtc/peerconnection.h @@ -57,11 +57,12 @@ class PeerConnection : public PeerConnectionInterface, public: explicit PeerConnection(PeerConnectionFactory* factory); - bool Initialize(const PeerConnectionInterface::IceServers& configuration, - const MediaConstraintsInterface* constraints, - PortAllocatorFactoryInterface* allocator_factory, - DTLSIdentityServiceInterface* dtls_identity_service, - PeerConnectionObserver* observer); + bool Initialize( + const PeerConnectionInterface::RTCConfiguration& configuration, + const MediaConstraintsInterface* constraints, + PortAllocatorFactoryInterface* allocator_factory, + DTLSIdentityServiceInterface* dtls_identity_service, + PeerConnectionObserver* observer); virtual talk_base::scoped_refptr local_streams(); virtual talk_base::scoped_refptr remote_streams(); virtual bool AddStream(MediaStreamInterface* local_stream, @@ -97,10 +98,15 @@ class PeerConnection : public PeerConnectionInterface, SessionDescriptionInterface* desc); virtual void SetRemoteDescription(SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc); + // TODO(mallinath) : Deprecated version, remove after all clients are updated. virtual bool UpdateIce(const IceServers& configuration, const MediaConstraintsInterface* constraints); + virtual bool UpdateIce( + const PeerConnectionInterface::RTCConfiguration& config); virtual bool AddIceCandidate(const IceCandidateInterface* candidate); + virtual void RegisterUMAObserver(UMAObserver* observer); + virtual void Close(); protected: @@ -152,7 +158,8 @@ class PeerConnection : public PeerConnectionInterface, cricket::BaseSession::State state); void ChangeSignalingState(SignalingState signaling_state); - bool DoInitialize(const StunConfigurations& stun_config, + bool DoInitialize(IceTransportsType type, + const StunConfigurations& stun_config, const TurnConfigurations& turn_config, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, @@ -178,6 +185,7 @@ class PeerConnection : public PeerConnectionInterface, // will refer to the same reference count. talk_base::scoped_refptr factory_; PeerConnectionObserver* observer_; + UMAObserver* uma_observer_; SignalingState signaling_state_; // TODO(bemasc): Remove ice_state_. IceState ice_state_; diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc index 0c334d14a..0c39297ab 100644 --- a/talk/app/webrtc/peerconnection_unittest.cc +++ b/talk/app/webrtc/peerconnection_unittest.cc @@ -83,9 +83,15 @@ using webrtc::PeerConnectionInterface; using webrtc::SessionDescriptionInterface; using webrtc::StreamCollectionInterface; -static const int kMaxWaitMs = 1000; +static const int kMaxWaitMs = 2000; +// Disable for TSan v2, see +// https://code.google.com/p/webrtc/issues/detail?id=1205 for details. +// This declaration is also #ifdef'd as it causes uninitialized-variable +// warnings. +#if !defined(THREAD_SANITIZER) static const int kMaxWaitForStatsMs = 3000; -static const int kMaxWaitForFramesMs = 5000; +#endif +static const int kMaxWaitForFramesMs = 10000; static const int kEndAudioFrameCount = 3; static const int kEndVideoFrameCount = 3; @@ -726,16 +732,9 @@ class JsepTestClient ice_server.uri = "stun:stun.l.google.com:19302"; ice_servers.push_back(ice_server); - // TODO(jiayl): we should always pass a FakeIdentityService so that DTLS - // is enabled by default like in Chrome (issue 2838). - FakeIdentityService* dtls_service = NULL; - bool dtls; - if (FindConstraint(constraints, - MediaConstraintsInterface::kEnableDtlsSrtp, - &dtls, - NULL) && dtls) { - dtls_service = new FakeIdentityService(); - } + FakeIdentityService* dtls_service = + talk_base::SSLStreamAdapter::HaveDtlsSrtp() ? + new FakeIdentityService() : NULL; return peer_connection_factory()->CreatePeerConnection( ice_servers, constraints, factory, dtls_service, this); } @@ -1010,6 +1009,16 @@ class P2PTestConductor : public testing::Test { kMaxWaitForFramesMs); } + void SendRtpData(webrtc::DataChannelInterface* dc, const std::string& data) { + // Messages may get lost on the unreliable DataChannel, so we send multiple + // times to avoid test flakiness. + static const size_t kSendAttempts = 5; + + for (size_t i = 0; i < kSendAttempts; ++i) { + dc->Send(DataBuffer(data)); + } + } + SignalingClass* initializing_client() { return initiating_client_.get(); } SignalingClass* receiving_client() { return receiving_client_.get(); } @@ -1138,7 +1147,9 @@ TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestAnswerNone) { // runs for a while (10 frames), the caller sends an update offer with video // being rejected. Once the re-negotiation is done, the video flow should stop // and the audio flow should continue. -TEST_F(JsepPeerConnectionP2PTestClient, UpdateOfferWithRejectedContent) { +// Disabled due to b/14955157. +TEST_F(JsepPeerConnectionP2PTestClient, + DISABLED_UpdateOfferWithRejectedContent) { ASSERT_TRUE(CreateTestClients()); LocalP2PTest(); TestUpdateOfferWithRejectedContent(); @@ -1146,7 +1157,8 @@ TEST_F(JsepPeerConnectionP2PTestClient, UpdateOfferWithRejectedContent) { // This test sets up a Jsep call between two parties. The MSID is removed from // the SDP strings from the caller. -TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestWithoutMsid) { +// Disabled due to b/14955157. +TEST_F(JsepPeerConnectionP2PTestClient, DISABLED_LocalP2PTestWithoutMsid) { ASSERT_TRUE(CreateTestClients()); receiving_client()->RemoveMsidFromReceivedSdp(true); // TODO(perkj): Currently there is a bug that cause audio to stop playing if @@ -1249,12 +1261,7 @@ TEST_F(JsepPeerConnectionP2PTestClient, GetBytesSentStats) { } // This test sets up a call between two parties with audio, video and data. -// TODO(jiayl): fix the flakiness on Windows and reenable. Issue 2891. -#if defined(WIN32) -TEST_F(JsepPeerConnectionP2PTestClient, DISABLED_LocalP2PTestDataChannel) { -#else TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestDataChannel) { -#endif FakeConstraints setup_constraints; setup_constraints.SetAllowRtpDataChannels(); ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); @@ -1268,10 +1275,12 @@ TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestDataChannel) { kMaxWaitMs); std::string data = "hello world"; - initializing_client()->data_channel()->Send(DataBuffer(data)); + + SendRtpData(initializing_client()->data_channel(), data); EXPECT_EQ_WAIT(data, receiving_client()->data_observer()->last_message(), kMaxWaitMs); - receiving_client()->data_channel()->Send(DataBuffer(data)); + + SendRtpData(receiving_client()->data_channel(), data); EXPECT_EQ_WAIT(data, initializing_client()->data_observer()->last_message(), kMaxWaitMs); @@ -1305,8 +1314,10 @@ TEST_F(JsepPeerConnectionP2PTestClient, RegisterDataChannelObserver) { // Unregister the existing observer. receiving_client()->data_channel()->UnregisterObserver(); + std::string data = "hello world"; - initializing_client()->data_channel()->Send(DataBuffer(data)); + SendRtpData(initializing_client()->data_channel(), data); + // Wait a while to allow the sent data to arrive before an observer is // registered.. talk_base::Thread::Current()->ProcessMessages(100); @@ -1318,9 +1329,15 @@ TEST_F(JsepPeerConnectionP2PTestClient, RegisterDataChannelObserver) { // This test sets up a call between two parties with audio, video and but only // the initiating client support data. TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestReceiverDoesntSupportData) { - FakeConstraints setup_constraints; - setup_constraints.SetAllowRtpDataChannels(); - ASSERT_TRUE(CreateTestClients(&setup_constraints, NULL)); + FakeConstraints setup_constraints_1; + setup_constraints_1.SetAllowRtpDataChannels(); + // Must disable DTLS to make negotiation succeed. + setup_constraints_1.SetMandatory( + MediaConstraintsInterface::kEnableDtlsSrtp, false); + FakeConstraints setup_constraints_2; + setup_constraints_2.SetMandatory( + MediaConstraintsInterface::kEnableDtlsSrtp, false); + ASSERT_TRUE(CreateTestClients(&setup_constraints_1, &setup_constraints_2)); initializing_client()->CreateDataChannel(); LocalP2PTest(); EXPECT_TRUE(initializing_client()->data_channel() != NULL); @@ -1346,6 +1363,20 @@ TEST_F(JsepPeerConnectionP2PTestClient, AddDataChannelAfterRenegotiation) { kMaxWaitMs); } +// This test sets up a Jsep call with SCTP DataChannel and verifies the +// negotiation is completed without error. +#ifdef HAVE_SCTP +TEST_F(JsepPeerConnectionP2PTestClient, CreateOfferWithSctpDataChannel) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + FakeConstraints constraints; + constraints.SetMandatory( + MediaConstraintsInterface::kEnableDtlsSrtp, true); + ASSERT_TRUE(CreateTestClients(&constraints, &constraints)); + initializing_client()->CreateDataChannel(); + initializing_client()->Negotiate(false, false); +} +#endif + // This test sets up a call between two parties with audio, and video. // During the call, the initializing side restart ice and the test verifies that // new ice candidates are generated and audio and video still can flow. @@ -1407,6 +1438,4 @@ TEST_F(JsepPeerConnectionP2PTestClient, EnableVideoDecoderFactory(); LocalP2PTest(); } - #endif // if !defined(THREAD_SANITIZER) - diff --git a/talk/app/webrtc/peerconnectionendtoend_unittest.cc b/talk/app/webrtc/peerconnectionendtoend_unittest.cc index da3c03da4..f701e0635 100644 --- a/talk/app/webrtc/peerconnectionendtoend_unittest.cc +++ b/talk/app/webrtc/peerconnectionendtoend_unittest.cc @@ -26,6 +26,7 @@ */ #include "talk/app/webrtc/test/peerconnectiontestwrapper.h" +#include "talk/app/webrtc/test/mockpeerconnectionobservers.h" #include "talk/base/gunit.h" #include "talk/base/logging.h" #include "talk/base/ssladapter.h" @@ -33,6 +34,13 @@ #include "talk/base/stringencode.h" #include "talk/base/stringutils.h" +#define MAYBE_SKIP_TEST(feature) \ + if (!(feature())) { \ + LOG(LS_INFO) << "Feature disabled... skipping"; \ + return; \ + } + +using webrtc::DataChannelInterface; using webrtc::FakeConstraints; using webrtc::MediaConstraintsInterface; using webrtc::MediaStreamInterface; @@ -42,6 +50,7 @@ namespace { const char kExternalGiceUfrag[] = "1234567890123456"; const char kExternalGicePwd[] = "123456789012345678901234"; +const size_t kMaxWait = 10000; void RemoveLinesFromSdp(const std::string& line_start, std::string* sdp) { @@ -117,6 +126,9 @@ class PeerConnectionEndToEndTest : public sigslot::has_slots<>, public testing::Test { public: + typedef std::vector > + DataChannelList; + PeerConnectionEndToEndTest() : caller_(new talk_base::RefCountedObject( "caller")), @@ -133,6 +145,11 @@ class PeerConnectionEndToEndTest EXPECT_TRUE(caller_->CreatePc(pc_constraints)); EXPECT_TRUE(callee_->CreatePc(pc_constraints)); PeerConnectionTestWrapper::Connect(caller_.get(), callee_.get()); + + caller_->SignalOnDataChannel.connect( + this, &PeerConnectionEndToEndTest::OnCallerAddedDataChanel); + callee_->SignalOnDataChannel.connect( + this, &PeerConnectionEndToEndTest::OnCalleeAddedDataChannel); } void GetAndAddUserMedia() { @@ -158,6 +175,11 @@ class PeerConnectionEndToEndTest callee_->WaitForCallEstablished(); } + void WaitForConnection() { + caller_->WaitForConnection(); + callee_->WaitForConnection(); + } + void SetupLegacySdpConverter() { caller_->SignalOnSdpCreated.connect( this, &PeerConnectionEndToEndTest::ConvertToLegacySdp); @@ -189,6 +211,57 @@ class PeerConnectionEndToEndTest LOG(LS_INFO) << "AddGiceCredsToCandidate: " << *sdp; } + void OnCallerAddedDataChanel(DataChannelInterface* dc) { + caller_signaled_data_channels_.push_back(dc); + } + + void OnCalleeAddedDataChannel(DataChannelInterface* dc) { + callee_signaled_data_channels_.push_back(dc); + } + + // Tests that |dc1| and |dc2| can send to and receive from each other. + void TestDataChannelSendAndReceive( + DataChannelInterface* dc1, DataChannelInterface* dc2) { + talk_base::scoped_ptr dc1_observer( + new webrtc::MockDataChannelObserver(dc1)); + + talk_base::scoped_ptr dc2_observer( + new webrtc::MockDataChannelObserver(dc2)); + + static const std::string kDummyData = "abcdefg"; + webrtc::DataBuffer buffer(kDummyData); + EXPECT_TRUE(dc1->Send(buffer)); + EXPECT_EQ_WAIT(kDummyData, dc2_observer->last_message(), kMaxWait); + + EXPECT_TRUE(dc2->Send(buffer)); + EXPECT_EQ_WAIT(kDummyData, dc1_observer->last_message(), kMaxWait); + + EXPECT_EQ(1U, dc1_observer->received_message_count()); + EXPECT_EQ(1U, dc2_observer->received_message_count()); + } + + void WaitForDataChannelsToOpen(DataChannelInterface* local_dc, + const DataChannelList& remote_dc_list, + size_t remote_dc_index) { + EXPECT_EQ_WAIT(DataChannelInterface::kOpen, local_dc->state(), kMaxWait); + + EXPECT_TRUE_WAIT(remote_dc_list.size() > remote_dc_index, kMaxWait); + EXPECT_EQ_WAIT(DataChannelInterface::kOpen, + remote_dc_list[remote_dc_index]->state(), + kMaxWait); + EXPECT_EQ(local_dc->id(), remote_dc_list[remote_dc_index]->id()); + } + + void CloseDataChannels(DataChannelInterface* local_dc, + const DataChannelList& remote_dc_list, + size_t remote_dc_index) { + local_dc->Close(); + EXPECT_EQ_WAIT(DataChannelInterface::kClosed, local_dc->state(), kMaxWait); + EXPECT_EQ_WAIT(DataChannelInterface::kClosed, + remote_dc_list[remote_dc_index]->state(), + kMaxWait); + } + ~PeerConnectionEndToEndTest() { talk_base::CleanupSSL(); } @@ -196,6 +269,8 @@ class PeerConnectionEndToEndTest protected: talk_base::scoped_refptr caller_; talk_base::scoped_refptr callee_; + DataChannelList caller_signaled_data_channels_; + DataChannelList callee_signaled_data_channels_; }; // Disable for TSan v2, see @@ -209,7 +284,8 @@ TEST_F(PeerConnectionEndToEndTest, Call) { WaitForCallEstablished(); } -TEST_F(PeerConnectionEndToEndTest, CallWithLegacySdp) { +// Disabled per b/14899892 +TEST_F(PeerConnectionEndToEndTest, DISABLED_CallWithLegacySdp) { FakeConstraints pc_constraints; pc_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, false); @@ -221,4 +297,126 @@ TEST_F(PeerConnectionEndToEndTest, CallWithLegacySdp) { WaitForCallEstablished(); } +// Verifies that a DataChannel created before the negotiation can transition to +// "OPEN" and transfer data. +TEST_F(PeerConnectionEndToEndTest, CreateDataChannelBeforeNegotiate) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + + CreatePcs(); + + webrtc::DataChannelInit init; + talk_base::scoped_refptr caller_dc( + caller_->CreateDataChannel("data", init)); + talk_base::scoped_refptr callee_dc( + callee_->CreateDataChannel("data", init)); + + Negotiate(); + WaitForConnection(); + + WaitForDataChannelsToOpen(caller_dc, callee_signaled_data_channels_, 0); + WaitForDataChannelsToOpen(callee_dc, caller_signaled_data_channels_, 0); + + TestDataChannelSendAndReceive(caller_dc, callee_signaled_data_channels_[0]); + TestDataChannelSendAndReceive(callee_dc, caller_signaled_data_channels_[0]); + + CloseDataChannels(caller_dc, callee_signaled_data_channels_, 0); + CloseDataChannels(callee_dc, caller_signaled_data_channels_, 0); +} + +// Verifies that a DataChannel created after the negotiation can transition to +// "OPEN" and transfer data. +TEST_F(PeerConnectionEndToEndTest, CreateDataChannelAfterNegotiate) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + + CreatePcs(); + + webrtc::DataChannelInit init; + + // This DataChannel is for creating the data content in the negotiation. + talk_base::scoped_refptr dummy( + caller_->CreateDataChannel("data", init)); + Negotiate(); + WaitForConnection(); + + // Creates new DataChannels after the negotiation and verifies their states. + talk_base::scoped_refptr caller_dc( + caller_->CreateDataChannel("hello", init)); + talk_base::scoped_refptr callee_dc( + callee_->CreateDataChannel("hello", init)); + + WaitForDataChannelsToOpen(caller_dc, callee_signaled_data_channels_, 1); + WaitForDataChannelsToOpen(callee_dc, caller_signaled_data_channels_, 0); + + TestDataChannelSendAndReceive(caller_dc, callee_signaled_data_channels_[1]); + TestDataChannelSendAndReceive(callee_dc, caller_signaled_data_channels_[0]); + + CloseDataChannels(caller_dc, callee_signaled_data_channels_, 1); + CloseDataChannels(callee_dc, caller_signaled_data_channels_, 0); +} + +// Verifies that DataChannel IDs are even/odd based on the DTLS roles. +TEST_F(PeerConnectionEndToEndTest, DataChannelIdAssignment) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + + CreatePcs(); + + webrtc::DataChannelInit init; + talk_base::scoped_refptr caller_dc_1( + caller_->CreateDataChannel("data", init)); + talk_base::scoped_refptr callee_dc_1( + callee_->CreateDataChannel("data", init)); + + Negotiate(); + WaitForConnection(); + + EXPECT_EQ(1U, caller_dc_1->id() % 2); + EXPECT_EQ(0U, callee_dc_1->id() % 2); + + talk_base::scoped_refptr caller_dc_2( + caller_->CreateDataChannel("data", init)); + talk_base::scoped_refptr callee_dc_2( + callee_->CreateDataChannel("data", init)); + + EXPECT_EQ(1U, caller_dc_2->id() % 2); + EXPECT_EQ(0U, callee_dc_2->id() % 2); +} + +// Verifies that the message is received by the right remote DataChannel when +// there are multiple DataChannels. +TEST_F(PeerConnectionEndToEndTest, + MessageTransferBetweenTwoPairsOfDataChannels) { + MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp); + + CreatePcs(); + + webrtc::DataChannelInit init; + + talk_base::scoped_refptr caller_dc_1( + caller_->CreateDataChannel("data", init)); + talk_base::scoped_refptr caller_dc_2( + caller_->CreateDataChannel("data", init)); + + Negotiate(); + WaitForConnection(); + WaitForDataChannelsToOpen(caller_dc_1, callee_signaled_data_channels_, 0); + WaitForDataChannelsToOpen(caller_dc_2, callee_signaled_data_channels_, 1); + + talk_base::scoped_ptr dc_1_observer( + new webrtc::MockDataChannelObserver(callee_signaled_data_channels_[0])); + + talk_base::scoped_ptr dc_2_observer( + new webrtc::MockDataChannelObserver(callee_signaled_data_channels_[1])); + + const std::string message_1 = "hello 1"; + const std::string message_2 = "hello 2"; + + caller_dc_1->Send(webrtc::DataBuffer(message_1)); + EXPECT_EQ_WAIT(message_1, dc_1_observer->last_message(), kMaxWait); + + caller_dc_2->Send(webrtc::DataBuffer(message_2)); + EXPECT_EQ_WAIT(message_2, dc_2_observer->last_message(), kMaxWait); + + EXPECT_EQ(1U, dc_1_observer->received_message_count()); + EXPECT_EQ(1U, dc_2_observer->received_message_count()); +} #endif // if !defined(THREAD_SANITIZER) diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtc/peerconnectionfactory.cc index dc14bfb56..3628c5905 100644 --- a/talk/app/webrtc/peerconnectionfactory.cc +++ b/talk/app/webrtc/peerconnectionfactory.cc @@ -51,7 +51,7 @@ typedef talk_base::TypedMessageData InitMessageData; struct CreatePeerConnectionParams : public talk_base::MessageData { CreatePeerConnectionParams( - const webrtc::PeerConnectionInterface::IceServers& configuration, + const webrtc::PeerConnectionInterface::RTCConfiguration& configuration, const webrtc::MediaConstraintsInterface* constraints, webrtc::PortAllocatorFactoryInterface* allocator_factory, webrtc::DTLSIdentityServiceInterface* dtls_identity_service, @@ -63,28 +63,13 @@ struct CreatePeerConnectionParams : public talk_base::MessageData { observer(observer) { } scoped_refptr peerconnection; - const webrtc::PeerConnectionInterface::IceServers& configuration; + const webrtc::PeerConnectionInterface::RTCConfiguration& configuration; const webrtc::MediaConstraintsInterface* constraints; scoped_refptr allocator_factory; webrtc::DTLSIdentityServiceInterface* dtls_identity_service; webrtc::PeerConnectionObserver* observer; }; -struct CreatePeerConnectionParamsDeprecated : public talk_base::MessageData { - CreatePeerConnectionParamsDeprecated( - const std::string& configuration, - webrtc::PortAllocatorFactoryInterface* allocator_factory, - webrtc::PeerConnectionObserver* observer) - : configuration(configuration), - allocator_factory(allocator_factory), - observer(observer) { - } - scoped_refptr peerconnection; - const std::string& configuration; - scoped_refptr allocator_factory; - webrtc::PeerConnectionObserver* observer; -}; - struct CreateAudioSourceParams : public talk_base::MessageData { explicit CreateAudioSourceParams( const webrtc::MediaConstraintsInterface* constraints) @@ -126,9 +111,9 @@ enum { namespace webrtc { -scoped_refptr +talk_base::scoped_refptr CreatePeerConnectionFactory() { - scoped_refptr pc_factory( + talk_base::scoped_refptr pc_factory( new talk_base::RefCountedObject()); if (!pc_factory->Initialize()) { @@ -137,17 +122,19 @@ CreatePeerConnectionFactory() { return pc_factory; } -scoped_refptr +talk_base::scoped_refptr CreatePeerConnectionFactory( talk_base::Thread* worker_thread, talk_base::Thread* signaling_thread, AudioDeviceModule* default_adm, cricket::WebRtcVideoEncoderFactory* encoder_factory, cricket::WebRtcVideoDecoderFactory* decoder_factory) { - scoped_refptr pc_factory( - new talk_base::RefCountedObject( - worker_thread, signaling_thread, default_adm, - encoder_factory, decoder_factory)); + talk_base::scoped_refptr pc_factory( + new talk_base::RefCountedObject(worker_thread, + signaling_thread, + default_adm, + encoder_factory, + decoder_factory)); if (!pc_factory->Initialize()) { return NULL; } @@ -260,6 +247,7 @@ bool PeerConnectionFactory::Initialize_s() { channel_manager_.reset(new cricket::ChannelManager( webrtc_media_engine, device_manager, worker_thread_)); + channel_manager_->SetVideoRtxEnabled(true); if (!channel_manager_->Init()) { return false; } @@ -293,9 +281,9 @@ bool PeerConnectionFactory::StartAecDump_s(talk_base::PlatformFile file) { return channel_manager_->StartAecDump(file); } -scoped_refptr +talk_base::scoped_refptr PeerConnectionFactory::CreatePeerConnection( - const PeerConnectionInterface::IceServers& configuration, + const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, @@ -303,23 +291,14 @@ PeerConnectionFactory::CreatePeerConnection( CreatePeerConnectionParams params(configuration, constraints, allocator_factory, dtls_identity_service, observer); - signaling_thread_->Send(this, MSG_CREATE_PEERCONNECTION, ¶ms); + signaling_thread_->Send( + this, MSG_CREATE_PEERCONNECTION, ¶ms); return params.peerconnection; } -scoped_refptr -PeerConnectionFactory::CreatePeerConnection( - const PeerConnectionInterface::IceServers& configuration, - const MediaConstraintsInterface* constraints, - DTLSIdentityServiceInterface* dtls_identity_service, - PeerConnectionObserver* observer) { - return CreatePeerConnection( - configuration, constraints, NULL, dtls_identity_service, observer); -} - talk_base::scoped_refptr PeerConnectionFactory::CreatePeerConnection_s( - const PeerConnectionInterface::IceServers& configuration, + const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, @@ -338,7 +317,7 @@ PeerConnectionFactory::CreatePeerConnection_s( return PeerConnectionProxy::Create(signaling_thread(), pc); } -scoped_refptr +talk_base::scoped_refptr PeerConnectionFactory::CreateLocalMediaStream(const std::string& label) { return MediaStreamProxy::Create(signaling_thread_, MediaStream::Create(label)); @@ -372,9 +351,9 @@ PeerConnectionFactory::CreateVideoTrack( return VideoTrackProxy::Create(signaling_thread_, track); } -scoped_refptr PeerConnectionFactory::CreateAudioTrack( - const std::string& id, - AudioSourceInterface* source) { +talk_base::scoped_refptr +PeerConnectionFactory::CreateAudioTrack(const std::string& id, + AudioSourceInterface* source) { talk_base::scoped_refptr track( AudioTrack::Create(id, source)); return AudioTrackProxy::Create(signaling_thread_, track); diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtc/peerconnectionfactory.h index 46b095fd8..633d28114 100644 --- a/talk/app/webrtc/peerconnectionfactory.h +++ b/talk/app/webrtc/peerconnectionfactory.h @@ -46,18 +46,12 @@ class PeerConnectionFactory : public PeerConnectionFactoryInterface, virtual talk_base::scoped_refptr CreatePeerConnection( - const PeerConnectionInterface::IceServers& configuration, - const MediaConstraintsInterface* constraints, - DTLSIdentityServiceInterface* dtls_identity_service, - PeerConnectionObserver* observer); - - virtual talk_base::scoped_refptr - CreatePeerConnection( - const PeerConnectionInterface::IceServers& configuration, + const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, PeerConnectionObserver* observer); + bool Initialize(); virtual talk_base::scoped_refptr @@ -103,12 +97,14 @@ class PeerConnectionFactory : public PeerConnectionFactoryInterface, talk_base::scoped_refptr CreateVideoSource_s( cricket::VideoCapturer* capturer, const MediaConstraintsInterface* constraints); + talk_base::scoped_refptr CreatePeerConnection_s( - const PeerConnectionInterface::IceServers& configuration, + const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, PeerConnectionObserver* observer); + bool StartAecDump_s(talk_base::PlatformFile file); // Implements talk_base::MessageHandler. diff --git a/talk/app/webrtc/peerconnectionfactory_unittest.cc b/talk/app/webrtc/peerconnectionfactory_unittest.cc index 4ab9e35c8..01f35d940 100644 --- a/talk/app/webrtc/peerconnectionfactory_unittest.cc +++ b/talk/app/webrtc/peerconnectionfactory_unittest.cc @@ -156,7 +156,7 @@ TEST(PeerConnectionFactoryTestInternal, CreatePCUsingInternalModules) { webrtc::PeerConnectionInterface::IceServers servers; talk_base::scoped_refptr pc( - factory->CreatePeerConnection(servers, NULL, NULL, &observer)); + factory->CreatePeerConnection(servers, NULL, NULL, NULL, &observer)); EXPECT_TRUE(pc.get() != NULL); } @@ -164,6 +164,42 @@ TEST(PeerConnectionFactoryTestInternal, CreatePCUsingInternalModules) { // This test verifies creation of PeerConnection with valid STUN and TURN // configuration. Also verifies the URL's parsed correctly as expected. TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServers) { + PeerConnectionInterface::RTCConfiguration config; + webrtc::PeerConnectionInterface::IceServer ice_server; + ice_server.uri = kStunIceServer; + config.servers.push_back(ice_server); + ice_server.uri = kTurnIceServer; + ice_server.password = kTurnPassword; + config.servers.push_back(ice_server); + ice_server.uri = kTurnIceServerWithTransport; + ice_server.password = kTurnPassword; + config.servers.push_back(ice_server); + talk_base::scoped_refptr pc( + factory_->CreatePeerConnection(config, NULL, + allocator_factory_.get(), + NULL, + &observer_)); + EXPECT_TRUE(pc.get() != NULL); + StunConfigurations stun_configs; + webrtc::PortAllocatorFactoryInterface::StunConfiguration stun1( + "stun.l.google.com", 19302); + stun_configs.push_back(stun1); + VerifyStunConfigurations(stun_configs); + TurnConfigurations turn_configs; + webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn1( + "test.com", 1234, "test@hello.com", kTurnPassword, "udp", false); + turn_configs.push_back(turn1); + webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn2( + "hello.com", kDefaultStunPort, "test", kTurnPassword, "tcp", false); + turn_configs.push_back(turn2); + VerifyTurnConfigurations(turn_configs); +} + +// This test verifies creation of PeerConnection with valid STUN and TURN +// configuration. Also verifies the URL's parsed correctly as expected. +// This version doesn't use RTCConfiguration. +// TODO(mallinath) - Remove this method after clients start using RTCConfig. +TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServersOldSignature) { webrtc::PeerConnectionInterface::IceServers ice_servers; webrtc::PeerConnectionInterface::IceServer ice_server; ice_server.uri = kStunIceServer; @@ -184,12 +220,6 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServers) { webrtc::PortAllocatorFactoryInterface::StunConfiguration stun1( "stun.l.google.com", 19302); stun_configs.push_back(stun1); - webrtc::PortAllocatorFactoryInterface::StunConfiguration stun2( - "test.com", 1234); - stun_configs.push_back(stun2); - webrtc::PortAllocatorFactoryInterface::StunConfiguration stun3( - "hello.com", kDefaultStunPort); - stun_configs.push_back(stun3); VerifyStunConfigurations(stun_configs); TurnConfigurations turn_configs; webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn1( @@ -202,16 +232,16 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServers) { } TEST_F(PeerConnectionFactoryTest, CreatePCUsingNoUsernameInUri) { - webrtc::PeerConnectionInterface::IceServers ice_servers; + PeerConnectionInterface::RTCConfiguration config; webrtc::PeerConnectionInterface::IceServer ice_server; ice_server.uri = kStunIceServer; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kTurnIceServerWithNoUsernameInUri; ice_server.username = kTurnUsername; ice_server.password = kTurnPassword; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); talk_base::scoped_refptr pc( - factory_->CreatePeerConnection(ice_servers, NULL, + factory_->CreatePeerConnection(config, NULL, allocator_factory_.get(), NULL, &observer_)); @@ -226,13 +256,13 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingNoUsernameInUri) { // This test verifies the PeerConnection created properly with TURN url which // has transport parameter in it. TEST_F(PeerConnectionFactoryTest, CreatePCUsingTurnUrlWithTransportParam) { - webrtc::PeerConnectionInterface::IceServers ice_servers; + PeerConnectionInterface::RTCConfiguration config; webrtc::PeerConnectionInterface::IceServer ice_server; ice_server.uri = kTurnIceServerWithTransport; ice_server.password = kTurnPassword; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); talk_base::scoped_refptr pc( - factory_->CreatePeerConnection(ice_servers, NULL, + factory_->CreatePeerConnection(config, NULL, allocator_factory_.get(), NULL, &observer_)); @@ -242,27 +272,22 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingTurnUrlWithTransportParam) { "hello.com", kDefaultStunPort, "test", kTurnPassword, "tcp", false); turn_configs.push_back(turn); VerifyTurnConfigurations(turn_configs); - StunConfigurations stun_configs; - webrtc::PortAllocatorFactoryInterface::StunConfiguration stun( - "hello.com", kDefaultStunPort); - stun_configs.push_back(stun); - VerifyStunConfigurations(stun_configs); } TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) { - webrtc::PeerConnectionInterface::IceServers ice_servers; + PeerConnectionInterface::RTCConfiguration config; webrtc::PeerConnectionInterface::IceServer ice_server; ice_server.uri = kSecureTurnIceServer; ice_server.password = kTurnPassword; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kSecureTurnIceServerWithoutTransportParam; ice_server.password = kTurnPassword; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kSecureTurnIceServerWithoutTransportAndPortParam; ice_server.password = kTurnPassword; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); talk_base::scoped_refptr pc( - factory_->CreatePeerConnection(ice_servers, NULL, + factory_->CreatePeerConnection(config, NULL, allocator_factory_.get(), NULL, &observer_)); @@ -283,23 +308,23 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) { } TEST_F(PeerConnectionFactoryTest, CreatePCUsingIPLiteralAddress) { - webrtc::PeerConnectionInterface::IceServers ice_servers; + PeerConnectionInterface::RTCConfiguration config; webrtc::PeerConnectionInterface::IceServer ice_server; ice_server.uri = kStunIceServerWithIPv4Address; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kStunIceServerWithIPv4AddressWithoutPort; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kStunIceServerWithIPv6Address; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kStunIceServerWithIPv6AddressWithoutPort; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kStunIceServerWithInvalidIPv6Address; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); ice_server.uri = kTurnIceServerWithIPv6Address; ice_server.password = kTurnPassword; - ice_servers.push_back(ice_server); + config.servers.push_back(ice_server); talk_base::scoped_refptr pc( - factory_->CreatePeerConnection(ice_servers, NULL, + factory_->CreatePeerConnection(config, NULL, allocator_factory_.get(), NULL, &observer_)); @@ -317,9 +342,8 @@ TEST_F(PeerConnectionFactoryTest, CreatePCUsingIPLiteralAddress) { webrtc::PortAllocatorFactoryInterface::StunConfiguration stun4( "2401:fa00:4::", 3478); stun_configs.push_back(stun4); // Default port - // Turn Address has the same host information as |stun3|. - stun_configs.push_back(stun3); VerifyStunConfigurations(stun_configs); + TurnConfigurations turn_configs; webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn1( "2401:fa00:4::", 1234, "test", kTurnPassword, "udp", false); diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h index cda98fdd9..ed4033c17 100644 --- a/talk/app/webrtc/peerconnectioninterface.h +++ b/talk/app/webrtc/peerconnectioninterface.h @@ -76,6 +76,7 @@ #include "talk/app/webrtc/jsep.h" #include "talk/app/webrtc/mediastreaminterface.h" #include "talk/app/webrtc/statstypes.h" +#include "talk/app/webrtc/umametrics.h" #include "talk/base/fileutils.h" #include "talk/base/socketaddress.h" @@ -118,6 +119,16 @@ class StatsObserver : public talk_base::RefCountInterface { virtual ~StatsObserver() {} }; +class UMAObserver : public talk_base::RefCountInterface { + public: + virtual void IncrementCounter(PeerConnectionUMAMetricsCounter type) = 0; + virtual void AddHistogramSample(PeerConnectionUMAMetricsName type, + int value) = 0; + + protected: + virtual ~UMAObserver() {} +}; + class PeerConnectionInterface : public talk_base::RefCountInterface { public: // See http://dev.w3.org/2011/webrtc/editor/webrtc.html#state-definitions . @@ -166,6 +177,21 @@ class PeerConnectionInterface : public talk_base::RefCountInterface { }; typedef std::vector IceServers; + enum IceTransportsType { + kNone, + kRelay, + kNoHost, + kAll + }; + + struct RTCConfiguration { + IceTransportsType type; + IceServers servers; + + RTCConfiguration() : type(kAll) {} + explicit RTCConfiguration(IceTransportsType type) : type(type) {} + }; + // Used by GetStats to decide which stats to include in the stats reports. // |kStatsOutputLevelStandard| includes the standard stats for Javascript API; // |kStatsOutputLevelDebug| includes both the standard stats and additional @@ -240,6 +266,8 @@ class PeerConnectionInterface : public talk_base::RefCountInterface { // take the ownership of the |candidate|. virtual bool AddIceCandidate(const IceCandidateInterface* candidate) = 0; + virtual void RegisterUMAObserver(UMAObserver* observer) = 0; + // Returns the current SignalingState. virtual SignalingState signaling_state() = 0; @@ -412,19 +440,34 @@ class PeerConnectionFactoryInterface : public talk_base::RefCountInterface { }; virtual void SetOptions(const Options& options) = 0; - virtual talk_base::scoped_refptr - CreatePeerConnection( - const PeerConnectionInterface::IceServers& configuration, - const MediaConstraintsInterface* constraints, - DTLSIdentityServiceInterface* dtls_identity_service, - PeerConnectionObserver* observer) = 0; + virtual talk_base::scoped_refptr CreatePeerConnection( - const PeerConnectionInterface::IceServers& configuration, + const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, PortAllocatorFactoryInterface* allocator_factory, DTLSIdentityServiceInterface* dtls_identity_service, PeerConnectionObserver* observer) = 0; + + // TODO(mallinath) : Remove below versions after clients are updated + // to above method. + // In latest W3C WebRTC draft, PC constructor will take RTCConfiguration, + // and not IceServers. RTCConfiguration is made up of ice servers and + // ice transport type. + // http://dev.w3.org/2011/webrtc/editor/webrtc.html + inline talk_base::scoped_refptr + CreatePeerConnection( + const PeerConnectionInterface::IceServers& configuration, + const MediaConstraintsInterface* constraints, + PortAllocatorFactoryInterface* allocator_factory, + DTLSIdentityServiceInterface* dtls_identity_service, + PeerConnectionObserver* observer) { + PeerConnectionInterface::RTCConfiguration rtc_config; + rtc_config.servers = configuration; + return CreatePeerConnection(rtc_config, constraints, allocator_factory, + dtls_identity_service, observer); + } + virtual talk_base::scoped_refptr CreateLocalMediaStream(const std::string& label) = 0; diff --git a/talk/app/webrtc/peerconnectioninterface_unittest.cc b/talk/app/webrtc/peerconnectioninterface_unittest.cc index 97a4176df..5c9b826da 100644 --- a/talk/app/webrtc/peerconnectioninterface_unittest.cc +++ b/talk/app/webrtc/peerconnectioninterface_unittest.cc @@ -259,8 +259,9 @@ class PeerConnectionInterfaceTest : public testing::Test { port_allocator_factory_ = FakePortAllocatorFactory::Create(); - // TODO(jiayl): we should always pass a FakeIdentityService so that DTLS - // is enabled by default like in Chrome (issue 2838). + // DTLS does not work in a loopback call, so is disabled for most of the + // tests in this file. We only create a FakeIdentityService if the test + // explicitly sets the constraint. FakeIdentityService* dtls_service = NULL; bool dtls; if (FindConstraint(constraints, @@ -300,7 +301,7 @@ class PeerConnectionInterfaceTest : public testing::Test { EXPECT_EQ(0u, port_allocator_factory_->turn_configs().size()); CreatePeerConnection(kTurnIceServerUri, kTurnPassword, NULL); - EXPECT_EQ(1u, port_allocator_factory_->stun_configs().size()); + EXPECT_EQ(0u, port_allocator_factory_->stun_configs().size()); EXPECT_EQ(1u, port_allocator_factory_->turn_configs().size()); EXPECT_EQ(kTurnUsername, port_allocator_factory_->turn_configs()[0].username); @@ -308,8 +309,6 @@ class PeerConnectionInterfaceTest : public testing::Test { port_allocator_factory_->turn_configs()[0].password); EXPECT_EQ(kTurnHostname, port_allocator_factory_->turn_configs()[0].server.hostname()); - EXPECT_EQ(kTurnHostname, - port_allocator_factory_->stun_configs()[0].server.hostname()); } void ReleasePeerConnection() { @@ -947,23 +946,28 @@ TEST_F(PeerConnectionInterfaceTest, CreateSctpDataChannel) { pc_->CreateDataChannel("1", &config); EXPECT_TRUE(channel != NULL); EXPECT_TRUE(channel->reliable()); + EXPECT_TRUE(observer_.renegotiation_needed_); + observer_.renegotiation_needed_ = false; config.ordered = false; channel = pc_->CreateDataChannel("2", &config); EXPECT_TRUE(channel != NULL); EXPECT_TRUE(channel->reliable()); + EXPECT_FALSE(observer_.renegotiation_needed_); config.ordered = true; config.maxRetransmits = 0; channel = pc_->CreateDataChannel("3", &config); EXPECT_TRUE(channel != NULL); EXPECT_FALSE(channel->reliable()); + EXPECT_FALSE(observer_.renegotiation_needed_); config.maxRetransmits = -1; config.maxRetransmitTime = 0; channel = pc_->CreateDataChannel("4", &config); EXPECT_TRUE(channel != NULL); EXPECT_FALSE(channel->reliable()); + EXPECT_FALSE(observer_.renegotiation_needed_); } // This tests that no data channel is returned if both maxRetransmits and @@ -1013,6 +1017,23 @@ TEST_F(PeerConnectionInterfaceTest, EXPECT_TRUE(channel == NULL); } +// This test verifies that OnRenegotiationNeeded is fired for every new RTP +// DataChannel. +TEST_F(PeerConnectionInterfaceTest, RenegotiationNeededForNewRtpDataChannel) { + FakeConstraints constraints; + constraints.SetAllowRtpDataChannels(); + CreatePeerConnection(&constraints); + + scoped_refptr dc1 = + pc_->CreateDataChannel("test1", NULL); + EXPECT_TRUE(observer_.renegotiation_needed_); + observer_.renegotiation_needed_ = false; + + scoped_refptr dc2 = + pc_->CreateDataChannel("test2", NULL); + EXPECT_TRUE(observer_.renegotiation_needed_); +} + // This test that a data channel closes when a PeerConnection is deleted/closed. TEST_F(PeerConnectionInterfaceTest, DataChannelCloseWhenPeerConnectionClose) { FakeConstraints constraints; diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtc/peerconnectionproxy.h index a57110584..74e5012bb 100644 --- a/talk/app/webrtc/peerconnectionproxy.h +++ b/talk/app/webrtc/peerconnectionproxy.h @@ -62,6 +62,7 @@ BEGIN_PROXY_MAP(PeerConnection) PROXY_METHOD2(bool, UpdateIce, const IceServers&, const MediaConstraintsInterface*) PROXY_METHOD1(bool, AddIceCandidate, const IceCandidateInterface*) + PROXY_METHOD1(void, RegisterUMAObserver, UMAObserver*) PROXY_METHOD0(SignalingState, signaling_state) PROXY_METHOD0(IceState, ice_state) PROXY_METHOD0(IceConnectionState, ice_connection_state) diff --git a/talk/app/webrtc/statscollector.cc b/talk/app/webrtc/statscollector.cc index b87f45b4c..86a88c159 100644 --- a/talk/app/webrtc/statscollector.cc +++ b/talk/app/webrtc/statscollector.cc @@ -63,6 +63,18 @@ const char StatsReport::kStatsValueNameComponent[] = "googComponent"; const char StatsReport::kStatsValueNameContentName[] = "googContentName"; const char StatsReport::kStatsValueNameCpuLimitedResolution[] = "googCpuLimitedResolution"; +const char StatsReport::kStatsValueNameDecodingCTSG[] = + "googDecodingCTSG"; +const char StatsReport::kStatsValueNameDecodingCTN[] = + "googDecodingCTN"; +const char StatsReport::kStatsValueNameDecodingNormal[] = + "googDecodingNormal"; +const char StatsReport::kStatsValueNameDecodingPLC[] = + "googDecodingPLC"; +const char StatsReport::kStatsValueNameDecodingCNG[] = + "googDecodingCNG"; +const char StatsReport::kStatsValueNameDecodingPLCCNG[] = + "googDecodingPLCCNG"; const char StatsReport::kStatsValueNameDer[] = "googDerBase64"; // Echo metrics from the audio processing module. const char StatsReport::kStatsValueNameEchoCancellationQualityMin[] = @@ -76,6 +88,8 @@ const char StatsReport::kStatsValueNameEchoReturnLoss[] = const char StatsReport::kStatsValueNameEchoReturnLossEnhancement[] = "googEchoCancellationReturnLossEnhancement"; +const char StatsReport::kStatsValueNameEncodeRelStdDev[] = + "googEncodeRelStdDev"; const char StatsReport::kStatsValueNameEncodeUsagePercent[] = "googEncodeUsagePercent"; const char StatsReport::kStatsValueNameExpandRate[] = "googExpandRate"; @@ -105,6 +119,9 @@ const char StatsReport::kStatsValueNameMinPlayoutDelayMs[] = "googMinPlayoutDelayMs"; const char StatsReport::kStatsValueNameRenderDelayMs[] = "googRenderDelayMs"; +const char StatsReport::kStatsValueNameCaptureStartNtpTimeMs[] = + "googCaptureStartNtpTimeMs"; + const char StatsReport::kStatsValueNameFrameRateInput[] = "googFrameRateInput"; const char StatsReport::kStatsValueNameFrameRateSent[] = "googFrameRateSent"; const char StatsReport::kStatsValueNameFrameWidthInput[] = @@ -120,6 +137,8 @@ const char StatsReport::kStatsValueNameLocalCandidateType[] = "googLocalCandidateType"; const char StatsReport::kStatsValueNameLocalCertificateId[] = "googLocalCertificateId"; +const char StatsReport::kStatsValueNameAdaptationChanges[] = + "googAdaptationChanges"; const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived"; const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent"; const char StatsReport::kStatsValueNamePlisReceived[] = "googPlisReceived"; @@ -172,7 +191,6 @@ const char StatsReport::kStatsReportTypeCertificate[] = "googCertificate"; const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo"; - // Implementations of functions in statstypes.h void StatsReport::AddValue(const std::string& name, const std::string& value) { Value temp; @@ -223,6 +241,20 @@ std::string StatsId(const std::string& type, const std::string& id) { return type + "_" + id; } +std::string StatsId(const std::string& type, const std::string& id, + StatsCollector::TrackDirection direction) { + ASSERT(direction == StatsCollector::kSending || + direction == StatsCollector::kReceiving); + + // Strings for the direction of the track. + const char kSendDirection[] = "send"; + const char kRecvDirection[] = "recv"; + + const std::string direction_id = (direction == StatsCollector::kSending) ? + kSendDirection : kRecvDirection; + return type + "_" + id + "_" + direction_id; +} + bool ExtractValueFromReport( const StatsReport& report, const std::string& name, @@ -270,6 +302,21 @@ void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) { info.packets_rcvd); report->AddValue(StatsReport::kStatsValueNamePacketsLost, info.packets_lost); + report->AddValue(StatsReport::kStatsValueNameDecodingCTSG, + info.decoding_calls_to_silence_generator); + report->AddValue(StatsReport::kStatsValueNameDecodingCTN, + info.decoding_calls_to_neteq); + report->AddValue(StatsReport::kStatsValueNameDecodingNormal, + info.decoding_normal); + report->AddValue(StatsReport::kStatsValueNameDecodingPLC, + info.decoding_plc); + report->AddValue(StatsReport::kStatsValueNameDecodingCNG, + info.decoding_cng); + report->AddValue(StatsReport::kStatsValueNameDecodingPLCCNG, + info.decoding_plc_cng); + report->AddValue(StatsReport::kStatsValueNameCaptureStartNtpTimeMs, + info.capture_start_ntp_time_ms); + report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name); } void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) { @@ -338,6 +385,9 @@ void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) { info.min_playout_delay_ms); report->AddValue(StatsReport::kStatsValueNameRenderDelayMs, info.render_delay_ms); + + report->AddValue(StatsReport::kStatsValueNameCaptureStartNtpTimeMs, + info.capture_start_ntp_time_ms); } void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) { @@ -374,6 +424,8 @@ void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) { (info.adapt_reason & 0x2) > 0); report->AddBoolean(StatsReport::kStatsValueNameViewLimitedResolution, (info.adapt_reason & 0x4) > 0); + report->AddValue(StatsReport::kStatsValueNameAdaptationChanges, + info.adapt_changes); report->AddValue(StatsReport::kStatsValueNameAvgEncodeMs, info.avg_encode_ms); report->AddValue(StatsReport::kStatsValueNameCaptureJitterMs, info.capture_jitter_ms); @@ -381,6 +433,8 @@ void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) { info.capture_queue_delay_ms_per_s); report->AddValue(StatsReport::kStatsValueNameEncodeUsagePercent, info.encode_usage_percent); + report->AddValue(StatsReport::kStatsValueNameEncodeRelStdDev, + info.encode_rsd); } void ExtractStats(const cricket::BandwidthEstimationInfo& info, @@ -444,20 +498,22 @@ void ExtractRemoteStats(const cricket::MediaReceiverInfo& info, template void ExtractStatsFromList(const std::vector& data, const std::string& transport_id, - StatsCollector* collector) { + StatsCollector* collector, + StatsCollector::TrackDirection direction) { typename std::vector::const_iterator it = data.begin(); for (; it != data.end(); ++it) { std::string id; uint32 ssrc = it->ssrc(); - // Each object can result in 2 objects, a local and a remote object. + // Each track can have stats for both local and remote objects. // TODO(hta): Handle the case of multiple SSRCs per object. - StatsReport* report = collector->PrepareLocalReport(ssrc, transport_id); - if (!report) { - continue; - } - ExtractStats(*it, report); + StatsReport* report = collector->PrepareLocalReport(ssrc, transport_id, + direction); + if (report) + ExtractStats(*it, report); + if (it->remote_stats.size() > 0) { - report = collector->PrepareRemoteReport(ssrc, transport_id); + report = collector->PrepareRemoteReport(ssrc, transport_id, + direction); if (!report) { continue; } @@ -574,18 +630,16 @@ StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) { StatsReport* StatsCollector::PrepareLocalReport( uint32 ssrc, - const std::string& transport_id) { - std::string ssrc_id = talk_base::ToString(ssrc); + const std::string& transport_id, + TrackDirection direction) { + const std::string ssrc_id = talk_base::ToString(ssrc); StatsMap::iterator it = reports_.find(StatsId( - StatsReport::kStatsReportTypeSsrc, ssrc_id)); + StatsReport::kStatsReportTypeSsrc, ssrc_id, direction)); std::string track_id; if (it == reports_.end()) { - if (!session()->GetTrackIdBySsrc(ssrc, &track_id)) { - LOG(LS_WARNING) << "The SSRC " << ssrc - << " is not associated with a track"; + if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) return NULL; - } } else { // Keeps the old track id since we want to report the stats for inactive // tracks. @@ -595,7 +649,7 @@ StatsReport* StatsCollector::PrepareLocalReport( } StatsReport* report = GetOrCreateReport(StatsReport::kStatsReportTypeSsrc, - ssrc_id); + ssrc_id, direction); // Clear out stats from previous GatherStats calls if any. if (report->timestamp != stats_gathering_started_) { @@ -613,18 +667,16 @@ StatsReport* StatsCollector::PrepareLocalReport( StatsReport* StatsCollector::PrepareRemoteReport( uint32 ssrc, - const std::string& transport_id) { - std::string ssrc_id = talk_base::ToString(ssrc); + const std::string& transport_id, + TrackDirection direction) { + const std::string ssrc_id = talk_base::ToString(ssrc); StatsMap::iterator it = reports_.find(StatsId( - StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id)); + StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction)); std::string track_id; if (it == reports_.end()) { - if (!session()->GetTrackIdBySsrc(ssrc, &track_id)) { - LOG(LS_WARNING) << "The SSRC " << ssrc - << " is not associated with a track"; + if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) return NULL; - } } else { // Keeps the old track id since we want to report the stats for inactive // tracks. @@ -634,7 +686,7 @@ StatsReport* StatsCollector::PrepareRemoteReport( } StatsReport* report = GetOrCreateReport( - StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id); + StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction); // Clear out stats from previous GatherStats calls if any. // The timestamp will be added later. Zero it for debugging. @@ -833,8 +885,8 @@ void StatsCollector::ExtractVoiceInfo() { << session_->voice_channel()->content_name(); return; } - ExtractStatsFromList(voice_info.receivers, transport_id, this); - ExtractStatsFromList(voice_info.senders, transport_id, this); + ExtractStatsFromList(voice_info.receivers, transport_id, this, kReceiving); + ExtractStatsFromList(voice_info.senders, transport_id, this, kSending); UpdateStatsFromExistingLocalAudioTracks(); } @@ -860,8 +912,8 @@ void StatsCollector::ExtractVideoInfo( << session_->video_channel()->content_name(); return; } - ExtractStatsFromList(video_info.receivers, transport_id, this); - ExtractStatsFromList(video_info.senders, transport_id, this); + ExtractStatsFromList(video_info.receivers, transport_id, this, kReceiving); + ExtractStatsFromList(video_info.senders, transport_id, this, kSending); if (video_info.bw_estimations.size() != 1) { LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size(); } else { @@ -894,8 +946,11 @@ bool StatsCollector::GetTransportIdFromProxy(const std::string& proxy, } StatsReport* StatsCollector::GetReport(const std::string& type, - const std::string& id) { - std::string statsid = StatsId(type, id); + const std::string& id, + TrackDirection direction) { + ASSERT(type == StatsReport::kStatsReportTypeSsrc || + type == StatsReport::kStatsReportTypeRemoteSsrc); + std::string statsid = StatsId(type, id, direction); StatsReport* report = NULL; std::map::iterator it = reports_.find(statsid); if (it != reports_.end()) @@ -905,10 +960,13 @@ StatsReport* StatsCollector::GetReport(const std::string& type, } StatsReport* StatsCollector::GetOrCreateReport(const std::string& type, - const std::string& id) { - StatsReport* report = GetReport(type, id); + const std::string& id, + TrackDirection direction) { + ASSERT(type == StatsReport::kStatsReportTypeSsrc || + type == StatsReport::kStatsReportTypeRemoteSsrc); + StatsReport* report = GetReport(type, id, direction); if (report == NULL) { - std::string statsid = StatsId(type, id); + std::string statsid = StatsId(type, id, direction); report = &reports_[statsid]; // Create new element. report->id = statsid; report->type = type; @@ -925,7 +983,8 @@ void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() { uint32 ssrc = it->second; std::string ssrc_id = talk_base::ToString(ssrc); StatsReport* report = GetReport(StatsReport::kStatsReportTypeSsrc, - ssrc_id); + ssrc_id, + kSending); if (report == NULL) { // This can happen if a local audio track is added to a stream on the // fly and the report has not been set up yet. Do nothing in this case. @@ -980,4 +1039,24 @@ void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track, talk_base::ToString(stats.echo_delay_std_ms)); } +bool StatsCollector::GetTrackIdBySsrc(uint32 ssrc, std::string* track_id, + TrackDirection direction) { + if (direction == kSending) { + if (!session()->GetLocalTrackIdBySsrc(ssrc, track_id)) { + LOG(LS_WARNING) << "The SSRC " << ssrc + << " is not associated with a sending track"; + return false; + } + } else { + ASSERT(direction == kReceiving); + if (!session()->GetRemoteTrackIdBySsrc(ssrc, track_id)) { + LOG(LS_WARNING) << "The SSRC " << ssrc + << " is not associated with a receiving track"; + return false; + } + } + + return true; +} + } // namespace webrtc diff --git a/talk/app/webrtc/statscollector.h b/talk/app/webrtc/statscollector.h index fdb296157..77a1ba086 100644 --- a/talk/app/webrtc/statscollector.h +++ b/talk/app/webrtc/statscollector.h @@ -46,6 +46,11 @@ namespace webrtc { class StatsCollector { public: + enum TrackDirection { + kSending = 0, + kReceiving, + }; + StatsCollector(); // Register the session Stats should operate on. @@ -77,9 +82,11 @@ class StatsCollector { // Prepare an SSRC report for the given ssrc. Used internally // in the ExtractStatsFromList template. - StatsReport* PrepareLocalReport(uint32 ssrc, const std::string& transport); + StatsReport* PrepareLocalReport(uint32 ssrc, const std::string& transport, + TrackDirection direction); // Prepare an SSRC report for the given remote ssrc. Used internally. - StatsReport* PrepareRemoteReport(uint32 ssrc, const std::string& transport); + StatsReport* PrepareRemoteReport(uint32 ssrc, const std::string& transport, + TrackDirection direction); // Extracts the ID of a Transport belonging to an SSRC. Used internally. bool GetTransportIdFromProxy(const std::string& proxy, std::string* transport_id); @@ -102,15 +109,22 @@ class StatsCollector { void BuildSsrcToTransportId(); WebRtcSession* session() { return session_; } webrtc::StatsReport* GetOrCreateReport(const std::string& type, - const std::string& id); + const std::string& id, + TrackDirection direction); webrtc::StatsReport* GetReport(const std::string& type, - const std::string& id); + const std::string& id, + TrackDirection direction); // Helper method to get stats from the local audio tracks. void UpdateStatsFromExistingLocalAudioTracks(); void UpdateReportFromAudioTrack(AudioTrackInterface* track, StatsReport* report); + // Helper method to get the id for the track identified by ssrc. + // |direction| tells if the track is for sending or receiving. + bool GetTrackIdBySsrc(uint32 ssrc, std::string* track_id, + TrackDirection direction); + // A map from the report id to the report. std::map reports_; // Raw pointer to the session the statistics are gathered from. diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc index 24be20af1..2cd716e02 100644 --- a/talk/app/webrtc/statscollector_unittest.cc +++ b/talk/app/webrtc/statscollector_unittest.cc @@ -1,5 +1,6 @@ /* * libjingle + * Copyright 2014, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -39,7 +40,8 @@ #include "talk/media/devices/fakedevicemanager.h" #include "talk/p2p/base/fakesession.h" #include "talk/session/media/channelmanager.h" -#include "testing/base/public/gmock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" using cricket::StatsOptions; using testing::_; @@ -66,8 +68,8 @@ const char kNotFound[] = "NOT FOUND"; const char kNoReports[] = "NO REPORTS"; // Constant names for track identification. -const char kTrackId[] = "somename"; -const char kAudioTrackId[] = "audio_track_id"; +const char kLocalTrackId[] = "local_track_id"; +const char kRemoteTrackId[] = "remote_track_id"; const uint32 kSsrcOfTrack = 1234; class MockWebRtcSession : public webrtc::WebRtcSession { @@ -78,7 +80,10 @@ class MockWebRtcSession : public webrtc::WebRtcSession { } MOCK_METHOD0(voice_channel, cricket::VoiceChannel*()); MOCK_METHOD0(video_channel, cricket::VideoChannel*()); - MOCK_METHOD2(GetTrackIdBySsrc, bool(uint32, std::string*)); + // Libjingle uses "local" for a outgoing track, and "remote" for a incoming + // track. + MOCK_METHOD2(GetLocalTrackIdBySsrc, bool(uint32, std::string*)); + MOCK_METHOD2(GetRemoteTrackIdBySsrc, bool(uint32, std::string*)); MOCK_METHOD1(GetStats, bool(cricket::SessionStats*)); MOCK_METHOD1(GetTransport, cricket::Transport*(const std::string&)); }; @@ -116,10 +121,10 @@ class FakeAudioProcessor : public webrtc::AudioProcessorInterface { } }; -class FakeLocalAudioTrack +class FakeAudioTrack : public webrtc::MediaStreamTrack { public: - explicit FakeLocalAudioTrack(const std::string& id) + explicit FakeAudioTrack(const std::string& id) : webrtc::MediaStreamTrack(id), processor_(new talk_base::RefCountedObject()) {} std::string kind() const OVERRIDE { @@ -263,6 +268,61 @@ void CheckCertChainReports(const StatsReports& reports, EXPECT_EQ(ders.size(), i); } +void VerifyVoiceReceiverInfoReport( + const StatsReport* report, + const cricket::VoiceReceiverInfo& info) { + std::string value_in_report; + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameAudioOutputLevel, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.audio_level), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameBytesReceived, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.bytes_rcvd), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameJitterReceived, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.jitter_ms), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameJitterBufferMs, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.jitter_buffer_ms), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNamePreferredJitterBufferMs, + &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.jitter_buffer_preferred_ms), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameCurrentDelayMs, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.delay_estimate_ms), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameExpandRate, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.expand_rate), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNamePacketsReceived, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.packets_rcvd), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameDecodingCTSG, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.decoding_calls_to_silence_generator), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameDecodingCTN, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.decoding_calls_to_neteq), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameDecodingNormal, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.decoding_normal), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameDecodingPLC, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.decoding_plc), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameDecodingCNG, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.decoding_cng), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameDecodingPLCCNG, &value_in_report)); + EXPECT_EQ(talk_base::ToString(info.decoding_plc_cng), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameCodecName, &value_in_report)); +} + + void VerifyVoiceSenderInfoReport(const StatsReport* report, const cricket::VoiceSenderInfo& sinfo) { std::string value_in_report; @@ -317,6 +377,59 @@ void VerifyVoiceSenderInfoReport(const StatsReport* report, EXPECT_EQ(typing_detected, value_in_report); } +// Helper methods to avoid duplication of code. +void InitVoiceSenderInfo(cricket::VoiceSenderInfo* voice_sender_info) { + voice_sender_info->add_ssrc(kSsrcOfTrack); + voice_sender_info->codec_name = "fake_codec"; + voice_sender_info->bytes_sent = 100; + voice_sender_info->packets_sent = 101; + voice_sender_info->rtt_ms = 102; + voice_sender_info->fraction_lost = 103; + voice_sender_info->jitter_ms = 104; + voice_sender_info->packets_lost = 105; + voice_sender_info->ext_seqnum = 106; + voice_sender_info->audio_level = 107; + voice_sender_info->echo_return_loss = 108; + voice_sender_info->echo_return_loss_enhancement = 109; + voice_sender_info->echo_delay_median_ms = 110; + voice_sender_info->echo_delay_std_ms = 111; + voice_sender_info->aec_quality_min = 112.0f; + voice_sender_info->typing_noise_detected = false; +} + +void UpdateVoiceSenderInfoFromAudioTrack( + FakeAudioTrack* audio_track, cricket::VoiceSenderInfo* voice_sender_info) { + audio_track->GetSignalLevel(&voice_sender_info->audio_level); + webrtc::AudioProcessorInterface::AudioProcessorStats audio_processor_stats; + audio_track->GetAudioProcessor()->GetStats(&audio_processor_stats); + voice_sender_info->typing_noise_detected = + audio_processor_stats.typing_noise_detected; + voice_sender_info->echo_return_loss = audio_processor_stats.echo_return_loss; + voice_sender_info->echo_return_loss_enhancement = + audio_processor_stats.echo_return_loss_enhancement; + voice_sender_info->echo_delay_median_ms = + audio_processor_stats.echo_delay_median_ms; + voice_sender_info->aec_quality_min = audio_processor_stats.aec_quality_min; + voice_sender_info->echo_delay_std_ms = + audio_processor_stats.echo_delay_std_ms; +} + +void InitVoiceReceiverInfo(cricket::VoiceReceiverInfo* voice_receiver_info) { + voice_receiver_info->add_ssrc(kSsrcOfTrack); + voice_receiver_info->bytes_rcvd = 110; + voice_receiver_info->packets_rcvd = 111; + voice_receiver_info->packets_lost = 112; + voice_receiver_info->fraction_lost = 113; + voice_receiver_info->packets_lost = 114; + voice_receiver_info->ext_seqnum = 115; + voice_receiver_info->jitter_ms = 116; + voice_receiver_info->jitter_buffer_ms = 117; + voice_receiver_info->jitter_buffer_preferred_ms = 118; + voice_receiver_info->delay_estimate_ms = 119; + voice_receiver_info->audio_level = 120; + voice_receiver_info->expand_rate = 121; +} + class StatsCollectorTest : public testing::Test { protected: StatsCollectorTest() @@ -325,8 +438,7 @@ class StatsCollectorTest : public testing::Test { new cricket::ChannelManager(media_engine_, new cricket::FakeDeviceManager(), talk_base::Thread::Current())), - session_(channel_manager_.get()), - track_id_(kTrackId) { + session_(channel_manager_.get()) { // By default, we ignore session GetStats calls. EXPECT_CALL(session_, GetStats(_)).WillRepeatedly(Return(false)); } @@ -346,27 +458,56 @@ class StatsCollectorTest : public testing::Test { session_stats_.proxy_to_transport[vc_name] = kTransportName; } - // Adds a track with a given SSRC into the stats. - void AddVideoTrackStats() { + // Adds a outgoing video track with a given SSRC into the stats. + void AddOutgoingVideoTrackStats() { stream_ = webrtc::MediaStream::Create("streamlabel"); - track_= webrtc::VideoTrack::Create(kTrackId, NULL); + track_= webrtc::VideoTrack::Create(kLocalTrackId, NULL); stream_->AddTrack(track_); - EXPECT_CALL(session_, GetTrackIdBySsrc(kSsrcOfTrack, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(track_id_), - Return(true))); + EXPECT_CALL(session_, GetLocalTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kLocalTrackId), Return(true))); + EXPECT_CALL(session_, GetRemoteTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(Return(false)); } - // Adds a local audio track with a given SSRC into the stats. - void AddLocalAudioTrackStats() { + // Adds a incoming video track with a given SSRC into the stats. + void AddIncomingVideoTrackStats() { + stream_ = webrtc::MediaStream::Create("streamlabel"); + track_= webrtc::VideoTrack::Create(kRemoteTrackId, NULL); + stream_->AddTrack(track_); + EXPECT_CALL(session_, GetLocalTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(session_, GetRemoteTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kRemoteTrackId), + Return(true))); + } + + // Adds a outgoing audio track with a given SSRC into the stats. + void AddOutgoingAudioTrackStats() { if (stream_ == NULL) stream_ = webrtc::MediaStream::Create("streamlabel"); - audio_track_ = - new talk_base::RefCountedObject(kAudioTrackId); + audio_track_ = new talk_base::RefCountedObject( + kLocalTrackId); stream_->AddTrack(audio_track_); - EXPECT_CALL(session_, GetTrackIdBySsrc(kSsrcOfTrack, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(kAudioTrackId), + EXPECT_CALL(session_, GetLocalTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kLocalTrackId), Return(true))); + EXPECT_CALL(session_, GetRemoteTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(Return(false)); + } + + // Adds a incoming audio track with a given SSRC into the stats. + void AddIncomingAudioTrackStats() { + if (stream_ == NULL) + stream_ = webrtc::MediaStream::Create("streamlabel"); + + audio_track_ = new talk_base::RefCountedObject( + kRemoteTrackId); + stream_->AddTrack(audio_track_); + EXPECT_CALL(session_, GetLocalTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(session_, GetRemoteTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kRemoteTrackId), Return(true))); } void TestCertificateReports(const talk_base::FakeSSLCertificate& local_cert, @@ -455,14 +596,13 @@ class StatsCollectorTest : public testing::Test { cricket::SessionStats session_stats_; talk_base::scoped_refptr stream_; talk_base::scoped_refptr track_; - talk_base::scoped_refptr audio_track_; - std::string track_id_; + talk_base::scoped_refptr audio_track_; }; // This test verifies that 64-bit counters are passed successfully. TEST_F(StatsCollectorTest, BytesCounterHandles64Bits) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, "", false, NULL); StatsReports reports; // returned values. @@ -473,7 +613,7 @@ TEST_F(StatsCollectorTest, BytesCounterHandles64Bits) { const std::string kBytesSentString("12345678901234"); stats.set_session(&session_); - AddVideoTrackStats(); + AddOutgoingVideoTrackStats(); stats.AddStream(stream_); // Construct a stats value to read. @@ -495,7 +635,7 @@ TEST_F(StatsCollectorTest, BytesCounterHandles64Bits) { // Test that BWE information is reported via stats. TEST_F(StatsCollectorTest, BandwidthEstimationInfoIsReported) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, "", false, NULL); StatsReports reports; // returned values. @@ -507,7 +647,7 @@ TEST_F(StatsCollectorTest, BandwidthEstimationInfoIsReported) { const std::string kBytesSentString("12345678901234"); stats.set_session(&session_); - AddVideoTrackStats(); + AddOutgoingVideoTrackStats(); stats.AddStream(stream_); // Construct a stats value to read. @@ -572,10 +712,10 @@ TEST_F(StatsCollectorTest, OnlyOneSessionObjectExists) { // without calling StatsCollector::UpdateStats. TEST_F(StatsCollectorTest, TrackObjectExistsWithoutUpdateStats) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, "", false, NULL); - AddVideoTrackStats(); + AddOutgoingVideoTrackStats(); stats.AddStream(stream_); stats.set_session(&session_); @@ -592,17 +732,17 @@ TEST_F(StatsCollectorTest, TrackObjectExistsWithoutUpdateStats) { ExtractStatsValue(StatsReport::kStatsReportTypeTrack, reports, StatsReport::kStatsValueNameTrackId); - EXPECT_EQ(kTrackId, trackValue); + EXPECT_EQ(kLocalTrackId, trackValue); } // This test verifies that the empty track report exists in the returned stats // when StatsCollector::UpdateStats is called with ssrc stats. TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, "", false, NULL); - AddVideoTrackStats(); + AddOutgoingVideoTrackStats(); stats.AddStream(stream_); stats.set_session(&session_); @@ -632,15 +772,16 @@ TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { EXPECT_LE((size_t)3, reports.size()); const StatsReport* track_report = FindNthReportByType( reports, StatsReport::kStatsReportTypeTrack, 1); - EXPECT_FALSE(track_report == NULL); + EXPECT_TRUE(track_report); + // Get report for the specific |track|. stats.GetStats(track_, &reports); // |reports| should contain at least one session report, one track report, // and one ssrc report. EXPECT_LE((size_t)3, reports.size()); track_report = FindNthReportByType( reports, StatsReport::kStatsReportTypeTrack, 1); - EXPECT_FALSE(track_report == NULL); + EXPECT_TRUE(track_report); std::string ssrc_id = ExtractSsrcStatsValue( reports, StatsReport::kStatsValueNameSsrc); @@ -648,19 +789,19 @@ TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { std::string track_id = ExtractSsrcStatsValue( reports, StatsReport::kStatsValueNameTrackId); - EXPECT_EQ(kTrackId, track_id); + EXPECT_EQ(kLocalTrackId, track_id); } // This test verifies that an SSRC object has the identifier of a Transport // stats object, and that this transport stats object exists in stats. TEST_F(StatsCollectorTest, TransportObjectLinkedFromSsrcObject) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); // The content_name known by the video channel. const std::string kVcName("vcname"); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, kVcName, false, NULL); - AddVideoTrackStats(); + AddOutgoingVideoTrackStats(); stats.AddStream(stream_); stats.set_session(&session_); @@ -704,12 +845,12 @@ TEST_F(StatsCollectorTest, TransportObjectLinkedFromSsrcObject) { // an outgoing SSRC where remote stats are not returned. TEST_F(StatsCollectorTest, RemoteSsrcInfoIsAbsent) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); // The content_name known by the video channel. const std::string kVcName("vcname"); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, kVcName, false, NULL); - AddVideoTrackStats(); + AddOutgoingVideoTrackStats(); stats.AddStream(stream_); stats.set_session(&session_); @@ -729,12 +870,12 @@ TEST_F(StatsCollectorTest, RemoteSsrcInfoIsAbsent) { // an outgoing SSRC where stats are returned. TEST_F(StatsCollectorTest, RemoteSsrcInfoIsPresent) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); // The content_name known by the video channel. const std::string kVcName("vcname"); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, kVcName, false, NULL); - AddVideoTrackStats(); + AddOutgoingVideoTrackStats(); stats.AddStream(stream_); stats.set_session(&session_); @@ -766,12 +907,61 @@ TEST_F(StatsCollectorTest, RemoteSsrcInfoIsPresent) { stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); + const StatsReport* remote_report = FindNthReportByType(reports, StatsReport::kStatsReportTypeRemoteSsrc, 1); EXPECT_FALSE(remote_report == NULL); EXPECT_NE(0, remote_report->timestamp); } +// This test verifies that the empty track report exists in the returned stats +// when StatsCollector::UpdateStats is called with ssrc stats. +TEST_F(StatsCollectorTest, ReportsFromRemoteTrack) { + webrtc::StatsCollector stats; // Implementation under test. + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); + cricket::VideoChannel video_channel(talk_base::Thread::Current(), + media_engine_, media_channel, &session_, "", false, NULL); + AddIncomingVideoTrackStats(); + stats.AddStream(stream_); + + stats.set_session(&session_); + + StatsReports reports; + + // Constructs an ssrc stats update. + cricket::VideoReceiverInfo video_receiver_info; + cricket::VideoMediaInfo stats_read; + const int64 kNumOfPacketsConcealed = 54321; + + // Construct a stats value to read. + video_receiver_info.add_ssrc(1234); + video_receiver_info.packets_concealed = kNumOfPacketsConcealed; + stats_read.receivers.push_back(video_receiver_info); + + EXPECT_CALL(session_, video_channel()).WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(*media_channel, GetStats(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(stats_read), + Return(true))); + + stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + stats.GetStats(NULL, &reports); + // |reports| should contain at least one session report, one track report, + // and one ssrc report. + EXPECT_LE(static_cast(3), reports.size()); + const StatsReport* track_report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeTrack, 1); + EXPECT_TRUE(track_report); + + std::string ssrc_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); + + std::string track_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kRemoteTrackId, track_id); +} + // This test verifies that all chained certificates are correctly // reported TEST_F(StatsCollectorTest, ChainedCertificateReportsCreated) { @@ -930,7 +1120,7 @@ TEST_F(StatsCollectorTest, UnsupportedDigestIgnored) { // verbose output level. TEST_F(StatsCollectorTest, StatsOutputLevelVerbose) { webrtc::StatsCollector stats; // Implementation under test. - MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; + MockVideoMediaChannel* media_channel = new MockVideoMediaChannel(); cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, "", false, NULL); stats.set_session(&session_); @@ -979,7 +1169,7 @@ TEST_F(StatsCollectorTest, GetStatsFromLocalAudioTrack) { const std::string kVcName("vcname"); cricket::VoiceChannel voice_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, kVcName, false); - AddLocalAudioTrackStats(); + AddOutgoingAudioTrackStats(); stats.AddStream(stream_); stats.AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); @@ -992,25 +1182,7 @@ TEST_F(StatsCollectorTest, GetStatsFromLocalAudioTrack) { Return(true))); cricket::VoiceSenderInfo voice_sender_info; - // Contents won't be modified by the AudioTrackInterface::GetStats(). - voice_sender_info.add_ssrc(kSsrcOfTrack); - voice_sender_info.codec_name = "fake_codec"; - voice_sender_info.bytes_sent = 100; - voice_sender_info.packets_sent = 101; - voice_sender_info.rtt_ms = 102; - voice_sender_info.fraction_lost = 103; - voice_sender_info.jitter_ms = 104; - voice_sender_info.packets_lost = 105; - voice_sender_info.ext_seqnum = 106; - - // Contents will be modified by the AudioTrackInterface::GetStats(). - voice_sender_info.audio_level = 107; - voice_sender_info.echo_return_loss = 108;; - voice_sender_info.echo_return_loss_enhancement = 109; - voice_sender_info.echo_delay_median_ms = 110; - voice_sender_info.echo_delay_std_ms = 111; - voice_sender_info.aec_quality_min = 112.0f; - voice_sender_info.typing_noise_detected = false; + InitVoiceSenderInfo(&voice_sender_info); // Constructs an ssrc stats update. cricket::VoiceMediaInfo stats_read; @@ -1032,24 +1204,13 @@ TEST_F(StatsCollectorTest, GetStatsFromLocalAudioTrack) { EXPECT_FALSE(report == NULL); std::string track_id = ExtractSsrcStatsValue( reports, StatsReport::kStatsValueNameTrackId); - EXPECT_EQ(kAudioTrackId, track_id); + EXPECT_EQ(kLocalTrackId, track_id); std::string ssrc_id = ExtractSsrcStatsValue( reports, StatsReport::kStatsValueNameSsrc); EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); // Verifies the values in the track report. - audio_track_->GetSignalLevel(&voice_sender_info.audio_level); - webrtc::AudioProcessorInterface::AudioProcessorStats audio_processor_stats; - audio_track_->GetAudioProcessor()->GetStats(&audio_processor_stats); - voice_sender_info.typing_noise_detected = - audio_processor_stats.typing_noise_detected; - voice_sender_info.echo_return_loss = audio_processor_stats.echo_return_loss; - voice_sender_info.echo_return_loss_enhancement = - audio_processor_stats.echo_return_loss_enhancement; - voice_sender_info.echo_delay_median_ms = - audio_processor_stats.echo_delay_median_ms; - voice_sender_info.aec_quality_min = audio_processor_stats.aec_quality_min; - voice_sender_info.echo_delay_std_ms = audio_processor_stats.echo_delay_std_ms; + UpdateVoiceSenderInfoFromAudioTrack(audio_track_.get(), &voice_sender_info); VerifyVoiceSenderInfoReport(report, voice_sender_info); // Verify we get the same result by passing a track to GetStats(). @@ -1057,14 +1218,70 @@ TEST_F(StatsCollectorTest, GetStatsFromLocalAudioTrack) { stats.GetStats(audio_track_.get(), &track_reports); const StatsReport* track_report = FindNthReportByType( track_reports, StatsReport::kStatsReportTypeSsrc, 1); - EXPECT_FALSE(track_report == NULL); + EXPECT_TRUE(track_report); track_id = ExtractSsrcStatsValue(track_reports, StatsReport::kStatsValueNameTrackId); - EXPECT_EQ(kAudioTrackId, track_id); + EXPECT_EQ(kLocalTrackId, track_id); ssrc_id = ExtractSsrcStatsValue(track_reports, StatsReport::kStatsValueNameSsrc); EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); VerifyVoiceSenderInfoReport(track_report, voice_sender_info); + + // Verify that there is no remote report for the local audio track because + // we did not set it up. + const StatsReport* remote_report = FindNthReportByType(reports, + StatsReport::kStatsReportTypeRemoteSsrc, 1); + EXPECT_TRUE(remote_report == NULL); +} + +// This test verifies that audio receive streams populate stats reports +// correctly. +TEST_F(StatsCollectorTest, GetStatsFromRemoteStream) { + webrtc::StatsCollector stats; // Implementation under test. + MockVoiceMediaChannel* media_channel = new MockVoiceMediaChannel(); + // The content_name known by the voice channel. + const std::string kVcName("vcname"); + cricket::VoiceChannel voice_channel(talk_base::Thread::Current(), + media_engine_, media_channel, &session_, kVcName, false); + AddIncomingAudioTrackStats(); + stats.AddStream(stream_); + + stats.set_session(&session_); + + // Instruct the session to return stats containing the transport channel. + InitSessionStats(kVcName); + EXPECT_CALL(session_, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), + Return(true))); + + cricket::VoiceReceiverInfo voice_receiver_info; + InitVoiceReceiverInfo(&voice_receiver_info); + voice_receiver_info.codec_name = "fake_codec"; + + // Constructs an ssrc stats update. + cricket::VoiceMediaInfo stats_read; + stats_read.receivers.push_back(voice_receiver_info); + + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(Return(&voice_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(*media_channel, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(stats_read), + Return(true))); + + StatsReports reports; // returned values. + stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + stats.GetStats(NULL, &reports); + + // Verify the track id is |kRemoteTrackId|. + const std::string track_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kRemoteTrackId, track_id); + + // Verify the report for this remote track. + const StatsReport* report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeSsrc, 1); + EXPECT_FALSE(report == NULL); + VerifyVoiceReceiverInfoReport(report, voice_receiver_info); } // This test verifies that a local stats object won't update its statistics @@ -1076,7 +1293,7 @@ TEST_F(StatsCollectorTest, GetStatsAfterRemoveAudioStream) { const std::string kVcName("vcname"); cricket::VoiceChannel voice_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, kVcName, false); - AddLocalAudioTrackStats(); + AddOutgoingAudioTrackStats(); stats.AddStream(stream_); stats.AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); @@ -1090,25 +1307,7 @@ TEST_F(StatsCollectorTest, GetStatsAfterRemoveAudioStream) { stats.RemoveLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); cricket::VoiceSenderInfo voice_sender_info; - // Contents won't be modified by the AudioTrackInterface::GetStats(). - voice_sender_info.add_ssrc(kSsrcOfTrack); - voice_sender_info.codec_name = "fake_codec"; - voice_sender_info.bytes_sent = 100; - voice_sender_info.packets_sent = 101; - voice_sender_info.rtt_ms = 102; - voice_sender_info.fraction_lost = 103; - voice_sender_info.jitter_ms = 104; - voice_sender_info.packets_lost = 105; - voice_sender_info.ext_seqnum = 106; - - // Contents will be modified by the AudioTrackInterface::GetStats(). - voice_sender_info.audio_level = 107; - voice_sender_info.echo_return_loss = 108;; - voice_sender_info.echo_return_loss_enhancement = 109; - voice_sender_info.echo_delay_median_ms = 110; - voice_sender_info.echo_delay_std_ms = 111; - voice_sender_info.aec_quality_min = 112; - voice_sender_info.typing_noise_detected = false; + InitVoiceSenderInfo(&voice_sender_info); // Constructs an ssrc stats update. cricket::VoiceMediaInfo stats_read; @@ -1130,7 +1329,7 @@ TEST_F(StatsCollectorTest, GetStatsAfterRemoveAudioStream) { EXPECT_FALSE(report == NULL); std::string track_id = ExtractSsrcStatsValue( reports, StatsReport::kStatsValueNameTrackId); - EXPECT_EQ(kAudioTrackId, track_id); + EXPECT_EQ(kLocalTrackId, track_id); std::string ssrc_id = ExtractSsrcStatsValue( reports, StatsReport::kStatsValueNameSsrc); EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); @@ -1141,4 +1340,82 @@ TEST_F(StatsCollectorTest, GetStatsAfterRemoveAudioStream) { VerifyVoiceSenderInfoReport(report, voice_sender_info); } +// This test verifies that when ongoing and incoming audio tracks are using +// the same ssrc, they populate stats reports correctly. +TEST_F(StatsCollectorTest, LocalAndRemoteTracksWithSameSsrc) { + webrtc::StatsCollector stats; // Implementation under test. + MockVoiceMediaChannel* media_channel = new MockVoiceMediaChannel(); + // The content_name known by the voice channel. + const std::string kVcName("vcname"); + cricket::VoiceChannel voice_channel(talk_base::Thread::Current(), + media_engine_, media_channel, &session_, kVcName, false); + + // Create a local stream with a local audio track and adds it to the stats. + AddOutgoingAudioTrackStats(); + stats.AddStream(stream_); + stats.AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + // Create a remote stream with a remote audio track and adds it to the stats. + talk_base::scoped_refptr remote_stream( + webrtc::MediaStream::Create("remotestreamlabel")); + talk_base::scoped_refptr remote_track( + new talk_base::RefCountedObject(kRemoteTrackId)); + EXPECT_CALL(session_, GetRemoteTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kRemoteTrackId), Return(true))); + remote_stream->AddTrack(remote_track); + stats.AddStream(remote_stream); + + stats.set_session(&session_); + + // Instruct the session to return stats containing the transport channel. + InitSessionStats(kVcName); + EXPECT_CALL(session_, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), + Return(true))); + + cricket::VoiceSenderInfo voice_sender_info; + InitVoiceSenderInfo(&voice_sender_info); + + // Some of the contents in |voice_sender_info| needs to be updated from the + // |audio_track_|. + UpdateVoiceSenderInfoFromAudioTrack(audio_track_.get(), &voice_sender_info); + + cricket::VoiceReceiverInfo voice_receiver_info; + InitVoiceReceiverInfo(&voice_receiver_info); + + // Constructs an ssrc stats update. + cricket::VoiceMediaInfo stats_read; + stats_read.senders.push_back(voice_sender_info); + stats_read.receivers.push_back(voice_receiver_info); + + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(Return(&voice_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(*media_channel, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(stats_read), + Return(true))); + + StatsReports reports; // returned values. + stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + + // Get stats for the local track. + stats.GetStats(audio_track_.get(), &reports); + const StatsReport* track_report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeSsrc, 1); + EXPECT_TRUE(track_report); + std::string track_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kLocalTrackId, track_id); + VerifyVoiceSenderInfoReport(track_report, voice_sender_info); + + // Get stats for the remote track. + stats.GetStats(remote_track.get(), &reports); + track_report = FindNthReportByType(reports, + StatsReport::kStatsReportTypeSsrc, 1); + EXPECT_TRUE(track_report); + track_id = ExtractSsrcStatsValue(reports, + StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kRemoteTrackId, track_id); + VerifyVoiceReceiverInfoReport(track_report, voice_receiver_info); +} + } // namespace diff --git a/talk/app/webrtc/statstypes.h b/talk/app/webrtc/statstypes.h index abaf4e156..0a2aa03a5 100644 --- a/talk/app/webrtc/statstypes.h +++ b/talk/app/webrtc/statstypes.h @@ -133,6 +133,7 @@ class StatsReport { // Internal StatsValue names static const char kStatsValueNameAvgEncodeMs[]; + static const char kStatsValueNameEncodeRelStdDev[]; static const char kStatsValueNameEncodeUsagePercent[]; static const char kStatsValueNameCaptureJitterMs[]; static const char kStatsValueNameCaptureQueueDelayMsPerS[]; @@ -140,6 +141,7 @@ class StatsReport { static const char kStatsValueNameBandwidthLimitedResolution[]; static const char kStatsValueNameCpuLimitedResolution[]; static const char kStatsValueNameViewLimitedResolution[]; + static const char kStatsValueNameAdaptationChanges[]; static const char kStatsValueNameEchoCancellationQualityMin[]; static const char kStatsValueNameEchoDelayMedian[]; static const char kStatsValueNameEchoDelayStdDev[]; @@ -161,6 +163,7 @@ class StatsReport { static const char kStatsValueNameJitterBufferMs[]; static const char kStatsValueNameMinPlayoutDelayMs[]; static const char kStatsValueNameRenderDelayMs[]; + static const char kStatsValueNameCaptureStartNtpTimeMs[]; static const char kStatsValueNameFrameRateInput[]; static const char kStatsValueNameFrameRateSent[]; static const char kStatsValueNameFrameWidthInput[]; @@ -199,6 +202,12 @@ class StatsReport { static const char kStatsValueNameRecvPacketGroupArrivalTimeDebug[]; static const char kStatsValueNameRecvPacketGroupPropagationDeltaDebug[]; static const char kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug[]; + static const char kStatsValueNameDecodingCTSG[]; + static const char kStatsValueNameDecodingCTN[]; + static const char kStatsValueNameDecodingNormal[]; + static const char kStatsValueNameDecodingPLC[]; + static const char kStatsValueNameDecodingCNG[]; + static const char kStatsValueNameDecodingPLCCNG[]; }; typedef std::vector StatsReports; diff --git a/talk/app/webrtc/test/fakeaudiocapturemodule.cc b/talk/app/webrtc/test/fakeaudiocapturemodule.cc index 3b3616324..c22ed6f5d 100644 --- a/talk/app/webrtc/test/fakeaudiocapturemodule.cc +++ b/talk/app/webrtc/test/fakeaudiocapturemodule.cc @@ -728,11 +728,22 @@ void FakeAudioCaptureModule::ReceiveFrameP() { } ResetRecBuffer(); uint32_t nSamplesOut = 0; +#ifdef USE_WEBRTC_DEV_BRANCH + int64_t elapsed_time_ms = 0; + int64_t ntp_time_ms = 0; + if (audio_callback_->NeedMorePlayData(kNumberSamples, kNumberBytesPerSample, + kNumberOfChannels, kSamplesPerSecond, + rec_buffer_, nSamplesOut, + &elapsed_time_ms, &ntp_time_ms) != 0) { + ASSERT(false); + } +#else if (audio_callback_->NeedMorePlayData(kNumberSamples, kNumberBytesPerSample, kNumberOfChannels, kSamplesPerSecond, rec_buffer_, nSamplesOut) != 0) { ASSERT(false); } +#endif ASSERT(nSamplesOut == kNumberSamples); } // The SetBuffer() function ensures that after decoding, the audio buffer diff --git a/talk/app/webrtc/test/fakeaudiocapturemodule_unittest.cc b/talk/app/webrtc/test/fakeaudiocapturemodule_unittest.cc index 5738955ec..ab0db0623 100644 --- a/talk/app/webrtc/test/fakeaudiocapturemodule_unittest.cc +++ b/talk/app/webrtc/test/fakeaudiocapturemodule_unittest.cc @@ -84,13 +84,23 @@ class FakeAdmTest : public testing::Test, const uint8_t nChannels, const uint32_t samplesPerSec, void* audioSamples, +#ifdef USE_WEBRTC_DEV_BRANCH + uint32_t& nSamplesOut, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) { +#else uint32_t& nSamplesOut) { +#endif ++pull_iterations_; const uint32_t audio_buffer_size = nSamples * nBytesPerSample; const uint32_t bytes_out = RecordedDataReceived() ? CopyFromRecBuffer(audioSamples, audio_buffer_size): GenerateZeroBuffer(audioSamples, audio_buffer_size); nSamplesOut = bytes_out / nBytesPerSample; +#ifdef USE_WEBRTC_DEV_BRANCH + *elapsed_time_ms = 0; + *ntp_time_ms = 0; +#endif return 0; } diff --git a/talk/app/webrtc/test/fakedatachannelprovider.h b/talk/app/webrtc/test/fakedatachannelprovider.h index 5b196983c..053b3acce 100644 --- a/talk/app/webrtc/test/fakedatachannelprovider.h +++ b/talk/app/webrtc/test/fakedatachannelprovider.h @@ -32,7 +32,8 @@ class FakeDataChannelProvider : public webrtc::DataChannelProviderInterface { FakeDataChannelProvider() : send_blocked_(false), transport_available_(false), - ready_to_send_(false) {} + ready_to_send_(false), + transport_error_(false) {} virtual ~FakeDataChannelProvider() {} virtual bool SendData(const cricket::SendDataParams& params, @@ -43,6 +44,12 @@ class FakeDataChannelProvider : public webrtc::DataChannelProviderInterface { *result = cricket::SDR_BLOCK; return false; } + + if (transport_error_) { + *result = cricket::SDR_ERROR; + return false; + } + last_send_data_params_ = params; return true; } @@ -115,6 +122,10 @@ class FakeDataChannelProvider : public webrtc::DataChannelProviderInterface { } } + void set_transport_error() { + transport_error_ = true; + } + cricket::SendDataParams last_send_data_params() const { return last_send_data_params_; } @@ -136,6 +147,7 @@ class FakeDataChannelProvider : public webrtc::DataChannelProviderInterface { bool send_blocked_; bool transport_available_; bool ready_to_send_; + bool transport_error_; std::set connected_channels_; std::set send_ssrcs_; std::set recv_ssrcs_; diff --git a/talk/app/webrtc/test/mockpeerconnectionobservers.h b/talk/app/webrtc/test/mockpeerconnectionobservers.h index e2de37930..3ae2162bc 100644 --- a/talk/app/webrtc/test/mockpeerconnectionobservers.h +++ b/talk/app/webrtc/test/mockpeerconnectionobservers.h @@ -90,7 +90,7 @@ class MockSetSessionDescriptionObserver class MockDataChannelObserver : public webrtc::DataChannelObserver { public: explicit MockDataChannelObserver(webrtc::DataChannelInterface* channel) - : channel_(channel) { + : channel_(channel), received_message_count_(0) { channel_->RegisterObserver(this); state_ = channel_->state(); } @@ -101,15 +101,18 @@ class MockDataChannelObserver : public webrtc::DataChannelObserver { virtual void OnStateChange() { state_ = channel_->state(); } virtual void OnMessage(const DataBuffer& buffer) { last_message_.assign(buffer.data.data(), buffer.data.length()); + ++received_message_count_; } bool IsOpen() const { return state_ == DataChannelInterface::kOpen; } const std::string& last_message() const { return last_message_; } + size_t received_message_count() const { return received_message_count_; } private: talk_base::scoped_refptr channel_; DataChannelInterface::DataState state_; std::string last_message_; + size_t received_message_count_; }; class MockStatsObserver : public webrtc::StatsObserver { diff --git a/talk/app/webrtc/test/peerconnectiontestwrapper.cc b/talk/app/webrtc/test/peerconnectiontestwrapper.cc index ca4b6d2f9..be70969db 100644 --- a/talk/app/webrtc/test/peerconnectiontestwrapper.cc +++ b/talk/app/webrtc/test/peerconnectiontestwrapper.cc @@ -26,6 +26,7 @@ */ #include "talk/app/webrtc/fakeportallocatorfactory.h" +#include "talk/app/webrtc/test/fakedtlsidentityservice.h" #include "talk/app/webrtc/test/fakeperiodicvideocapturer.h" #include "talk/app/webrtc/test/mockpeerconnectionobservers.h" #include "talk/app/webrtc/test/peerconnectiontestwrapper.h" @@ -35,7 +36,7 @@ static const char kStreamLabelBase[] = "stream_label"; static const char kVideoTrackLabelBase[] = "video_track"; static const char kAudioTrackLabelBase[] = "audio_track"; -static const int kMaxWait = 5000; +static const int kMaxWait = 10000; static const int kTestAudioFrameCount = 3; static const int kTestVideoFrameCount = 3; @@ -93,12 +94,22 @@ bool PeerConnectionTestWrapper::CreatePc( webrtc::PeerConnectionInterface::IceServer ice_server; ice_server.uri = "stun:stun.l.google.com:19302"; ice_servers.push_back(ice_server); + FakeIdentityService* dtls_service = + talk_base::SSLStreamAdapter::HaveDtlsSrtp() ? + new FakeIdentityService() : NULL; peer_connection_ = peer_connection_factory_->CreatePeerConnection( - ice_servers, constraints, allocator_factory_.get(), NULL, this); + ice_servers, constraints, allocator_factory_.get(), dtls_service, this); return peer_connection_.get() != NULL; } +talk_base::scoped_refptr +PeerConnectionTestWrapper::CreateDataChannel( + const std::string& label, + const webrtc::DataChannelInit& init) { + return peer_connection_->CreateDataChannel(label, &init); +} + void PeerConnectionTestWrapper::OnAddStream(MediaStreamInterface* stream) { LOG(LS_INFO) << "PeerConnectionTestWrapper " << name_ << ": OnAddStream"; @@ -118,6 +129,11 @@ void PeerConnectionTestWrapper::OnIceCandidate( sdp); } +void PeerConnectionTestWrapper::OnDataChannel( + webrtc::DataChannelInterface* data_channel) { + SignalOnDataChannel(data_channel); +} + void PeerConnectionTestWrapper::OnSuccess(SessionDescriptionInterface* desc) { // This callback should take the ownership of |desc|. talk_base::scoped_ptr owned_desc(desc); diff --git a/talk/app/webrtc/test/peerconnectiontestwrapper.h b/talk/app/webrtc/test/peerconnectiontestwrapper.h index 46fefafbd..05e9b623c 100644 --- a/talk/app/webrtc/test/peerconnectiontestwrapper.h +++ b/talk/app/webrtc/test/peerconnectiontestwrapper.h @@ -52,6 +52,10 @@ class PeerConnectionTestWrapper bool CreatePc(const webrtc::MediaConstraintsInterface* constraints); + talk_base::scoped_refptr CreateDataChannel( + const std::string& label, + const webrtc::DataChannelInit& init); + // Implements PeerConnectionObserver. virtual void OnError() {} virtual void OnSignalingChange( @@ -60,7 +64,7 @@ class PeerConnectionTestWrapper webrtc::PeerConnectionObserver::StateType state_changed) {} virtual void OnAddStream(webrtc::MediaStreamInterface* stream); virtual void OnRemoveStream(webrtc::MediaStreamInterface* stream) {} - virtual void OnDataChannel(webrtc::DataChannelInterface* data_channel) {} + virtual void OnDataChannel(webrtc::DataChannelInterface* data_channel); virtual void OnRenegotiationNeeded() {} virtual void OnIceConnectionChange( webrtc::PeerConnectionInterface::IceConnectionState new_state) {} @@ -94,6 +98,7 @@ class PeerConnectionTestWrapper const std::string&> SignalOnIceCandidateReady; sigslot::signal1 SignalOnSdpCreated; sigslot::signal1 SignalOnSdpReady; + sigslot::signal1 SignalOnDataChannel; private: void SetLocalDescription(const std::string& type, const std::string& sdp); diff --git a/talk/app/webrtc/umametrics.h b/talk/app/webrtc/umametrics.h new file mode 100755 index 000000000..81f7bac40 --- /dev/null +++ b/talk/app/webrtc/umametrics.h @@ -0,0 +1,59 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This file contains enums related to IPv4/IPv6 metrics. + +#ifndef TALK_APP_WEBRTC_UMAMETRICS_H_ +#define TALK_APP_WEBRTC_UMAMETRICS_H_ + +namespace webrtc { + +// Currently this contains information related to WebRTC network/transport +// information. + +// This enum is backed by Chromium's histograms.xml, +// chromium/src/tools/metrics/histograms/histograms.xml +// Existing values cannot be re-ordered and new enums must be added +// before kBoundary. +enum PeerConnectionUMAMetricsCounter { + kPeerConnection_IPv4, + kPeerConnection_IPv6, + kBestConnections_IPv4, + kBestConnections_IPv6, + kBoundary, +}; + +// This enum defines types for UMA samples, which will have a range. +enum PeerConnectionUMAMetricsName { + kNetworkInterfaces_IPv4, // Number of IPv4 interfaces. + kNetworkInterfaces_IPv6, // Number of IPv6 interfaces. + kTimeToConnect, // In milliseconds. +}; + +} // namespace webrtc + +#endif // TALK_APP_WEBRTC_UMA6METRICS_H_ diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc index 1b3e6f566..412825e5c 100644 --- a/talk/app/webrtc/webrtcsdp.cc +++ b/talk/app/webrtc/webrtcsdp.cc @@ -65,11 +65,13 @@ using cricket::kCodecParamMinBitrate; using cricket::kCodecParamMinPTime; using cricket::kCodecParamPTime; using cricket::kCodecParamSPropStereo; +using cricket::kCodecParamStartBitrate; using cricket::kCodecParamStereo; using cricket::kCodecParamUseInbandFec; using cricket::kCodecParamSctpProtocol; using cricket::kCodecParamSctpStreams; using cricket::kCodecParamMaxAverageBitrate; +using cricket::kCodecParamAssociatedPayloadType; using cricket::kWildcardPayloadType; using cricket::MediaContentDescription; using cricket::MediaType; @@ -207,7 +209,6 @@ static const char kIsacCodecName[] = "ISAC"; // From webrtcvoiceengine.cc static const int kIsacWbDefaultRate = 32000; // From acm_common_defs.h static const int kIsacSwbDefaultRate = 56000; // From acm_common_defs.h -static const int kDefaultSctpFmt = 5000; static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel"; struct SsrcInfo { @@ -240,7 +241,7 @@ static void BuildMediaDescription(const ContentInfo* content_info, const TransportInfo* transport_info, const MediaType media_type, std::string* message); -static void BuildSctpContentAttributes(std::string* message); +static void BuildSctpContentAttributes(std::string* message, int sctp_port); static void BuildRtpContentAttributes( const MediaContentDescription* media_desc, const MediaType media_type, @@ -912,6 +913,12 @@ std::string SdpSerializeCandidate( std::vector candidates; candidates.push_back(candidate.candidate()); BuildCandidate(candidates, &message); + // From WebRTC draft section 4.8.1.1 candidate-attribute will be + // just candidate: not a=candidate:CRLF + ASSERT(message.find("a=") == 0); + message.erase(0, 2); + ASSERT(message.find(kLineBreak) == message.size() - 2); + message.resize(message.size() - 2); return message; } @@ -1166,6 +1173,7 @@ void BuildMediaDescription(const ContentInfo* content_info, ASSERT(media_desc != NULL); bool is_sctp = (media_desc->protocol() == cricket::kMediaProtocolDtlsSctp); + int sctp_port = cricket::kSctpDefaultPort; // RFC 4566 // m= @@ -1200,14 +1208,22 @@ void BuildMediaDescription(const ContentInfo* content_info, fmt.append(talk_base::ToString(it->id)); } } else if (media_type == cricket::MEDIA_TYPE_DATA) { + const DataContentDescription* data_desc = + static_cast(media_desc); if (is_sctp) { fmt.append(" "); - // TODO(jiayl): Replace the hard-coded string with the fmt read out of the - // ContentDescription. - fmt.append(talk_base::ToString(kDefaultSctpFmt)); + + for (std::vector::const_iterator it = + data_desc->codecs().begin(); + it != data_desc->codecs().end(); ++it) { + if (it->id == cricket::kGoogleSctpDataCodecId && + it->GetParam(cricket::kCodecParamPort, &sctp_port)) { + break; + } + } + + fmt.append(talk_base::ToString(sctp_port)); } else { - const DataContentDescription* data_desc = - static_cast(media_desc); for (std::vector::const_iterator it = data_desc->codecs().begin(); it != data_desc->codecs().end(); ++it) { @@ -1289,18 +1305,18 @@ void BuildMediaDescription(const ContentInfo* content_info, AddLine(os.str(), message); if (is_sctp) { - BuildSctpContentAttributes(message); + BuildSctpContentAttributes(message, sctp_port); } else { BuildRtpContentAttributes(media_desc, media_type, message); } } -void BuildSctpContentAttributes(std::string* message) { +void BuildSctpContentAttributes(std::string* message, int sctp_port) { // draft-ietf-mmusic-sctp-sdp-04 // a=sctpmap:sctpmap-number protocol [streams] std::ostringstream os; InitAttrLine(kAttributeSctpmap, &os); - os << kSdpDelimiterColon << kDefaultSctpFmt << kSdpDelimiterSpace + os << kSdpDelimiterColon << sctp_port << kSdpDelimiterSpace << kDefaultSctpmapProtocol << kSdpDelimiterSpace << (cricket::kMaxSctpSid + 1); AddLine(os.str(), message); @@ -1500,10 +1516,10 @@ void WriteFmtpParameters(const cricket::CodecParameterMap& parameters, bool IsFmtpParam(const std::string& name) { const char* kFmtpParams[] = { kCodecParamMinPTime, kCodecParamSPropStereo, - kCodecParamStereo, kCodecParamUseInbandFec, + kCodecParamStereo, kCodecParamUseInbandFec, kCodecParamStartBitrate, kCodecParamMaxBitrate, kCodecParamMinBitrate, kCodecParamMaxQuantization, kCodecParamSctpProtocol, kCodecParamSctpStreams, - kCodecParamMaxAverageBitrate + kCodecParamMaxAverageBitrate, kCodecParamAssociatedPayloadType }; for (size_t i = 0; i < ARRAY_SIZE(kFmtpParams); ++i) { if (_stricmp(name.c_str(), kFmtpParams[i]) == 0) { @@ -2115,6 +2131,12 @@ bool ParseMediaDescription(const std::string& message, // std::vector codec_preference; for (size_t j = 3 ; j < fields.size(); ++j) { + // TODO(wu): Remove when below bug is fixed. + // https://bugzilla.mozilla.org/show_bug.cgi?id=996329 + if (fields[j] == "" && j == fields.size() - 1) { + continue; + } + int pl = 0; if (!GetValueFromString(line, fields[j], &pl, error)) { return false; @@ -2160,6 +2182,7 @@ bool ParseMediaDescription(const std::string& message, codec_port.SetParam(cricket::kCodecParamPort, fields[3]); LOG(INFO) << "ParseMediaDescription: Got SCTP Port Number " << fields[3]; + ASSERT(!desc->HasCodec(cricket::kGoogleSctpDataCodecId)); desc->AddCodec(codec_port); } diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc index aec144745..e28599fc6 100644 --- a/talk/app/webrtc/webrtcsdp_unittest.cc +++ b/talk/app/webrtc/webrtcsdp_unittest.cc @@ -299,7 +299,7 @@ static const char kSdpSctpDataChannelWithCandidatesString[] = "a=mid:data_content_name\r\n" "a=sctpmap:5000 webrtc-datachannel 1024\r\n"; - static const char kSdpConferenceString[] = +static const char kSdpConferenceString[] = "v=0\r\n" "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n" "s=-\r\n" @@ -1153,6 +1153,15 @@ class WebRtcSdpTest : public testing::Test { << "a=maxptime:" << params.max_ptime << "\r\n"; sdp += os.str(); + os.clear(); + os.str(""); + // Pl type 100 preferred. + os << "m=video 1 RTP/SAVPF 99 95\r\n" + << "a=rtpmap:99 VP8/90000\r\n" + << "a=rtpmap:95 RTX/90000\r\n" + << "a=fmtp:95 apt=99;rtx-time=1000\r\n"; + sdp += os.str(); + // Deserialize SdpParseError error; EXPECT_TRUE(webrtc::SdpDeserialize(sdp, jdesc_output, &error)); @@ -1183,6 +1192,19 @@ class WebRtcSdpTest : public testing::Test { } } } + + const ContentInfo* vc = GetFirstVideoContent(jdesc_output->description()); + ASSERT_TRUE(vc != NULL); + const VideoContentDescription* vcd = + static_cast(vc->description); + ASSERT_FALSE(vcd->codecs().empty()); + cricket::VideoCodec vp8 = vcd->codecs()[0]; + EXPECT_EQ("VP8", vp8.name); + EXPECT_EQ(99, vp8.id); + cricket::VideoCodec rtx = vcd->codecs()[1]; + EXPECT_EQ("RTX", rtx.name); + EXPECT_EQ(95, rtx.id); + VerifyCodecParameter(rtx.params, "apt", vp8.id); } void TestDeserializeRtcpFb(JsepSessionDescription* jdesc_output, @@ -1466,6 +1488,37 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithSctpDataChannel) { EXPECT_EQ(message, expected_sdp); } +TEST_F(WebRtcSdpTest, SerializeWithSctpDataChannelAndNewPort) { + AddSctpDataChannel(); + JsepSessionDescription jsep_desc(kDummyString); + + ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + DataContentDescription* dcdesc = static_cast( + jsep_desc.description()->GetContentDescriptionByName(kDataContentName)); + + const int kNewPort = 1234; + cricket::DataCodec codec( + cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName, 0); + codec.SetParam(cricket::kCodecParamPort, kNewPort); + dcdesc->AddOrReplaceCodec(codec); + + std::string message = webrtc::SdpSerialize(jsep_desc); + + std::string expected_sdp = kSdpString; + expected_sdp.append(kSdpSctpDataChannelString); + + char default_portstr[16]; + char new_portstr[16]; + talk_base::sprintfn(default_portstr, sizeof(default_portstr), "%d", + kDefaultSctpPort); + talk_base::sprintfn(new_portstr, sizeof(new_portstr), "%d", kNewPort); + talk_base::replace_substrs(default_portstr, strlen(default_portstr), + new_portstr, strlen(new_portstr), + &expected_sdp); + + EXPECT_EQ(expected_sdp, message); +} + TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithDataChannelAndBandwidth) { AddRtpDataChannel(); data_desc_->set_bandwidth(100*1000); @@ -1522,7 +1575,7 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithBufferLatency) { TEST_F(WebRtcSdpTest, SerializeCandidates) { std::string message = webrtc::SdpSerializeCandidate(*jcandidate_); - EXPECT_EQ(std::string(kSdpOneCandidate), message); + EXPECT_EQ(std::string(kRawCandidate), message); } TEST_F(WebRtcSdpTest, DeserializeSessionDescription) { @@ -1898,6 +1951,7 @@ TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndNewPort) { talk_base::sprintfn(unusual_portstr, sizeof(unusual_portstr), "%d", kUnusualSctpPort); + // First setup the expected JsepSessionDescription. JsepSessionDescription jdesc(kDummyString); // take our pre-built session description and change the SCTP port. cricket::SessionDescription* mutant = desc_.Copy(); @@ -1907,11 +1961,13 @@ TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndNewPort) { EXPECT_EQ(codecs.size(), 1UL); EXPECT_EQ(codecs[0].id, cricket::kGoogleSctpDataCodecId); codecs[0].SetParam(cricket::kCodecParamPort, kUnusualSctpPort); + dcdesc->set_codecs(codecs); // note: mutant's owned by jdesc now. ASSERT_TRUE(jdesc.Initialize(mutant, kSessionId, kSessionVersion)); mutant = NULL; + // Then get the deserialized JsepSessionDescription. std::string sdp_with_data = kSdpString; sdp_with_data.append(kSdpSctpDataChannelString); talk_base::replace_substrs(default_portstr, strlen(default_portstr), diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc index 7f3bd35c2..299511a12 100644 --- a/talk/app/webrtc/webrtcsession.cc +++ b/talk/app/webrtc/webrtcsession.cc @@ -41,6 +41,7 @@ #include "talk/base/helpers.h" #include "talk/base/logging.h" #include "talk/base/stringencode.h" +#include "talk/base/stringutils.h" #include "talk/media/base/constants.h" #include "talk/media/base/videocapturer.h" #include "talk/session/media/channel.h" @@ -86,6 +87,15 @@ static bool VerifyMediaDescriptions( if ((offer->contents()[i].name) != answer->contents()[i].name) { return false; } + const MediaContentDescription* offer_mdesc = + static_cast( + offer->contents()[i].description); + const MediaContentDescription* answer_mdesc = + static_cast( + answer->contents()[i].description); + if (offer_mdesc->type() != answer_mdesc->type()) { + return false; + } } return true; } @@ -361,14 +371,15 @@ static std::string MakeTdErrorString(const std::string& desc) { // Set |option| to the highest-priority value of |key| in the optional // constraints if the key is found and has a valid value. +template static void SetOptionFromOptionalConstraint( const MediaConstraintsInterface* constraints, - const std::string& key, cricket::Settable* option) { + const std::string& key, cricket::Settable* option) { if (!constraints) { return; } std::string string_value; - int value; + T value; if (constraints->GetOptional().FindFirst(key, &string_value)) { if (talk_base::FromString(string_value, &value)) { option->Set(value); @@ -419,8 +430,10 @@ class IceRestartAnswerLatch { // No transport description exist. This is not an ice restart. continue; } - if (new_transport_desc->ice_pwd != old_transport_desc->ice_pwd && - new_transport_desc->ice_ufrag != old_transport_desc->ice_ufrag) { + if (cricket::IceCredentialsChanged(old_transport_desc->ice_ufrag, + old_transport_desc->ice_pwd, + new_transport_desc->ice_ufrag, + new_transport_desc->ice_pwd)) { LOG(LS_INFO) << "Remote peer request ice restart."; ice_restart_ = true; break; @@ -476,8 +489,9 @@ WebRtcSession::~WebRtcSession() { bool WebRtcSession::Initialize( const PeerConnectionFactoryInterface::Options& options, - const MediaConstraintsInterface* constraints, - DTLSIdentityServiceInterface* dtls_identity_service) { + const MediaConstraintsInterface* constraints, + DTLSIdentityServiceInterface* dtls_identity_service, + PeerConnectionInterface::IceTransportsType ice_transport) { // TODO(perkj): Take |constraints| into consideration. Return false if not all // mandatory constraints can be fulfilled. Note that |constraints| // can be null. @@ -553,21 +567,23 @@ bool WebRtcSession::Initialize( SetOptionFromOptionalConstraint(constraints, MediaConstraintsInterface::kCpuOveruseThreshold, &video_options_.cpu_overuse_threshold); - - if (FindConstraint( - constraints, + SetOptionFromOptionalConstraint(constraints, MediaConstraintsInterface::kCpuOveruseDetection, - &value, - NULL)) { - video_options_.cpu_overuse_detection.Set(value); - } - if (FindConstraint( - constraints, + &video_options_.cpu_overuse_detection); + SetOptionFromOptionalConstraint(constraints, MediaConstraintsInterface::kCpuOveruseEncodeUsage, - &value, - NULL)) { - video_options_.cpu_overuse_encode_usage.Set(value); - } + &video_options_.cpu_overuse_encode_usage); + SetOptionFromOptionalConstraint(constraints, + MediaConstraintsInterface::kCpuUnderuseEncodeRsdThreshold, + &video_options_.cpu_underuse_encode_rsd_threshold); + SetOptionFromOptionalConstraint(constraints, + MediaConstraintsInterface::kCpuOveruseEncodeRsdThreshold, + &video_options_.cpu_overuse_encode_rsd_threshold); + + // Find payload padding constraint. + SetOptionFromOptionalConstraint(constraints, + MediaConstraintsInterface::kPayloadPadding, + &video_options_.use_payload_padding); // Find improved wifi bwe constraint. if (FindConstraint( @@ -576,8 +592,15 @@ bool WebRtcSession::Initialize( &value, NULL)) { video_options_.use_improved_wifi_bandwidth_estimator.Set(value); + } else { + // Enable by default if the constraint is not set. + video_options_.use_improved_wifi_bandwidth_estimator.Set(true); } + SetOptionFromOptionalConstraint(constraints, + MediaConstraintsInterface::kHighStartBitrate, + &video_options_.video_start_bitrate); + if (FindConstraint( constraints, MediaConstraintsInterface::kVeryHighBitrate, @@ -594,6 +617,10 @@ bool WebRtcSession::Initialize( cricket::VideoOptions::HIGH); } + SetOptionFromOptionalConstraint(constraints, + MediaConstraintsInterface::kOpusFec, + &audio_options_.opus_fec); + const cricket::VideoCodec default_codec( JsepSessionDescription::kDefaultVideoCodecId, JsepSessionDescription::kDefaultVideoCodecName, @@ -865,9 +892,32 @@ bool WebRtcSession::ProcessIceMessage(const IceCandidateInterface* candidate) { return false; } - if (!local_description() || !remote_description()) { - LOG(LS_INFO) << "ProcessIceMessage: Remote description not set, " - << "save the candidate for later use."; + cricket::TransportProxy* transport_proxy = NULL; + if (remote_description()) { + size_t mediacontent_index = + static_cast(candidate->sdp_mline_index()); + size_t remote_content_size = + BaseSession::remote_description()->contents().size(); + if (mediacontent_index >= remote_content_size) { + LOG(LS_ERROR) + << "ProcessIceMessage: Invalid candidate media index."; + return false; + } + + cricket::ContentInfo content = + BaseSession::remote_description()->contents()[mediacontent_index]; + transport_proxy = GetTransportProxy(content.name); + } + + // We need to check the local/remote description for the Transport instead of + // the session, because a new Transport added during renegotiation may have + // them unset while the session has them set from the previou negotiation. Not + // doing so may trigger the auto generation of transport description and mess + // up DTLS identity information, ICE credential, etc. + if (!transport_proxy || !(transport_proxy->local_description_set() && + transport_proxy->remote_description_set())) { + LOG(LS_INFO) << "ProcessIceMessage: Local/Remote description not set " + << "on the Transport, save the candidate for later use."; saved_candidates_.push_back( new JsepIceCandidate(candidate->sdp_mid(), candidate->sdp_mline_index(), candidate->candidate())); @@ -883,31 +933,22 @@ bool WebRtcSession::ProcessIceMessage(const IceCandidateInterface* candidate) { return UseCandidate(candidate); } -bool WebRtcSession::GetTrackIdBySsrc(uint32 ssrc, std::string* id) { - if (GetLocalTrackId(ssrc, id)) { - if (GetRemoteTrackId(ssrc, id)) { - LOG(LS_WARNING) << "SSRC " << ssrc - << " exists in both local and remote descriptions"; - return true; // We return the remote track id. - } - return true; - } else { - return GetRemoteTrackId(ssrc, id); - } +bool WebRtcSession::UpdateIce(PeerConnectionInterface::IceTransportsType type) { + return false; } -bool WebRtcSession::GetLocalTrackId(uint32 ssrc, std::string* track_id) { +bool WebRtcSession::GetLocalTrackIdBySsrc(uint32 ssrc, std::string* track_id) { if (!BaseSession::local_description()) return false; return webrtc::GetTrackIdBySsrc( - BaseSession::local_description(), ssrc, track_id); + BaseSession::local_description(), ssrc, track_id); } -bool WebRtcSession::GetRemoteTrackId(uint32 ssrc, std::string* track_id) { +bool WebRtcSession::GetRemoteTrackIdBySsrc(uint32 ssrc, std::string* track_id) { if (!BaseSession::remote_description()) - return false; + return false; return webrtc::GetTrackIdBySsrc( - BaseSession::remote_description(), ssrc, track_id); + BaseSession::remote_description(), ssrc, track_id); } std::string WebRtcSession::BadStateErrMsg(State state) { @@ -1109,6 +1150,8 @@ void WebRtcSession::AddSctpDataStream(uint32 sid) { } void WebRtcSession::RemoveSctpDataStream(uint32 sid) { + mediastream_signaling_->RemoveSctpDataChannel(static_cast(sid)); + if (!data_channel_.get()) { LOG(LS_ERROR) << "RemoveDataChannelStreams called when data_channel_ is " << "NULL."; @@ -1513,6 +1556,9 @@ bool WebRtcSession::CreateDataChannel(const cricket::ContentInfo* content) { mediastream_signaling_->OnDataTransportCreatedForSctp(); data_channel_->SignalDataReceived.connect( this, &WebRtcSession::OnDataChannelMessageReceived); + data_channel_->SignalStreamClosedRemotely.connect( + mediastream_signaling_, + &MediaStreamSignaling::OnRemoteSctpDataChannelClosed); } return true; } diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h index 1b8dac84a..3ffea8ffa 100644 --- a/talk/app/webrtc/webrtcsession.h +++ b/talk/app/webrtc/webrtcsession.h @@ -55,7 +55,9 @@ class VoiceChannel; } // namespace cricket namespace webrtc { + class IceRestartAnswerLatch; +class JsepIceCandidate; class MediaStreamSignaling; class WebRtcSessionDescriptionFactory; @@ -112,7 +114,8 @@ class WebRtcSession : public cricket::BaseSession, bool Initialize(const PeerConnectionFactoryInterface::Options& options, const MediaConstraintsInterface* constraints, - DTLSIdentityServiceInterface* dtls_identity_service); + DTLSIdentityServiceInterface* dtls_identity_service, + PeerConnectionInterface::IceTransportsType ice_transport); // Deletes the voice, video and data channel and changes the session state // to STATE_RECEIVEDTERMINATE. void Terminate(); @@ -152,6 +155,9 @@ class WebRtcSession : public cricket::BaseSession, bool SetRemoteDescription(SessionDescriptionInterface* desc, std::string* err_desc); bool ProcessIceMessage(const IceCandidateInterface* ice_candidate); + + bool UpdateIce(PeerConnectionInterface::IceTransportsType type); + const SessionDescriptionInterface* local_description() const { return local_desc_.get(); } @@ -160,7 +166,9 @@ class WebRtcSession : public cricket::BaseSession, } // Get the id used as a media stream track's "id" field from ssrc. - virtual bool GetTrackIdBySsrc(uint32 ssrc, std::string* id); + virtual bool GetLocalTrackIdBySsrc(uint32 ssrc, std::string* track_id); + virtual bool GetRemoteTrackIdBySsrc(uint32 ssrc, std::string* track_id); + // AudioMediaProviderInterface implementation. virtual void SetAudioPlayout(uint32 ssrc, bool enable, @@ -283,9 +291,6 @@ class WebRtcSession : public cricket::BaseSession, const cricket::ReceiveDataParams& params, const talk_base::Buffer& payload); - bool GetLocalTrackId(uint32 ssrc, std::string* track_id); - bool GetRemoteTrackId(uint32 ssrc, std::string* track_id); - std::string BadStateErrMsg(State state); void SetIceConnectionState(PeerConnectionInterface::IceConnectionState state); diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc index 79ea2a508..956f8a66f 100644 --- a/talk/app/webrtc/webrtcsession_unittest.cc +++ b/talk/app/webrtc/webrtcsession_unittest.cc @@ -53,6 +53,7 @@ #include "talk/media/devices/fakedevicemanager.h" #include "talk/p2p/base/stunserver.h" #include "talk/p2p/base/teststunserver.h" +#include "talk/p2p/base/testturnserver.h" #include "talk/p2p/client/basicportallocator.h" #include "talk/session/media/channelmanager.h" #include "talk/session/media/mediasession.h" @@ -72,6 +73,7 @@ using cricket::NS_JINGLE_ICE_UDP; using cricket::TransportInfo; using talk_base::SocketAddress; using talk_base::scoped_ptr; +using talk_base::Thread; using webrtc::CreateSessionDescription; using webrtc::CreateSessionDescriptionObserver; using webrtc::CreateSessionDescriptionRequest; @@ -101,6 +103,8 @@ static const int kClientAddrPort = 0; static const char kClientAddrHost1[] = "11.11.11.11"; static const char kClientAddrHost2[] = "22.22.22.22"; static const char kStunAddrHost[] = "99.99.99.1"; +static const SocketAddress kTurnUdpIntAddr("99.99.99.4", 3478); +static const SocketAddress kTurnUdpExtAddr("99.99.99.6", 0); static const char kSessionVersion[] = "1"; @@ -118,6 +122,12 @@ static const char kFakeDtlsFingerprint[] = "BB:CD:72:F7:2F:D0:BA:43:F3:68:B1:0C:23:72:B6:4A:" "0F:DE:34:06:BC:E0:FE:01:BC:73:C8:6D:F4:65:D5:24"; +static const char kTooLongIceUfragPwd[] = + "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag" + "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag" + "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag" + "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag"; + // Add some extra |newlines| to the |message| after |line|. static void InjectAfter(const std::string& line, const std::string& newlines, @@ -294,16 +304,20 @@ class WebRtcSessionTest : public testing::Test { ss_scope_(fss_.get()), stun_socket_addr_(talk_base::SocketAddress(kStunAddrHost, cricket::STUN_SERVER_PORT)), - stun_server_(talk_base::Thread::Current(), stun_socket_addr_), - allocator_(&network_manager_, stun_socket_addr_, - SocketAddress(), SocketAddress(), SocketAddress()), - mediastream_signaling_(channel_manager_.get()) { + stun_server_(Thread::Current(), stun_socket_addr_), + turn_server_(Thread::Current(), kTurnUdpIntAddr, kTurnUdpExtAddr), + allocator_(new cricket::BasicPortAllocator( + &network_manager_, stun_socket_addr_, + SocketAddress(), SocketAddress(), SocketAddress())), + mediastream_signaling_(channel_manager_.get()), + ice_type_(PeerConnectionInterface::kAll) { tdesc_factory_->set_protocol(cricket::ICEPROTO_HYBRID); - allocator_.set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | + allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | cricket::PORTALLOCATOR_DISABLE_RELAY | cricket::PORTALLOCATOR_ENABLE_BUNDLE); EXPECT_TRUE(channel_manager_->Init()); desc_factory_->set_add_legacy_streams(false); + allocator_->set_step_delay(cricket::kMinimumStepDelay); } static void SetUpTestCase() { @@ -318,11 +332,15 @@ class WebRtcSessionTest : public testing::Test { network_manager_.AddInterface(addr); } + void SetIceTransportType(PeerConnectionInterface::IceTransportsType type) { + ice_type_ = type; + } + void Init(DTLSIdentityServiceInterface* identity_service) { ASSERT_TRUE(session_.get() == NULL); session_.reset(new WebRtcSessionForTest( channel_manager_.get(), talk_base::Thread::Current(), - talk_base::Thread::Current(), &allocator_, + talk_base::Thread::Current(), allocator_.get(), &observer_, &mediastream_signaling_)); @@ -332,7 +350,7 @@ class WebRtcSessionTest : public testing::Test { observer_.ice_gathering_state_); EXPECT_TRUE(session_->Initialize(options_, constraints_.get(), - identity_service)); + identity_service, ice_type_)); } void InitWithDtmfCodec() { @@ -558,6 +576,35 @@ class WebRtcSessionTest : public testing::Test { } } + void ModifyIceUfragPwdLines(const SessionDescriptionInterface* current_desc, + const std::string& modified_ice_ufrag, + const std::string& modified_ice_pwd, + std::string* sdp) { + const cricket::SessionDescription* desc = current_desc->description(); + EXPECT_TRUE(current_desc->ToString(sdp)); + + const cricket::ContentInfos& contents = desc->contents(); + cricket::ContentInfos::const_iterator it = contents.begin(); + // Replace ufrag and pwd lines with |modified_ice_ufrag| and + // |modified_ice_pwd| strings. + for (; it != contents.end(); ++it) { + const cricket::TransportDescription* transport_desc = + desc->GetTransportDescriptionByName(it->name); + std::string ufrag_line = "a=ice-ufrag:" + transport_desc->ice_ufrag + + "\r\n"; + std::string pwd_line = "a=ice-pwd:" + transport_desc->ice_pwd + + "\r\n"; + std::string mod_ufrag = "a=ice-ufrag:" + modified_ice_ufrag + "\r\n"; + std::string mod_pwd = "a=ice-pwd:" + modified_ice_pwd + "\r\n"; + talk_base::replace_substrs(ufrag_line.c_str(), ufrag_line.length(), + mod_ufrag.c_str(), mod_ufrag.length(), + sdp); + talk_base::replace_substrs(pwd_line.c_str(), pwd_line.length(), + mod_pwd.c_str(), mod_pwd.length(), + sdp); + } + } + // Creates a remote offer and and applies it as a remote description, // creates a local answer and applies is as a local description. // Call mediastream_signaling_.UseOptionsWithStreamX() before this function @@ -1009,8 +1056,9 @@ class WebRtcSessionTest : public testing::Test { talk_base::SocketServerScope ss_scope_; talk_base::SocketAddress stun_socket_addr_; cricket::TestStunServer stun_server_; + cricket::TestTurnServer turn_server_; talk_base::FakeNetworkManager network_manager_; - cricket::BasicPortAllocator allocator_; + talk_base::scoped_ptr allocator_; PeerConnectionFactoryInterface::Options options_; talk_base::scoped_ptr constraints_; FakeMediaStreamSignaling mediastream_signaling_; @@ -1018,6 +1066,7 @@ class WebRtcSessionTest : public testing::Test { MockIceObserver observer_; cricket::FakeVideoMediaChannel* video_channel_; cricket::FakeVoiceMediaChannel* voice_channel_; + PeerConnectionInterface::IceTransportsType ice_type_; }; TEST_F(WebRtcSessionTest, TestInitializeWithDtls) { @@ -2232,13 +2281,61 @@ TEST_F(WebRtcSessionTest, TestSetRemoteDescriptionWithoutIce) { SetRemoteDescriptionOfferExpectError(kSdpWithoutIceUfragPwd, modified_offer); } +// This test verifies that setLocalDescription fails if local offer has +// too short ice ufrag and pwd strings. +TEST_F(WebRtcSessionTest, TestSetLocalDescriptionInvalidIceCredentials) { + Init(NULL); + tdesc_factory_->set_protocol(cricket::ICEPROTO_RFC5245); + mediastream_signaling_.SendAudioVideoStream1(); + talk_base::scoped_ptr offer(CreateOffer(NULL)); + std::string sdp; + // Modifying ice ufrag and pwd in local offer with strings smaller than the + // recommended values of 4 and 22 bytes respectively. + ModifyIceUfragPwdLines(offer.get(), "ice", "icepwd", &sdp); + SessionDescriptionInterface* modified_offer = + CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL); + std::string error; + EXPECT_FALSE(session_->SetLocalDescription(modified_offer, &error)); + + // Test with string greater than 256. + sdp.clear(); + ModifyIceUfragPwdLines(offer.get(), kTooLongIceUfragPwd, kTooLongIceUfragPwd, + &sdp); + modified_offer = CreateSessionDescription(JsepSessionDescription::kOffer, sdp, + NULL); + EXPECT_FALSE(session_->SetLocalDescription(modified_offer, &error)); +} + +// This test verifies that setRemoteDescription fails if remote offer has +// too short ice ufrag and pwd strings. +TEST_F(WebRtcSessionTest, TestSetRemoteDescriptionInvalidIceCredentials) { + Init(NULL); + tdesc_factory_->set_protocol(cricket::ICEPROTO_RFC5245); + talk_base::scoped_ptr offer(CreateRemoteOffer()); + std::string sdp; + // Modifying ice ufrag and pwd in remote offer with strings smaller than the + // recommended values of 4 and 22 bytes respectively. + ModifyIceUfragPwdLines(offer.get(), "ice", "icepwd", &sdp); + SessionDescriptionInterface* modified_offer = + CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL); + std::string error; + EXPECT_FALSE(session_->SetRemoteDescription(modified_offer, &error)); + + sdp.clear(); + ModifyIceUfragPwdLines(offer.get(), kTooLongIceUfragPwd, kTooLongIceUfragPwd, + &sdp); + modified_offer = CreateSessionDescription(JsepSessionDescription::kOffer, sdp, + NULL); + EXPECT_FALSE(session_->SetRemoteDescription(modified_offer, &error)); +} + TEST_F(WebRtcSessionTest, VerifyBundleFlagInPA) { // This test verifies BUNDLE flag in PortAllocator, if BUNDLE information in // local description is removed by the application, BUNDLE flag should be // disabled in PortAllocator. By default BUNDLE is enabled in the WebRtc. Init(NULL); - EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) == - cricket::PORTALLOCATOR_ENABLE_BUNDLE); + EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & + allocator_->flags()) == cricket::PORTALLOCATOR_ENABLE_BUNDLE); talk_base::scoped_ptr offer( CreateOffer(NULL)); cricket::SessionDescription* offer_copy = @@ -2249,14 +2346,14 @@ TEST_F(WebRtcSessionTest, VerifyBundleFlagInPA) { modified_offer->Initialize(offer_copy, "1", "1"); SetLocalDescriptionWithoutError(modified_offer); - EXPECT_FALSE(allocator_.flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE); + EXPECT_FALSE(allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE); } TEST_F(WebRtcSessionTest, TestDisabledBundleInAnswer) { Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) == - cricket::PORTALLOCATOR_ENABLE_BUNDLE); + EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & + allocator_->flags()) == cricket::PORTALLOCATOR_ENABLE_BUNDLE); FakeConstraints constraints; constraints.SetMandatoryUseRtpMux(true); SessionDescriptionInterface* offer = CreateOffer(&constraints); @@ -2270,8 +2367,8 @@ TEST_F(WebRtcSessionTest, TestDisabledBundleInAnswer) { new JsepSessionDescription(JsepSessionDescription::kAnswer); modified_answer->Initialize(answer_copy, "1", "1"); SetRemoteDescriptionWithoutError(modified_answer); - EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) == - cricket::PORTALLOCATOR_ENABLE_BUNDLE); + EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & + allocator_->flags()) == cricket::PORTALLOCATOR_ENABLE_BUNDLE); video_channel_ = media_engine_->GetVideoChannel(0); voice_channel_ = media_engine_->GetVoiceChannel(0); @@ -2293,8 +2390,8 @@ TEST_F(WebRtcSessionTest, TestDisabledBundleInAnswer) { TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) { WebRtcSessionTest::Init(NULL); mediastream_signaling_.SendAudioVideoStream1(); - EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) == - cricket::PORTALLOCATOR_ENABLE_BUNDLE); + EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & + allocator_->flags()) == cricket::PORTALLOCATOR_ENABLE_BUNDLE); FakeConstraints constraints; constraints.SetMandatoryUseRtpMux(true); SessionDescriptionInterface* offer = CreateOffer(&constraints); @@ -2611,22 +2708,31 @@ TEST_F(WebRtcSessionTest, TestIncorrectMLinesInRemoteAnswer) { answer->session_version())); SetRemoteDescriptionAnswerExpectError(kMlineMismatch, modified_answer); - // Modifying content names. + // Different content names. std::string sdp; EXPECT_TRUE(answer->ToString(&sdp)); const std::string kAudioMid = "a=mid:audio"; const std::string kAudioMidReplaceStr = "a=mid:audio_content_name"; - - // Replacing |audio| with |audio_content_name|. talk_base::replace_substrs(kAudioMid.c_str(), kAudioMid.length(), kAudioMidReplaceStr.c_str(), kAudioMidReplaceStr.length(), &sdp); - SessionDescriptionInterface* modified_answer1 = CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL); SetRemoteDescriptionAnswerExpectError(kMlineMismatch, modified_answer1); + // Different media types. + EXPECT_TRUE(answer->ToString(&sdp)); + const std::string kAudioMline = "m=audio"; + const std::string kAudioMlineReplaceStr = "m=video"; + talk_base::replace_substrs(kAudioMline.c_str(), kAudioMline.length(), + kAudioMlineReplaceStr.c_str(), + kAudioMlineReplaceStr.length(), + &sdp); + SessionDescriptionInterface* modified_answer2 = + CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL); + SetRemoteDescriptionAnswerExpectError(kMlineMismatch, modified_answer2); + SetRemoteDescriptionWithoutError(answer.release()); } @@ -2807,7 +2913,7 @@ TEST_F(WebRtcSessionTest, TestSessionContentError) { // Runs the loopback call test with BUNDLE and STUN disabled. TEST_F(WebRtcSessionTest, TestIceStatesBasic) { // Lets try with only UDP ports. - allocator_.set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + allocator_->set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | cricket::PORTALLOCATOR_DISABLE_TCP | cricket::PORTALLOCATOR_DISABLE_STUN | cricket::PORTALLOCATOR_DISABLE_RELAY); @@ -2816,7 +2922,7 @@ TEST_F(WebRtcSessionTest, TestIceStatesBasic) { // Runs the loopback call test with BUNDLE and STUN enabled. TEST_F(WebRtcSessionTest, TestIceStatesBundle) { - allocator_.set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + allocator_->set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | cricket::PORTALLOCATOR_ENABLE_BUNDLE | cricket::PORTALLOCATOR_DISABLE_TCP | cricket::PORTALLOCATOR_DISABLE_RELAY); diff --git a/talk/base/common.cc b/talk/base/common.cc index 40755ddbd..9f63aa4da 100644 --- a/talk/base/common.cc +++ b/talk/base/common.cc @@ -78,4 +78,12 @@ void LogAssert(const char* function, const char* file, int line, } } +bool IsOdd(int n) { + return (n & 0x1); +} + +bool IsEven(int n) { + return !IsOdd(n); +} + } // namespace talk_base diff --git a/talk/base/common.h b/talk/base/common.h index 381b6b5ac..ed7d59ed6 100644 --- a/talk/base/common.h +++ b/talk/base/common.h @@ -116,6 +116,10 @@ typedef void (*AssertLogger)(const char* function, // only by one component. void SetCustomAssertLogger(AssertLogger logger); +bool IsOdd(int n); + +bool IsEven(int n); + } // namespace talk_base #if ENABLE_DEBUG diff --git a/talk/base/cpumonitor.cc b/talk/base/cpumonitor.cc index e9b481fdb..aaec77208 100644 --- a/talk/base/cpumonitor.cc +++ b/talk/base/cpumonitor.cc @@ -48,6 +48,7 @@ #if defined(IOS) || defined(OSX) #include #include +#include #include #include #endif // defined(IOS) || defined(OSX) @@ -241,11 +242,14 @@ float CpuSampler::GetSystemLoad() { #endif // WIN32 #if defined(IOS) || defined(OSX) + mach_port_t mach_host = mach_host_self(); host_cpu_load_info_data_t cpu_info; mach_msg_type_number_t info_count = HOST_CPU_LOAD_INFO_COUNT; - if (KERN_SUCCESS != host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, - reinterpret_cast(&cpu_info), - &info_count)) { + kern_return_t kr = host_statistics(mach_host, HOST_CPU_LOAD_INFO, + reinterpret_cast(&cpu_info), + &info_count); + mach_port_deallocate(mach_task_self(), mach_host); + if (KERN_SUCCESS != kr) { LOG(LS_ERROR) << "::host_statistics() failed"; return 0.f; } diff --git a/talk/base/fakenetwork.h b/talk/base/fakenetwork.h index 497ff209c..3bdc97fe7 100644 --- a/talk/base/fakenetwork.h +++ b/talk/base/fakenetwork.h @@ -109,12 +109,10 @@ class FakeNetworkManager : public NetworkManagerBase, prefix_length = kFakeIPv6NetworkPrefixLength; } IPAddress prefix = TruncateIP(it->ipaddr(), prefix_length); - std::string key = MakeNetworkKey(it->hostname(), prefix, prefix_length); scoped_ptr net(new Network(it->hostname(), it->hostname(), prefix, - prefix_length, - key)); + prefix_length)); net->AddIP(it->ipaddr()); networks.push_back(net.release()); } diff --git a/talk/base/genericslot.h b/talk/base/genericslot.h deleted file mode 100755 index 112bb88ac..000000000 --- a/talk/base/genericslot.h +++ /dev/null @@ -1,258 +0,0 @@ -// This file was GENERATED by command: -// pump.py genericslot.h.pump -// DO NOT EDIT BY HAND!!! - -/* - * libjingle - * Copyright 2014 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_BASE_GENERICSLOT_H_ -#define TALK_BASE_GENERICSLOT_H_ - -// To generate genericslot.h from genericslot.h.pump, execute: -// /home/build/google3/third_party/gtest/scripts/pump.py genericslot.h.pump - -// Generic-slots are pure slots that can be hooked up to signals. They're -// mainly intended to be used in tests where we want to check if a signal -// was invoked and what arguments were passed. NOTE: They do not do any -// lifetime management of the arguments received via callbacks. -// -// Example: -// /* Some signal */ -// sigslot::signal1 foo; -// -// /* We want to monitor foo in some test */ -// talk_base::GenericSlot1 slot(&foo, 0); -// foo.emit(5); -// EXPECT_TRUE(slot.callback_received()); -// EXPECT_EQ(5, *(slot.arg1())); -// - -#include "talk/base/constructormagic.h" -#include "talk/base/sigslot.h" - -namespace talk_base { - -template -class GenericSlot1 : public sigslot::has_slots<> { - public: - GenericSlot1(sigslot::signal1* signal, - const A1& arg1_initial) - : arg1_initial_(arg1_initial) { - Reset(); - signal->connect(this, &GenericSlot1::OnSignalCallback); - } - - void Reset() { - callback_received_ = false; - arg1_ = arg1_initial_; - } - - bool callback_received() const { return callback_received_; } - const A1& arg1() const { return arg1_; } - - private: - void OnSignalCallback(A1 arg1) { - callback_received_ = true; - arg1_ = arg1; - } - - bool callback_received_; - A1 arg1_initial_, arg1_; - - DISALLOW_COPY_AND_ASSIGN(GenericSlot1); -}; - -template -class GenericSlot2 : public sigslot::has_slots<> { - public: - GenericSlot2(sigslot::signal2* signal, - const A1& arg1_initial, const A2& arg2_initial) - : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial) { - Reset(); - signal->connect(this, &GenericSlot2::OnSignalCallback); - } - - void Reset() { - callback_received_ = false; - arg1_ = arg1_initial_; - arg2_ = arg2_initial_; - } - - bool callback_received() const { return callback_received_; } - const A1& arg1() const { return arg1_; } - const A2& arg2() const { return arg2_; } - - private: - void OnSignalCallback(A1 arg1, A2 arg2) { - callback_received_ = true; - arg1_ = arg1; - arg2_ = arg2; - } - - bool callback_received_; - A1 arg1_initial_, arg1_; - A2 arg2_initial_, arg2_; - - DISALLOW_COPY_AND_ASSIGN(GenericSlot2); -}; - -template -class GenericSlot3 : public sigslot::has_slots<> { - public: - GenericSlot3(sigslot::signal3* signal, - const A1& arg1_initial, const A2& arg2_initial, - const A3& arg3_initial) - : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial), - arg3_initial_(arg3_initial) { - Reset(); - signal->connect(this, &GenericSlot3::OnSignalCallback); - } - - void Reset() { - callback_received_ = false; - arg1_ = arg1_initial_; - arg2_ = arg2_initial_; - arg3_ = arg3_initial_; - } - - bool callback_received() const { return callback_received_; } - const A1& arg1() const { return arg1_; } - const A2& arg2() const { return arg2_; } - const A3& arg3() const { return arg3_; } - - private: - void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3) { - callback_received_ = true; - arg1_ = arg1; - arg2_ = arg2; - arg3_ = arg3; - } - - bool callback_received_; - A1 arg1_initial_, arg1_; - A2 arg2_initial_, arg2_; - A3 arg3_initial_, arg3_; - - DISALLOW_COPY_AND_ASSIGN(GenericSlot3); -}; - -template -class GenericSlot4 : public sigslot::has_slots<> { - public: - GenericSlot4(sigslot::signal4* signal, - const A1& arg1_initial, const A2& arg2_initial, - const A3& arg3_initial, const A4& arg4_initial) - : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial), - arg3_initial_(arg3_initial), arg4_initial_(arg4_initial) { - Reset(); - signal->connect(this, &GenericSlot4::OnSignalCallback); - } - - void Reset() { - callback_received_ = false; - arg1_ = arg1_initial_; - arg2_ = arg2_initial_; - arg3_ = arg3_initial_; - arg4_ = arg4_initial_; - } - - bool callback_received() const { return callback_received_; } - const A1& arg1() const { return arg1_; } - const A2& arg2() const { return arg2_; } - const A3& arg3() const { return arg3_; } - const A4& arg4() const { return arg4_; } - - private: - void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4) { - callback_received_ = true; - arg1_ = arg1; - arg2_ = arg2; - arg3_ = arg3; - arg4_ = arg4; - } - - bool callback_received_; - A1 arg1_initial_, arg1_; - A2 arg2_initial_, arg2_; - A3 arg3_initial_, arg3_; - A4 arg4_initial_, arg4_; - - DISALLOW_COPY_AND_ASSIGN(GenericSlot4); -}; - -template -class GenericSlot5 : public sigslot::has_slots<> { - public: - GenericSlot5(sigslot::signal5* signal, - const A1& arg1_initial, const A2& arg2_initial, - const A3& arg3_initial, const A4& arg4_initial, - const A5& arg5_initial) - : arg1_initial_(arg1_initial), arg2_initial_(arg2_initial), - arg3_initial_(arg3_initial), arg4_initial_(arg4_initial), - arg5_initial_(arg5_initial) { - Reset(); - signal->connect(this, &GenericSlot5::OnSignalCallback); - } - - void Reset() { - callback_received_ = false; - arg1_ = arg1_initial_; - arg2_ = arg2_initial_; - arg3_ = arg3_initial_; - arg4_ = arg4_initial_; - arg5_ = arg5_initial_; - } - - bool callback_received() const { return callback_received_; } - const A1& arg1() const { return arg1_; } - const A2& arg2() const { return arg2_; } - const A3& arg3() const { return arg3_; } - const A4& arg4() const { return arg4_; } - const A5& arg5() const { return arg5_; } - - private: - void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) { - callback_received_ = true; - arg1_ = arg1; - arg2_ = arg2; - arg3_ = arg3; - arg4_ = arg4; - arg5_ = arg5; - } - - bool callback_received_; - A1 arg1_initial_, arg1_; - A2 arg2_initial_, arg2_; - A3 arg3_initial_, arg3_; - A4 arg4_initial_, arg4_; - A5 arg5_initial_, arg5_; - - DISALLOW_COPY_AND_ASSIGN(GenericSlot5); -}; -} // namespace talk_base - -#endif // TALK_BASE_GENERICSLOT_H_ diff --git a/talk/base/genericslot.h.pump b/talk/base/genericslot.h.pump deleted file mode 100755 index 9f3f181a3..000000000 --- a/talk/base/genericslot.h.pump +++ /dev/null @@ -1,101 +0,0 @@ -/* - * libjingle - * Copyright 2014 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_BASE_GENERICSLOT_H_ -#define TALK_BASE_GENERICSLOT_H_ - -// To generate genericslot.h from genericslot.h.pump, execute: -// /home/build/google3/third_party/gtest/scripts/pump.py genericslot.h.pump - -// Generic-slots are pure slots that can be hooked up to signals. They're -// mainly intended to be used in tests where we want to check if a signal -// was invoked and what arguments were passed. NOTE: They do not do any -// lifetime management of the arguments received via callbacks. -// -// Example: -// /* Some signal */ -// sigslot::signal1 foo; -// -// /* We want to monitor foo in some test */ -// talk_base::GenericSlot1 slot(&foo, 0); -// foo.emit(5); -// EXPECT_TRUE(slot.callback_received()); -// EXPECT_EQ(5, *(slot.arg1())); -// - -#include "talk/base/constructormagic.h" -#include "talk/base/sigslot.h" - -namespace talk_base { - -$var n = 5 -$range i 1..n -$for i [[ -$range j 1..i - -template <$for j , [[class A$j]]> -class GenericSlot$i : public sigslot::has_slots<> { - public: - GenericSlot$i(sigslot::signal$i<$for j , [[A$j]]>* signal, - $for j , [[const A$j& arg$j[[]]_initial]]) - : $for j , [[arg$j[[]]_initial_(arg$j[[]]_initial)]] { - Reset(); - signal->connect(this, &GenericSlot$i::OnSignalCallback); - } - - void Reset() { - callback_received_ = false;$for j [[ - - arg$j[[]]_ = arg$j[[]]_initial_; ]] - - } - - bool callback_received() const { return callback_received_; }$for j [[ - - const A$j& arg$j() const { return arg$j[[]]_; }]] - - - private: - void OnSignalCallback($for j , [[A$j arg$j]]) { - callback_received_ = true;$for j [[ - - arg$j[[]]_ = arg$j;]] - - } - - bool callback_received_;$for j [[ - - A$j arg$j[[]]_initial_, arg$j[[]]_;]] - - - DISALLOW_COPY_AND_ASSIGN(GenericSlot$i); -}; - -]] -} // namespace talk_base - -#endif // TALK_BASE_GENERICSLOT_H_ diff --git a/talk/base/genericslot_unittest.cc b/talk/base/genericslot_unittest.cc deleted file mode 100755 index ea3025fa5..000000000 --- a/talk/base/genericslot_unittest.cc +++ /dev/null @@ -1,28 +0,0 @@ -#include "talk/base/genericslot.h" -#include "talk/base/gunit.h" -#include "talk/base/sigslot.h" - -namespace talk_base { - -TEST(GenericSlotTest, TestSlot1) { - sigslot::signal1 source1; - GenericSlot1 slot1(&source1, 1); - EXPECT_FALSE(slot1.callback_received()); - source1.emit(10); - EXPECT_TRUE(slot1.callback_received()); - EXPECT_EQ(10, slot1.arg1()); -} - -TEST(GenericSlotTest, TestSlot2) { - sigslot::signal2 source2; - GenericSlot2 slot2(&source2, 1, '0'); - EXPECT_FALSE(slot2.callback_received()); - source2.emit(10, 'x'); - EXPECT_TRUE(slot2.callback_received()); - EXPECT_EQ(10, slot2.arg1()); - EXPECT_EQ('x', slot2.arg2()); -} - -// By induction we assume the rest work too... - -} // namespace talk_base diff --git a/talk/base/gunit.h b/talk/base/gunit.h index 3a0321468..e56dd6f61 100644 --- a/talk/base/gunit.h +++ b/talk/base/gunit.h @@ -36,11 +36,6 @@ #include "testing/base/public/gunit.h" #endif -// forward declarations -namespace talk_base { -class Pathname; -} - // Wait until "ex" is true, or "timeout" expires. #define WAIT(ex, timeout) \ for (uint32 start = talk_base::Time(); \ @@ -107,6 +102,4 @@ class Pathname; } \ } while (0); -talk_base::Pathname GetTalkDirectory(); - #endif // TALK_BASE_GUNIT_H_ diff --git a/talk/base/iosfilesystem.mm b/talk/base/iosfilesystem.mm new file mode 100644 index 000000000..aaefcb03f --- /dev/null +++ b/talk/base/iosfilesystem.mm @@ -0,0 +1,70 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This file only exists because various iOS system APIs are only +// available from Objective-C. See unixfilesystem.cc for the only use +// (enforced by a lack of a header file). + +#import +#import +#include + +#include "talk/base/common.h" +#include "talk/base/pathutils.h" + +// Return a new[]'d |char*| copy of the UTF8 representation of |s|. +// Caller owns the returned memory and must use delete[] on it. +static char* copyString(NSString* s) { + const char* utf8 = [s UTF8String]; + size_t len = strlen(utf8) + 1; + char* copy = new char[len]; + // This uses a new[] + strcpy (instead of strdup) because the + // receiver expects to be able to delete[] the returned pointer + // (instead of free()ing it). + strcpy(copy, utf8); + return copy; +} + +// Return a (leaked) copy of a directory name suitable for application data. +char* IOSDataDirectory() { + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, NSUserDomainMask, YES); + ASSERT([paths count] == 1); + return copyString([paths objectAtIndex:0]); +} + +// Return a (leaked) copy of a directory name suitable for use as a $TEMP. +char* IOSTempDirectory() { + return copyString(NSTemporaryDirectory()); +} + +// Return the binary's path. +void IOSAppName(talk_base::Pathname* path) { + NSProcessInfo *pInfo = [NSProcessInfo processInfo]; + NSString* argv0 = [[pInfo arguments] objectAtIndex:0]; + path->SetPathname([argv0 UTF8String]); +} diff --git a/talk/base/latebindingsymboltable.cc.def b/talk/base/latebindingsymboltable.cc.def index 1f84f30fc..64fc0dce2 100644 --- a/talk/base/latebindingsymboltable.cc.def +++ b/talk/base/latebindingsymboltable.cc.def @@ -63,16 +63,17 @@ #error You must define LATE_BINDING_SYMBOL_TABLE_DLL_NAME #endif +#define X(sym) #sym, +const char* const LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kSymbolNames[] = { + LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +}; +#undef X + const ::talk_base::LateBindingSymbolTable::TableInfo LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kTableInfo = { LATE_BINDING_SYMBOL_TABLE_DLL_NAME, SYMBOL_TABLE_SIZE, - (const char *const []){ -#define X(sym) \ - #sym, -LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST -#undef X - }, + LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kSymbolNames }; LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::LATE_BINDING_SYMBOL_TABLE_CLASS_NAME() diff --git a/talk/base/latebindingsymboltable.h.def b/talk/base/latebindingsymboltable.h.def index cd8c176f3..3e1cdab59 100644 --- a/talk/base/latebindingsymboltable.h.def +++ b/talk/base/latebindingsymboltable.h.def @@ -89,6 +89,7 @@ LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST }; static const ::talk_base::LateBindingSymbolTable::TableInfo kTableInfo; + static const char *const kSymbolNames[]; void *table_[SYMBOL_TABLE_SIZE]; diff --git a/talk/base/messagequeue.cc b/talk/base/messagequeue.cc index 64e63ae3d..7bda92411 100644 --- a/talk/base/messagequeue.cc +++ b/talk/base/messagequeue.cc @@ -126,7 +126,7 @@ void MessageQueueManager::ClearInternal(MessageHandler *handler) { // MessageQueue MessageQueue::MessageQueue(SocketServer* ss) - : ss_(ss), fStop_(false), fPeekKeep_(false), active_(false), + : ss_(ss), fStop_(false), fPeekKeep_(false), dmsgq_next_num_(0) { if (!ss_) { // Currently, MessageQueue holds a socket server, and is the base class for @@ -138,6 +138,7 @@ MessageQueue::MessageQueue(SocketServer* ss) ss_ = default_ss_.get(); } ss_->SetMessageQueue(this); + MessageQueueManager::Add(this); } MessageQueue::~MessageQueue() { @@ -145,10 +146,8 @@ MessageQueue::~MessageQueue() { // that it always gets called when the queue // is going away. SignalQueueDestroyed(); - if (active_) { - MessageQueueManager::Remove(this); - Clear(NULL); - } + MessageQueueManager::Remove(this); + Clear(NULL); if (ss_) { ss_->SetMessageQueue(NULL); } @@ -296,7 +295,6 @@ void MessageQueue::Post(MessageHandler *phandler, uint32 id, // Signal for the multiplexer to return CritScope cs(&crit_); - EnsureActive(); Message msg; msg.phandler = phandler; msg.message_id = id; @@ -318,7 +316,6 @@ void MessageQueue::DoDelayPost(int cmsDelay, uint32 tstamp, // Signal for the multiplexer to return. CritScope cs(&crit_); - EnsureActive(); Message msg; msg.phandler = phandler; msg.message_id = id; @@ -401,12 +398,4 @@ void MessageQueue::Dispatch(Message *pmsg) { pmsg->phandler->OnMessage(pmsg); } -void MessageQueue::EnsureActive() { - ASSERT(crit_.CurrentThreadIsOwner()); - if (!active_) { - active_ = true; - MessageQueueManager::Add(this); - } -} - } // namespace talk_base diff --git a/talk/base/messagequeue.h b/talk/base/messagequeue.h index fc72166e4..dc2b5a89a 100644 --- a/talk/base/messagequeue.h +++ b/talk/base/messagequeue.h @@ -75,7 +75,7 @@ class MessageQueueManager { void ClearInternal(MessageHandler *handler); static MessageQueueManager* instance_; - // This list contains 'active' MessageQueues. + // This list contains all live MessageQueues. std::vector message_queues_; CriticalSection crit_; }; @@ -247,7 +247,6 @@ class MessageQueue { void reheap() { make_heap(c.begin(), c.end(), comp); } }; - void EnsureActive(); void DoDelayPost(int cmsDelay, uint32 tstamp, MessageHandler *phandler, uint32 id, MessageData* pdata); @@ -258,9 +257,6 @@ class MessageQueue { bool fStop_; bool fPeekKeep_; Message msgPeek_; - // A message queue is active if it has ever had a message posted to it. - // This also corresponds to being in MessageQueueManager's global list. - bool active_; MessageList msgq_; PriorityQueue dmsgq_; uint32 dmsgq_next_num_; diff --git a/talk/base/network.cc b/talk/base/network.cc index e3458eb78..829507aec 100644 --- a/talk/base/network.cc +++ b/talk/base/network.cc @@ -112,6 +112,24 @@ bool SortNetworks(const Network* a, const Network* b) { return a->key() > b->key(); } +std::string AdapterTypeToString(AdapterType type) { + switch (type) { + case ADAPTER_TYPE_UNKNOWN: + return "Unknown"; + case ADAPTER_TYPE_ETHERNET: + return "Ethernet"; + case ADAPTER_TYPE_WIFI: + return "Wifi"; + case ADAPTER_TYPE_CELLULAR: + return "Cellular"; + case ADAPTER_TYPE_VPN: + return "VPN"; + default: + ASSERT(false); + return std::string(); + } +} + } // namespace std::string MakeNetworkKey(const std::string& name, const IPAddress& prefix, @@ -292,8 +310,7 @@ void BasicNetworkManager::ConvertIfAddrs(struct ifaddrs* interfaces, scoped_ptr network(new Network(cursor->ifa_name, cursor->ifa_name, prefix, - prefix_length, - key)); + prefix_length)); network->set_scope_id(scope_id); network->AddIP(ip); bool ignored = ((cursor->ifa_flags & IFF_LOOPBACK) || @@ -439,8 +456,7 @@ bool BasicNetworkManager::CreateNetworks(bool include_ignored, scoped_ptr network(new Network(name, description, prefix, - prefix_length, - key)); + prefix_length)); network->set_scope_id(scope_id); network->AddIP(ip); bool ignore = ((adapter_addrs->IfType == IF_TYPE_SOFTWARE_LOOPBACK) || @@ -608,18 +624,19 @@ void BasicNetworkManager::DumpNetworks(bool include_ignored) { } Network::Network(const std::string& name, const std::string& desc, - const IPAddress& prefix, int prefix_length, - const std::string& key) + const IPAddress& prefix, int prefix_length) : name_(name), description_(desc), prefix_(prefix), - prefix_length_(prefix_length), key_(key), scope_id_(0), ignored_(false), - type_(ADAPTER_TYPE_UNKNOWN), preference_(0) { + prefix_length_(prefix_length), + key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0), + ignored_(false), type_(ADAPTER_TYPE_UNKNOWN), preference_(0) { } Network::Network(const std::string& name, const std::string& desc, - const IPAddress& prefix, int prefix_length) + const IPAddress& prefix, int prefix_length, AdapterType type) : name_(name), description_(desc), prefix_(prefix), - prefix_length_(prefix_length), scope_id_(0), ignored_(false), - type_(ADAPTER_TYPE_UNKNOWN), preference_(0) { + prefix_length_(prefix_length), + key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0), + ignored_(false), type_(type), preference_(0) { } std::string Network::ToString() const { @@ -627,7 +644,8 @@ std::string Network::ToString() const { // Print out the first space-terminated token of the network desc, plus // the IP address. ss << "Net[" << description_.substr(0, description_.find(' ')) - << ":" << prefix_.ToSensitiveString() << "/" << prefix_length_ << "]"; + << ":" << prefix_.ToSensitiveString() << "/" << prefix_length_ + << ":" << AdapterTypeToString(type_) << "]"; return ss.str(); } diff --git a/talk/base/network.h b/talk/base/network.h index e4ff5f8f6..2be81bb1c 100644 --- a/talk/base/network.h +++ b/talk/base/network.h @@ -182,14 +182,11 @@ class BasicNetworkManager : public NetworkManagerBase, // Represents a Unix-type network interface, with a name and single address. class Network { public: - Network() : prefix_(INADDR_ANY), scope_id_(0), - type_(ADAPTER_TYPE_UNKNOWN) {} Network(const std::string& name, const std::string& description, - const IPAddress& prefix, int prefix_length, - const std::string& key); + const IPAddress& prefix, int prefix_length); Network(const std::string& name, const std::string& description, - const IPAddress& prefix, int prefix_length); + const IPAddress& prefix, int prefix_length, AdapterType type); // Returns the name of the interface this network is associated wtih. const std::string& name() const { return name_; } diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc index 85aa2f87a..56b11c617 100644 --- a/talk/base/network_unittest.cc +++ b/talk/base/network_unittest.cc @@ -554,6 +554,24 @@ TEST_F(NetworkTest, TestNetworkListSorting) { EXPECT_TRUE(net1->preference() < net2->preference()); } +TEST_F(NetworkTest, TestNetworkAdapterTypes) { + Network wifi("wlan0", "Wireless Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_WIFI); + EXPECT_EQ(ADAPTER_TYPE_WIFI, wifi.type()); + Network ethernet("eth0", "Ethernet", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_ETHERNET); + EXPECT_EQ(ADAPTER_TYPE_ETHERNET, ethernet.type()); + Network cellular("test_cell", "Cellular Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_CELLULAR); + EXPECT_EQ(ADAPTER_TYPE_CELLULAR, cellular.type()); + Network vpn("bridge_test", "VPN Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_VPN); + EXPECT_EQ(ADAPTER_TYPE_VPN, vpn.type()); + Network unknown("test", "Test Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_UNKNOWN); + EXPECT_EQ(ADAPTER_TYPE_UNKNOWN, unknown.type()); +} + #if defined(POSIX) // Verify that we correctly handle interfaces with no address. TEST_F(NetworkTest, TestConvertIfAddrsNoAddress) { diff --git a/talk/base/opensslstreamadapter.cc b/talk/base/opensslstreamadapter.cc index 08798a158..218f656be 100644 --- a/talk/base/opensslstreamadapter.cc +++ b/talk/base/opensslstreamadapter.cc @@ -779,6 +779,18 @@ int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { return 0; } X509* cert = X509_STORE_CTX_get_current_cert(store); + int depth = X509_STORE_CTX_get_error_depth(store); + + // For now We ignore the parent certificates and verify the leaf against + // the digest. + // + // TODO(jiayl): Verify the chain is a proper chain and report the chain to + // |stream->peer_certificate_|, like what NSS does. + if (depth > 0) { + LOG(LS_INFO) << "Ignored chained certificate at depth " << depth; + return 1; + } + unsigned char digest[EVP_MAX_MD_SIZE]; size_t digest_length; if (!OpenSSLCertificate::ComputeDigest( @@ -789,6 +801,7 @@ int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { LOG(LS_WARNING) << "Failed to compute peer cert digest."; return 0; } + Buffer computed_digest(digest, digest_length); if (computed_digest != stream->peer_certificate_digest_value_) { LOG(LS_WARNING) << "Rejected peer certificate due to mismatched digest."; @@ -798,6 +811,7 @@ int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { // value in checking the validity of a self-signed cert issued by untrusted // sources. LOG(LS_INFO) << "Accepted peer certificate."; + // Record the peer's certificate. stream->peer_certificate_.reset(new OpenSSLCertificate(cert)); return 1; diff --git a/talk/base/optionsfile_unittest.cc b/talk/base/optionsfile_unittest.cc index 65861ff49..afb79cf9a 100644 --- a/talk/base/optionsfile_unittest.cc +++ b/talk/base/optionsfile_unittest.cc @@ -25,19 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "talk/base/fileutils.h" #include "talk/base/gunit.h" #include "talk/base/optionsfile.h" +#include "talk/base/pathutils.h" namespace talk_base { -#ifdef ANDROID -static const char *kTestFile = "/sdcard/.testfile"; -#elif CHROMEOS -static const char *kTestFile = "/tmp/.testfile"; -#else -static const char *kTestFile = ".testfile"; -#endif - static const std::string kTestOptionA = "test-option-a"; static const std::string kTestOptionB = "test-option-b"; static const std::string kTestString1 = "a string"; @@ -56,122 +50,135 @@ static int kTestInt2 = 67890; static int kNegInt = -634; static int kZero = 0; -TEST(OptionsFile, GetSetString) { - OptionsFile store(kTestFile); +class OptionsFileTest : public testing::Test { + public: + OptionsFileTest() { + Pathname dir; + ASSERT(Filesystem::GetTemporaryFolder(dir, true, NULL)); + test_file_ = Filesystem::TempFilename(dir, ".testfile"); + OpenStore(); + } + + protected: + void OpenStore() { + store_.reset(new OptionsFile(test_file_)); + } + + talk_base::scoped_ptr store_; + + private: + std::string test_file_; +}; + +TEST_F(OptionsFileTest, GetSetString) { // Clear contents of the file on disk. - EXPECT_TRUE(store.Save()); + EXPECT_TRUE(store_->Save()); std::string out1, out2; - EXPECT_FALSE(store.GetStringValue(kTestOptionA, &out1)); - EXPECT_FALSE(store.GetStringValue(kTestOptionB, &out2)); - EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.SetStringValue(kTestOptionB, kTestString2)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out1)); - EXPECT_TRUE(store.GetStringValue(kTestOptionB, &out2)); + EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kTestString2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out2)); EXPECT_EQ(kTestString1, out1); EXPECT_EQ(kTestString2, out2); - EXPECT_TRUE(store.RemoveValue(kTestOptionA)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.RemoveValue(kTestOptionB)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_FALSE(store.GetStringValue(kTestOptionA, &out1)); - EXPECT_FALSE(store.GetStringValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); } -TEST(OptionsFile, GetSetInt) { - OptionsFile store(kTestFile); +TEST_F(OptionsFileTest, GetSetInt) { // Clear contents of the file on disk. - EXPECT_TRUE(store.Save()); + EXPECT_TRUE(store_->Save()); int out1, out2; - EXPECT_FALSE(store.GetIntValue(kTestOptionA, &out1)); - EXPECT_FALSE(store.GetIntValue(kTestOptionB, &out2)); - EXPECT_TRUE(store.SetIntValue(kTestOptionA, kTestInt1)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.SetIntValue(kTestOptionB, kTestInt2)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1)); - EXPECT_TRUE(store.GetIntValue(kTestOptionB, &out2)); + EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kTestInt1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kTestInt2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); EXPECT_EQ(kTestInt1, out1); EXPECT_EQ(kTestInt2, out2); - EXPECT_TRUE(store.RemoveValue(kTestOptionA)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.RemoveValue(kTestOptionB)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_FALSE(store.GetIntValue(kTestOptionA, &out1)); - EXPECT_FALSE(store.GetIntValue(kTestOptionB, &out2)); - EXPECT_TRUE(store.SetIntValue(kTestOptionA, kNegInt)); - EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kNegInt)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); EXPECT_EQ(kNegInt, out1); - EXPECT_TRUE(store.SetIntValue(kTestOptionA, kZero)); - EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kZero)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); EXPECT_EQ(kZero, out1); } -TEST(OptionsFile, Persist) { - { - OptionsFile store(kTestFile); - // Clear contents of the file on disk. - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1)); - EXPECT_TRUE(store.SetIntValue(kTestOptionB, kNegInt)); - EXPECT_TRUE(store.Save()); - } - { - OptionsFile store(kTestFile); - // Load the saved contents from above. - EXPECT_TRUE(store.Load()); - std::string out1; - int out2; - EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out1)); - EXPECT_TRUE(store.GetIntValue(kTestOptionB, &out2)); - EXPECT_EQ(kTestString1, out1); - EXPECT_EQ(kNegInt, out2); - } +TEST_F(OptionsFileTest, Persist) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kNegInt)); + EXPECT_TRUE(store_->Save()); + + // Load the saved contents from above. + OpenStore(); + EXPECT_TRUE(store_->Load()); + std::string out1; + int out2; + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_EQ(kTestString1, out1); + EXPECT_EQ(kNegInt, out2); } -TEST(OptionsFile, SpecialCharacters) { - OptionsFile store(kTestFile); +TEST_F(OptionsFileTest, SpecialCharacters) { // Clear contents of the file on disk. - EXPECT_TRUE(store.Save()); + EXPECT_TRUE(store_->Save()); std::string out; - EXPECT_FALSE(store.SetStringValue(kOptionWithEquals, kTestString1)); - EXPECT_FALSE(store.GetStringValue(kOptionWithEquals, &out)); - EXPECT_FALSE(store.SetStringValue(kOptionWithNewline, kTestString1)); - EXPECT_FALSE(store.GetStringValue(kOptionWithNewline, &out)); - EXPECT_TRUE(store.SetStringValue(kOptionWithUtf8, kValueWithUtf8)); - EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out)); + EXPECT_FALSE(store_->SetStringValue(kOptionWithEquals, kTestString1)); + EXPECT_FALSE(store_->GetStringValue(kOptionWithEquals, &out)); + EXPECT_FALSE(store_->SetStringValue(kOptionWithNewline, kTestString1)); + EXPECT_FALSE(store_->GetStringValue(kOptionWithNewline, &out)); + EXPECT_TRUE(store_->SetStringValue(kOptionWithUtf8, kValueWithUtf8)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); EXPECT_EQ(kTestString1, out); - EXPECT_TRUE(store.GetStringValue(kOptionWithUtf8, &out)); + EXPECT_TRUE(store_->GetStringValue(kOptionWithUtf8, &out)); EXPECT_EQ(kValueWithUtf8, out); - EXPECT_FALSE(store.SetStringValue(kTestOptionA, kValueWithNewline)); - EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out)); + EXPECT_FALSE(store_->SetStringValue(kTestOptionA, kValueWithNewline)); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); EXPECT_EQ(kTestString1, out); - EXPECT_TRUE(store.SetStringValue(kTestOptionA, kValueWithEquals)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kValueWithEquals)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); EXPECT_EQ(kValueWithEquals, out); - EXPECT_TRUE(store.SetStringValue(kEmptyString, kTestString2)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.GetStringValue(kEmptyString, &out)); + EXPECT_TRUE(store_->SetStringValue(kEmptyString, kTestString2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kEmptyString, &out)); EXPECT_EQ(kTestString2, out); - EXPECT_TRUE(store.SetStringValue(kTestOptionB, kEmptyString)); - EXPECT_TRUE(store.Save()); - EXPECT_TRUE(store.Load()); - EXPECT_TRUE(store.GetStringValue(kTestOptionB, &out)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kEmptyString)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out)); EXPECT_EQ(kEmptyString, out); } diff --git a/talk/base/physicalsocketserver.cc b/talk/base/physicalsocketserver.cc index 12c1bccb2..a7f65c57a 100644 --- a/talk/base/physicalsocketserver.cc +++ b/talk/base/physicalsocketserver.cc @@ -501,7 +501,7 @@ class PhysicalSocket : public AsyncSocket, public sigslot::has_slots<> { } void MaybeRemapSendError() { -#if defined(OSX) +#if defined(OSX) || defined(IOS) // https://developer.apple.com/library/mac/documentation/Darwin/ // Reference/ManPages/man2/sendto.2.html // ENOBUFS - The output queue for a network interface is full. diff --git a/talk/base/physicalsocketserver_unittest.cc b/talk/base/physicalsocketserver_unittest.cc index 329cf5d5f..a27b98073 100644 --- a/talk/base/physicalsocketserver_unittest.cc +++ b/talk/base/physicalsocketserver_unittest.cc @@ -33,6 +33,7 @@ #include "talk/base/physicalsocketserver.h" #include "talk/base/scoped_ptr.h" #include "talk/base/socket_unittest.h" +#include "talk/base/testutils.h" #include "talk/base/thread.h" namespace talk_base { @@ -74,21 +75,11 @@ TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv6) { } -#ifdef OSX -// This test crashes the OS X kernel on 10.6 (at bsd/netinet/tcp_subr.c:2118). -TEST_F(PhysicalSocketTest, DISABLED_TestConnectWithClosedSocketIPv4) { -#else TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv4) { -#endif SocketTest::TestConnectWithClosedSocketIPv4(); } -#ifdef OSX -// This test crashes the OS X kernel on 10.6 (at bsd/netinet/tcp_subr.c:2118). -TEST_F(PhysicalSocketTest, DISABLED_TestConnectWithClosedSocketIPv6) { -#else TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv6) { -#endif SocketTest::TestConnectWithClosedSocketIPv6(); } @@ -156,10 +147,16 @@ TEST_F(PhysicalSocketTest, TestUdpIPv6) { SocketTest::TestUdpIPv6(); } +// Disable for TSan v2, see +// https://code.google.com/p/webrtc/issues/detail?id=3498 for details. +#if !defined(THREAD_SANITIZER) + TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv4) { SocketTest::TestUdpReadyToSendIPv4(); } +#endif // if !defined(THREAD_SANITIZER) + TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv6) { SocketTest::TestUdpReadyToSendIPv6(); } @@ -227,7 +224,7 @@ Thread *PosixSignalDeliveryTest::signaled_thread_ = NULL; // Test receiving a synchronous signal while not in Wait() and then entering // Wait() afterwards. TEST_F(PosixSignalDeliveryTest, RaiseThenWait) { - ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal); + ASSERT_TRUE(ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal)); raise(SIGTERM); EXPECT_TRUE(ss_->Wait(0, true)); EXPECT_TRUE(ExpectSignal(SIGTERM)); diff --git a/talk/base/profiler_unittest.cc b/talk/base/profiler_unittest.cc index f451e5fab..a39b32c49 100644 --- a/talk/base/profiler_unittest.cc +++ b/talk/base/profiler_unittest.cc @@ -47,13 +47,15 @@ namespace talk_base { TEST(ProfilerTest, TestFunction) { ASSERT_TRUE(Profiler::Instance()->Clear()); + // Profile a long-running function. const char* function_name = TestFunc(); const ProfilerEvent* event = Profiler::Instance()->GetEvent(function_name); ASSERT_TRUE(event != NULL); EXPECT_FALSE(event->is_started()); EXPECT_EQ(1, event->event_count()); - EXPECT_NEAR(kWaitSec, event->mean(), kTolerance); + EXPECT_NEAR(kWaitSec, event->mean(), kTolerance * 3); + // Run it a second time. TestFunc(); EXPECT_FALSE(event->is_started()); @@ -95,7 +97,9 @@ TEST(ProfilerTest, TestScopedEvents) { // Check the result. EXPECT_FALSE(event2->is_started()); EXPECT_EQ(1, event2->event_count()); - EXPECT_NEAR(kEvent2WaitSec, event2->mean(), kTolerance); + + // The difference here can be as much as 0.33, so we need high tolerance. + EXPECT_NEAR(kEvent2WaitSec, event2->mean(), kTolerance * 4); // Make sure event1 is unchanged. EXPECT_FALSE(event1->is_started()); EXPECT_EQ(1, event1->event_count()); diff --git a/talk/base/proxydetect.cc b/talk/base/proxydetect.cc index 7292f3b9f..8f7f7f872 100644 --- a/talk/base/proxydetect.cc +++ b/talk/base/proxydetect.cc @@ -638,27 +638,27 @@ bool IsDefaultBrowserFirefox() { if (ERROR_SUCCESS != result) return false; - wchar_t* value = NULL; DWORD size, type; + bool success = false; result = RegQueryValueEx(key, L"", 0, &type, NULL, &size); - if (REG_SZ != type) { - result = ERROR_ACCESS_DENIED; // Any error is fine - } else if (ERROR_SUCCESS == result) { - value = new wchar_t[size+1]; + if (result == ERROR_SUCCESS && type == REG_SZ) { + wchar_t* value = new wchar_t[size+1]; BYTE* buffer = reinterpret_cast(value); result = RegQueryValueEx(key, L"", 0, &type, buffer, &size); - } - RegCloseKey(key); - - bool success = false; - if (ERROR_SUCCESS == result) { - value[size] = L'\0'; - for (size_t i = 0; i < size; ++i) { - value[i] = tolowercase(value[i]); + if (result == ERROR_SUCCESS) { + // Size returned by RegQueryValueEx is in bytes, convert to number of + // wchar_t's. + size /= sizeof(value[0]); + value[size] = L'\0'; + for (size_t i = 0; i < size; ++i) { + value[i] = tolowercase(value[i]); + } + success = (NULL != strstr(value, L"firefox.exe")); } - success = (NULL != strstr(value, L"firefox.exe")); + delete[] value; } - delete [] value; + + RegCloseKey(key); return success; } diff --git a/talk/base/scoped_ref_ptr.h b/talk/base/scoped_ref_ptr.h index 3ce72cbce..ae1ab0f23 100644 --- a/talk/base/scoped_ref_ptr.h +++ b/talk/base/scoped_ref_ptr.h @@ -80,6 +80,8 @@ #ifndef TALK_BASE_SCOPED_REF_PTR_H_ #define TALK_BASE_SCOPED_REF_PTR_H_ +#include + namespace talk_base { template diff --git a/talk/base/sharedexclusivelock_unittest.cc b/talk/base/sharedexclusivelock_unittest.cc index 46b7fdfdc..e280aa28a 100644 --- a/talk/base/sharedexclusivelock_unittest.cc +++ b/talk/base/sharedexclusivelock_unittest.cc @@ -148,7 +148,8 @@ class SharedExclusiveLockTest int value_; }; -TEST_F(SharedExclusiveLockTest, TestSharedShared) { +// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3318 +TEST_F(SharedExclusiveLockTest, DISABLED_TestSharedShared) { int value0, value1; bool done0, done1; ReadTask reader0(shared_exclusive_lock_.get(), &value_, &done0); diff --git a/talk/base/signalthread_unittest.cc b/talk/base/signalthread_unittest.cc index e5734d4df..7bc73f05d 100644 --- a/talk/base/signalthread_unittest.cc +++ b/talk/base/signalthread_unittest.cc @@ -50,19 +50,19 @@ class SignalThreadTest : public testing::Test, public sigslot::has_slots<> { ASSERT_TRUE(harness_ != NULL); ++harness_->thread_started_; EXPECT_EQ(harness_->main_thread_, Thread::Current()); - EXPECT_FALSE(worker()->started()); // not started yet + EXPECT_FALSE(worker()->RunningForTest()); // not started yet } virtual void OnWorkStop() { ++harness_->thread_stopped_; EXPECT_EQ(harness_->main_thread_, Thread::Current()); - EXPECT_TRUE(worker()->started()); // not stopped yet + EXPECT_TRUE(worker()->RunningForTest()); // not stopped yet } virtual void OnWorkDone() { ++harness_->thread_done_; EXPECT_EQ(harness_->main_thread_, Thread::Current()); - EXPECT_TRUE(worker()->started()); // not stopped yet + EXPECT_TRUE(worker()->RunningForTest()); // not stopped yet } virtual void DoWork() { diff --git a/talk/base/sigslottester.h b/talk/base/sigslottester.h new file mode 100755 index 000000000..9422318e2 --- /dev/null +++ b/talk/base/sigslottester.h @@ -0,0 +1,216 @@ +// This file was GENERATED by command: +// pump.py sigslottester.h.pump +// DO NOT EDIT BY HAND!!! + +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_BASE_SIGSLOTTESTER_H_ +#define TALK_BASE_SIGSLOTTESTER_H_ + +// To generate sigslottester.h from sigslottester.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py sigslottester.h.pump + + +// SigslotTester(s) are utility classes to check if signals owned by an +// object are being invoked at the right time and with the right arguments. +// They are meant to be used in tests. Tests must provide "capture" pointers +// (i.e. address of variables) where the arguments from the signal callback +// can be stored. +// +// Example: +// /* Some signal */ +// sigslot::signal1 foo; +// +// /* We want to monitor foo in some test. Note how signal argument is +// const std::string&, but capture-type is std::string. Capture type +// must be type that can be assigned to. */ +// std::string capture; +// SigslotTester1 slot(&foo, &capture); +// foo.emit("hello"); +// EXPECT_EQ(1, slot.callback_count()); +// EXPECT_EQ("hello", capture); +// /* See unit-tests for more examples */ + +#include "talk/base/constructormagic.h" +#include "talk/base/sigslot.h" + +namespace talk_base { + +// For all the templates below: +// - A1-A5 is the type of the argument i in the callback. Signals may and often +// do use const-references here for efficiency. +// - C1-C5 is the type of the variable to capture argument i. These should be +// non-const value types suitable for use as lvalues. + +template +class SigslotTester1 : public sigslot::has_slots<> { + public: + SigslotTester1(sigslot::signal1* signal, + C1* capture1) + : callback_count_(0), + capture1_(capture1) { + signal->connect(this, &SigslotTester1::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1) { + callback_count_++; + *capture1_ = arg1; + } + + int callback_count_; + C1* capture1_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester1); +}; + +template +class SigslotTester2 : public sigslot::has_slots<> { + public: + SigslotTester2(sigslot::signal2* signal, + C1* capture1, C2* capture2) + : callback_count_(0), + capture1_(capture1), capture2_(capture2) { + signal->connect(this, &SigslotTester2::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester2); +}; + +template +class SigslotTester3 : public sigslot::has_slots<> { + public: + SigslotTester3(sigslot::signal3* signal, + C1* capture1, C2* capture2, C3* capture3) + : callback_count_(0), + capture1_(capture1), capture2_(capture2), capture3_(capture3) { + signal->connect(this, &SigslotTester3::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + *capture3_ = arg3; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + C3* capture3_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester3); +}; + +template +class SigslotTester4 : public sigslot::has_slots<> { + public: + SigslotTester4(sigslot::signal4* signal, + C1* capture1, C2* capture2, C3* capture3, C4* capture4) + : callback_count_(0), + capture1_(capture1), capture2_(capture2), capture3_(capture3), + capture4_(capture4) { + signal->connect(this, &SigslotTester4::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + *capture3_ = arg3; + *capture4_ = arg4; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + C3* capture3_; + C4* capture4_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester4); +}; + +template +class SigslotTester5 : public sigslot::has_slots<> { + public: + SigslotTester5(sigslot::signal5* signal, + C1* capture1, C2* capture2, C3* capture3, C4* capture4, + C5* capture5) + : callback_count_(0), + capture1_(capture1), capture2_(capture2), capture3_(capture3), + capture4_(capture4), capture5_(capture5) { + signal->connect(this, &SigslotTester5::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + *capture3_ = arg3; + *capture4_ = arg4; + *capture5_ = arg5; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + C3* capture3_; + C4* capture4_; + C5* capture5_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester5); +}; +} // namespace talk_base + +#endif // TALK_BASE_SIGSLOTTESTER_H_ diff --git a/talk/base/sigslottester.h.pump b/talk/base/sigslottester.h.pump new file mode 100755 index 000000000..dce1c7b26 --- /dev/null +++ b/talk/base/sigslottester.h.pump @@ -0,0 +1,102 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_BASE_SIGSLOTTESTER_H_ +#define TALK_BASE_SIGSLOTTESTER_H_ + +// To generate sigslottester.h from sigslottester.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py sigslottester.h.pump + + +// SigslotTester(s) are utility classes to check if signals owned by an +// object are being invoked at the right time and with the right arguments. +// They are meant to be used in tests. Tests must provide "capture" pointers +// (i.e. address of variables) where the arguments from the signal callback +// can be stored. +// +// Example: +// /* Some signal */ +// sigslot::signal1 foo; +// +// /* We want to monitor foo in some test. Note how signal argument is +// const std::string&, but capture-type is std::string. Capture type +// must be type that can be assigned to. */ +// std::string capture; +// SigslotTester1 slot(&foo, &capture); +// foo.emit("hello"); +// EXPECT_EQ(1, slot.callback_count()); +// EXPECT_EQ("hello", capture); +// /* See unit-tests for more examples */ + +#include "talk/base/constructormagic.h" +#include "talk/base/sigslot.h" + +namespace talk_base { + +// For all the templates below: +// - A1-A5 is the type of the argument i in the callback. Signals may and often +// do use const-references here for efficiency. +// - C1-C5 is the type of the variable to capture argument i. These should be +// non-const value types suitable for use as lvalues. + +$var n = 5 +$range i 1..n +$for i [[ +$range j 1..i + +template <$for j , [[class A$j]], $for j , [[class C$j]]> +class SigslotTester$i : public sigslot::has_slots<> { + public: + SigslotTester$i(sigslot::signal$i<$for j , [[A$j]]>* signal, + $for j , [[C$j* capture$j]]) + : callback_count_(0), + $for j , [[capture$j[[]]_(capture$j)]] { + signal->connect(this, &SigslotTester$i::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback($for j , [[A$j arg$j]]) { + callback_count_++;$for j [[ + + *capture$j[[]]_ = arg$j;]] + + } + + int callback_count_;$for j [[ + + C$j* capture$j[[]]_;]] + + + DISALLOW_COPY_AND_ASSIGN(SigslotTester$i); +}; + +]] +} // namespace talk_base + +#endif // TALK_BASE_SIGSLOTTESTER_H_ diff --git a/talk/base/sigslottester_unittest.cc b/talk/base/sigslottester_unittest.cc new file mode 100755 index 000000000..b427ef67c --- /dev/null +++ b/talk/base/sigslottester_unittest.cc @@ -0,0 +1,74 @@ +#include "talk/base/sigslottester.h" + +#include "talk/base/gunit.h" +#include "talk/base/sigslot.h" + +namespace talk_base { + +TEST(SigslotTester, TestSignal1Arg) { + sigslot::signal1 source1; + int capture1; + SigslotTester1 slot1(&source1, &capture1); + EXPECT_EQ(0, slot1.callback_count()); + + source1.emit(10); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ(10, capture1); + + source1.emit(20); + EXPECT_EQ(2, slot1.callback_count()); + EXPECT_EQ(20, capture1); +} + +TEST(SigslotTester, TestSignal2Args) { + sigslot::signal2 source2; + int capture1; + char capture2; + SigslotTester2 slot2(&source2, &capture1, &capture2); + EXPECT_EQ(0, slot2.callback_count()); + + source2.emit(10, 'x'); + EXPECT_EQ(1, slot2.callback_count()); + EXPECT_EQ(10, capture1); + EXPECT_EQ('x', capture2); + + source2.emit(20, 'y'); + EXPECT_EQ(2, slot2.callback_count()); + EXPECT_EQ(20, capture1); + EXPECT_EQ('y', capture2); +} + +// Since it applies for 1 and 2 args, we assume it will work for up to 5 args. + +TEST(SigslotTester, TestSignalWithConstReferenceArgs) { + sigslot::signal1 source1; + std::string capture1; + SigslotTester1 slot1(&source1, &capture1); + EXPECT_EQ(0, slot1.callback_count()); + source1.emit("hello"); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ("hello", capture1); +} + +TEST(SigslotTester, TestSignalWithPointerToConstArgs) { + sigslot::signal1 source1; + const std::string* capture1; + SigslotTester1 slot1(&source1, + &capture1); + EXPECT_EQ(0, slot1.callback_count()); + source1.emit(NULL); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ(NULL, capture1); +} + +TEST(SigslotTester, TestSignalWithConstPointerArgs) { + sigslot::signal1 source1; + std::string* capture1; + SigslotTester1 slot1(&source1, &capture1); + EXPECT_EQ(0, slot1.callback_count()); + source1.emit(NULL); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ(NULL, capture1); +} + +} // namespace talk_base diff --git a/talk/base/socket_unittest.cc b/talk/base/socket_unittest.cc index a9c4dbb0d..e76d113b2 100644 --- a/talk/base/socket_unittest.cc +++ b/talk/base/socket_unittest.cc @@ -172,8 +172,8 @@ void SocketTest::TestUdpIPv6() { } void SocketTest::TestUdpReadyToSendIPv4() { -#if !defined(OSX) - // TODO(ronghuawu): Enable this test (currently failed on build bots) on mac. +#if !defined(OSX) && !defined(IOS) + // TODO(ronghuawu): Enable this test on mac/ios. UdpReadyToSend(kIPv4Loopback); #endif } diff --git a/talk/base/testutils.h b/talk/base/testutils.h index 363fb4cff..86e946fc0 100644 --- a/talk/base/testutils.h +++ b/talk/base/testutils.h @@ -45,6 +45,7 @@ #include "talk/base/common.h" #include "talk/base/gunit.h" #include "talk/base/nethelpers.h" +#include "talk/base/pathutils.h" #include "talk/base/stream.h" #include "talk/base/stringencode.h" #include "talk/base/stringutils.h" @@ -451,6 +452,30 @@ inline bool ReadFile(const char* filename, std::string* contents) { return success; } +// Look in parent dir for parallel directory. +inline talk_base::Pathname GetSiblingDirectory( + const std::string& parallel_dir) { + talk_base::Pathname path = talk_base::Filesystem::GetCurrentDirectory(); + while (!path.empty()) { + talk_base::Pathname potential_parallel_dir = path; + potential_parallel_dir.AppendFolder(parallel_dir); + if (talk_base::Filesystem::IsFolder(potential_parallel_dir)) { + return potential_parallel_dir; + } + + path.SetFolder(path.parent_folder()); + } + return path; +} + +inline talk_base::Pathname GetGoogle3Directory() { + return GetSiblingDirectory("google3"); +} + +inline talk_base::Pathname GetTalkDirectory() { + return GetSiblingDirectory("talk"); +} + /////////////////////////////////////////////////////////////////////////////// // Unittest predicates which are similar to STREQ, but for raw memory /////////////////////////////////////////////////////////////////////////////// diff --git a/talk/base/thread.cc b/talk/base/thread.cc index 7b4d37084..23800b7ce 100644 --- a/talk/base/thread.cc +++ b/talk/base/thread.cc @@ -145,20 +145,18 @@ struct ThreadInit { Thread::Thread(SocketServer* ss) : MessageQueue(ss), priority_(PRIORITY_NORMAL), - started_(false), + running_(true, false), #if defined(WIN32) thread_(NULL), thread_id_(0), #endif - owned_(true), - delete_self_when_complete_(false) { + owned_(true) { SetName("Thread", this); // default name } Thread::~Thread() { Stop(); - if (active_) - Clear(NULL); + Clear(NULL); } bool Thread::SleepMs(int milliseconds) { @@ -181,7 +179,7 @@ bool Thread::SleepMs(int milliseconds) { } bool Thread::SetName(const std::string& name, const void* obj) { - if (started_) return false; + if (running()) return false; name_ = name; if (obj) { char buf[16]; @@ -193,7 +191,7 @@ bool Thread::SetName(const std::string& name, const void* obj) { bool Thread::SetPriority(ThreadPriority priority) { #if defined(WIN32) - if (started_) { + if (running()) { BOOL ret = FALSE; if (priority == PRIORITY_NORMAL) { ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_NORMAL); @@ -212,7 +210,7 @@ bool Thread::SetPriority(ThreadPriority priority) { return true; #else // TODO: Implement for Linux/Mac if possible. - if (started_) return false; + if (running()) return false; priority_ = priority; return true; #endif @@ -221,8 +219,8 @@ bool Thread::SetPriority(ThreadPriority priority) { bool Thread::Start(Runnable* runnable) { ASSERT(owned_); if (!owned_) return false; - ASSERT(!started_); - if (started_) return false; + ASSERT(!running()); + if (running()) return false; Restart(); // reset fStop_ if the thread is being restarted @@ -241,7 +239,7 @@ bool Thread::Start(Runnable* runnable) { thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreRun, init, flags, &thread_id_); if (thread_) { - started_ = true; + running_.Set(); if (priority_ != PRIORITY_NORMAL) { SetPriority(priority_); ::ResumeThread(thread_); @@ -289,13 +287,13 @@ bool Thread::Start(Runnable* runnable) { LOG(LS_ERROR) << "Unable to create pthread, error " << error_code; return false; } - started_ = true; + running_.Set(); #endif return true; } void Thread::Join() { - if (started_) { + if (running()) { ASSERT(!IsCurrent()); #if defined(WIN32) WaitForSingleObject(thread_, INFINITE); @@ -306,7 +304,7 @@ void Thread::Join() { void *pv; pthread_join(thread_, &pv); #endif - started_ = false; + running_.Reset(); } } @@ -357,10 +355,6 @@ void* Thread::PreRun(void* pv) { } else { init->thread->Run(); } - if (init->thread->delete_self_when_complete_) { - init->thread->started_ = false; - delete init->thread; - } delete init; return NULL; } @@ -403,7 +397,6 @@ void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { bool ready = false; { CritScope cs(&crit_); - EnsureActive(); _SendMessage smsg; smsg.thread = current_thread; smsg.msg = msg; @@ -524,7 +517,7 @@ bool Thread::WrapCurrent() { } bool Thread::WrapCurrentWithThreadManager(ThreadManager* thread_manager) { - if (started_) + if (running()) return false; #if defined(WIN32) // We explicitly ask for no rights other than synchronization. @@ -539,7 +532,7 @@ bool Thread::WrapCurrentWithThreadManager(ThreadManager* thread_manager) { thread_ = pthread_self(); #endif owned_ = false; - started_ = true; + running_.Set(); thread_manager->SetCurrentThread(this); return true; } @@ -552,7 +545,7 @@ void Thread::UnwrapCurrent() { LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle."; } #endif - started_ = false; + running_.Reset(); } diff --git a/talk/base/thread.h b/talk/base/thread.h index abef891ac..0a7b76d19 100644 --- a/talk/base/thread.h +++ b/talk/base/thread.h @@ -37,6 +37,7 @@ #include #endif #include "talk/base/constructormagic.h" +#include "talk/base/event.h" #include "talk/base/messagequeue.h" #ifdef WIN32 @@ -143,15 +144,8 @@ class Thread : public MessageQueue { bool SetPriority(ThreadPriority priority); // Starts the execution of the thread. - bool started() const { return started_; } bool Start(Runnable* runnable = NULL); - // Used for fire-and-forget threads. Deletes this thread object when the - // Run method returns. - void Release() { - delete_self_when_complete_ = true; - } - // Tells the thread to stop and waits until it is joined. // Never call Stop on the current thread. Instead use the inherited Quit // function which will exit the base MessageQueue without terminating the @@ -217,6 +211,20 @@ class Thread : public MessageQueue { // only for testing. bool WrapCurrent(); void UnwrapCurrent(); + + // Expose private method running() for tests. + // + // DANGER: this is a terrible public API. Most callers that might want to + // call this likely do not have enough control/knowledge of the Thread in + // question to guarantee that the returned value remains true for the duration + // of whatever code is conditionally executing because of the return value! + bool RunningForTest() { return running(); } + // This is a legacy call-site that probably doesn't need to exist in the first + // place. + // TODO(fischman): delete once the ASSERT added in channelmanager.cc sticks + // for a month (ETA 2014/06/22). + bool RunningForChannelManager() { return running(); } + // Blocks the calling thread until this thread has terminated. void Join(); protected: @@ -231,10 +239,13 @@ class Thread : public MessageQueue { // being created. bool WrapCurrentWithThreadManager(ThreadManager* thread_manager); + // Return true if the thread was started and hasn't yet stopped. + bool running() { return running_.Wait(0); } + std::list<_SendMessage> sendlist_; std::string name_; ThreadPriority priority_; - bool started_; + Event running_; // Signalled means running. #ifdef POSIX pthread_t thread_; @@ -246,7 +257,6 @@ class Thread : public MessageQueue { #endif bool owned_; - bool delete_self_when_complete_; friend class ThreadManager; diff --git a/talk/base/thread_unittest.cc b/talk/base/thread_unittest.cc index cd35f1741..d7d6a0129 100644 --- a/talk/base/thread_unittest.cc +++ b/talk/base/thread_unittest.cc @@ -99,7 +99,7 @@ class SocketClient : public TestGenerator, public sigslot::has_slots<> { class MessageClient : public MessageHandler, public TestGenerator { public: MessageClient(Thread* pth, Socket* socket) - : thread_(pth), socket_(socket) { + : socket_(socket) { } virtual ~MessageClient() { @@ -114,7 +114,6 @@ class MessageClient : public MessageHandler, public TestGenerator { } private: - Thread* thread_; Socket* socket_; }; @@ -262,32 +261,14 @@ TEST(ThreadTest, Wrap) { current_thread->UnwrapCurrent(); CustomThread* cthread = new CustomThread(); EXPECT_TRUE(cthread->WrapCurrent()); - EXPECT_TRUE(cthread->started()); + EXPECT_TRUE(cthread->RunningForTest()); EXPECT_FALSE(cthread->IsOwned()); cthread->UnwrapCurrent(); - EXPECT_FALSE(cthread->started()); + EXPECT_FALSE(cthread->RunningForTest()); delete cthread; current_thread->WrapCurrent(); } -// Test that calling Release on a thread causes it to self-destruct when -// it's finished running -TEST(ThreadTest, Release) { - scoped_ptr event(new Event(true, false)); - // Ensure the event is initialized. - event->Reset(); - - Thread* thread = new SignalWhenDestroyedThread(event.get()); - thread->Start(); - thread->Release(); - - // The event should get signaled when the thread completes, which should - // be nearly instantaneous, since it doesn't do anything. For safety, - // give it 3 seconds in case the machine is under load. - bool signaled = event->Wait(3000); - EXPECT_TRUE(signaled); -} - TEST(ThreadTest, Invoke) { // Create and start the thread. Thread thread; diff --git a/talk/base/timeutils.cc b/talk/base/timeutils.cc index fee85aa64..c4e84cc20 100644 --- a/talk/base/timeutils.cc +++ b/talk/base/timeutils.cc @@ -203,4 +203,18 @@ int32 TimeDiff(uint32 later, uint32 earlier) { #endif } +TimestampWrapAroundHandler::TimestampWrapAroundHandler() + : last_ts_(0), num_wrap_(0) {} + +int64 TimestampWrapAroundHandler::Unwrap(uint32 ts) { + if (ts < last_ts_) { + if (last_ts_ > 0xf0000000 && ts < 0x0fffffff) { + ++num_wrap_; + } + } + last_ts_ = ts; + int64_t unwrapped_ts = ts + (num_wrap_ << 32); + return unwrapped_ts; +} + } // namespace talk_base diff --git a/talk/base/timeutils.h b/talk/base/timeutils.h index f13c3f2ef..6de9df671 100644 --- a/talk/base/timeutils.h +++ b/talk/base/timeutils.h @@ -97,6 +97,17 @@ inline int64 UnixTimestampNanosecsToNtpMillisecs(int64 unix_ts_ns) { return unix_ts_ns / kNumNanosecsPerMillisec + kJan1970AsNtpMillisecs; } +class TimestampWrapAroundHandler { + public: + TimestampWrapAroundHandler(); + + int64 Unwrap(uint32 ts); + + private: + uint32 last_ts_; + int64 num_wrap_; +}; + } // namespace talk_base #endif // TALK_BASE_TIMEUTILS_H_ diff --git a/talk/base/timeutils_unittest.cc b/talk/base/timeutils_unittest.cc index 0fc5eb19c..a078abee2 100644 --- a/talk/base/timeutils_unittest.cc +++ b/talk/base/timeutils_unittest.cc @@ -160,4 +160,27 @@ TEST(TimeTest, DISABLED_CurrentTmTime) { EXPECT_TRUE(0 <= microseconds && microseconds < 1000000); } +class TimestampWrapAroundHandlerTest : public testing::Test { + public: + TimestampWrapAroundHandlerTest() {} + + protected: + TimestampWrapAroundHandler wraparound_handler_; +}; + +TEST_F(TimestampWrapAroundHandlerTest, Unwrap) { + uint32 ts = 0xfffffff2; + int64 unwrapped_ts = ts; + EXPECT_EQ(ts, wraparound_handler_.Unwrap(ts)); + ts = 2; + unwrapped_ts += 0x10; + EXPECT_EQ(unwrapped_ts, wraparound_handler_.Unwrap(ts)); + ts = 0xfffffff2; + unwrapped_ts += 0xfffffff0; + EXPECT_EQ(unwrapped_ts, wraparound_handler_.Unwrap(ts)); + ts = 0; + unwrapped_ts += 0xe; + EXPECT_EQ(unwrapped_ts, wraparound_handler_.Unwrap(ts)); +} + } // namespace talk_base diff --git a/talk/base/unittest_main.cc b/talk/base/unittest_main.cc index bca3671b0..def763c30 100644 --- a/talk/base/unittest_main.cc +++ b/talk/base/unittest_main.cc @@ -12,7 +12,6 @@ #include "talk/base/fileutils.h" #include "talk/base/gunit.h" #include "talk/base/logging.h" -#include "talk/base/pathutils.h" DEFINE_bool(help, false, "prints this message"); DEFINE_string(log, "", "logging options to use"); @@ -47,28 +46,6 @@ int TestCrtReportHandler(int report_type, char* msg, int* retval) { } #endif // WIN32 -talk_base::Pathname GetTalkDirectory() { - // Locate talk directory. - talk_base::Pathname path = talk_base::Filesystem::GetCurrentDirectory(); - std::string talk_folder_name("talk"); - talk_folder_name += path.folder_delimiter(); - while (path.folder_name() != talk_folder_name && !path.empty()) { - path.SetFolder(path.parent_folder()); - } - - // If not running inside "talk" folder, then assume running in its parent - // folder. - if (path.empty()) { - path = talk_base::Filesystem::GetCurrentDirectory(); - path.AppendFolder("talk"); - // Make sure the folder exist. - if (!talk_base::Filesystem::IsFolder(path)) { - path.clear(); - } - } - return path; -} - int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); FlagList::SetFlagsFromCommandLine(&argc, argv, false); diff --git a/talk/base/unixfilesystem.cc b/talk/base/unixfilesystem.cc index 1a5b75ead..8ac749945 100644 --- a/talk/base/unixfilesystem.cc +++ b/talk/base/unixfilesystem.cc @@ -66,6 +66,15 @@ #include "talk/base/stream.h" #include "talk/base/stringutils.h" +#if defined(IOS) +// Defined in iosfilesystem.mm. No header file to discourage use +// elsewhere; other places should use GetApp{Data,Temp}Folder() in +// this file. Don't copy/paste. I mean it. +char* IOSDataDirectory(); +char* IOSTempDirectory(); +void IOSAppName(talk_base::Pathname* path); +#endif + namespace talk_base { #if !defined(ANDROID) && !defined(IOS) @@ -85,6 +94,17 @@ void UnixFilesystem::SetAppTempFolder(const std::string& folder) { } #endif +UnixFilesystem::UnixFilesystem() { +#if defined(IOS) + if (!provided_app_data_folder_) + provided_app_data_folder_ = IOSDataDirectory(); + if (!provided_app_temp_folder_) + provided_app_temp_folder_ = IOSTempDirectory(); +#endif +} + +UnixFilesystem::~UnixFilesystem() {} + bool UnixFilesystem::CreateFolder(const Pathname &path, mode_t mode) { std::string pathname(path.pathname()); int len = pathname.length(); @@ -369,10 +389,13 @@ bool UnixFilesystem::GetAppPathname(Pathname* path) { return success; #elif defined(__native_client__) return false; +#elif IOS + IOSAppName(path); + return true; #else // OSX - char buffer[NAME_MAX+1]; - size_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1); - if (len <= 0) + char buffer[PATH_MAX + 2]; + ssize_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1); + if ((len <= 0) || (len == PATH_MAX + 1)) return false; buffer[len] = '\0'; path->SetPathname(buffer); @@ -521,7 +544,7 @@ bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { #endif // ANDROID #if defined(LINUX) || defined(ANDROID) *freebytes = static_cast(vfs.f_bsize) * vfs.f_bavail; -#elif defined(OSX) +#elif defined(OSX) || defined(IOS) *freebytes = static_cast(vfs.f_frsize) * vfs.f_bavail; #endif diff --git a/talk/base/unixfilesystem.h b/talk/base/unixfilesystem.h index aa9c920e6..d709115fe 100644 --- a/talk/base/unixfilesystem.h +++ b/talk/base/unixfilesystem.h @@ -36,13 +36,17 @@ namespace talk_base { class UnixFilesystem : public FilesystemInterface { public: + UnixFilesystem(); + virtual ~UnixFilesystem(); #if defined(ANDROID) || defined(IOS) -// Android does not have a native code API to fetch the app data or temp -// folders. That needs to be passed into this class from Java. Similarly, iOS -// only supports an Objective-C API for fetching the folder locations, so that -// needs to be passed in here from Objective-C. - + // Android does not have a native code API to fetch the app data or temp + // folders. That needs to be passed into this class from Java. Similarly, iOS + // only supports an Objective-C API for fetching the folder locations, so that + // needs to be passed in here from Objective-C. Or at least that used to be + // the case; now the ctor will do the work if necessary and possible. + // TODO(fischman): add an Android version that uses JNI and drop the + // SetApp*Folder() APIs once external users stop using them. static void SetAppDataFolder(const std::string& folder); static void SetAppTempFolder(const std::string& folder); #endif diff --git a/talk/build/OWNERS b/talk/build/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/talk/build/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/talk/build/common.gypi b/talk/build/common.gypi index 1011871ce..00dc10146 100644 --- a/talk/build/common.gypi +++ b/talk/build/common.gypi @@ -93,6 +93,9 @@ 'conditions': [ ['clang==1', { 'cflags': [ + '-Wall', + '-Wextra', + '-Wunused-variable', # TODO(ronghuawu): Fix the warning caused by # LateBindingSymbolTable::TableInfo from # latebindingsymboltable.cc.def and remove below flag. diff --git a/talk/build/ios_test.plist b/talk/build/ios_test.plist new file mode 100644 index 000000000..c2fb0617f --- /dev/null +++ b/talk/build/ios_test.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.Google.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + + diff --git a/talk/build/ios_tests.gypi b/talk/build/ios_tests.gypi new file mode 100644 index 000000000..baf1f100a --- /dev/null +++ b/talk/build/ios_tests.gypi @@ -0,0 +1,55 @@ +# +# libjingle +# Copyright 2014, Google Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# Include this .gypi in an ObjC target's definition to allow it to be +# used as an iOS or OS/X application. + +{ + 'conditions': [ + ['OS=="ios"', { + 'variables': { + 'infoplist_file': './ios_test.plist', + }, + 'mac_bundle': 1, + 'mac_bundle_resources': [ + '<(infoplist_file)', + ], + # The plist is listed above so that it appears in XCode's file list, + # but we don't actually want to bundle it. + 'mac_bundle_resources!': [ + '<(infoplist_file)', + ], + 'xcode_settings': { + 'CLANG_ENABLE_OBJC_ARC': 'YES', + # common.gypi enables this for mac but we want this to be disabled + # like it is for ios. + 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', + 'INFOPLIST_FILE': '<(infoplist_file)', + }, + }], + ], # conditions +} diff --git a/talk/build/isolate.gypi b/talk/build/isolate.gypi index d1ff291ef..24f7ea1ed 100644 --- a/talk/build/isolate.gypi +++ b/talk/build/isolate.gypi @@ -113,6 +113,7 @@ # Path variables are used to replace file paths when loading a .isolate # file + '--path-variable', 'DEPTH', '<(DEPTH)', '--path-variable', 'PRODUCT_DIR', '<(PRODUCT_DIR) ', '--config-variable', 'OS=<(OS)', diff --git a/talk/examples/android/AndroidManifest.xml b/talk/examples/android/AndroidManifest.xml index 1fb60ad5d..f898641f8 100644 --- a/talk/examples/android/AndroidManifest.xml +++ b/talk/examples/android/AndroidManifest.xml @@ -21,7 +21,8 @@ android:allowBackup="false"> diff --git a/talk/examples/android/README b/talk/examples/android/README index 4e3db86e1..faf4e924d 100644 --- a/talk/examples/android/README +++ b/talk/examples/android/README @@ -1,19 +1,9 @@ This directory contains an example Android client for http://apprtc.appspot.com Prerequisites: -- Make sure gclient is checking out tools necessary to target Android: your - .gclient file should contain a line like: - target_os = ['android', 'unix'] - Make sure to re-run gclient sync after adding this to download the tools. -- Env vars need to be set up to target Android; easiest way to do this is to run - (from the libjingle trunk directory): - . ./build/android/envsetup.sh - Note that this clobbers any previously-set $GYP_DEFINES so it must be done - before the next item. +- "Android Specific Steps" on http://www.webrtc.org/reference/getting-started - Set up webrtc-related GYP variables: export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 libjingle_java=1 $GYP_DEFINES" - export JAVA_HOME= - export PATH=$JAVA_HOME/bin:$PATH To cause WEBRTC_LOGGING to emit to Android's logcat, add enable_tracing=1 to the $GYP_DEFINES above. - When targeting both desktop & android, make sure to use a different output_dir diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java index f84430ce9..f1f5d1254 100644 --- a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java @@ -45,8 +45,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Scanner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Negotiates signaling for chatting with apprtc.appspot.com "rooms". @@ -215,6 +213,8 @@ protected AppRTCSignalingParameters doInBackground(String... urls) { } try { return getParametersForRoomUrl(urls[0]); + } catch (JSONException e) { + throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } @@ -231,33 +231,26 @@ protected void onPostExecute(AppRTCSignalingParameters params) { iceServersObserver.onIceServers(appRTCSignalingParameters.iceServers); } - // Fetches |url| and fishes the signaling parameters out of the HTML via - // regular expressions. - // - // TODO(fischman): replace this hackery with a dedicated JSON-serving URL in - // apprtc so that this isn't necessary (here and in other future apps that - // want to interop with apprtc). + // Fetches |url| and fishes the signaling parameters out of the JSON. private AppRTCSignalingParameters getParametersForRoomUrl(String url) - throws IOException { - final Pattern fullRoomPattern = Pattern.compile( - ".*\n *Sorry, this room is full\\..*"); - - String roomHtml = - drainStream((new URL(url)).openConnection().getInputStream()); - - Matcher fullRoomMatcher = fullRoomPattern.matcher(roomHtml); - if (fullRoomMatcher.find()) { - throw new IOException("Room is full!"); + throws IOException, JSONException { + url = url + "&t=json"; + JSONObject roomJson = new JSONObject( + drainStream((new URL(url)).openConnection().getInputStream())); + + if (roomJson.has("error")) { + JSONArray errors = roomJson.getJSONArray("error_messages"); + throw new IOException(errors.toString()); } String gaeBaseHref = url.substring(0, url.indexOf('?')); - String token = getVarValue(roomHtml, "channelToken", true); + String token = roomJson.getString("token"); String postMessageUrl = "/message?r=" + - getVarValue(roomHtml, "roomKey", true) + "&u=" + - getVarValue(roomHtml, "me", true); - boolean initiator = getVarValue(roomHtml, "initiator", false).equals("1"); + roomJson.getString("room_key") + "&u=" + + roomJson.getString("me"); + boolean initiator = roomJson.getInt("initiator") == 1; LinkedList iceServers = - iceServersFromPCConfigJSON(getVarValue(roomHtml, "pcConfig", false)); + iceServersFromPCConfigJSON(roomJson.getString("pc_config")); boolean isTurnPresent = false; for (PeerConnection.IceServer server : iceServers) { @@ -267,21 +260,20 @@ private AppRTCSignalingParameters getParametersForRoomUrl(String url) } } if (!isTurnPresent) { - iceServers.add( - requestTurnServer(getVarValue(roomHtml, "turnUrl", true))); + iceServers.add(requestTurnServer(roomJson.getString("turn_url"))); } MediaConstraints pcConstraints = constraintsFromJSON( - getVarValue(roomHtml, "pcConstraints", false)); + roomJson.getString("pc_constraints")); addDTLSConstraintIfMissing(pcConstraints); Log.d(TAG, "pcConstraints: " + pcConstraints); MediaConstraints videoConstraints = constraintsFromJSON( getAVConstraints("video", - getVarValue(roomHtml, "mediaConstraints", false))); + roomJson.getString("media_constraints"))); Log.d(TAG, "videoConstraints: " + videoConstraints); MediaConstraints audioConstraints = constraintsFromJSON( getAVConstraints("audio", - getVarValue(roomHtml, "mediaConstraints", false))); + roomJson.getString("media_constraints"))); Log.d(TAG, "audioConstraints: " + audioConstraints); return new AppRTCSignalingParameters( @@ -379,27 +371,6 @@ private MediaConstraints constraintsFromJSON(String jsonString) { } } - // Scan |roomHtml| for declaration & assignment of |varName| and return its - // value, optionally stripping outside quotes if |stripQuotes| requests it. - private String getVarValue( - String roomHtml, String varName, boolean stripQuotes) - throws IOException { - final Pattern pattern = Pattern.compile( - ".*\n *var " + varName + " = ([^\n]*);\n.*"); - Matcher matcher = pattern.matcher(roomHtml); - if (!matcher.find()) { - throw new IOException("Missing " + varName + " in HTML: " + roomHtml); - } - String varValue = matcher.group(1); - if (matcher.find()) { - throw new IOException("Too many " + varName + " in HTML: " + roomHtml); - } - if (stripQuotes) { - varValue = varValue.substring(1, varValue.length() - 1); - } - return varValue; - } - // Requests & returns a TURN ICE Server based on a request URL. Must be run // off the main thread! private PeerConnection.IceServer requestTurnServer(String url) { diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java index 3cf05d80b..ef97cda61 100644 --- a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java @@ -31,20 +31,25 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.Point; import android.media.AudioManager; import android.os.Bundle; import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.webkit.JavascriptInterface; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import org.json.JSONException; import org.json.JSONObject; import org.webrtc.DataChannel; import org.webrtc.IceCandidate; -import org.webrtc.Logging; import org.webrtc.MediaConstraints; import org.webrtc.MediaStream; import org.webrtc.PeerConnection; @@ -55,11 +60,10 @@ import org.webrtc.StatsReport; import org.webrtc.VideoCapturer; import org.webrtc.VideoRenderer; -import org.webrtc.VideoRenderer.I420Frame; +import org.webrtc.VideoRendererGui; import org.webrtc.VideoSource; import org.webrtc.VideoTrack; -import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; @@ -73,6 +77,7 @@ public class AppRTCDemoActivity extends Activity implements AppRTCClient.IceServersObserver { private static final String TAG = "AppRTCDemoActivity"; + private static boolean factoryStaticInitialized; private PeerConnectionFactory factory; private VideoSource videoSource; private boolean videoSourceStopped; @@ -81,8 +86,13 @@ public class AppRTCDemoActivity extends Activity private final SDPObserver sdpObserver = new SDPObserver(); private final GAEChannelClient.MessageHandler gaeHandler = new GAEHandler(); private AppRTCClient appRtcClient = new AppRTCClient(this, gaeHandler, this); - private VideoStreamsView vsv; + private AppRTCGLView vsv; + private VideoRenderer.Callbacks localRender; + private VideoRenderer.Callbacks remoteRender; private Toast logToast; + private final LayoutParams hudLayout = + new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + private TextView hudView; private LinkedList queuedRemoteCandidates = new LinkedList(); // Synchronize on quit[0] to avoid teardown-related crashes. @@ -100,12 +110,35 @@ public void onCreate(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Point displaySize = new Point(); - getWindowManager().getDefaultDisplay().getSize(displaySize); - vsv = new VideoStreamsView(this, displaySize); - setContentView(vsv); + getWindowManager().getDefaultDisplay().getRealSize(displaySize); - abortUnless(PeerConnectionFactory.initializeAndroidGlobals(this), + vsv = new AppRTCGLView(this, displaySize); + VideoRendererGui.setView(vsv); + remoteRender = VideoRendererGui.create(0, 0, 100, 100); + localRender = VideoRendererGui.create(70, 5, 25, 25); + + vsv.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + toggleHUD(); + } + }); + setContentView(vsv); + logAndToast("Tap the screen to toggle stats visibility"); + + hudView = new TextView(this); + hudView.setTextColor(Color.BLACK); + hudView.setBackgroundColor(Color.WHITE); + hudView.setAlpha(0.4f); + hudView.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5); + hudView.setVisibility(View.INVISIBLE); + addContentView(hudView, hudLayout); + + if (!factoryStaticInitialized) { + abortUnless(PeerConnectionFactory.initializeAndroidGlobals( + this, true, true), "Failed to initializeAndroidGlobals"); + factoryStaticInitialized = true; + } AudioManager audioManager = ((AudioManager) getSystemService(AUDIO_SERVICE)); @@ -154,6 +187,32 @@ private void connectToRoom(String roomUrl) { appRtcClient.connectToRoom(roomUrl); } + // Toggle visibility of the heads-up display. + private void toggleHUD() { + if (hudView.getVisibility() == View.VISIBLE) { + hudView.setVisibility(View.INVISIBLE); + } else { + hudView.setVisibility(View.VISIBLE); + } + } + + // Update the heads-up display with information from |reports|. + private void updateHUD(StatsReport[] reports) { + StringBuilder builder = new StringBuilder(); + for (StatsReport report : reports) { + if (!report.id.equals("bweforvideo")) { + continue; + } + for (StatsReport.Value value : report.values) { + String name = value.name.replace("goog", "").replace("Available", "") + .replace("Bandwidth", "").replace("Bitrate", "").replace("Enc", ""); + builder.append(name).append("=").append(value.value).append(" "); + } + builder.append("\n"); + } + hudView.setText(builder.toString() + hudView.getText()); + } + @Override public void onPause() { super.onPause(); @@ -173,6 +232,13 @@ public void onResume() { } } + @Override + public void onConfigurationChanged (Configuration newConfig) { + Point displaySize = new Point(); + getWindowManager().getDefaultDisplay().getSize(displaySize); + vsv.updateDisplaySize(displaySize); + super.onConfigurationChanged(newConfig); + } // Just for fun (and to regression-test bug 2302) make sure that DataChannels // can be created, queried, and disposed. @@ -211,12 +277,21 @@ public void run() { return; } final Runnable runnableThis = this; + if (hudView.getVisibility() == View.INVISIBLE) { + vsv.postDelayed(runnableThis, 1000); + return; + } boolean success = finalPC.getStats(new StatsObserver() { - public void onComplete(StatsReport[] reports) { + public void onComplete(final StatsReport[] reports) { + runOnUiThread(new Runnable() { + public void run() { + updateHUD(reports); + } + }); for (StatsReport report : reports) { Log.d(TAG, "Stats: " + report.toString()); } - vsv.postDelayed(runnableThis, 10000); + vsv.postDelayed(runnableThis, 1000); } }, null); if (!success) { @@ -225,7 +300,7 @@ public void onComplete(StatsReport[] reports) { } } }; - vsv.postDelayed(repeatedStatsLogger, 10000); + vsv.postDelayed(repeatedStatsLogger, 1000); } { @@ -237,8 +312,7 @@ public void onComplete(StatsReport[] reports) { capturer, appRtcClient.videoConstraints()); VideoTrack videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource); - videoTrack.addRenderer(new VideoRenderer(new VideoCallbacks( - vsv, VideoStreamsView.Endpoint.LOCAL))); + videoTrack.addRenderer(new VideoRenderer(localRender)); lMS.addTrack(videoTrack); } if (appRtcClient.audioConstraints() != null) { @@ -311,7 +385,7 @@ private static void jsonPut(JSONObject json, String key, Object value) { } // Mangle SDP to prefer ISAC/16000 over any other audio codec. - private String preferISAC(String sdpDescription) { + private static String preferISAC(String sdpDescription) { String[] lines = sdpDescription.split("\r\n"); int mLineIndex = -1; String isac16kRtpMap = null; @@ -401,8 +475,8 @@ public void run() { stream.videoTracks.size() <= 1, "Weird-looking stream: " + stream); if (stream.videoTracks.size() == 1) { - stream.videoTracks.get(0).addRenderer(new VideoRenderer( - new VideoCallbacks(vsv, VideoStreamsView.Endpoint.REMOTE))); + stream.videoTracks.get(0).addRenderer( + new VideoRenderer(remoteRender)); } } }); @@ -435,24 +509,30 @@ public void run() { // Implementation detail: handle offer creation/signaling and answer setting, // as well as adding remote ICE candidates once the answer SDP is set. private class SDPObserver implements SdpObserver { + private SessionDescription localSdp; + @Override public void onCreateSuccess(final SessionDescription origSdp) { + abortUnless(localSdp == null, "multiple SDP create?!?"); + final SessionDescription sdp = new SessionDescription( + origSdp.type, preferISAC(origSdp.description)); + localSdp = sdp; runOnUiThread(new Runnable() { public void run() { - SessionDescription sdp = new SessionDescription( - origSdp.type, preferISAC(origSdp.description)); pc.setLocalDescription(sdpObserver, sdp); } }); } // Helper for sending local SDP (offer or answer, depending on role) to the - // other participant. - private void sendLocalDescription(PeerConnection pc) { - SessionDescription sdp = pc.getLocalDescription(); - logAndToast("Sending " + sdp.type); + // other participant. Note that it is important to send the output of + // create{Offer,Answer} and not merely the current value of + // getLocalDescription() because the latter may include ICE candidates that + // we might want to filter elsewhere. + private void sendLocalDescription() { + logAndToast("Sending " + localSdp.type); JSONObject json = new JSONObject(); - jsonPut(json, "type", sdp.type.canonicalForm()); - jsonPut(json, "sdp", sdp.description); + jsonPut(json, "type", localSdp.type.canonicalForm()); + jsonPut(json, "sdp", localSdp.description); sendMessage(json); } @@ -466,7 +546,7 @@ public void run() { drainRemoteCandidates(); } else { // We've just set our local description so time to send it. - sendLocalDescription(pc); + sendLocalDescription(); } } else { if (pc.getLocalDescription() == null) { @@ -476,7 +556,7 @@ public void run() { } else { // Answer now set as local description; send it and drain // candidates. - sendLocalDescription(pc); + sendLocalDescription(); drainRemoteCandidates(); } } @@ -586,30 +666,4 @@ private void disconnectAndExit() { } } - // Implementation detail: bridge the VideoRenderer.Callbacks interface to the - // VideoStreamsView implementation. - private class VideoCallbacks implements VideoRenderer.Callbacks { - private final VideoStreamsView view; - private final VideoStreamsView.Endpoint stream; - - public VideoCallbacks( - VideoStreamsView view, VideoStreamsView.Endpoint stream) { - this.view = view; - this.stream = stream; - } - - @Override - public void setSize(final int width, final int height) { - view.queueEvent(new Runnable() { - public void run() { - view.setSize(stream, width, height); - } - }); - } - - @Override - public void renderFrame(I420Frame frame) { - view.queueFrame(stream, frame); - } - } } diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCGLView.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCGLView.java new file mode 100644 index 000000000..e622ab904 --- /dev/null +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCGLView.java @@ -0,0 +1,58 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.appspot.apprtc; + +import android.content.Context; +import android.graphics.Point; +import android.opengl.GLSurfaceView; + +public class AppRTCGLView extends GLSurfaceView { + private Point screenDimensions; + + public AppRTCGLView(Context c, Point screenDimensions) { + super(c); + this.screenDimensions = screenDimensions; + } + + public void updateDisplaySize(Point screenDimensions) { + this.screenDimensions = screenDimensions; + } + + @Override + protected void onMeasure(int unusedX, int unusedY) { + // Go big or go home! + setMeasuredDimension(screenDimensions.x, screenDimensions.y); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setSystemUiVisibility(SYSTEM_UI_FLAG_HIDE_NAVIGATION | + SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } +} diff --git a/talk/examples/android/src/org/appspot/apprtc/FramePool.java b/talk/examples/android/src/org/appspot/apprtc/FramePool.java deleted file mode 100644 index 6f1128650..000000000 --- a/talk/examples/android/src/org/appspot/apprtc/FramePool.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * libjingle - * Copyright 2013, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.appspot.apprtc; - -import org.webrtc.VideoRenderer.I420Frame; - -import java.util.HashMap; -import java.util.LinkedList; - -/** - * This class acts as an allocation pool meant to minimize GC churn caused by - * frame allocation & disposal. The public API comprises of just two methods: - * copyFrame(), which allocates as necessary and copies, and - * returnFrame(), which returns frame ownership to the pool for use by a later - * call to copyFrame(). - * - * This class is thread-safe; calls to copyFrame() and returnFrame() are allowed - * to happen on any thread. - */ -class FramePool { - // Maps each summary code (see summarizeFrameDimensions()) to a list of frames - // of that description. - private final HashMap> availableFrames = - new HashMap>(); - // Every dimension (e.g. width, height, stride) of a frame must be less than - // this value. - private static final long MAX_DIMENSION = 4096; - - public I420Frame takeFrame(I420Frame source) { - long desc = summarizeFrameDimensions(source); - I420Frame dst = null; - synchronized (availableFrames) { - LinkedList frames = availableFrames.get(desc); - if (frames == null) { - frames = new LinkedList(); - availableFrames.put(desc, frames); - } - if (!frames.isEmpty()) { - dst = frames.pop(); - } else { - dst = new I420Frame( - source.width, source.height, source.yuvStrides, null); - } - } - return dst; - } - - public void returnFrame(I420Frame frame) { - long desc = summarizeFrameDimensions(frame); - synchronized (availableFrames) { - LinkedList frames = availableFrames.get(desc); - if (frames == null) { - throw new IllegalArgumentException("Unexpected frame dimensions"); - } - frames.add(frame); - } - } - - /** Validate that |frame| can be managed by the pool. */ - public static boolean validateDimensions(I420Frame frame) { - return frame.width < MAX_DIMENSION && frame.height < MAX_DIMENSION && - frame.yuvStrides[0] < MAX_DIMENSION && - frame.yuvStrides[1] < MAX_DIMENSION && - frame.yuvStrides[2] < MAX_DIMENSION; - } - - // Return a code summarizing the dimensions of |frame|. Two frames that - // return the same summary are guaranteed to be able to store each others' - // contents. Used like Object.hashCode(), but we need all the bits of a long - // to do a good job, and hashCode() returns int, so we do this. - private static long summarizeFrameDimensions(I420Frame frame) { - long ret = frame.width; - ret = ret * MAX_DIMENSION + frame.height; - ret = ret * MAX_DIMENSION + frame.yuvStrides[0]; - ret = ret * MAX_DIMENSION + frame.yuvStrides[1]; - ret = ret * MAX_DIMENSION + frame.yuvStrides[2]; - return ret; - } -} diff --git a/talk/examples/android/src/org/appspot/apprtc/VideoStreamsView.java b/talk/examples/android/src/org/appspot/apprtc/VideoStreamsView.java deleted file mode 100644 index 906aa92ce..000000000 --- a/talk/examples/android/src/org/appspot/apprtc/VideoStreamsView.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * libjingle - * Copyright 2013, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.appspot.apprtc; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.opengl.GLES20; -import android.opengl.GLSurfaceView; -import android.util.Log; - -import org.webrtc.VideoRenderer.I420Frame; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.util.EnumMap; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - -/** - * A GLSurfaceView{,.Renderer} that efficiently renders YUV frames from local & - * remote VideoTracks using the GPU for CSC. Clients will want to call the - * constructor, setSize() and updateFrame() as appropriate, but none of the - * other public methods of this class are of interest to clients (only to system - * classes). - */ -public class VideoStreamsView - extends GLSurfaceView - implements GLSurfaceView.Renderer { - - /** Identify which of the two video streams is being addressed. */ - public static enum Endpoint { LOCAL, REMOTE }; - - private final static String TAG = "VideoStreamsView"; - private EnumMap rects = - new EnumMap(Endpoint.class); - private Point screenDimensions; - // [0] are local Y,U,V, [1] are remote Y,U,V. - private int[][] yuvTextures = { { -1, -1, -1}, {-1, -1, -1 }}; - private int posLocation = -1; - private long lastFPSLogTime = System.nanoTime(); - private long numFramesSinceLastLog = 0; - private FramePool framePool = new FramePool(); - // Accessed on multiple threads! Must be synchronized. - private EnumMap framesToRender = - new EnumMap(Endpoint.class); - - public VideoStreamsView(Context c, Point screenDimensions) { - super(c); - this.screenDimensions = screenDimensions; - setPreserveEGLContextOnPause(true); - setEGLContextClientVersion(2); - setRenderer(this); - setRenderMode(RENDERMODE_WHEN_DIRTY); - } - - /** Queue |frame| to be uploaded. */ - public void queueFrame(final Endpoint stream, I420Frame frame) { - // Paying for the copy of the YUV data here allows CSC and painting time - // to get spent on the render thread instead of the UI thread. - abortUnless(framePool.validateDimensions(frame), "Frame too large!"); - final I420Frame frameCopy = framePool.takeFrame(frame).copyFrom(frame); - boolean needToScheduleRender; - synchronized (framesToRender) { - // A new render needs to be scheduled (via updateFrames()) iff there isn't - // already a render scheduled, which is true iff framesToRender is empty. - needToScheduleRender = framesToRender.isEmpty(); - I420Frame frameToDrop = framesToRender.put(stream, frameCopy); - if (frameToDrop != null) { - framePool.returnFrame(frameToDrop); - } - } - if (needToScheduleRender) { - queueEvent(new Runnable() { - public void run() { - updateFrames(); - } - }); - } - } - - // Upload the planes from |framesToRender| to the textures owned by this View. - private void updateFrames() { - I420Frame localFrame = null; - I420Frame remoteFrame = null; - synchronized (framesToRender) { - localFrame = framesToRender.remove(Endpoint.LOCAL); - remoteFrame = framesToRender.remove(Endpoint.REMOTE); - } - if (localFrame != null) { - texImage2D(localFrame, yuvTextures[0]); - framePool.returnFrame(localFrame); - } - if (remoteFrame != null) { - texImage2D(remoteFrame, yuvTextures[1]); - framePool.returnFrame(remoteFrame); - } - abortUnless(localFrame != null || remoteFrame != null, - "Nothing to render!"); - requestRender(); - } - - /** Inform this View of the dimensions of frames coming from |stream|. */ - public void setSize(Endpoint stream, int width, int height) { - // Generate 3 texture ids for Y/U/V and place them into |textures|, - // allocating enough storage for |width|x|height| pixels. - int[] textures = yuvTextures[stream == Endpoint.LOCAL ? 0 : 1]; - GLES20.glGenTextures(3, textures, 0); - for (int i = 0; i < 3; ++i) { - int w = i == 0 ? width : width / 2; - int h = i == 0 ? height : height / 2; - GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]); - GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, w, h, 0, - GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, null); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); - } - checkNoGLES2Error(); - } - - @Override - protected void onMeasure(int unusedX, int unusedY) { - // Go big or go home! - setMeasuredDimension(screenDimensions.x, screenDimensions.y); - } - - @Override - public void onSurfaceChanged(GL10 unused, int width, int height) { - GLES20.glViewport(0, 0, width, height); - checkNoGLES2Error(); - } - - @Override - public void onDrawFrame(GL10 unused) { - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - drawRectangle(yuvTextures[1], remoteVertices); - drawRectangle(yuvTextures[0], localVertices); - ++numFramesSinceLastLog; - long now = System.nanoTime(); - if (lastFPSLogTime == -1 || now - lastFPSLogTime > 1e9) { - double fps = numFramesSinceLastLog / ((now - lastFPSLogTime) / 1e9); - Log.d(TAG, "Rendered FPS: " + fps); - lastFPSLogTime = now; - numFramesSinceLastLog = 1; - } - checkNoGLES2Error(); - } - - @Override - public void onSurfaceCreated(GL10 unused, EGLConfig config) { - int program = GLES20.glCreateProgram(); - addShaderTo(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_STRING, program); - addShaderTo(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_STRING, program); - - GLES20.glLinkProgram(program); - int[] result = new int[] { GLES20.GL_FALSE }; - result[0] = GLES20.GL_FALSE; - GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, result, 0); - abortUnless(result[0] == GLES20.GL_TRUE, - GLES20.glGetProgramInfoLog(program)); - GLES20.glUseProgram(program); - - GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "y_tex"), 0); - GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "u_tex"), 1); - GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "v_tex"), 2); - - // Actually set in drawRectangle(), but queried only once here. - posLocation = GLES20.glGetAttribLocation(program, "in_pos"); - - int tcLocation = GLES20.glGetAttribLocation(program, "in_tc"); - GLES20.glEnableVertexAttribArray(tcLocation); - GLES20.glVertexAttribPointer( - tcLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords); - - GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - checkNoGLES2Error(); - } - - // Wrap a float[] in a direct FloatBuffer using native byte order. - private static FloatBuffer directNativeFloatBuffer(float[] array) { - FloatBuffer buffer = ByteBuffer.allocateDirect(array.length * 4).order( - ByteOrder.nativeOrder()).asFloatBuffer(); - buffer.put(array); - buffer.flip(); - return buffer; - } - - // Upload the YUV planes from |frame| to |textures|. - private void texImage2D(I420Frame frame, int[] textures) { - for (int i = 0; i < 3; ++i) { - ByteBuffer plane = frame.yuvPlanes[i]; - GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]); - int w = i == 0 ? frame.width : frame.width / 2; - int h = i == 0 ? frame.height : frame.height / 2; - abortUnless(w == frame.yuvStrides[i], frame.yuvStrides[i] + "!=" + w); - GLES20.glTexImage2D( - GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, w, h, 0, - GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, plane); - } - checkNoGLES2Error(); - } - - // Draw |textures| using |vertices| (X,Y coordinates). - private void drawRectangle(int[] textures, FloatBuffer vertices) { - for (int i = 0; i < 3; ++i) { - GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]); - } - - GLES20.glVertexAttribPointer( - posLocation, 2, GLES20.GL_FLOAT, false, 0, vertices); - GLES20.glEnableVertexAttribArray(posLocation); - - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - checkNoGLES2Error(); - } - - // Compile & attach a |type| shader specified by |source| to |program|. - private static void addShaderTo( - int type, String source, int program) { - int[] result = new int[] { GLES20.GL_FALSE }; - int shader = GLES20.glCreateShader(type); - GLES20.glShaderSource(shader, source); - GLES20.glCompileShader(shader); - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); - abortUnless(result[0] == GLES20.GL_TRUE, - GLES20.glGetShaderInfoLog(shader) + ", source: " + source); - GLES20.glAttachShader(program, shader); - GLES20.glDeleteShader(shader); - checkNoGLES2Error(); - } - - // Poor-man's assert(): die with |msg| unless |condition| is true. - private static void abortUnless(boolean condition, String msg) { - if (!condition) { - throw new RuntimeException(msg); - } - } - - // Assert that no OpenGL ES 2.0 error has been raised. - private static void checkNoGLES2Error() { - int error = GLES20.glGetError(); - abortUnless(error == GLES20.GL_NO_ERROR, "GLES20 error: " + error); - } - - // Remote image should span the full screen. - private static final FloatBuffer remoteVertices = directNativeFloatBuffer( - new float[] { -1, 1, -1, -1, 1, 1, 1, -1 }); - - // Local image should be thumbnailish. - private static final FloatBuffer localVertices = directNativeFloatBuffer( - new float[] { 0.6f, 0.9f, 0.6f, 0.6f, 0.9f, 0.9f, 0.9f, 0.6f }); - - // Texture Coordinates mapping the entire texture. - private static final FloatBuffer textureCoords = directNativeFloatBuffer( - new float[] { 0, 0, 0, 1, 1, 0, 1, 1 }); - - // Pass-through vertex shader. - private static final String VERTEX_SHADER_STRING = - "varying vec2 interp_tc;\n" + - "\n" + - "attribute vec4 in_pos;\n" + - "attribute vec2 in_tc;\n" + - "\n" + - "void main() {\n" + - " gl_Position = in_pos;\n" + - " interp_tc = in_tc;\n" + - "}\n"; - - // YUV to RGB pixel shader. Loads a pixel from each plane and pass through the - // matrix. - private static final String FRAGMENT_SHADER_STRING = - "precision mediump float;\n" + - "varying vec2 interp_tc;\n" + - "\n" + - "uniform sampler2D y_tex;\n" + - "uniform sampler2D u_tex;\n" + - "uniform sampler2D v_tex;\n" + - "\n" + - "void main() {\n" + - " float y = texture2D(y_tex, interp_tc).r;\n" + - " float u = texture2D(u_tex, interp_tc).r - .5;\n" + - " float v = texture2D(v_tex, interp_tc).r - .5;\n" + - // CSC according to http://www.fourcc.org/fccyvrgb.php - " gl_FragColor = vec4(y + 1.403 * v, " + - " y - 0.344 * u - 0.714 * v, " + - " y + 1.77 * u, 1);\n" + - "}\n"; -} diff --git a/talk/examples/call/console.cc b/talk/examples/call/console.cc index 9ad8bc754..647601e81 100644 --- a/talk/examples/call/console.cc +++ b/talk/examples/call/console.cc @@ -48,28 +48,29 @@ static void DoNothing(int unused) {} Console::Console(talk_base::Thread *thread, CallClient *client) : client_(client), client_thread_(thread), - console_thread_(new talk_base::Thread()) {} + stopped_(false) {} Console::~Console() { Stop(); } void Console::Start() { - if (!console_thread_) { + if (stopped_) { // stdin was closed in Stop(), so we can't restart. LOG(LS_ERROR) << "Cannot re-start"; return; } - if (console_thread_->started()) { + if (console_thread_) { LOG(LS_WARNING) << "Already started"; return; } + console_thread_.reset(new talk_base::Thread()); console_thread_->Start(); console_thread_->Post(this, MSG_START); } void Console::Stop() { - if (console_thread_ && console_thread_->started()) { + if (console_thread_) { #ifdef WIN32 CloseHandle(GetStdHandle(STD_INPUT_HANDLE)); #else @@ -80,6 +81,7 @@ void Console::Stop() { #endif console_thread_->Stop(); console_thread_.reset(); + stopped_ = true; } } diff --git a/talk/examples/call/console.h b/talk/examples/call/console.h index 589603896..f0f36e346 100644 --- a/talk/examples/call/console.h +++ b/talk/examples/call/console.h @@ -64,6 +64,7 @@ class Console : public talk_base::MessageHandler { CallClient *client_; talk_base::Thread *client_thread_; talk_base::scoped_ptr console_thread_; + bool stopped_; }; #endif // TALK_EXAMPLES_CALL_CONSOLE_H_ diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m deleted file mode 100644 index 9ac83ffc1..000000000 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m +++ /dev/null @@ -1,354 +0,0 @@ -/* - * libjingle - * Copyright 2013, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "APPRTCAppClient.h" - -#import - -#import "GAEChannelClient.h" -#import "RTCICEServer.h" -#import "APPRTCAppDelegate.h" -#import "RTCMediaConstraints.h" - -@interface APPRTCAppClient () - -@property(nonatomic, strong) dispatch_queue_t backgroundQueue; -@property(nonatomic, copy) NSString* baseURL; -@property(nonatomic, strong) GAEChannelClient* gaeChannel; -@property(nonatomic, copy) NSString* postMessageUrl; -@property(nonatomic, copy) NSString* pcConfig; -@property(nonatomic, strong) NSMutableString* roomHtml; -@property(atomic, strong) NSMutableArray* sendQueue; -@property(nonatomic, copy) NSString* token; - -@property(nonatomic, assign) BOOL verboseLogging; - -@end - -@implementation APPRTCAppClient - -- (id)initWithICEServerDelegate:(id)delegate - messageHandler:(id)handler { - if (self = [super init]) { - _ICEServerDelegate = delegate; - _messageHandler = handler; - _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue", NULL); - _sendQueue = [NSMutableArray array]; - // Uncomment to see Request/Response logging. - // _verboseLogging = YES; - } - return self; -} - -#pragma mark - Public methods - -- (void)connectToRoom:(NSURL*)url { - NSURLRequest* request = [self getRequestFromUrl:url]; - [NSURLConnection connectionWithRequest:request delegate:self]; -} - -- (void)sendData:(NSData*)data { - @synchronized(self) { - [self maybeLogMessage:@"Send message"]; - [self.sendQueue addObject:[data copy]]; - } - [self requestQueueDrainInBackground]; -} - -#pragma mark - Internal methods - -- (NSString*)findVar:(NSString*)name strippingQuotes:(BOOL)strippingQuotes { - NSError* error; - NSString* pattern = - [NSString stringWithFormat:@".*\n *var %@ = ([^\n]*);\n.*", name]; - NSRegularExpression* regexp = - [NSRegularExpression regularExpressionWithPattern:pattern - options:0 - error:&error]; - NSAssert(!error, - @"Unexpected error compiling regex: ", - error.localizedDescription); - - NSRange fullRange = NSMakeRange(0, [self.roomHtml length]); - NSArray* matches = - [regexp matchesInString:self.roomHtml options:0 range:fullRange]; - if ([matches count] != 1) { - [self showMessage:[NSString stringWithFormat:@"%d matches for %@ in %@", - [matches count], - name, - self.roomHtml]]; - return nil; - } - NSRange matchRange = [matches[0] rangeAtIndex:1]; - NSString* value = [self.roomHtml substringWithRange:matchRange]; - if (strippingQuotes) { - NSAssert([value length] > 2, - @"Can't strip quotes from short string: [%@]", - value); - NSAssert(([value characterAtIndex:0] == '\'' && - [value characterAtIndex:[value length] - 1] == '\''), - @"Can't strip quotes from unquoted string: [%@]", - value); - value = [value substringWithRange:NSMakeRange(1, [value length] - 2)]; - } - return value; -} - -- (NSURLRequest*)getRequestFromUrl:(NSURL*)url { - self.roomHtml = [NSMutableString stringWithCapacity:20000]; - NSString* path = - [NSString stringWithFormat:@"https:%@", [url resourceSpecifier]]; - NSURLRequest* request = - [NSURLRequest requestWithURL:[NSURL URLWithString:path]]; - return request; -} - -- (void)maybeLogMessage:(NSString*)message { - if (self.verboseLogging) { - NSLog(@"%@", message); - } -} - -- (void)requestQueueDrainInBackground { - dispatch_async(self.backgroundQueue, ^(void) { - // TODO(hughv): This can block the UI thread. Fix. - @synchronized(self) { - if ([self.postMessageUrl length] < 1) { - return; - } - for (NSData* data in self.sendQueue) { - NSString* url = - [NSString stringWithFormat:@"%@/%@", - self.baseURL, self.postMessageUrl]; - [self sendData:data withUrl:url]; - } - [self.sendQueue removeAllObjects]; - } - }); -} - -- (void)sendData:(NSData*)data withUrl:(NSString*)url { - NSMutableURLRequest* request = - [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; - request.HTTPMethod = @"POST"; - [request setHTTPBody:data]; - NSURLResponse* response; - NSError* error; - NSData* responseData = [NSURLConnection sendSynchronousRequest:request - returningResponse:&response - error:&error]; - NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; - int status = [httpResponse statusCode]; - NSAssert(status == 200, - @"Bad response [%d] to message: %@\n\n%@", - status, - [NSString stringWithUTF8String:[data bytes]], - [NSString stringWithUTF8String:[responseData bytes]]); -} - -- (void)showMessage:(NSString*)message { - NSLog(@"%@", message); - UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Unable to join" - message:message - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alertView show]; -} - -- (void)updateICEServers:(NSMutableArray*)ICEServers - withTurnServer:(NSString*)turnServerUrl { - if ([turnServerUrl length] < 1) { - [self.ICEServerDelegate onICEServers:ICEServers]; - return; - } - dispatch_async(self.backgroundQueue, ^(void) { - NSMutableURLRequest* request = [NSMutableURLRequest - requestWithURL:[NSURL URLWithString:turnServerUrl]]; - [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"]; - [request addValue:@"https://apprtc.appspot.com" - forHTTPHeaderField:@"origin"]; - NSURLResponse* response; - NSError* error; - NSData* responseData = [NSURLConnection sendSynchronousRequest:request - returningResponse:&response - error:&error]; - if (!error) { - NSDictionary* json = - [NSJSONSerialization JSONObjectWithData:responseData - options:0 - error:&error]; - NSAssert(!error, @"Unable to parse. %@", error.localizedDescription); - NSString* username = json[@"username"]; - NSString* password = json[@"password"]; - NSArray* uris = json[@"uris"]; - for (int i = 0; i < [uris count]; ++i) { - NSString* turnServer = [uris objectAtIndex:i]; - RTCICEServer* ICEServer = - [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:turnServer] - username:username - password:password]; - NSLog(@"Added ICE Server: %@", ICEServer); - [ICEServers addObject:ICEServer]; - } - } else { - NSLog(@"Unable to get TURN server. Error: %@", error.description); - } - - dispatch_async(dispatch_get_main_queue(), ^(void) { - [self.ICEServerDelegate onICEServers:ICEServers]; - }); - }); -} - -#pragma mark - NSURLConnectionDataDelegate methods - -- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data { - NSString* roomHtml = [NSString stringWithUTF8String:[data bytes]]; - [self maybeLogMessage:[NSString stringWithFormat:@"Received %d chars", - [roomHtml length]]]; - [self.roomHtml appendString:roomHtml]; -} - -- (void)connection:(NSURLConnection*)connection - didReceiveResponse:(NSURLResponse*)response { - NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; - int statusCode = [httpResponse statusCode]; - [self - maybeLogMessage: - [NSString stringWithFormat: - @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@", - [httpResponse URL], - statusCode, - [httpResponse allHeaderFields]]]; - NSAssert(statusCode == 200, @"Invalid response of %d received.", statusCode); -} - -- (void)connectionDidFinishLoading:(NSURLConnection*)connection { - [self maybeLogMessage:[NSString stringWithFormat:@"finished loading %d chars", - [self.roomHtml length]]]; - NSRegularExpression* fullRegex = - [NSRegularExpression regularExpressionWithPattern:@"room is full" - options:0 - error:nil]; - if ([fullRegex - numberOfMatchesInString:self.roomHtml - options:0 - range:NSMakeRange(0, [self.roomHtml length])]) { - [self showMessage:@"Room full"]; - APPRTCAppDelegate* ad = - (APPRTCAppDelegate*)[[UIApplication sharedApplication] delegate]; - [ad closeVideoUI]; - return; - } - - NSString* fullUrl = [[[connection originalRequest] URL] absoluteString]; - NSRange queryRange = [fullUrl rangeOfString:@"?"]; - self.baseURL = [fullUrl substringToIndex:queryRange.location]; - [self maybeLogMessage:[NSString - stringWithFormat:@"Base URL: %@", self.baseURL]]; - - self.initiator = [[self findVar:@"initiator" strippingQuotes:NO] boolValue]; - self.token = [self findVar:@"channelToken" strippingQuotes:YES]; - if (!self.token) - return; - [self maybeLogMessage:[NSString stringWithFormat:@"Token: %@", self.token]]; - - NSString* roomKey = [self findVar:@"roomKey" strippingQuotes:YES]; - NSString* me = [self findVar:@"me" strippingQuotes:YES]; - if (!roomKey || !me) - return; - self.postMessageUrl = - [NSString stringWithFormat:@"/message?r=%@&u=%@", roomKey, me]; - [self maybeLogMessage:[NSString stringWithFormat:@"POST message URL: %@", - self.postMessageUrl]]; - - NSString* pcConfig = [self findVar:@"pcConfig" strippingQuotes:NO]; - if (!pcConfig) - return; - [self maybeLogMessage:[NSString - stringWithFormat:@"PC Config JSON: %@", pcConfig]]; - - NSString* turnServerUrl = [self findVar:@"turnUrl" strippingQuotes:YES]; - if (turnServerUrl) { - [self maybeLogMessage:[NSString - stringWithFormat:@"TURN server request URL: %@", - turnServerUrl]]; - } - - NSError* error; - NSData* pcData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary* json = - [NSJSONSerialization JSONObjectWithData:pcData options:0 error:&error]; - NSAssert(!error, @"Unable to parse. %@", error.localizedDescription); - NSArray* servers = [json objectForKey:@"iceServers"]; - NSMutableArray* ICEServers = [NSMutableArray array]; - for (NSDictionary* server in servers) { - NSString* url = [server objectForKey:@"urls"]; - NSString* username = json[@"username"]; - NSString* credential = [server objectForKey:@"credential"]; - if (!username) { - username = @""; - } - if (!credential) { - credential = @""; - } - [self maybeLogMessage:[NSString - stringWithFormat:@"url [%@] - credential [%@]", - url, - credential]]; - RTCICEServer* ICEServer = - [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url] - username:username - password:credential]; - NSLog(@"Added ICE Server: %@", ICEServer); - [ICEServers addObject:ICEServer]; - } - [self updateICEServers:ICEServers withTurnServer:turnServerUrl]; - - NSString* mc = [self findVar:@"mediaConstraints" strippingQuotes:NO]; - if (mc) { - error = nil; - NSData* mcData = [mc dataUsingEncoding:NSUTF8StringEncoding]; - json = - [NSJSONSerialization JSONObjectWithData:mcData options:0 error:&error]; - NSAssert(!error, @"Unable to parse. %@", error.localizedDescription); - if ([[json objectForKey:@"video"] boolValue]) { - _videoConstraints = [[RTCMediaConstraints alloc] init]; - } - } - - [self - maybeLogMessage:[NSString - stringWithFormat:@"About to open GAE with token: %@", - self.token]]; - self.gaeChannel = - [[GAEChannelClient alloc] initWithToken:self.token - delegate:self.messageHandler]; -} - -@end diff --git a/talk/examples/ios/AppRTCDemo/APPRTCVideoView.m b/talk/examples/ios/AppRTCDemo/APPRTCVideoView.m deleted file mode 100644 index 23466b6c9..000000000 --- a/talk/examples/ios/AppRTCDemo/APPRTCVideoView.m +++ /dev/null @@ -1,82 +0,0 @@ -/* - * libjingle - * Copyright 2013, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * This APPRTCVideoView must be initialzed and added to a View to get - * either the local or remote video stream rendered. - * It is a view itself and it encapsulates - * an object of VideoRenderIosView and UIActivityIndicatorView. - * Both of the views will get resized as per the frame of their parent. - */ - -#import "APPRTCVideoView.h" - -#import "RTCVideoRenderer.h" -#import "RTCVideoTrack.h" - -@interface APPRTCVideoView () { - RTCVideoTrack* _track; - RTCVideoRenderer* _renderer; -} - -@property(nonatomic, weak) UIView* renderView; -@property(nonatomic, weak) UIActivityIndicatorView* activityView; - -@end - -@implementation APPRTCVideoView - -@synthesize videoOrientation = _videoOrientation; - -- (void)layoutSubviews { - [super layoutSubviews]; - if (!_renderer) { - // Left-right (mirror) flip the remote view. - CGAffineTransform xform = - CGAffineTransformMakeScale(self.isRemote ? -1 : 1, 1); - // TODO(fischman): why is this rotate (vertical+horizontal flip) needed?!? - xform = CGAffineTransformRotate(xform, M_PI); - // TODO(fischman): ensure back-camera flip is correct in all orientations, - // when back-camera support is added. - [self setTransform:xform]; - _renderer = [[RTCVideoRenderer alloc] initWithView:self]; - } -} - -- (void)renderVideoTrackInterface:(RTCVideoTrack*)videoTrack { - [_track removeRenderer:_renderer]; - [_renderer stop]; - - _track = videoTrack; - - if (_track) { - [_track addRenderer:_renderer]; - [_renderer start]; - } -} - -@end diff --git a/talk/examples/ios/AppRTCDemo/APPRTCViewController.m b/talk/examples/ios/AppRTCDemo/APPRTCViewController.m deleted file mode 100644 index 0ac92823f..000000000 --- a/talk/examples/ios/AppRTCDemo/APPRTCViewController.m +++ /dev/null @@ -1,170 +0,0 @@ -/* - * libjingle - * Copyright 2013, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "APPRTCViewController.h" - -#import "APPRTCVideoView.h" - -@interface APPRTCViewController () - -@property(nonatomic, assign) UIInterfaceOrientation statusBarOrientation; - -@end - -@implementation APPRTCViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - self.statusBarOrientation = - [UIApplication sharedApplication].statusBarOrientation; - self.roomInput.delegate = self; - [self.roomInput becomeFirstResponder]; -} - -- (void)viewDidLayoutSubviews { - if (self.statusBarOrientation != - [UIApplication sharedApplication].statusBarOrientation) { - self.statusBarOrientation = - [UIApplication sharedApplication].statusBarOrientation; - [[NSNotificationCenter defaultCenter] - postNotificationName:@"StatusBarOrientationDidChange" - object:nil]; - } -} - -- (void)displayText:(NSString*)text { - dispatch_async(dispatch_get_main_queue(), ^(void) { - NSString* output = - [NSString stringWithFormat:@"%@\n%@", self.logView.text, text]; - self.logView.text = output; - [self.logView - scrollRangeToVisible:NSMakeRange([self.logView.text length], 0)]; - }); -} - -- (void)resetUI { - [self.roomInput resignFirstResponder]; - self.roomInput.text = nil; - self.roomInput.hidden = NO; - self.instructionsView.hidden = NO; - self.logView.hidden = YES; - self.logView.text = nil; - self.blackView.hidden = YES; - - [_remoteVideoView renderVideoTrackInterface:nil]; - [_remoteVideoView removeFromSuperview]; - self.remoteVideoView = nil; - - [_localVideoView renderVideoTrackInterface:nil]; - [_localVideoView removeFromSuperview]; - self.localVideoView = nil; -} - -// TODO(fischman): Use video dimensions from the incoming video stream -// and resize the Video View accordingly w.r.t. aspect ratio. -enum { - // Remote video view dimensions. - kRemoteVideoWidth = 640, - kRemoteVideoHeight = 480, - // Padding space for local video view with its parent. - kLocalViewPadding = 20 -}; - -- (void)setupCaptureSession { - self.blackView.hidden = NO; - - CGRect frame = - CGRectMake((self.blackView.bounds.size.width - kRemoteVideoWidth) / 2, - (self.blackView.bounds.size.height - kRemoteVideoHeight) / 2, - kRemoteVideoWidth, - kRemoteVideoHeight); - APPRTCVideoView* videoView = [[APPRTCVideoView alloc] initWithFrame:frame]; - videoView.isRemote = TRUE; - - [self.blackView addSubview:videoView]; - videoView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | - UIViewAutoresizingFlexibleRightMargin | - UIViewAutoresizingFlexibleBottomMargin | - UIViewAutoresizingFlexibleTopMargin; - videoView.translatesAutoresizingMaskIntoConstraints = YES; - _remoteVideoView = videoView; - - CGSize screenSize = [[UIScreen mainScreen] bounds].size; - CGFloat localVideoViewWidth = - UIInterfaceOrientationIsPortrait(self.statusBarOrientation) - ? screenSize.width / 4 - : screenSize.height / 4; - CGFloat localVideoViewHeight = - UIInterfaceOrientationIsPortrait(self.statusBarOrientation) - ? screenSize.height / 4 - : screenSize.width / 4; - frame = CGRectMake(self.blackView.bounds.size.width - localVideoViewWidth - - kLocalViewPadding, - kLocalViewPadding, - localVideoViewWidth, - localVideoViewHeight); - videoView = [[APPRTCVideoView alloc] initWithFrame:frame]; - videoView.isRemote = FALSE; - - [self.blackView addSubview:videoView]; - videoView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | - UIViewAutoresizingFlexibleBottomMargin | - UIViewAutoresizingFlexibleHeight | - UIViewAutoresizingFlexibleWidth; - videoView.translatesAutoresizingMaskIntoConstraints = YES; - _localVideoView = videoView; -} - -#pragma mark - UITextFieldDelegate - -- (void)textFieldDidEndEditing:(UITextField*)textField { - NSString* room = textField.text; - if ([room length] == 0) { - return; - } - textField.hidden = YES; - self.instructionsView.hidden = YES; - self.logView.hidden = NO; - // TODO(hughv): Instead of launching a URL with apprtc scheme, change to - // prepopulating the textField with a valid URL missing the room. This allows - // the user to have the simplicity of just entering the room or the ability to - // override to a custom appspot instance. Remove apprtc:// when this is done. - NSString* url = - [NSString stringWithFormat:@"apprtc://apprtc.appspot.com/?r=%@", room]; - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; - - dispatch_async(dispatch_get_main_queue(), ^{ [self setupCaptureSession]; }); -} - -- (BOOL)textFieldShouldReturn:(UITextField*)textField { - // There is no other control that can take focus, so manually resign focus - // when return (Join) is pressed to trigger |textFieldDidEndEditing|. - [textField resignFirstResponder]; - return YES; -} - -@end diff --git a/talk/examples/ios/README b/talk/examples/ios/README deleted file mode 100644 index 9c0d13417..000000000 --- a/talk/examples/ios/README +++ /dev/null @@ -1,3 +0,0 @@ -This directory contains an example iOS client for http://apprtc.appspot.com - -See ../../app/webrtc/objc/README for information on how to use it. diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.h b/talk/examples/objc/AppRTCDemo/APPRTCAppClient.h similarity index 81% rename from talk/examples/ios/AppRTCDemo/APPRTCAppClient.h rename to talk/examples/objc/AppRTCDemo/APPRTCAppClient.h index 41a795eba..8b21db585 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.h +++ b/talk/examples/objc/AppRTCDemo/APPRTCAppClient.h @@ -29,10 +29,13 @@ #import "GAEChannelClient.h" -// Called when there are RTCICEServers. -@protocol ICEServerDelegate +@class APPRTCAppClient; +@protocol APPRTCAppClientDelegate -- (void)onICEServers:(NSArray*)servers; +- (void)appClient:(APPRTCAppClient*)appClient + didErrorWithMessage:(NSString*)message; +- (void)appClient:(APPRTCAppClient*)appClient + didReceiveICEServers:(NSArray*)servers; @end @@ -45,21 +48,20 @@ // call connectToRoom(). apprtc.appspot.com will signal that is successful via // onOpen through the browser channel. Then you should call sendData() and wait // for the registered handler to be called with received messages. -@interface APPRTCAppClient : NSObject +@interface APPRTCAppClient : NSObject -@property(nonatomic, weak, readonly) id ICEServerDelegate; -@property(nonatomic, weak, readonly) id messageHandler; -@property(nonatomic, assign) BOOL initiator; +@property(nonatomic) BOOL initiator; @property(nonatomic, copy, readonly) RTCMediaConstraints* videoConstraints; +@property(nonatomic, weak) id delegate; -- (id)initWithICEServerDelegate:(id)delegate - messageHandler:(id)handler; +- (instancetype)initWithDelegate:(id)delegate + messageHandler:(id)handler; - (void)connectToRoom:(NSURL*)room; - (void)sendData:(NSData*)data; #ifndef DOXYGEN_SHOULD_SKIP_THIS // Disallow init and don't add to documentation -- (id)init __attribute__(( +- (instancetype)init __attribute__(( unavailable("init is not a supported initializer for this class."))); #endif /* DOXYGEN_SHOULD_SKIP_THIS */ diff --git a/talk/examples/objc/AppRTCDemo/APPRTCAppClient.m b/talk/examples/objc/AppRTCDemo/APPRTCAppClient.m new file mode 100644 index 000000000..853496f81 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/APPRTCAppClient.m @@ -0,0 +1,320 @@ +/* + * libjingle + * Copyright 2013, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "APPRTCAppClient.h" + +#import + +#import "GAEChannelClient.h" +#import "RTCICEServer.h" +#import "RTCMediaConstraints.h" +#import "RTCPair.h" + +@implementation APPRTCAppClient { + dispatch_queue_t _backgroundQueue; + GAEChannelClient* _gaeChannel; + NSURL* _postMessageURL; + BOOL _verboseLogging; + __weak id _messageHandler; +} + +- (instancetype)initWithDelegate:(id)delegate + messageHandler:(id)handler { + if (self = [super init]) { + _delegate = delegate; + _messageHandler = handler; + _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue", + DISPATCH_QUEUE_SERIAL); + // Uncomment to see Request/Response logging. + // _verboseLogging = YES; + } + return self; +} + +- (void)connectToRoom:(NSURL*)url { + NSString* urlString = + [[url absoluteString] stringByAppendingString:@"&t=json"]; + NSURL* requestURL = [NSURL URLWithString:urlString]; + NSURLRequest* request = [NSURLRequest requestWithURL:requestURL]; + [self sendURLRequest:request + completionHandler:^(NSError* error, + NSHTTPURLResponse* httpResponse, + NSData* responseData) { + int statusCode = [httpResponse statusCode]; + [self logVerbose:[NSString stringWithFormat: + @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@", + [httpResponse URL], + statusCode, + [httpResponse allHeaderFields]]]; + NSAssert(statusCode == 200, + @"Invalid response of %d received while connecting to: %@", + statusCode, + urlString); + if (statusCode != 200) { + return; + } + [self handleResponseData:responseData + forRoomRequest:request]; + }]; +} + +- (void)sendData:(NSData*)data { + NSParameterAssert([data length] > 0); + NSString* message = [NSString stringWithUTF8String:[data bytes]]; + [self logVerbose:[NSString stringWithFormat:@"Send message:\n%@", message]]; + if (!_postMessageURL) { + return; + } + NSMutableURLRequest* request = + [NSMutableURLRequest requestWithURL:_postMessageURL]; + request.HTTPMethod = @"POST"; + [request setHTTPBody:data]; + [self sendURLRequest:request + completionHandler:^(NSError* error, + NSHTTPURLResponse* httpResponse, + NSData* responseData) { + int status = [httpResponse statusCode]; + NSString* response = [responseData length] > 0 ? + [NSString stringWithUTF8String:[responseData bytes]] : + nil; + NSAssert(status == 200, + @"Bad response [%d] to message: %@\n\n%@", + status, + message, + response); + }]; +} + +#pragma mark - Private + +- (void)logVerbose:(NSString*)message { + if (_verboseLogging) { + NSLog(@"%@", message); + } +} + +- (void)handleResponseData:(NSData*)responseData + forRoomRequest:(NSURLRequest*)request { + NSDictionary* roomJSON = [self parseJSONData:responseData]; + [self logVerbose:[NSString stringWithFormat:@"Room JSON:\n%@", roomJSON]]; + NSParameterAssert(roomJSON); + if (roomJSON[@"error"]) { + NSArray* errorMessages = roomJSON[@"error_messages"]; + NSMutableString* message = [NSMutableString string]; + for (NSString* errorMessage in errorMessages) { + [message appendFormat:@"%@\n", errorMessage]; + } + [self.delegate appClient:self didErrorWithMessage:message]; + return; + } + NSString* pcConfig = roomJSON[@"pc_config"]; + NSData* pcConfigData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary* pcConfigJSON = [self parseJSONData:pcConfigData]; + [self logVerbose:[NSString stringWithFormat:@"PCConfig JSON:\n%@", + pcConfigJSON]]; + NSParameterAssert(pcConfigJSON); + + NSArray* iceServers = [self parseICEServersForPCConfigJSON:pcConfigJSON]; + [self requestTURNServerForICEServers:iceServers + turnServerUrl:roomJSON[@"turn_url"]]; + + _initiator = [roomJSON[@"initiator"] boolValue]; + [self logVerbose:[NSString stringWithFormat:@"Initiator: %d", _initiator]]; + _postMessageURL = [self parsePostMessageURLForRoomJSON:roomJSON + request:request]; + [self logVerbose:[NSString stringWithFormat:@"POST message URL:\n%@", + _postMessageURL]]; + _videoConstraints = [self parseVideoConstraintsForRoomJSON:roomJSON]; + [self logVerbose:[NSString stringWithFormat:@"Media constraints:\n%@", + _videoConstraints]]; + NSString* token = roomJSON[@"token"]; + [self logVerbose: + [NSString stringWithFormat:@"About to open GAE with token: %@", + token]]; + _gaeChannel = + [[GAEChannelClient alloc] initWithToken:token + delegate:_messageHandler]; +} + +- (NSDictionary*)parseJSONData:(NSData*)data { + NSError* error = nil; + NSDictionary* json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + NSAssert(!error, @"Unable to parse. %@", error.localizedDescription); + return json; +} + +- (NSArray*)parseICEServersForPCConfigJSON:(NSDictionary*)pcConfigJSON { + NSMutableArray* result = [NSMutableArray array]; + NSArray* iceServers = pcConfigJSON[@"iceServers"]; + for (NSDictionary* iceServer in iceServers) { + NSString* url = iceServer[@"urls"]; + NSString* username = pcConfigJSON[@"username"]; + NSString* credential = iceServer[@"credential"]; + username = username ? username : @""; + credential = credential ? credential : @""; + [self logVerbose:[NSString stringWithFormat:@"url [%@] - credential [%@]", + url, + credential]]; + RTCICEServer* server = + [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url] + username:username + password:credential]; + [result addObject:server]; + } + return result; +} + +- (NSURL*)parsePostMessageURLForRoomJSON:(NSDictionary*)roomJSON + request:(NSURLRequest*)request { + NSString* requestUrl = [[request URL] absoluteString]; + NSRange queryRange = [requestUrl rangeOfString:@"?"]; + NSString* baseUrl = [requestUrl substringToIndex:queryRange.location]; + NSString* roomKey = roomJSON[@"room_key"]; + NSParameterAssert([roomKey length] > 0); + NSString* me = roomJSON[@"me"]; + NSParameterAssert([me length] > 0); + NSString* postMessageUrl = + [NSString stringWithFormat:@"%@/message?r=%@&u=%@", baseUrl, roomKey, me]; + return [NSURL URLWithString:postMessageUrl]; +} + +- (RTCMediaConstraints*)parseVideoConstraintsForRoomJSON: + (NSDictionary*)roomJSON { + NSString* mediaConstraints = roomJSON[@"media_constraints"]; + RTCMediaConstraints* constraints = nil; + if ([mediaConstraints length] > 0) { + NSData* constraintsData = + [mediaConstraints dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary* constraintsJSON = [self parseJSONData:constraintsData]; + id video = constraintsJSON[@"video"]; + if ([video isKindOfClass:[NSDictionary class]]) { + NSDictionary* mandatory = video[@"mandatory"]; + NSMutableArray* mandatoryContraints = + [NSMutableArray arrayWithCapacity:[mandatory count]]; + [mandatory enumerateKeysAndObjectsUsingBlock:^( + id key, id obj, BOOL* stop) { + [mandatoryContraints addObject:[[RTCPair alloc] initWithKey:key + value:obj]]; + }]; + // TODO(tkchin): figure out json formats for optional constraints. + constraints = + [[RTCMediaConstraints alloc] + initWithMandatoryConstraints:mandatoryContraints + optionalConstraints:nil]; + } else if ([video isKindOfClass:[NSNumber class]] && [video boolValue]) { + constraints = [[RTCMediaConstraints alloc] init]; + } + } + return constraints; +} + +- (void)requestTURNServerWithUrl:(NSString*)turnServerUrl + completionHandler: + (void (^)(RTCICEServer* turnServer))completionHandler { + NSURL* turnServerURL = [NSURL URLWithString:turnServerUrl]; + NSMutableURLRequest* request = + [NSMutableURLRequest requestWithURL:turnServerURL]; + [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"]; + [request addValue:@"https://apprtc.appspot.com" + forHTTPHeaderField:@"origin"]; + [self sendURLRequest:request + completionHandler:^(NSError* error, + NSHTTPURLResponse* response, + NSData* responseData) { + if (error) { + NSLog(@"Unable to get TURN server."); + completionHandler(nil); + return; + } + NSDictionary* json = [self parseJSONData:responseData]; + NSString* username = json[@"username"]; + NSString* password = json[@"password"]; + NSArray* uris = json[@"uris"]; + NSParameterAssert([uris count] > 0); + RTCICEServer* turnServer = + [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:uris[0]] + username:username + password:password]; + completionHandler(turnServer); + }]; +} + +- (void)requestTURNServerForICEServers:(NSArray*)iceServers + turnServerUrl:(NSString*)turnServerUrl { + BOOL isTurnPresent = NO; + for (RTCICEServer* iceServer in iceServers) { + if ([[iceServer.URI scheme] isEqualToString:@"turn"]) { + isTurnPresent = YES; + break; + } + } + if (!isTurnPresent) { + [self requestTURNServerWithUrl:turnServerUrl + completionHandler:^(RTCICEServer* turnServer) { + NSArray* servers = iceServers; + if (turnServer) { + servers = [servers arrayByAddingObject:turnServer]; + } + NSLog(@"ICE servers:\n%@", servers); + [self.delegate appClient:self didReceiveICEServers:servers]; + }]; + } else { + NSLog(@"ICE servers:\n%@", iceServers); + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate appClient:self didReceiveICEServers:iceServers]; + }); + } +} + +- (void)sendURLRequest:(NSURLRequest*)request + completionHandler:(void (^)(NSError* error, + NSHTTPURLResponse* httpResponse, + NSData* responseData))completionHandler { + dispatch_async(_backgroundQueue, ^{ + NSError* error = nil; + NSURLResponse* response = nil; + NSData* responseData = [NSURLConnection sendSynchronousRequest:request + returningResponse:&response + error:&error]; + NSParameterAssert(!response || + [response isKindOfClass:[NSHTTPURLResponse class]]); + if (error) { + NSLog(@"Failed URL request for:%@\nError:%@", request, error); + } + dispatch_async(dispatch_get_main_queue(), ^{ + NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; + completionHandler(error, httpResponse, responseData); + }); + }); +} + +@end diff --git a/talk/examples/objc/AppRTCDemo/APPRTCConnectionManager.h b/talk/examples/objc/AppRTCDemo/APPRTCConnectionManager.h new file mode 100644 index 000000000..98fe755ef --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/APPRTCConnectionManager.h @@ -0,0 +1,66 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +// Used to log messages to destination like UI. +@protocol APPRTCLogger +- (void)logMessage:(NSString*)message; +@end + +@class RTCVideoTrack; +@class APPRTCConnectionManager; + +// Used to provide AppRTC connection information. +@protocol APPRTCConnectionManagerDelegate + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didReceiveLocalVideoTrack:(RTCVideoTrack*)localVideoTrack; + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didReceiveRemoteVideoTrack:(RTCVideoTrack*)remoteVideoTrack; + +- (void)connectionManagerDidReceiveHangup:(APPRTCConnectionManager*)manager; + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didErrorWithMessage:(NSString*)errorMessage; + +@end + +// Abstracts the network connection aspect of AppRTC. The delegate will receive +// information about connection status as changes occur. +@interface APPRTCConnectionManager : NSObject + +@property(nonatomic, weak) id delegate; +@property(nonatomic, weak) id logger; + +- (instancetype)initWithDelegate:(id)delegate + logger:(id)logger; +- (BOOL)connectToRoomWithURL:(NSURL*)url; +- (void)disconnect; + +@end diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m b/talk/examples/objc/AppRTCDemo/APPRTCConnectionManager.m similarity index 52% rename from talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m rename to talk/examples/objc/AppRTCDemo/APPRTCConnectionManager.m index e25181672..b411a6215 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m +++ b/talk/examples/objc/AppRTCDemo/APPRTCConnectionManager.m @@ -1,6 +1,6 @@ /* * libjingle - * Copyright 2013, Google Inc. + * Copyright 2014, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -25,13 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import - -#import "APPRTCAppDelegate.h" +#import "APPRTCConnectionManager.h" -#import "APPRTCViewController.h" +#import +#import "APPRTCAppClient.h" +#import "GAEChannelClient.h" #import "RTCICECandidate.h" -#import "RTCICEServer.h" #import "RTCMediaConstraints.h" #import "RTCMediaStream.h" #import "RTCPair.h" @@ -39,193 +38,84 @@ #import "RTCPeerConnectionDelegate.h" #import "RTCPeerConnectionFactory.h" #import "RTCSessionDescription.h" -#import "RTCVideoRenderer.h" +#import "RTCSessionDescriptionDelegate.h" +#import "RTCStatsDelegate.h" #import "RTCVideoCapturer.h" -#import "RTCVideoTrack.h" -#import "APPRTCVideoView.h" - -@interface PCObserver : NSObject - -- (id)initWithDelegate:(id)delegate; - -@property(nonatomic, strong) APPRTCVideoView* videoView; - -@end - -@implementation PCObserver { - id _delegate; -} - -- (id)initWithDelegate:(id)delegate { - if (self = [super init]) { - _delegate = delegate; - } - return self; -} - -- (void)peerConnectionOnError:(RTCPeerConnection*)peerConnection { - dispatch_async(dispatch_get_main_queue(), ^(void) { - NSLog(@"PCO onError."); - NSAssert(NO, @"PeerConnection failed."); - }); -} - -- (void)peerConnection:(RTCPeerConnection*)peerConnection - signalingStateChanged:(RTCSignalingState)stateChanged { - dispatch_async(dispatch_get_main_queue(), ^(void) { - NSLog(@"PCO onSignalingStateChange: %d", stateChanged); - }); -} - -- (void)peerConnection:(RTCPeerConnection*)peerConnection - addedStream:(RTCMediaStream*)stream { - dispatch_async(dispatch_get_main_queue(), ^(void) { - NSLog(@"PCO onAddStream."); - NSAssert([stream.audioTracks count] <= 1, - @"Expected at most 1 audio stream"); - NSAssert([stream.videoTracks count] <= 1, - @"Expected at most 1 video stream"); - if ([stream.videoTracks count] != 0) { - [self.videoView - renderVideoTrackInterface:[stream.videoTracks objectAtIndex:0]]; - } - }); -} - -- (void)peerConnection:(RTCPeerConnection*)peerConnection - removedStream:(RTCMediaStream*)stream { - dispatch_async(dispatch_get_main_queue(), - ^(void) { NSLog(@"PCO onRemoveStream."); }); -} - -- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection*)peerConnection { - dispatch_async(dispatch_get_main_queue(), ^(void) { - NSLog(@"PCO onRenegotiationNeeded - ignoring because AppRTC has a " - "predefined negotiation strategy"); - }); -} - -- (void)peerConnection:(RTCPeerConnection*)peerConnection - gotICECandidate:(RTCICECandidate*)candidate { - dispatch_async(dispatch_get_main_queue(), ^(void) { - NSLog(@"PCO onICECandidate.\n Mid[%@] Index[%d] Sdp[%@]", - candidate.sdpMid, - candidate.sdpMLineIndex, - candidate.sdp); - NSDictionary* json = @{ - @"type" : @"candidate", - @"label" : [NSNumber numberWithInt:candidate.sdpMLineIndex], - @"id" : candidate.sdpMid, - @"candidate" : candidate.sdp - }; - NSError* error; - NSData* data = - [NSJSONSerialization dataWithJSONObject:json options:0 error:&error]; - if (!error) { - [_delegate sendData:data]; - } else { - NSAssert(NO, - @"Unable to serialize JSON object with error: %@", - error.localizedDescription); - } - }); -} - -- (void)peerConnection:(RTCPeerConnection*)peerConnection - iceGatheringChanged:(RTCICEGatheringState)newState { - dispatch_async(dispatch_get_main_queue(), - ^(void) { NSLog(@"PCO onIceGatheringChange. %d", newState); }); -} - -- (void)peerConnection:(RTCPeerConnection*)peerConnection - iceConnectionChanged:(RTCICEConnectionState)newState { - dispatch_async(dispatch_get_main_queue(), ^(void) { - NSLog(@"PCO onIceConnectionChange. %d", newState); - if (newState == RTCICEConnectionConnected) - [self displayLogMessage:@"ICE Connection Connected."]; - NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!"); - }); -} - -- (void)displayLogMessage:(NSString*)message { - [_delegate displayLogMessage:message]; -} - -@end +#import "RTCVideoSource.h" -@interface APPRTCAppDelegate () +@interface APPRTCConnectionManager () + @property(nonatomic, strong) APPRTCAppClient* client; -@property(nonatomic, strong) PCObserver* pcObserver; @property(nonatomic, strong) RTCPeerConnection* peerConnection; @property(nonatomic, strong) RTCPeerConnectionFactory* peerConnectionFactory; +@property(nonatomic, strong) RTCVideoSource* videoSource; @property(nonatomic, strong) NSMutableArray* queuedRemoteCandidates; @end -@implementation APPRTCAppDelegate - -#pragma mark - UIApplicationDelegate methods - -- (BOOL)application:(UIApplication*)application - didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - [RTCPeerConnectionFactory initializeSSL]; - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.viewController = - [[APPRTCViewController alloc] initWithNibName:@"APPRTCViewController" - bundle:nil]; - self.window.rootViewController = self.viewController; - [self.window makeKeyAndVisible]; - return YES; -} - -- (void)applicationWillResignActive:(UIApplication*)application { - [self displayLogMessage:@"Application lost focus, connection broken."]; - [self closeVideoUI]; +@implementation APPRTCConnectionManager { + NSTimer* _statsTimer; } -- (void)applicationDidEnterBackground:(UIApplication*)application { -} - -- (void)applicationWillEnterForeground:(UIApplication*)application { -} - -- (void)applicationDidBecomeActive:(UIApplication*)application { +- (instancetype)initWithDelegate:(id)delegate + logger:(id)logger { + if (self = [super init]) { + self.delegate = delegate; + self.logger = logger; + self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init]; + // TODO(tkchin): turn this into a button. + // Uncomment for stat logs. + // _statsTimer = + // [NSTimer scheduledTimerWithTimeInterval:10 + // target:self + // selector:@selector(didFireStatsTimer:) + // userInfo:nil + // repeats:YES]; + } + return self; } -- (void)applicationWillTerminate:(UIApplication*)application { +- (void)dealloc { + [self disconnect]; } -- (BOOL)application:(UIApplication*)application - openURL:(NSURL*)url - sourceApplication:(NSString*)sourceApplication - annotation:(id)annotation { +- (BOOL)connectToRoomWithURL:(NSURL*)url { if (self.client) { + // Already have a connection. return NO; } - self.client = [[APPRTCAppClient alloc] initWithICEServerDelegate:self - messageHandler:self]; + self.client = [[APPRTCAppClient alloc] initWithDelegate:self + messageHandler:self]; [self.client connectToRoom:url]; return YES; } -- (void)displayLogMessage:(NSString*)message { - NSAssert([NSThread isMainThread], @"Called off main thread!"); - NSLog(@"%@", message); - [self.viewController displayText:message]; +- (void)disconnect { + if (!self.client) { + return; + } + [self.client + sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]]; + [self.peerConnection close]; + self.peerConnection = nil; + self.client = nil; + self.videoSource = nil; + self.queuedRemoteCandidates = nil; } -#pragma mark - RTCSendMessage method +#pragma mark - APPRTCAppClientDelegate -- (void)sendData:(NSData*)data { - [self.client sendData:data]; +- (void)appClient:(APPRTCAppClient*)appClient + didErrorWithMessage:(NSString*)message { + [self.delegate connectionManager:self + didErrorWithMessage:message]; } -#pragma mark - ICEServerDelegate method - -- (void)onICEServers:(NSArray*)servers { +- (void)appClient:(APPRTCAppClient*)appClient + didReceiveICEServers:(NSArray*)servers { self.queuedRemoteCandidates = [NSMutableArray array]; - self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init]; RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints: @[ @@ -239,11 +129,10 @@ - (void)onICEServers:(NSArray*)servers { [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"] ]]; - self.pcObserver = [[PCObserver alloc] initWithDelegate:self]; self.peerConnection = [self.peerConnectionFactory peerConnectionWithICEServers:servers constraints:constraints - delegate:self.pcObserver]; + delegate:self]; RTCMediaStream* lms = [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"]; @@ -251,7 +140,10 @@ - (void)onICEServers:(NSArray*)servers { // support or emulation (http://goo.gl/rHAnC1) so don't bother // trying to open a local stream. RTCVideoTrack* localVideoTrack; -#if !TARGET_IPHONE_SIMULATOR + + // TODO(tkchin): local video capture for OSX. See + // https://code.google.com/p/webrtc/issues/detail?id=3417. +#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE NSString* cameraID = nil; for (AVCaptureDevice* captureDevice in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { @@ -273,26 +165,23 @@ - (void)onICEServers:(NSArray*)servers { if (localVideoTrack) { [lms addVideoTrack:localVideoTrack]; } + [self.delegate connectionManager:self + didReceiveLocalVideoTrack:localVideoTrack]; #endif - [self.viewController.localVideoView - renderVideoTrackInterface:localVideoTrack]; - - self.pcObserver.videoView = self.viewController.remoteVideoView; - [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]]; [self.peerConnection addStream:lms constraints:constraints]; - [self displayLogMessage:@"onICEServers - added local stream."]; + [self.logger logMessage:@"onICEServers - added local stream."]; } #pragma mark - GAEMessageHandler methods - (void)onOpen { if (!self.client.initiator) { - [self displayLogMessage:@"Callee; waiting for remote offer"]; + [self.logger logMessage:@"Callee; waiting for remote offer"]; return; } - [self displayLogMessage:@"GAE onOpen - create offer."]; + [self.logger logMessage:@"GAE onOpen - create offer."]; RTCPair* audio = [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"]; RTCPair* video = @@ -302,14 +191,14 @@ - (void)onOpen { [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory optionalConstraints:nil]; [self.peerConnection createOfferWithDelegate:self constraints:constraints]; - [self displayLogMessage:@"PC - createOffer."]; + [self.logger logMessage:@"PC - createOffer."]; } - (void)onMessage:(NSDictionary*)messageData { NSString* type = messageData[@"type"]; NSAssert(type, @"Missing type: %@", messageData); - [self displayLogMessage:[NSString stringWithFormat:@"GAE onMessage type - %@", - type]]; + [self.logger logMessage:[NSString stringWithFormat:@"GAE onMessage type - %@", + type]]; if ([type isEqualToString:@"candidate"]) { NSString* mid = messageData[@"id"]; NSNumber* sdpLineIndex = messageData[@"label"]; @@ -328,36 +217,202 @@ - (void)onMessage:(NSDictionary*)messageData { NSString* sdpString = messageData[@"sdp"]; RTCSessionDescription* sdp = [[RTCSessionDescription alloc] initWithType:type - sdp:[APPRTCAppDelegate preferISAC:sdpString]]; + sdp:[[self class] preferISAC:sdpString]]; [self.peerConnection setRemoteDescriptionWithDelegate:self sessionDescription:sdp]; - [self displayLogMessage:@"PC - setRemoteDescription."]; + [self.logger logMessage:@"PC - setRemoteDescription."]; } else if ([type isEqualToString:@"bye"]) { - [self closeVideoUI]; - UIAlertView* alertView = - [[UIAlertView alloc] initWithTitle:@"Remote end hung up" - message:@"dropping PeerConnection" - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alertView show]; + [self.delegate connectionManagerDidReceiveHangup:self]; } else { NSAssert(NO, @"Invalid message: %@", messageData); } } - (void)onClose { - [self displayLogMessage:@"GAE onClose."]; - [self closeVideoUI]; + [self.logger logMessage:@"GAE onClose."]; + [self.delegate connectionManagerDidReceiveHangup:self]; } - (void)onError:(int)code withDescription:(NSString*)description { - [self displayLogMessage:[NSString stringWithFormat:@"GAE onError: %d, %@", - code, description]]; - [self closeVideoUI]; + NSString* message = [NSString stringWithFormat:@"GAE onError: %d, %@", + code, description]; + [self.logger logMessage:message]; + [self.delegate connectionManager:self + didErrorWithMessage:message]; +} + +#pragma mark - RTCPeerConnectionDelegate + +- (void)peerConnectionOnError:(RTCPeerConnection*)peerConnection { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString* message = @"PeerConnection error"; + NSLog(@"%@", message); + NSAssert(NO, @"PeerConnection failed."); + [self.delegate connectionManager:self + didErrorWithMessage:message]; + }); +} + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + signalingStateChanged:(RTCSignalingState)stateChanged { + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"PCO onSignalingStateChange: %d", stateChanged); + }); +} + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + addedStream:(RTCMediaStream*)stream { + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"PCO onAddStream."); + NSAssert([stream.audioTracks count] == 1 || [stream.videoTracks count] == 1, + @"Expected audio or video track"); + NSAssert([stream.audioTracks count] <= 1, + @"Expected at most 1 audio stream"); + NSAssert([stream.videoTracks count] <= 1, + @"Expected at most 1 video stream"); + if ([stream.videoTracks count] != 0) { + [self.delegate connectionManager:self + didReceiveRemoteVideoTrack:stream.videoTracks[0]]; + } + }); +} + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + removedStream:(RTCMediaStream*)stream { + dispatch_async(dispatch_get_main_queue(), + ^{ NSLog(@"PCO onRemoveStream."); }); +} + +- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection*)peerConnection { + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"PCO onRenegotiationNeeded - ignoring because AppRTC has a " + "predefined negotiation strategy"); + }); +} + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + gotICECandidate:(RTCICECandidate*)candidate { + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"PCO onICECandidate.\n Mid[%@] Index[%li] Sdp[%@]", + candidate.sdpMid, + (long)candidate.sdpMLineIndex, + candidate.sdp); + NSDictionary* json = @{ + @"type" : @"candidate", + @"label" : @(candidate.sdpMLineIndex), + @"id" : candidate.sdpMid, + @"candidate" : candidate.sdp + }; + NSError* error; + NSData* data = + [NSJSONSerialization dataWithJSONObject:json options:0 error:&error]; + if (!error) { + [self.client sendData:data]; + } else { + NSAssert(NO, + @"Unable to serialize JSON object with error: %@", + error.localizedDescription); + } + }); +} + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + iceGatheringChanged:(RTCICEGatheringState)newState { + dispatch_async(dispatch_get_main_queue(), + ^{ NSLog(@"PCO onIceGatheringChange. %d", newState); }); +} + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + iceConnectionChanged:(RTCICEConnectionState)newState { + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"PCO onIceConnectionChange. %d", newState); + if (newState == RTCICEConnectionConnected) + [self.logger logMessage:@"ICE Connection Connected."]; + NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!"); + }); +} + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didOpenDataChannel:(RTCDataChannel*)dataChannel { + NSAssert(NO, @"AppRTC doesn't use DataChannels"); +} + +#pragma mark - RTCSessionDescriptionDelegate + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didCreateSessionDescription:(RTCSessionDescription*)origSdp + error:(NSError*)error { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + [self.logger logMessage:@"SDP onFailure."]; + NSAssert(NO, error.description); + return; + } + [self.logger logMessage:@"SDP onSuccess(SDP) - set local description."]; + RTCSessionDescription* sdp = [[RTCSessionDescription alloc] + initWithType:origSdp.type + sdp:[[self class] preferISAC:origSdp.description]]; + [self.peerConnection setLocalDescriptionWithDelegate:self + sessionDescription:sdp]; + [self.logger logMessage:@"PC setLocalDescription."]; + NSDictionary* json = @{@"type" : sdp.type, @"sdp" : sdp.description}; + NSError* jsonError; + NSData* data = [NSJSONSerialization dataWithJSONObject:json + options:0 + error:&jsonError]; + NSAssert(!jsonError, @"Error: %@", jsonError.description); + [self.client sendData:data]; + }); } -#pragma mark - RTCSessionDescriptonDelegate methods +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didSetSessionDescriptionWithError:(NSError*)error { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + [self.logger logMessage:@"SDP onFailure."]; + NSAssert(NO, error.description); + return; + } + [self.logger logMessage:@"SDP onSuccess() - possibly drain candidates"]; + if (!self.client.initiator) { + if (self.peerConnection.remoteDescription && + !self.peerConnection.localDescription) { + [self.logger logMessage:@"Callee, setRemoteDescription succeeded"]; + RTCPair* audio = [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" + value:@"true"]; + RTCPair* video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" + value:@"true"]; + NSArray* mandatory = @[ audio, video ]; + RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] + initWithMandatoryConstraints:mandatory + optionalConstraints:nil]; + [self.peerConnection createAnswerWithDelegate:self + constraints:constraints]; + [self.logger logMessage:@"PC - createAnswer."]; + } else { + [self.logger logMessage:@"SDP onSuccess - drain candidates"]; + [self drainRemoteCandidates]; + } + } else { + if (self.peerConnection.remoteDescription) { + [self.logger logMessage:@"SDP onSuccess - drain candidates"]; + [self drainRemoteCandidates]; + } + } + }); +} + +#pragma mark - RTCStatsDelegate methods + +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didGetStats:(NSArray*)stats { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString* message = [NSString stringWithFormat:@"Stats:\n %@", stats]; + [self.logger logMessage:message]; + }); +} + +#pragma mark - Private // Match |pattern| to |string| and return the first group of the first // match, or nil if no match was found. @@ -422,86 +477,6 @@ + (NSString*)preferISAC:(NSString*)origSDP { return [newLines componentsJoinedByString:@"\n"]; } -- (void)peerConnection:(RTCPeerConnection*)peerConnection - didCreateSessionDescription:(RTCSessionDescription*)origSdp - error:(NSError*)error { - dispatch_async(dispatch_get_main_queue(), ^(void) { - if (error) { - [self displayLogMessage:@"SDP onFailure."]; - NSAssert(NO, error.description); - return; - } - [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."]; - RTCSessionDescription* sdp = [[RTCSessionDescription alloc] - initWithType:origSdp.type - sdp:[APPRTCAppDelegate preferISAC:origSdp.description]]; - [self.peerConnection setLocalDescriptionWithDelegate:self - sessionDescription:sdp]; - - [self displayLogMessage:@"PC setLocalDescription."]; - NSDictionary* json = @{@"type" : sdp.type, @"sdp" : sdp.description}; - NSError* error; - NSData* data = - [NSJSONSerialization dataWithJSONObject:json options:0 error:&error]; - NSAssert(!error, - @"%@", - [NSString stringWithFormat:@"Error: %@", error.description]); - [self sendData:data]; - }); -} - -- (void)peerConnection:(RTCPeerConnection*)peerConnection - didSetSessionDescriptionWithError:(NSError*)error { - dispatch_async(dispatch_get_main_queue(), ^(void) { - if (error) { - [self displayLogMessage:@"SDP onFailure."]; - NSAssert(NO, error.description); - return; - } - - [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"]; - if (!self.client.initiator) { - if (self.peerConnection.remoteDescription && - !self.peerConnection.localDescription) { - [self displayLogMessage:@"Callee, setRemoteDescription succeeded"]; - RTCPair* audio = [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" - value:@"true"]; - RTCPair* video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" - value:@"true"]; - NSArray* mandatory = @[ audio, video ]; - RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] - initWithMandatoryConstraints:mandatory - optionalConstraints:nil]; - [self.peerConnection createAnswerWithDelegate:self - constraints:constraints]; - [self displayLogMessage:@"PC - createAnswer."]; - } else { - [self displayLogMessage:@"SDP onSuccess - drain candidates"]; - [self drainRemoteCandidates]; - } - } else { - if (self.peerConnection.remoteDescription) { - [self displayLogMessage:@"SDP onSuccess - drain candidates"]; - [self drainRemoteCandidates]; - } - } - }); -} - -#pragma mark - internal methods - -- (void)disconnect { - [self.client - sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]]; - [self.peerConnection close]; - self.peerConnection = nil; - self.pcObserver = nil; - self.client = nil; - self.videoSource = nil; - self.peerConnectionFactory = nil; - [RTCPeerConnectionFactory deinitializeSSL]; -} - - (void)drainRemoteCandidates { for (RTCICECandidate* candidate in self.queuedRemoteCandidates) { [self.peerConnection addICECandidate:candidate]; @@ -509,33 +484,12 @@ - (void)drainRemoteCandidates { self.queuedRemoteCandidates = nil; } -- (NSString*)unHTMLifyString:(NSString*)base { - // TODO(hughv): Investigate why percent escapes are being added. Removing - // them isn't necessary on Android. - // convert HTML escaped characters to UTF8. - NSString* removePercent = - [base stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - // remove leading and trailing ". - NSRange range; - range.length = [removePercent length] - 2; - range.location = 1; - NSString* removeQuotes = [removePercent substringWithRange:range]; - // convert \" to ". - NSString* removeEscapedQuotes = - [removeQuotes stringByReplacingOccurrencesOfString:@"\\\"" - withString:@"\""]; - // convert \\ to \. - NSString* removeBackslash = - [removeEscapedQuotes stringByReplacingOccurrencesOfString:@"\\\\" - withString:@"\\"]; - return removeBackslash; -} - -#pragma mark - public methods - -- (void)closeVideoUI { - [self.viewController resetUI]; - [self disconnect]; +- (void)didFireStatsTimer:(NSTimer*)timer { + if (self.peerConnection) { + [self.peerConnection getStatsWithDelegate:self + mediaStreamTrack:nil + statsOutputLevel:RTCStatsOutputLevelDebug]; + } } @end diff --git a/talk/examples/ios/AppRTCDemo/GAEChannelClient.h b/talk/examples/objc/AppRTCDemo/GAEChannelClient.h similarity index 86% rename from talk/examples/ios/AppRTCDemo/GAEChannelClient.h rename to talk/examples/objc/AppRTCDemo/GAEChannelClient.h index 8c7d5d347..dbaecebd9 100644 --- a/talk/examples/ios/AppRTCDemo/GAEChannelClient.h +++ b/talk/examples/objc/AppRTCDemo/GAEChannelClient.h @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import +#import // These methods will be called by the AppEngine chanel. The documentation // for these methods is found here. (Yes, it is a JS API.) @@ -35,15 +35,18 @@ - (void)onOpen; - (void)onMessage:(NSDictionary*)data; - (void)onClose; -- (void)onError:(int)code withDescription:(NSString *)description; +- (void)onError:(int)code withDescription:(NSString*)description; @end // Initialize with a token for an AppRTC data channel. This will load // ios_channel.html and use the token to establish a data channel between the // application and AppEngine. -@interface GAEChannelClient : NSObject +@interface GAEChannelClient : NSObject -- (id)initWithToken:(NSString *)token delegate:(id)delegate; +@property(nonatomic, weak) id delegate; + +- (instancetype)initWithToken:(NSString*)token + delegate:(id)delegate; @end diff --git a/talk/examples/ios/AppRTCDemo/GAEChannelClient.m b/talk/examples/objc/AppRTCDemo/GAEChannelClient.m similarity index 75% rename from talk/examples/ios/AppRTCDemo/GAEChannelClient.m rename to talk/examples/objc/AppRTCDemo/GAEChannelClient.m index fcd0787ef..a95e99a8d 100644 --- a/talk/examples/ios/AppRTCDemo/GAEChannelClient.m +++ b/talk/examples/objc/AppRTCDemo/GAEChannelClient.m @@ -29,59 +29,94 @@ #import "RTCPeerConnectionFactory.h" -@interface GAEChannelClient () +#if TARGET_OS_IPHONE + +#import + +@interface GAEChannelClient () -@property(nonatomic, assign) id delegate; @property(nonatomic, strong) UIWebView* webView; +#else + +#import + +@interface GAEChannelClient () + +@property(nonatomic, strong) WebView* webView; + +#endif + @end @implementation GAEChannelClient -- (id)initWithToken:(NSString*)token delegate:(id)delegate { +- (instancetype)initWithToken:(NSString*)token + delegate:(id)delegate { + NSParameterAssert([token length] > 0); + NSParameterAssert(delegate); self = [super init]; if (self) { +#if TARGET_OS_IPHONE _webView = [[UIWebView alloc] init]; _webView.delegate = self; +#else + _webView = [[WebView alloc] init]; + _webView.policyDelegate = self; +#endif _delegate = delegate; NSString* htmlPath = - [[NSBundle mainBundle] pathForResource:@"ios_channel" ofType:@"html"]; + [[NSBundle mainBundle] pathForResource:@"channel" ofType:@"html"]; NSURL* htmlUrl = [NSURL fileURLWithPath:htmlPath]; NSString* path = [NSString stringWithFormat:@"%@?token=%@", [htmlUrl absoluteString], token]; - +#if TARGET_OS_IPHONE [_webView +#else + [[_webView mainFrame] +#endif loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:path]]]; } return self; } - (void)dealloc { +#if TARGET_OS_IPHONE _webView.delegate = nil; [_webView stopLoading]; +#else + _webView.policyDelegate = nil; + [[_webView mainFrame] stopLoading]; +#endif } -#pragma mark - UIWebViewDelegate method - -+ (NSDictionary*)jsonStringToDictionary:(NSString*)str { - NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding]; - NSError* error; - NSDictionary* dict = - [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - NSAssert(!error, @"Invalid JSON? %@", str); - return dict; -} +#if TARGET_OS_IPHONE +#pragma mark - UIWebViewDelegate - (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType { +#else +// WebPolicyDelegate is an informal delegate. +#pragma mark - WebPolicyDelegate + +- (void)webView:(WebView*)webView + decidePolicyForNavigationAction:(NSDictionary*)actionInformation + request:(NSURLRequest*)request + frame:(WebFrame*)frame + decisionListener:(id)listener { +#endif NSString* scheme = [request.URL scheme]; NSAssert(scheme, @"scheme is nil: %@", request); if (![scheme isEqualToString:@"js-frame"]) { +#if TARGET_OS_IPHONE return YES; +#else + [listener use]; + return; +#endif } - - dispatch_async(dispatch_get_main_queue(), ^(void) { + dispatch_async(dispatch_get_main_queue(), ^{ NSString* queuedMessage = [webView stringByEvaluatingJavaScriptFromString:@"popQueuedMessage();"]; NSAssert([queuedMessage length], @"Empty queued message from JS"); @@ -110,7 +145,23 @@ - (BOOL)webView:(UIWebView*)webView NSAssert(NO, @"Invalid message sent from UIWebView: %@", queuedMessage); } }); +#if TARGET_OS_IPHONE return NO; +#else + [listener ignore]; + return; +#endif +} + +#pragma mark - Private + ++ (NSDictionary*)jsonStringToDictionary:(NSString*)str { + NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding]; + NSError* error; + NSDictionary* dict = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + NSAssert(!error, @"Invalid JSON? %@", str); + return dict; } @end diff --git a/talk/examples/ios/AppRTCDemo/ios_channel.html b/talk/examples/objc/AppRTCDemo/channel.html similarity index 100% rename from talk/examples/ios/AppRTCDemo/ios_channel.html rename to talk/examples/objc/AppRTCDemo/channel.html diff --git a/talk/examples/ios/AppRTCDemo/APPRTCVideoView.h b/talk/examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.h similarity index 76% rename from talk/examples/ios/AppRTCDemo/APPRTCVideoView.h rename to talk/examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.h index 238798e39..196b39fd9 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCVideoView.h +++ b/talk/examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.h @@ -27,17 +27,8 @@ #import -@class RTCVideoTrack; - -// This class encapsulates VideoRenderIosView. -@interface APPRTCVideoView : UIView - -// Property to get/set required video orientation. -@property(nonatomic, assign) UIInterfaceOrientation videoOrientation; -// Specifies whether the object represents a local or remote video stream. -@property(nonatomic, assign) BOOL isRemote; - -// Sets up the underlying renderer and track objects. -- (void)renderVideoTrackInterface:(RTCVideoTrack*)track; - +// The main application class of the AppRTCDemo iOS app demonstrating +// interoperability between the Objective C implementation of PeerConnection +// and the apprtc.appspot.com demo webapp. +@interface APPRTCAppDelegate : NSObject @end diff --git a/talk/examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.m b/talk/examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.m new file mode 100644 index 000000000..58963d53e --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.m @@ -0,0 +1,65 @@ +/* + * libjingle + * Copyright 2013, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "APPRTCAppDelegate.h" + +#import "APPRTCViewController.h" +#import "RTCPeerConnectionFactory.h" + +@implementation APPRTCAppDelegate { + UIWindow* _window; +} + +#pragma mark - UIApplicationDelegate methods + +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + [RTCPeerConnectionFactory initializeSSL]; + _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + APPRTCViewController* viewController = + [[APPRTCViewController alloc] initWithNibName:@"APPRTCViewController" + bundle:nil]; + _window.rootViewController = viewController; + [_window makeKeyAndVisible]; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication*)application { + [[self appRTCViewController] applicationWillResignActive:application]; +} + +- (void)applicationWillTerminate:(UIApplication*)application { + [RTCPeerConnectionFactory deinitializeSSL]; +} + +#pragma mark - Private + +- (APPRTCViewController*)appRTCViewController { + return (APPRTCViewController*)_window.rootViewController; +} + +@end diff --git a/talk/examples/ios/AppRTCDemo/APPRTCViewController.h b/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.h similarity index 90% rename from talk/examples/ios/AppRTCDemo/APPRTCViewController.h rename to talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.h index f5fcee41f..5b10199c2 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCViewController.h +++ b/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.h @@ -27,8 +27,6 @@ #import -@class APPRTCVideoView; - // The view controller that is displayed when AppRTCDemo is loaded. @interface APPRTCViewController : UIViewController @@ -37,10 +35,6 @@ @property(weak, nonatomic) IBOutlet UITextView* logView; @property(weak, nonatomic) IBOutlet UIView* blackView; -@property(nonatomic, strong) APPRTCVideoView* remoteVideoView; -@property(nonatomic, strong) APPRTCVideoView* localVideoView; - -- (void)displayText:(NSString*)text; -- (void)resetUI; +- (void)applicationWillResignActive:(UIApplication*)application; @end diff --git a/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.m b/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.m new file mode 100644 index 000000000..a4a0bd33d --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.m @@ -0,0 +1,231 @@ +/* + * libjingle + * Copyright 2013, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "APPRTCViewController.h" + +#import +#import "APPRTCConnectionManager.h" +#import "RTCEAGLVideoView.h" + +// Padding space for local video view with its parent. +static CGFloat const kLocalViewPadding = 20; + +@interface APPRTCViewController () + +@property(nonatomic, assign) UIInterfaceOrientation statusBarOrientation; +@property(nonatomic, strong) RTCEAGLVideoView* localVideoView; +@property(nonatomic, strong) RTCEAGLVideoView* remoteVideoView; +@end + +@implementation APPRTCViewController { + APPRTCConnectionManager* _connectionManager; + CGSize _localVideoSize; + CGSize _remoteVideoSize; +} + +- (instancetype)initWithNibName:(NSString*)nibName + bundle:(NSBundle*)bundle { + if (self = [super initWithNibName:nibName bundle:bundle]) { + _connectionManager = + [[APPRTCConnectionManager alloc] initWithDelegate:self + logger:self]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.statusBarOrientation = + [UIApplication sharedApplication].statusBarOrientation; + self.roomInput.delegate = self; + [self.roomInput becomeFirstResponder]; +} + +- (void)viewDidLayoutSubviews { + if (self.statusBarOrientation != + [UIApplication sharedApplication].statusBarOrientation) { + self.statusBarOrientation = + [UIApplication sharedApplication].statusBarOrientation; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"StatusBarOrientationDidChange" + object:nil]; + } +} + +- (void)applicationWillResignActive:(UIApplication*)application { + [self logMessage:@"Application lost focus, connection broken."]; + [self disconnect]; +} + +#pragma mark - APPRTCConnectionManagerDelegate + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didReceiveLocalVideoTrack:(RTCVideoTrack*)localVideoTrack { + self.localVideoView.hidden = NO; + self.localVideoView.videoTrack = localVideoTrack; +} + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didReceiveRemoteVideoTrack:(RTCVideoTrack*)remoteVideoTrack { + self.remoteVideoView.videoTrack = remoteVideoTrack; +} + +- (void)connectionManagerDidReceiveHangup:(APPRTCConnectionManager*)manager { + [self showAlertWithMessage:@"Remote hung up."]; + [self disconnect]; +} + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didErrorWithMessage:(NSString*)message { + [self showAlertWithMessage:message]; + [self disconnect]; +} + +#pragma mark - APPRTCLogger + +- (void)logMessage:(NSString*)message { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString* output = + [NSString stringWithFormat:@"%@\n%@", self.logView.text, message]; + self.logView.text = output; + [self.logView + scrollRangeToVisible:NSMakeRange([self.logView.text length], 0)]; + }); +} + +#pragma mark - RTCEAGLVideoViewDelegate + +- (void)videoView:(RTCEAGLVideoView*)videoView + didChangeVideoSize:(CGSize)size { + if (videoView == self.localVideoView) { + _localVideoSize = size; + } else if (videoView == self.remoteVideoView) { + _remoteVideoSize = size; + } else { + NSParameterAssert(NO); + } + [self updateVideoViewLayout]; +} + +#pragma mark - UITextFieldDelegate + +- (void)textFieldDidEndEditing:(UITextField*)textField { + NSString* room = textField.text; + if ([room length] == 0) { + return; + } + textField.hidden = YES; + self.instructionsView.hidden = YES; + self.logView.hidden = NO; + NSString* url = + [NSString stringWithFormat:@"https://apprtc.appspot.com/?r=%@", room]; + [_connectionManager connectToRoomWithURL:[NSURL URLWithString:url]]; + [self setupCaptureSession]; +} + +- (BOOL)textFieldShouldReturn:(UITextField*)textField { + // There is no other control that can take focus, so manually resign focus + // when return (Join) is pressed to trigger |textFieldDidEndEditing|. + [textField resignFirstResponder]; + return YES; +} + +#pragma mark - Private + +- (void)disconnect { + [self resetUI]; + [_connectionManager disconnect]; +} + +- (void)showAlertWithMessage:(NSString*)message { + UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:nil + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alertView show]; +} + +- (void)resetUI { + [self.roomInput resignFirstResponder]; + self.roomInput.text = nil; + self.roomInput.hidden = NO; + self.instructionsView.hidden = NO; + self.logView.hidden = YES; + self.logView.text = nil; + self.blackView.hidden = YES; + [self.remoteVideoView removeFromSuperview]; + self.remoteVideoView = nil; + [self.localVideoView removeFromSuperview]; + self.localVideoView = nil; +} + +- (void)setupCaptureSession { + self.blackView.hidden = NO; + self.remoteVideoView = + [[RTCEAGLVideoView alloc] initWithFrame:self.blackView.bounds]; + self.remoteVideoView.delegate = self; + self.remoteVideoView.transform = CGAffineTransformMakeScale(-1, 1); + [self.blackView addSubview:self.remoteVideoView]; + + self.localVideoView = + [[RTCEAGLVideoView alloc] initWithFrame:self.blackView.bounds]; + self.localVideoView.delegate = self; + [self.blackView addSubview:self.localVideoView]; + [self updateVideoViewLayout]; +} + +- (void)updateVideoViewLayout { + // TODO(tkchin): handle rotation. + CGSize defaultAspectRatio = CGSizeMake(4, 3); + CGSize localAspectRatio = CGSizeEqualToSize(_localVideoSize, CGSizeZero) ? + defaultAspectRatio : _localVideoSize; + CGSize remoteAspectRatio = CGSizeEqualToSize(_remoteVideoSize, CGSizeZero) ? + defaultAspectRatio : _remoteVideoSize; + + CGRect remoteVideoFrame = + AVMakeRectWithAspectRatioInsideRect(remoteAspectRatio, + self.blackView.bounds); + self.remoteVideoView.frame = remoteVideoFrame; + + CGRect localVideoFrame = + AVMakeRectWithAspectRatioInsideRect(localAspectRatio, + self.blackView.bounds); + localVideoFrame.size.width = localVideoFrame.size.width / 3; + localVideoFrame.size.height = localVideoFrame.size.height / 3; + localVideoFrame.origin.x = CGRectGetMaxX(self.blackView.bounds) + - localVideoFrame.size.width - kLocalViewPadding; + localVideoFrame.origin.y = CGRectGetMaxY(self.blackView.bounds) + - localVideoFrame.size.height - kLocalViewPadding; + self.localVideoView.frame = localVideoFrame; +} + +@end diff --git a/talk/examples/ios/AppRTCDemo/AppRTCDemo-Prefix.pch b/talk/examples/objc/AppRTCDemo/ios/AppRTCDemo-Prefix.pch similarity index 100% rename from talk/examples/ios/AppRTCDemo/AppRTCDemo-Prefix.pch rename to talk/examples/objc/AppRTCDemo/ios/AppRTCDemo-Prefix.pch diff --git a/talk/examples/ios/AppRTCDemo/Default.png b/talk/examples/objc/AppRTCDemo/ios/Default.png similarity index 100% rename from talk/examples/ios/AppRTCDemo/Default.png rename to talk/examples/objc/AppRTCDemo/ios/Default.png diff --git a/talk/examples/ios/AppRTCDemo/Info.plist b/talk/examples/objc/AppRTCDemo/ios/Info.plist similarity index 76% rename from talk/examples/ios/AppRTCDemo/Info.plist rename to talk/examples/objc/AppRTCDemo/ios/Info.plist index 72504aa5c..b61648057 100644 --- a/talk/examples/ios/AppRTCDemo/Info.plist +++ b/talk/examples/objc/AppRTCDemo/ios/Info.plist @@ -38,19 +38,6 @@ iPhoneOS - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - com.google.apprtcdemo - CFBundleURLSchemes - - apprtc - - - CFBundleVersion 1.0 UIRequiredDeviceCapabilities @@ -70,8 +57,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight diff --git a/talk/examples/ios/AppRTCDemo/ResourceRules.plist b/talk/examples/objc/AppRTCDemo/ios/ResourceRules.plist similarity index 100% rename from talk/examples/ios/AppRTCDemo/ResourceRules.plist rename to talk/examples/objc/AppRTCDemo/ios/ResourceRules.plist diff --git a/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib b/talk/examples/objc/AppRTCDemo/ios/en.lproj/APPRTCViewController.xib similarity index 99% rename from talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib rename to talk/examples/objc/AppRTCDemo/ios/en.lproj/APPRTCViewController.xib index 62807fe1a..cb2dc8394 100644 --- a/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib +++ b/talk/examples/objc/AppRTCDemo/ios/en.lproj/APPRTCViewController.xib @@ -52,7 +52,7 @@ YES NO IBCocoaTouchFramework - Use Safari and open a URL with a scheme of apprtc to load the test app and connect. i.e. apprtc://apprtc.appspot.com/?r=12345678 Or just enter the room below to connect to apprtc. + Enter the room below to connect to apprtc. 2 IBCocoaTouchFramework diff --git a/talk/examples/ios/AppRTCDemo/main.m b/talk/examples/objc/AppRTCDemo/ios/main.m similarity index 100% rename from talk/examples/ios/AppRTCDemo/main.m rename to talk/examples/objc/AppRTCDemo/ios/main.m diff --git a/talk/examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.h b/talk/examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.h new file mode 100644 index 000000000..77011f194 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.h @@ -0,0 +1,31 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +@interface APPRTCAppDelegate : NSObject +@end diff --git a/talk/examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.m b/talk/examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.m new file mode 100644 index 000000000..d6bd4fc03 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.m @@ -0,0 +1,77 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "APPRTCAppDelegate.h" + +#import "APPRTCViewController.h" +#import "RTCPeerConnectionFactory.h" + +@interface APPRTCAppDelegate () +@end + +@implementation APPRTCAppDelegate { + APPRTCViewController* _viewController; + NSWindow* _window; +} + +#pragma mark - NSApplicationDelegate + +- (void)applicationDidFinishLaunching:(NSNotification*)notification { + [RTCPeerConnectionFactory initializeSSL]; + NSScreen* screen = [NSScreen mainScreen]; + NSRect visibleRect = [screen visibleFrame]; + NSRect windowRect = NSMakeRect(NSMidX(visibleRect), + NSMidY(visibleRect), + 1320, + 1140); + NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask; + _window = [[NSWindow alloc] initWithContentRect:windowRect + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + _window.delegate = self; + [_window makeKeyAndOrderFront:self]; + [_window makeMainWindow]; + _viewController = [[APPRTCViewController alloc] initWithNibName:nil + bundle:nil]; + [_window setContentView:[_viewController view]]; +} + +#pragma mark - NSWindow + +- (void)windowWillClose:(NSNotification*)notification { + [_viewController windowWillClose:notification]; + [RTCPeerConnectionFactory deinitializeSSL]; + [NSApp terminate:self]; +} + +@end + diff --git a/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.h b/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.h new file mode 100644 index 000000000..3ef058c6c --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.h @@ -0,0 +1,34 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +@interface APPRTCViewController : NSViewController + +- (void)windowWillClose:(NSNotification*)notification; + +@end diff --git a/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.m b/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.m new file mode 100644 index 000000000..cf5b83601 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.m @@ -0,0 +1,312 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "APPRTCViewController.h" + +#import +#import "APPRTCConnectionManager.h" +#import "RTCNSGLVideoView.h" + +static NSUInteger const kContentWidth = 1280; +static NSUInteger const kContentHeight = 720; +static NSUInteger const kRoomFieldWidth = 80; +static NSUInteger const kLogViewHeight = 280; + +@class APPRTCMainView; +@protocol APPRTCMainViewDelegate + +- (void)appRTCMainView:(APPRTCMainView*)mainView + didEnterRoomId:(NSString*)roomId; + +@end + +@interface APPRTCMainView : NSView + +@property(nonatomic, weak) id delegate; +@property(nonatomic, readonly) RTCNSGLVideoView* localVideoView; +@property(nonatomic, readonly) RTCNSGLVideoView* remoteVideoView; + +- (void)displayLogMessage:(NSString*)message; + +@end + +@interface APPRTCMainView () +@end +@implementation APPRTCMainView { + NSScrollView* _scrollView; + NSTextField* _roomLabel; + NSTextField* _roomField; + NSTextView* _logView; + RTCNSGLVideoView* _localVideoView; + RTCNSGLVideoView* _remoteVideoView; + CGSize _localVideoSize; + CGSize _remoteVideoSize; +} + ++ (BOOL)requiresConstraintBasedLayout { + return YES; +} + +- (instancetype)initWithFrame:(NSRect)frame { + if (self = [super initWithFrame:frame]) { + [self setupViews]; + } + return self; +} + +- (void)updateConstraints { + NSParameterAssert( + _roomField != nil && _scrollView != nil && _remoteVideoView != nil); + [self removeConstraints:[self constraints]]; + NSDictionary* viewsDictionary = + NSDictionaryOfVariableBindings(_roomLabel, + _roomField, + _scrollView, + _remoteVideoView); + + NSSize remoteViewSize = [self remoteVideoViewSize]; + NSDictionary* metrics = @{ + @"kLogViewHeight" : @(kLogViewHeight), + @"kRoomFieldWidth" : @(kRoomFieldWidth), + @"remoteViewWidth" : @(remoteViewSize.width), + @"remoteViewHeight" : @(remoteViewSize.height), + }; + // Declare this separately to avoid compiler warning about splitting string + // within an NSArray expression. + NSString* verticalConstraint = + @"V:|-[_roomLabel]-[_roomField]-[_scrollView(kLogViewHeight)]" + "-[_remoteVideoView(remoteViewHeight)]-|"; + NSArray* constraintFormats = @[ + verticalConstraint, + @"|-[_roomLabel]", + @"|-[_roomField(kRoomFieldWidth)]", + @"|-[_scrollView(remoteViewWidth)]-|", + @"|-[_remoteVideoView(remoteViewWidth)]-|", + ]; + for (NSString* constraintFormat in constraintFormats) { + NSArray* constraints = + [NSLayoutConstraint constraintsWithVisualFormat:constraintFormat + options:0 + metrics:metrics + views:viewsDictionary]; + for (NSLayoutConstraint* constraint in constraints) { + [self addConstraint:constraint]; + } + } + [super updateConstraints]; +} + +- (void)displayLogMessage:(NSString*)message { + _logView.string = + [NSString stringWithFormat:@"%@%@\n", _logView.string, message]; + NSRange range = NSMakeRange([_logView.string length], 0); + [_logView scrollRangeToVisible:range]; +} + +#pragma mark - NSControl delegate + +- (void)controlTextDidEndEditing:(NSNotification*)notification { + NSDictionary* userInfo = [notification userInfo]; + NSInteger textMovement = [userInfo[@"NSTextMovement"] intValue]; + if (textMovement == NSReturnTextMovement) { + [self.delegate appRTCMainView:self didEnterRoomId:_roomField.stringValue]; + } +} + +#pragma mark - RTCNSGLVideoViewDelegate + +- (void)videoView:(RTCNSGLVideoView*)videoView + didChangeVideoSize:(NSSize)size { + if (videoView == _remoteVideoView) { + _remoteVideoSize = size; + } else if (videoView == _localVideoView) { + _localVideoSize = size; + } else { + return; + } + [self setNeedsUpdateConstraints:YES]; +} + +#pragma mark - Private + +- (void)setupViews { + NSParameterAssert([[self subviews] count] == 0); + + _roomLabel = [[NSTextField alloc] initWithFrame:NSZeroRect]; + [_roomLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_roomLabel setBezeled:NO]; + [_roomLabel setDrawsBackground:NO]; + [_roomLabel setEditable:NO]; + [_roomLabel setStringValue:@"Enter AppRTC room id:"]; + [self addSubview:_roomLabel]; + + _roomField = [[NSTextField alloc] initWithFrame:NSZeroRect]; + [_roomField setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:_roomField]; + [_roomField setEditable:YES]; + [_roomField setDelegate:self]; + + _logView = [[NSTextView alloc] initWithFrame:NSZeroRect]; + [_logView setMinSize:NSMakeSize(0, kLogViewHeight)]; + [_logView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + [_logView setVerticallyResizable:YES]; + [_logView setAutoresizingMask:NSViewWidthSizable]; + NSTextContainer* textContainer = [_logView textContainer]; + NSSize containerSize = NSMakeSize(kContentWidth, FLT_MAX); + [textContainer setContainerSize:containerSize]; + [textContainer setWidthTracksTextView:YES]; + [_logView setEditable:NO]; + + _scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; + [_scrollView setTranslatesAutoresizingMaskIntoConstraints:NO]; + [_scrollView setHasVerticalScroller:YES]; + [_scrollView setDocumentView:_logView]; + [self addSubview:_scrollView]; + + NSOpenGLPixelFormatAttribute attributes[] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFADepthSize, 24, + NSOpenGLPFAOpenGLProfile, + NSOpenGLProfileVersion3_2Core, + 0 + }; + NSOpenGLPixelFormat* pixelFormat = + [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; + _remoteVideoView = [[RTCNSGLVideoView alloc] initWithFrame:NSZeroRect + pixelFormat:pixelFormat]; + [_remoteVideoView setTranslatesAutoresizingMaskIntoConstraints:NO]; + _remoteVideoView.delegate = self; + [self addSubview:_remoteVideoView]; + + // TODO(tkchin): create local video view. + // https://code.google.com/p/webrtc/issues/detail?id=3417. +} + +- (NSSize)remoteVideoViewSize { + if (_remoteVideoSize.width > 0 && _remoteVideoSize.height > 0) { + return _remoteVideoSize; + } else { + return NSMakeSize(kContentWidth, kContentHeight); + } +} + +- (NSSize)localVideoViewSize { + return NSZeroSize; +} + +@end + +@interface APPRTCViewController () + +@property(nonatomic, readonly) APPRTCMainView* mainView; +@end + +@implementation APPRTCViewController { + APPRTCConnectionManager* _connectionManager; +} + +- (instancetype)initWithNibName:(NSString*)nibName + bundle:(NSBundle*)bundle { + if (self = [super initWithNibName:nibName bundle:bundle]) { + _connectionManager = + [[APPRTCConnectionManager alloc] initWithDelegate:self + logger:self]; + } + return self; +} + +- (void)dealloc { + [self disconnect]; +} + +- (void)loadView { + APPRTCMainView* view = [[APPRTCMainView alloc] initWithFrame:NSZeroRect]; + [view setTranslatesAutoresizingMaskIntoConstraints:NO]; + view.delegate = self; + self.view = view; +} + +- (void)windowWillClose:(NSNotification*)notification { + [self disconnect]; +} + +#pragma mark - APPRTCConnectionManagerDelegate + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didReceiveLocalVideoTrack:(RTCVideoTrack*)localVideoTrack { + self.mainView.localVideoView.videoTrack = localVideoTrack; +} + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didReceiveRemoteVideoTrack:(RTCVideoTrack*)remoteVideoTrack { + self.mainView.remoteVideoView.videoTrack = remoteVideoTrack; +} + +- (void)connectionManagerDidReceiveHangup:(APPRTCConnectionManager*)manager { + [self showAlertWithMessage:@"Remote closed connection"]; + [self disconnect]; +} + +- (void)connectionManager:(APPRTCConnectionManager*)manager + didErrorWithMessage:(NSString*)message { + [self showAlertWithMessage:message]; + [self disconnect]; +} + +#pragma mark - APPRTCLogger + +- (void)logMessage:(NSString*)message { + [self.mainView displayLogMessage:message]; +} + +#pragma mark - APPRTCMainViewDelegate + +- (void)appRTCMainView:(APPRTCMainView*)mainView + didEnterRoomId:(NSString*)roomId { + NSString* urlString = + [NSString stringWithFormat:@"https://apprtc.appspot.com/?r=%@", roomId]; + [_connectionManager connectToRoomWithURL:[NSURL URLWithString:urlString]]; +} + +#pragma mark - Private + +- (APPRTCMainView*)mainView { + return (APPRTCMainView*)self.view; +} + +- (void)showAlertWithMessage:(NSString*)message { + NSAlert* alert = [[NSAlert alloc] init]; + [alert setMessageText:message]; + [alert runModal]; +} + +- (void)disconnect { + self.mainView.remoteVideoView.videoTrack = nil; + [_connectionManager disconnect]; +} + +@end diff --git a/talk/examples/objc/AppRTCDemo/mac/Info.plist b/talk/examples/objc/AppRTCDemo/mac/Info.plist new file mode 100644 index 000000000..4dcb24038 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/mac/Info.plist @@ -0,0 +1,29 @@ + + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.Google.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSPrincipalClass + NSApplication + + \ No newline at end of file diff --git a/talk/examples/objc/AppRTCDemo/mac/main.m b/talk/examples/objc/AppRTCDemo/mac/main.m new file mode 100644 index 000000000..9ce3de27a --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/mac/main.m @@ -0,0 +1,39 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#import "APPRTCAppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + [NSApplication sharedApplication]; + APPRTCAppDelegate* delegate = [[APPRTCAppDelegate alloc] init]; + [NSApp setDelegate:delegate]; + [NSApp run]; + } +} diff --git a/talk/examples/ios/Icon.png b/talk/examples/objc/Icon.png similarity index 100% rename from talk/examples/ios/Icon.png rename to talk/examples/objc/Icon.png diff --git a/talk/examples/objc/README b/talk/examples/objc/README new file mode 100644 index 000000000..bfe18b37c --- /dev/null +++ b/talk/examples/objc/README @@ -0,0 +1,3 @@ +This directory contains sample iOS and mac clients for http://apprtc.appspot.com + +See ../../app/webrtc/objc/README for information on how to use it. diff --git a/talk/examples/peerconnection/client/conductor.cc b/talk/examples/peerconnection/client/conductor.cc index e9eaeadb5..aacbe63f6 100644 --- a/talk/examples/peerconnection/client/conductor.cc +++ b/talk/examples/peerconnection/client/conductor.cc @@ -103,6 +103,7 @@ bool Conductor::InitializePeerConnection() { server.uri = GetPeerConnectionString(); servers.push_back(server); peer_connection_ = peer_connection_factory_->CreatePeerConnection(servers, + NULL, NULL, NULL, this); diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index 1630f1ffd..8c4b6b9ce 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -124,6 +124,7 @@ # included here, or better yet, build a proper .jar in webrtc # and include it here. 'android_java_files': [ + 'app/webrtc/java/android/org/webrtc/VideoRendererGui.java', 'app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java', '<(webrtc_modules_dir)/audio_device/android/java/src/org/webrtc/voiceengine/AudioManagerAndroid.java', '<(webrtc_modules_dir)/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java', @@ -173,7 +174,8 @@ }, ], }], - ['OS=="ios" or (OS=="mac" and target_arch!="ia32")', { + ['OS=="ios" or (OS=="mac" and target_arch!="ia32" and mac_sdk>="10.7")', { + # The >= 10.7 above is required for ARC. 'targets': [ { 'target_name': 'libjingle_peerconnection_objc', @@ -184,8 +186,11 @@ 'sources': [ 'app/webrtc/objc/RTCAudioTrack+Internal.h', 'app/webrtc/objc/RTCAudioTrack.mm', + 'app/webrtc/objc/RTCDataChannel+Internal.h', + 'app/webrtc/objc/RTCDataChannel.mm', 'app/webrtc/objc/RTCEnumConverter.h', 'app/webrtc/objc/RTCEnumConverter.mm', + 'app/webrtc/objc/RTCI420Frame+Internal.h', 'app/webrtc/objc/RTCI420Frame.mm', 'app/webrtc/objc/RTCICECandidate+Internal.h', 'app/webrtc/objc/RTCICECandidate.mm', @@ -201,6 +206,7 @@ 'app/webrtc/objc/RTCMediaStream.mm', 'app/webrtc/objc/RTCMediaStreamTrack+Internal.h', 'app/webrtc/objc/RTCMediaStreamTrack.mm', + 'app/webrtc/objc/RTCOpenGLVideoRenderer.mm', 'app/webrtc/objc/RTCPair.m', 'app/webrtc/objc/RTCPeerConnection+Internal.h', 'app/webrtc/objc/RTCPeerConnection.mm', @@ -209,6 +215,8 @@ 'app/webrtc/objc/RTCPeerConnectionObserver.mm', 'app/webrtc/objc/RTCSessionDescription+Internal.h', 'app/webrtc/objc/RTCSessionDescription.mm', + 'app/webrtc/objc/RTCStatsReport+Internal.h', + 'app/webrtc/objc/RTCStatsReport.mm', 'app/webrtc/objc/RTCVideoCapturer+Internal.h', 'app/webrtc/objc/RTCVideoCapturer.mm', 'app/webrtc/objc/RTCVideoRenderer+Internal.h', @@ -219,6 +227,7 @@ 'app/webrtc/objc/RTCVideoTrack.mm', 'app/webrtc/objc/public/RTCAudioSource.h', 'app/webrtc/objc/public/RTCAudioTrack.h', + 'app/webrtc/objc/public/RTCDataChannel.h', 'app/webrtc/objc/public/RTCI420Frame.h', 'app/webrtc/objc/public/RTCICECandidate.h', 'app/webrtc/objc/public/RTCICEServer.h', @@ -226,16 +235,18 @@ 'app/webrtc/objc/public/RTCMediaSource.h', 'app/webrtc/objc/public/RTCMediaStream.h', 'app/webrtc/objc/public/RTCMediaStreamTrack.h', + 'app/webrtc/objc/public/RTCOpenGLVideoRenderer.h', 'app/webrtc/objc/public/RTCPair.h', 'app/webrtc/objc/public/RTCPeerConnection.h', 'app/webrtc/objc/public/RTCPeerConnectionDelegate.h', 'app/webrtc/objc/public/RTCPeerConnectionFactory.h', 'app/webrtc/objc/public/RTCSessionDescription.h', - 'app/webrtc/objc/public/RTCSessionDescriptonDelegate.h', + 'app/webrtc/objc/public/RTCSessionDescriptionDelegate.h', + 'app/webrtc/objc/public/RTCStatsDelegate.h', + 'app/webrtc/objc/public/RTCStatsReport.h', 'app/webrtc/objc/public/RTCTypes.h', 'app/webrtc/objc/public/RTCVideoCapturer.h', 'app/webrtc/objc/public/RTCVideoRenderer.h', - 'app/webrtc/objc/public/RTCVideoRendererDelegate.h', 'app/webrtc/objc/public/RTCVideoSource.h', 'app/webrtc/objc/public/RTCVideoTrack.h', ], @@ -251,13 +262,50 @@ ], 'link_settings': { 'libraries': [ - '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', '-lstdc++', ], }, 'xcode_settings': { 'CLANG_ENABLE_OBJC_ARC': 'YES', + # common.gypi enables this for mac but we want this to be disabled + # like it is for ios. + 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', }, + 'conditions': [ + ['OS=="ios"', { + 'sources': [ + 'app/webrtc/objc/RTCEAGLVideoView+Internal.h', + 'app/webrtc/objc/RTCEAGLVideoView.m', + 'app/webrtc/objc/public/RTCEAGLVideoView.h', + ], + 'link_settings': { + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '-framework CoreGraphics', + '-framework GLKit', + ], + }, + }, + }], + ['OS=="mac"', { + 'sources': [ + 'app/webrtc/objc/RTCNSGLVideoView.m', + 'app/webrtc/objc/public/RTCNSGLVideoView.h', + ], + 'xcode_settings': { + # Need to build against 10.7 framework for full ARC support + # on OSX. + 'MACOSX_DEPLOYMENT_TARGET' : '10.7', + }, + 'link_settings': { + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '-framework Cocoa', + ], + }, + }, + }], + ], }, # target libjingle_peerconnection_objc ], }], @@ -666,16 +714,16 @@ }], ['OS=="ios"', { 'sources': [ + 'base/iosfilesystem.mm', 'base/scoped_autorelease_pool.mm', ], 'dependencies': [ - '../net/third_party/nss/ssl.gyp:libssl', + '<(DEPTH)/net/third_party/nss/ssl.gyp:libssl', ], 'all_dependent_settings': { 'xcode_settings': { 'OTHER_LDFLAGS': [ '-framework Foundation', - '-framework IOKit', '-framework Security', '-framework SystemConfiguration', '-framework UIKit', @@ -801,9 +849,10 @@ '<(DEPTH)/third_party/usrsctp/usrsctp.gyp:usrsctplib', '<(webrtc_root)/modules/modules.gyp:video_capture_module', '<(webrtc_root)/modules/modules.gyp:video_render_module', - '<(webrtc_root)/video_engine/video_engine.gyp:video_engine_core', + '<(webrtc_root)/webrtc.gyp:webrtc', '<(webrtc_root)/voice_engine/voice_engine.gyp:voice_engine', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', 'libjingle', 'libjingle_sound', ], @@ -873,6 +922,7 @@ 'media/sctp/sctpdataengine.h', 'media/webrtc/webrtccommon.h', 'media/webrtc/webrtcexport.h', + 'media/webrtc/webrtcmediaengine.cc', 'media/webrtc/webrtcmediaengine.h', 'media/webrtc/webrtcpassthroughrender.cc', 'media/webrtc/webrtcpassthroughrender.h', @@ -884,6 +934,8 @@ 'media/webrtc/webrtcvideoencoderfactory.h', #'media/webrtc/webrtcvideoengine.cc', 'media/webrtc/webrtcvideoengine.h', + 'media/webrtc/webrtcvideoengine2.cc', + 'media/webrtc/webrtcvideoengine2.h', 'media/webrtc/webrtcvideoframe.cc', 'media/webrtc/webrtcvideoframe.h', 'media/webrtc/webrtcvie.h', @@ -978,7 +1030,6 @@ }], ['OS=="ios"', { 'sources': [ - 'media/devices/iosdeviceinfo.cc', 'media/devices/mobiledevicemanager.cc', ], 'include_dirs': [ @@ -1116,6 +1167,8 @@ 'session/tunnel/securetunnelsessionclient.h', 'session/media/audiomonitor.cc', 'session/media/audiomonitor.h', + 'session/media/bundlefilter.cc', + 'session/media/bundlefilter.h', 'session/media/call.cc', 'session/media/call.h', 'session/media/channel.cc', @@ -1141,8 +1194,6 @@ 'session/media/soundclip.h', 'session/media/srtpfilter.cc', 'session/media/srtpfilter.h', - 'session/media/ssrcmuxfilter.cc', - 'session/media/ssrcmuxfilter.h', 'session/media/typingmonitor.cc', 'session/media/typingmonitor.h', 'session/media/voicechannel.h', diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp index 0d3af31a1..34178503a 100755 --- a/talk/libjingle_examples.gyp +++ b/talk/libjingle_examples.gyp @@ -271,7 +271,7 @@ - ['OS=="ios"', { + ['OS=="ios" or (OS=="mac" and target_arch!="ia32" and mac_sdk>="10.8")', { 'targets': [ { 'target_name': 'AppRTCDemo', @@ -279,47 +279,71 @@ 'product_name': 'AppRTCDemo', 'mac_bundle': 1, 'mac_bundle_resources': [ - 'examples/ios/AppRTCDemo/ResourceRules.plist', - 'examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib', - 'examples/ios/AppRTCDemo/ios_channel.html', - 'examples/ios/Icon.png', + 'examples/objc/AppRTCDemo/channel.html', ], 'dependencies': [ 'libjingle.gyp:libjingle_peerconnection_objc', ], 'conditions': [ + ['OS=="ios"', { + 'mac_bundle_resources': [ + 'examples/objc/AppRTCDemo/ios/ResourceRules.plist', + 'examples/objc/AppRTCDemo/ios/en.lproj/APPRTCViewController.xib', + 'examples/objc/Icon.png', + ], + 'sources': [ + 'examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.h', + 'examples/objc/AppRTCDemo/ios/APPRTCAppDelegate.m', + 'examples/objc/AppRTCDemo/ios/APPRTCViewController.h', + 'examples/objc/AppRTCDemo/ios/APPRTCViewController.m', + 'examples/objc/AppRTCDemo/ios/AppRTCDemo-Prefix.pch', + 'examples/objc/AppRTCDemo/ios/main.m', + ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'examples/objc/AppRTCDemo/ios/Info.plist', + }, + }], + ['OS=="mac"', { + 'sources': [ + 'examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.h', + 'examples/objc/AppRTCDemo/mac/APPRTCAppDelegate.m', + 'examples/objc/AppRTCDemo/mac/APPRTCViewController.h', + 'examples/objc/AppRTCDemo/mac/APPRTCViewController.m', + 'examples/objc/AppRTCDemo/mac/main.m', + ], + 'xcode_settings': { + 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', + 'INFOPLIST_FILE': 'examples/objc/AppRTCDemo/mac/Info.plist', + 'MACOSX_DEPLOYMENT_TARGET' : '10.8', + 'OTHER_LDFLAGS': [ + '-framework AVFoundation', + '-framework WebKit', + ], + }, + }], ['target_arch=="ia32"', { 'dependencies' : [ '<(DEPTH)/testing/iossim/iossim.gyp:iossim#host', ], }], ], + 'include_dirs': [ + 'examples/objc/APPRTCDemo', + ], 'sources': [ - 'examples/ios/AppRTCDemo/APPRTCAppClient.h', - 'examples/ios/AppRTCDemo/APPRTCAppClient.m', - 'examples/ios/AppRTCDemo/APPRTCAppDelegate.h', - 'examples/ios/AppRTCDemo/APPRTCAppDelegate.m', - 'examples/ios/AppRTCDemo/APPRTCViewController.h', - 'examples/ios/AppRTCDemo/APPRTCViewController.m', - 'examples/ios/AppRTCDemo/APPRTCVideoView.h', - 'examples/ios/AppRTCDemo/APPRTCVideoView.m', - 'examples/ios/AppRTCDemo/AppRTCDemo-Prefix.pch', - 'examples/ios/AppRTCDemo/GAEChannelClient.h', - 'examples/ios/AppRTCDemo/GAEChannelClient.m', - 'examples/ios/AppRTCDemo/main.m', + 'examples/objc/AppRTCDemo/APPRTCAppClient.h', + 'examples/objc/AppRTCDemo/APPRTCAppClient.m', + 'examples/objc/AppRTCDemo/APPRTCConnectionManager.h', + 'examples/objc/AppRTCDemo/APPRTCConnectionManager.m', + 'examples/objc/AppRTCDemo/GAEChannelClient.h', + 'examples/objc/AppRTCDemo/GAEChannelClient.m', ], 'xcode_settings': { 'CLANG_ENABLE_OBJC_ARC': 'YES', - 'INFOPLIST_FILE': 'examples/ios/AppRTCDemo/Info.plist', - 'OTHER_LDFLAGS': [ - '-framework CoreGraphics', - '-framework Foundation', - '-framework UIKit', - ], }, }, # target AppRTCDemo ], # targets - }], # OS=="ios" + }], # OS=="ios" or (OS=="mac" and target_arch!="ia32" and mac_sdk>="10.8") ['OS=="android"', { 'targets': [ @@ -352,10 +376,9 @@ 'examples/android/res/values/strings.xml', 'examples/android/src/org/appspot/apprtc/AppRTCClient.java', 'examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java', + 'examples/android/src/org/appspot/apprtc/AppRTCGLView.java', 'examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java', - 'examples/android/src/org/appspot/apprtc/FramePool.java', 'examples/android/src/org/appspot/apprtc/GAEChannelClient.java', - 'examples/android/src/org/appspot/apprtc/VideoStreamsView.java', ], 'outputs': [ '<(PRODUCT_DIR)/AppRTCDemo-debug.apk', @@ -371,7 +394,8 @@ 'cp <(PRODUCT_DIR)/libjingle_peerconnection.jar examples/android/libs/ &&' '<(android_strip) -o examples/android/libs/<(android_app_abi)/libjingle_peerconnection_so.so <(PRODUCT_DIR)/libjingle_peerconnection_so.so &&' 'cd examples/android && ' - '{ ant debug > <(ant_log) 2>&1 || ' + '{ ANDROID_SDK_ROOT=<(android_sdk_root) ' + 'ant debug > <(ant_log) 2>&1 || ' ' { cat <(ant_log) ; exit 1; } } && ' 'cd - > /dev/null && ' 'cp examples/android/bin/AppRTCDemo-debug.apk <(_outputs)' diff --git a/talk/libjingle_media_unittest.isolate b/talk/libjingle_media_unittest.isolate index 36b50b5f3..666478b43 100644 --- a/talk/libjingle_media_unittest.isolate +++ b/talk/libjingle_media_unittest.isolate @@ -29,16 +29,14 @@ ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_media_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ 'media/testdata/captured-320x240-2s-48.frames', - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_media_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/talk/libjingle_p2p_unittest.isolate b/talk/libjingle_p2p_unittest.isolate index b5ad4ff2d..9ff0d77ad 100644 --- a/talk/libjingle_p2p_unittest.isolate +++ b/talk/libjingle_p2p_unittest.isolate @@ -29,15 +29,13 @@ ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_p2p_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_p2p_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/talk/libjingle_peerconnection_unittest.isolate b/talk/libjingle_peerconnection_unittest.isolate index e7dd6878a..df07d4a9d 100644 --- a/talk/libjingle_peerconnection_unittest.isolate +++ b/talk/libjingle_peerconnection_unittest.isolate @@ -29,15 +29,13 @@ ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_peerconnection_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_peerconnection_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/talk/libjingle_sound_unittest.isolate b/talk/libjingle_sound_unittest.isolate index 716633795..728e81041 100644 --- a/talk/libjingle_sound_unittest.isolate +++ b/talk/libjingle_sound_unittest.isolate @@ -29,15 +29,13 @@ ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_sound_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_sound_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index 31dc55171..016f0a506 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -104,6 +104,7 @@ { 'target_name': 'libjingle_unittest', 'type': 'executable', + 'includes': [ 'build/ios_tests.gypi', ], 'dependencies': [ 'gunit', 'libjingle.gyp:libjingle', @@ -286,9 +287,7 @@ 'media/base/videoengine_unittest.h', 'media/devices/dummydevicemanager_unittest.cc', 'media/devices/filevideocapturer_unittest.cc', - # TODO(jiayl): Enable the SCTP test once the memcheck and tsan bots - # failures are fixed (issue 2846). - #'media/sctp/sctpdataengine_unittest.cc', + 'media/sctp/sctpdataengine_unittest.cc', 'media/webrtc/webrtcpassthroughrender_unittest.cc', 'media/webrtc/webrtcvideocapturer_unittest.cc', # Omitted because depends on non-open-source testdata files. @@ -299,6 +298,8 @@ # TODO(ronghuawu): Reenable these tests. # 'media/devices/devicemanager_unittest.cc', 'media/webrtc/webrtcvideoengine_unittest.cc', + 'media/webrtc/webrtcvideoengine2_unittest.cc', + 'media/webrtc/webrtcvideoengine2_unittest.h', 'media/webrtc/webrtcvoiceengine_unittest.cc', ], 'conditions': [ @@ -356,6 +357,7 @@ 'p2p/client/connectivitychecker_unittest.cc', 'p2p/client/fakeportallocator.h', 'p2p/client/portallocator_unittest.cc', + 'session/media/bundlefilter_unittest.cc', 'session/media/channel_unittest.cc', 'session/media/channelmanager_unittest.cc', 'session/media/currentspeakermonitor_unittest.cc', @@ -365,7 +367,6 @@ 'session/media/mediasessionclient_unittest.cc', 'session/media/rtcpmuxfilter_unittest.cc', 'session/media/srtpfilter_unittest.cc', - 'session/media/ssrcmuxfilter_unittest.cc', ], 'conditions': [ ['OS=="win"', { @@ -383,20 +384,25 @@ 'target_name': 'libjingle_peerconnection_unittest', 'type': 'executable', 'dependencies': [ + '<(DEPTH)/testing/gmock.gyp:gmock', 'gunit', 'libjingle.gyp:libjingle', 'libjingle.gyp:libjingle_p2p', 'libjingle.gyp:libjingle_peerconnection', 'libjingle_unittest_main', ], - # TODO(ronghuawu): Reenable below unit tests that require gmock. + 'direct_dependent_settings': { + 'include_dirs': [ + '<(DEPTH)/testing/gmock/include', + ], + }, 'sources': [ - # 'app/webrtc/datachannel_unittest.cc', + 'app/webrtc/datachannel_unittest.cc', 'app/webrtc/dtmfsender_unittest.cc', 'app/webrtc/jsepsessiondescription_unittest.cc', 'app/webrtc/localaudiosource_unittest.cc', - # 'app/webrtc/mediastream_unittest.cc', - # 'app/webrtc/mediastreamhandler_unittest.cc', + 'app/webrtc/mediastream_unittest.cc', + 'app/webrtc/mediastreamhandler_unittest.cc', 'app/webrtc/mediastreamsignaling_unittest.cc', 'app/webrtc/peerconnection_unittest.cc', 'app/webrtc/peerconnectionendtoend_unittest.cc', @@ -405,6 +411,7 @@ # 'app/webrtc/peerconnectionproxy_unittest.cc', 'app/webrtc/remotevideocapturer_unittest.cc', 'app/webrtc/sctputils.cc', + 'app/webrtc/statscollector_unittest.cc', 'app/webrtc/test/fakeaudiocapturemodule.cc', 'app/webrtc/test/fakeaudiocapturemodule.h', 'app/webrtc/test/fakeaudiocapturemodule_unittest.cc', @@ -423,6 +430,21 @@ 'app/webrtc/webrtcsdp_unittest.cc', 'app/webrtc/webrtcsession_unittest.cc', ], + 'conditions': [ + ['OS=="android"', { + # We want gmock features that use tr1::tuple, but we currently + # don't support the variadic templates used by libstdc++'s + # implementation. gmock supports this scenario by providing its + # own implementation but we must opt in to it. + 'defines': [ + 'GTEST_USE_OWN_TR1_TUPLE=1', + # GTEST_USE_OWN_TR1_TUPLE only works if GTEST_HAS_TR1_TUPLE is set. + # gmock r625 made it so that GTEST_HAS_TR1_TUPLE is set to 0 + # automatically on android, so it has to be set explicitly here. + 'GTEST_HAS_TR1_TUPLE=1', + ], + }], + ], }, # target libjingle_peerconnection_unittest ], 'conditions': [ @@ -492,11 +514,37 @@ # does just fine on 10.6 too). 'targets': [ { - 'target_name': 'libjingle_peerconnection_objc_test', + 'target_name': 'libjingle_peerconnection_objc_test', + 'type': 'executable', + 'includes': [ 'build/ios_tests.gypi', ], + 'dependencies': [ + 'gunit', + 'libjingle.gyp:libjingle_peerconnection_objc', + ], + 'sources': [ + 'app/webrtc/objctests/RTCPeerConnectionSyncObserver.h', + 'app/webrtc/objctests/RTCPeerConnectionSyncObserver.m', + 'app/webrtc/objctests/RTCPeerConnectionTest.mm', + 'app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h', + 'app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m', + # TODO(fischman): figure out if this works for ios or if it + # needs a GUI driver. + 'app/webrtc/objctests/mac/main.mm', + ], + 'FRAMEWORK_SEARCH_PATHS': [ + '$(inherited)', + '$(SDKROOT)/Developer/Library/Frameworks', + '$(DEVELOPER_LIBRARY_DIR)/Frameworks', + ], + + # TODO(fischman): there is duplication here with + # build/ios_tests.gypi, because for historical reasons the + # mac x64 bots expect this unittest to be in a bundle + # directory (.app). Once the bots don't expect this + # anymore, remove this duplication. 'variables': { - 'infoplist_file': './app/webrtc/objctests/Info.plist', + 'infoplist_file': 'build/ios_test.plist', }, - 'type': 'executable', 'mac_bundle': 1, 'mac_bundle_resources': [ '<(infoplist_file)', @@ -508,31 +556,18 @@ ], 'xcode_settings': { 'CLANG_ENABLE_OBJC_ARC': 'YES', + # common.gypi enables this for mac but we want this to be disabled + # like it is for ios. + 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', 'INFOPLIST_FILE': '<(infoplist_file)', }, - 'dependencies': [ - 'gunit', - 'libjingle.gyp:libjingle_peerconnection_objc', - ], - 'FRAMEWORK_SEARCH_PATHS': [ - '$(inherited)', - '$(SDKROOT)/Developer/Library/Frameworks', - '$(DEVELOPER_LIBRARY_DIR)/Frameworks', - ], - 'sources': [ - 'app/webrtc/objctests/RTCPeerConnectionSyncObserver.h', - 'app/webrtc/objctests/RTCPeerConnectionSyncObserver.m', - 'app/webrtc/objctests/RTCPeerConnectionTest.mm', - 'app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h', - 'app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m', - ], 'conditions': [ - ['OS=="mac" or OS=="ios"', { - 'sources': [ - # TODO(fischman): figure out if this works for ios or if it - # needs a GUI driver. - 'app/webrtc/objctests/mac/main.mm', - ], + ['OS=="mac"', { + 'xcode_settings': { + # Need to build against 10.7 framework for full ARC support + # on OSX. + 'MACOSX_DEPLOYMENT_TARGET' : '10.7', + }, }], ], }, # target libjingle_peerconnection_objc_test diff --git a/talk/libjingle_unittest.isolate b/talk/libjingle_unittest.isolate index e678af013..0507f6a8e 100644 --- a/talk/libjingle_unittest.isolate +++ b/talk/libjingle_unittest.isolate @@ -29,15 +29,13 @@ ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../testing/test_env.py', '<(PRODUCT_DIR)/libjingle_unittest<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/talk/media/base/codec.cc b/talk/media/base/codec.cc index 2d54c9907..c6f0ea583 100644 --- a/talk/media/base/codec.cc +++ b/talk/media/base/codec.cc @@ -31,12 +31,14 @@ #include #include "talk/base/common.h" +#include "talk/base/logging.h" #include "talk/base/stringencode.h" #include "talk/base/stringutils.h" namespace cricket { static const int kMaxStaticPayloadId = 95; +const int kMaxPayloadId = 127; bool FeedbackParam::operator==(const FeedbackParam& other) const { return _stricmp(other.id().c_str(), id().c_str()) == 0 && @@ -117,6 +119,10 @@ void Codec::SetParam(const std::string& name, int value) { params[name] = talk_base::ToString(value); } +bool Codec::RemoveParam(const std::string& name) { + return params.erase(name) == 1; +} + void Codec::AddFeedbackParam(const FeedbackParam& param) { feedback_params.Add(param); } @@ -160,6 +166,55 @@ std::string VideoCodec::ToString() const { return os.str(); } +VideoCodec VideoCodec::CreateRtxCodec(int rtx_payload_type, + int associated_payload_type) { + VideoCodec rtx_codec(rtx_payload_type, kRtxCodecName, 0, 0, 0, 0); + rtx_codec.SetParam(kCodecParamAssociatedPayloadType, associated_payload_type); + return rtx_codec; +} + +VideoCodec::CodecType VideoCodec::GetCodecType() const { + const char* payload_name = name.c_str(); + if (_stricmp(payload_name, kRedCodecName) == 0) { + return CODEC_RED; + } + if (_stricmp(payload_name, kUlpfecCodecName) == 0) { + return CODEC_ULPFEC; + } + if (_stricmp(payload_name, kRtxCodecName) == 0) { + return CODEC_RTX; + } + + return CODEC_VIDEO; +} + +bool VideoCodec::ValidateCodecFormat() const { + if (id < 0 || id > 127) { + LOG(LS_ERROR) << "Codec with invalid payload type: " << ToString(); + return false; + } + if (GetCodecType() != CODEC_VIDEO) { + return true; + } + + // Video validation from here on. + + if (width <= 0 || height <= 0) { + LOG(LS_ERROR) << "Codec with invalid dimensions: " << ToString(); + return false; + } + int min_bitrate = -1; + int max_bitrate = -1; + if (GetParam(kCodecParamMinBitrate, &min_bitrate) && + GetParam(kCodecParamMaxBitrate, &max_bitrate)) { + if (max_bitrate < min_bitrate) { + LOG(LS_ERROR) << "Codec with max < min bitrate: " << ToString(); + return false; + } + } + return true; +} + std::string DataCodec::ToString() const { std::ostringstream os; os << "DataCodec[" << id << ":" << name << "]"; diff --git a/talk/media/base/codec.h b/talk/media/base/codec.h index 56fc975ad..861962058 100644 --- a/talk/media/base/codec.h +++ b/talk/media/base/codec.h @@ -39,6 +39,8 @@ namespace cricket { typedef std::map CodecParameterMap; +extern const int kMaxPayloadId; + class FeedbackParam { public: FeedbackParam(const std::string& id, const std::string& param) @@ -104,6 +106,10 @@ struct Codec { void SetParam(const std::string& name, const std::string& value); void SetParam(const std::string& name, int value); + // It is safe to input a non-existent parameter. + // Returns true if the parameter existed, false if it did not exist. + bool RemoveParam(const std::string& name); + bool HasFeedbackParam(const FeedbackParam& param) const; void AddFeedbackParam(const FeedbackParam& param); @@ -120,6 +126,8 @@ struct Codec { name = c.name; clockrate = c.clockrate; preference = c.preference; + params = c.params; + feedback_params = c.feedback_params; return *this; } @@ -127,7 +135,9 @@ struct Codec { return this->id == c.id && // id is reserved in objective-c name == c.name && clockrate == c.clockrate && - preference == c.preference; + preference == c.preference && + params == c.params && + feedback_params == c.feedback_params; } bool operator!=(const Codec& c) const { @@ -242,6 +252,22 @@ struct VideoCodec : public Codec { bool operator!=(const VideoCodec& c) const { return !(*this == c); } + + static VideoCodec CreateRtxCodec(int rtx_payload_type, + int associated_payload_type); + + enum CodecType { + CODEC_VIDEO, + CODEC_RED, + CODEC_ULPFEC, + CODEC_RTX, + }; + + CodecType GetCodecType() const; + // Validates a VideoCodec's payload type, dimensions and bitrates etc. If they + // don't make sense (such as max < min bitrate), and error is logged and + // ValidateCodecFormat returns false. + bool ValidateCodecFormat() const; }; struct DataCodec : public Codec { diff --git a/talk/media/base/codec_unittest.cc b/talk/media/base/codec_unittest.cc index f0ffd8f5a..ea7a13112 100644 --- a/talk/media/base/codec_unittest.cc +++ b/talk/media/base/codec_unittest.cc @@ -34,12 +34,52 @@ using cricket::DataCodec; using cricket::FeedbackParam; using cricket::VideoCodec; using cricket::VideoEncoderConfig; +using cricket::kCodecParamAssociatedPayloadType; +using cricket::kCodecParamMaxBitrate; +using cricket::kCodecParamMinBitrate; class CodecTest : public testing::Test { public: CodecTest() {} }; +TEST_F(CodecTest, TestCodecOperators) { + Codec c0(96, "D", 1000, 0); + c0.SetParam("a", 1); + + Codec c1 = c0; + EXPECT_TRUE(c1 == c0); + + int param_value0; + int param_value1; + EXPECT_TRUE(c0.GetParam("a", ¶m_value0)); + EXPECT_TRUE(c1.GetParam("a", ¶m_value1)); + EXPECT_EQ(param_value0, param_value1); + + c1.id = 86; + EXPECT_TRUE(c0 != c1); + + c1 = c0; + c1.name = "x"; + EXPECT_TRUE(c0 != c1); + + c1 = c0; + c1.clockrate = 2000; + EXPECT_TRUE(c0 != c1); + + c1 = c0; + c1.preference = 1; + EXPECT_TRUE(c0 != c1); + + c1 = c0; + c1.SetParam("a", 2); + EXPECT_TRUE(c0 != c1); + + Codec c5; + Codec c6(0, "", 0, 0); + EXPECT_TRUE(c5 == c6); +} + TEST_F(CodecTest, TestAudioCodecOperators) { AudioCodec c0(96, "A", 44100, 20000, 2, 3); AudioCodec c1(95, "A", 44100, 20000, 2, 3); @@ -238,24 +278,7 @@ TEST_F(CodecTest, TestDataCodecMatches) { EXPECT_FALSE(c1.Matches(DataCodec(95, "D", 0))); } -TEST_F(CodecTest, TestDataCodecOperators) { - DataCodec c0(96, "D", 3); - DataCodec c1(95, "D", 3); - DataCodec c2(96, "x", 3); - DataCodec c3(96, "D", 1); - EXPECT_TRUE(c0 != c1); - EXPECT_TRUE(c0 != c2); - EXPECT_TRUE(c0 != c3); - - DataCodec c4; - DataCodec c5(0, "", 0); - DataCodec c6 = c0; - EXPECT_TRUE(c5 == c4); - EXPECT_TRUE(c6 != c4); - EXPECT_TRUE(c6 == c0); -} - -TEST_F(CodecTest, TestSetParamAndGetParam) { +TEST_F(CodecTest, TestSetParamGetParamAndRemoveParam) { AudioCodec codec; codec.SetParam("a", "1"); codec.SetParam("b", "x"); @@ -272,6 +295,8 @@ TEST_F(CodecTest, TestSetParamAndGetParam) { EXPECT_TRUE(codec.GetParam("b", &str_value)); EXPECT_EQ("x", str_value); EXPECT_FALSE(codec.GetParam("c", &str_value)); + EXPECT_TRUE(codec.RemoveParam("a")); + EXPECT_FALSE(codec.RemoveParam("c")); } TEST_F(CodecTest, TestIntersectFeedbackParams) { @@ -292,3 +317,81 @@ TEST_F(CodecTest, TestIntersectFeedbackParams) { EXPECT_FALSE(c1.HasFeedbackParam(b2)); EXPECT_FALSE(c1.HasFeedbackParam(c3)); } + +TEST_F(CodecTest, TestGetCodecType) { + // Codec type comparison should be case insenstive on names. + const VideoCodec codec(96, "V", 320, 200, 30, 3); + const VideoCodec rtx_codec(96, "rTx", 320, 200, 30, 3); + const VideoCodec ulpfec_codec(96, "ulpFeC", 320, 200, 30, 3); + const VideoCodec red_codec(96, "ReD", 320, 200, 30, 3); + EXPECT_EQ(VideoCodec::CODEC_VIDEO, codec.GetCodecType()); + EXPECT_EQ(VideoCodec::CODEC_RTX, rtx_codec.GetCodecType()); + EXPECT_EQ(VideoCodec::CODEC_ULPFEC, ulpfec_codec.GetCodecType()); + EXPECT_EQ(VideoCodec::CODEC_RED, red_codec.GetCodecType()); +} + +TEST_F(CodecTest, TestCreateRtxCodec) { + VideoCodec rtx_codec = VideoCodec::CreateRtxCodec(96, 120); + EXPECT_EQ(96, rtx_codec.id); + EXPECT_EQ(VideoCodec::CODEC_RTX, rtx_codec.GetCodecType()); + int associated_payload_type; + ASSERT_TRUE(rtx_codec.GetParam(kCodecParamAssociatedPayloadType, + &associated_payload_type)); + EXPECT_EQ(120, associated_payload_type); +} + +TEST_F(CodecTest, TestValidateCodecFormat) { + const VideoCodec codec(96, "V", 320, 200, 30, 3); + ASSERT_TRUE(codec.ValidateCodecFormat()); + + // Accept 0-127 as payload types. + VideoCodec low_payload_type = codec; + low_payload_type.id = 0; + VideoCodec high_payload_type = codec; + high_payload_type.id = 127; + ASSERT_TRUE(low_payload_type.ValidateCodecFormat()); + EXPECT_TRUE(high_payload_type.ValidateCodecFormat()); + + // Reject negative payloads. + VideoCodec negative_payload_type = codec; + negative_payload_type.id = -1; + EXPECT_FALSE(negative_payload_type.ValidateCodecFormat()); + + // Reject too-high payloads. + VideoCodec too_high_payload_type = codec; + too_high_payload_type.id = 128; + EXPECT_FALSE(too_high_payload_type.ValidateCodecFormat()); + + // Reject zero-width codecs. + VideoCodec zero_width = codec; + zero_width.width = 0; + EXPECT_FALSE(zero_width.ValidateCodecFormat()); + + // Reject zero-height codecs. + VideoCodec zero_height = codec; + zero_height.height = 0; + EXPECT_FALSE(zero_height.ValidateCodecFormat()); + + // Accept non-video codecs with zero dimensions. + VideoCodec zero_width_rtx_codec = VideoCodec::CreateRtxCodec(96, 120); + zero_width_rtx_codec.width = 0; + EXPECT_TRUE(zero_width_rtx_codec.ValidateCodecFormat()); + + // Reject codecs with min bitrate > max bitrate. + VideoCodec incorrect_bitrates = codec; + incorrect_bitrates.params[kCodecParamMinBitrate] = "100"; + incorrect_bitrates.params[kCodecParamMaxBitrate] = "80"; + EXPECT_FALSE(incorrect_bitrates.ValidateCodecFormat()); + + // Accept min bitrate == max bitrate. + VideoCodec equal_bitrates = codec; + equal_bitrates.params[kCodecParamMinBitrate] = "100"; + equal_bitrates.params[kCodecParamMaxBitrate] = "100"; + EXPECT_TRUE(equal_bitrates.ValidateCodecFormat()); + + // Accept min bitrate < max bitrate. + VideoCodec different_bitrates = codec; + different_bitrates.params[kCodecParamMinBitrate] = "99"; + different_bitrates.params[kCodecParamMaxBitrate] = "100"; + EXPECT_TRUE(different_bitrates.ValidateCodecFormat()); +} diff --git a/talk/media/base/constants.cc b/talk/media/base/constants.cc index dbddcfc30..cd10ef75f 100644 --- a/talk/media/base/constants.cc +++ b/talk/media/base/constants.cc @@ -40,6 +40,8 @@ const float kLowSystemCpuThreshold = 0.65f; const float kProcessCpuThreshold = 0.10f; const char kRtxCodecName[] = "rtx"; +const char kRedCodecName[] = "red"; +const char kUlpfecCodecName[] = "ulpfec"; // RTP payload type is in the 0-127 range. Use 128 to indicate "all" payload // types. @@ -85,6 +87,7 @@ const char kRtcpFbParamCcm[] = "ccm"; const char kRtcpFbCcmParamFir[] = "fir"; const char kCodecParamMaxBitrate[] = "x-google-max-bitrate"; const char kCodecParamMinBitrate[] = "x-google-min-bitrate"; +const char kCodecParamStartBitrate[] = "x-google-start-bitrate"; const char kCodecParamMaxQuantization[] = "x-google-max-quantization"; const char kCodecParamPort[] = "x-google-port"; diff --git a/talk/media/base/constants.h b/talk/media/base/constants.h index 9ff675006..5ac1be2b6 100644 --- a/talk/media/base/constants.h +++ b/talk/media/base/constants.h @@ -44,6 +44,8 @@ extern const float kLowSystemCpuThreshold; extern const float kProcessCpuThreshold; extern const char kRtxCodecName[]; +extern const char kRedCodecName[]; +extern const char kUlpfecCodecName[]; // Codec parameters extern const int kWildcardPayloadType; @@ -99,6 +101,7 @@ extern const char kRtcpFbCcmParamFir[]; // Google specific parameters extern const char kCodecParamMaxBitrate[]; extern const char kCodecParamMinBitrate[]; +extern const char kCodecParamStartBitrate[]; extern const char kCodecParamMaxQuantization[]; extern const char kCodecParamPort[]; diff --git a/talk/media/base/mediachannel.h b/talk/media/base/mediachannel.h index 9bdf4d9f4..60cdebda3 100644 --- a/talk/media/base/mediachannel.h +++ b/talk/media/base/mediachannel.h @@ -171,7 +171,6 @@ struct AudioOptions { experimental_aec.SetFrom(change.experimental_aec); experimental_ns.SetFrom(change.experimental_ns); aec_dump.SetFrom(change.aec_dump); - experimental_acm.SetFrom(change.experimental_acm); tx_agc_target_dbov.SetFrom(change.tx_agc_target_dbov); tx_agc_digital_compression_gain.SetFrom( change.tx_agc_digital_compression_gain); @@ -183,6 +182,7 @@ struct AudioOptions { recording_sample_rate.SetFrom(change.recording_sample_rate); playout_sample_rate.SetFrom(change.playout_sample_rate); dscp.SetFrom(change.dscp); + opus_fec.SetFrom(change.opus_fec); } bool operator==(const AudioOptions& o) const { @@ -200,7 +200,6 @@ struct AudioOptions { experimental_ns == o.experimental_ns && adjust_agc_delta == o.adjust_agc_delta && aec_dump == o.aec_dump && - experimental_acm == o.experimental_acm && tx_agc_target_dbov == o.tx_agc_target_dbov && tx_agc_digital_compression_gain == o.tx_agc_digital_compression_gain && tx_agc_limiter == o.tx_agc_limiter && @@ -209,7 +208,8 @@ struct AudioOptions { rx_agc_limiter == o.rx_agc_limiter && recording_sample_rate == o.recording_sample_rate && playout_sample_rate == o.playout_sample_rate && - dscp == o.dscp; + dscp == o.dscp && + opus_fec == o.opus_fec; } std::string ToString() const { @@ -229,7 +229,6 @@ struct AudioOptions { ost << ToStringIfSet("experimental_aec", experimental_aec); ost << ToStringIfSet("experimental_ns", experimental_ns); ost << ToStringIfSet("aec_dump", aec_dump); - ost << ToStringIfSet("experimental_acm", experimental_acm); ost << ToStringIfSet("tx_agc_target_dbov", tx_agc_target_dbov); ost << ToStringIfSet("tx_agc_digital_compression_gain", tx_agc_digital_compression_gain); @@ -241,6 +240,7 @@ struct AudioOptions { ost << ToStringIfSet("recording_sample_rate", recording_sample_rate); ost << ToStringIfSet("playout_sample_rate", playout_sample_rate); ost << ToStringIfSet("dscp", dscp); + ost << ToStringIfSet("opus_fec", opus_fec); ost << "}"; return ost.str(); } @@ -267,7 +267,6 @@ struct AudioOptions { Settable experimental_aec; Settable experimental_ns; Settable aec_dump; - Settable experimental_acm; // Note that tx_agc_* only applies to non-experimental AGC. Settable tx_agc_target_dbov; Settable tx_agc_digital_compression_gain; @@ -279,6 +278,8 @@ struct AudioOptions { Settable playout_sample_rate; // Set DSCP value for packet sent from audio channel. Settable dscp; + // Set Opus FEC + Settable opus_fec; }; // Options that can be applied to a VideoMediaChannel or a VideoMediaEngine. @@ -318,6 +319,10 @@ struct VideoOptions { cpu_overuse_detection.SetFrom(change.cpu_overuse_detection); cpu_underuse_threshold.SetFrom(change.cpu_underuse_threshold); cpu_overuse_threshold.SetFrom(change.cpu_overuse_threshold); + cpu_underuse_encode_rsd_threshold.SetFrom( + change.cpu_underuse_encode_rsd_threshold); + cpu_overuse_encode_rsd_threshold.SetFrom( + change.cpu_overuse_encode_rsd_threshold); cpu_overuse_encode_usage.SetFrom(change.cpu_overuse_encode_usage); conference_mode.SetFrom(change.conference_mode); process_adaptation_threshhold.SetFrom(change.process_adaptation_threshhold); @@ -335,6 +340,7 @@ struct VideoOptions { screencast_min_bitrate.SetFrom(change.screencast_min_bitrate); use_improved_wifi_bandwidth_estimator.SetFrom( change.use_improved_wifi_bandwidth_estimator); + use_payload_padding.SetFrom(change.use_payload_padding); } bool operator==(const VideoOptions& o) const { @@ -354,6 +360,10 @@ struct VideoOptions { cpu_overuse_detection == o.cpu_overuse_detection && cpu_underuse_threshold == o.cpu_underuse_threshold && cpu_overuse_threshold == o.cpu_overuse_threshold && + cpu_underuse_encode_rsd_threshold == + o.cpu_underuse_encode_rsd_threshold && + cpu_overuse_encode_rsd_threshold == + o.cpu_overuse_encode_rsd_threshold && cpu_overuse_encode_usage == o.cpu_overuse_encode_usage && conference_mode == o.conference_mode && process_adaptation_threshhold == o.process_adaptation_threshhold && @@ -370,7 +380,8 @@ struct VideoOptions { skip_encoding_unused_streams == o.skip_encoding_unused_streams && screencast_min_bitrate == o.screencast_min_bitrate && use_improved_wifi_bandwidth_estimator == - o.use_improved_wifi_bandwidth_estimator; + o.use_improved_wifi_bandwidth_estimator && + use_payload_padding == o.use_payload_padding; } std::string ToString() const { @@ -394,6 +405,10 @@ struct VideoOptions { ost << ToStringIfSet("cpu overuse detection", cpu_overuse_detection); ost << ToStringIfSet("cpu underuse threshold", cpu_underuse_threshold); ost << ToStringIfSet("cpu overuse threshold", cpu_overuse_threshold); + ost << ToStringIfSet("cpu underuse encode rsd threshold", + cpu_underuse_encode_rsd_threshold); + ost << ToStringIfSet("cpu overuse encode rsd threshold", + cpu_overuse_encode_rsd_threshold); ost << ToStringIfSet("cpu overuse encode usage", cpu_overuse_encode_usage); ost << ToStringIfSet("conference mode", conference_mode); @@ -413,6 +428,7 @@ struct VideoOptions { ost << ToStringIfSet("screencast min bitrate", screencast_min_bitrate); ost << ToStringIfSet("improved wifi bwe", use_improved_wifi_bandwidth_estimator); + ost << ToStringIfSet("payload padding", use_payload_padding); ost << "}"; return ost.str(); } @@ -447,10 +463,22 @@ struct VideoOptions { // adaptation algorithm. So this option will override the // |adapt_input_to_cpu_usage|. Settable cpu_overuse_detection; - // Low threshold for cpu overuse adaptation in ms. (Adapt up) + // Low threshold (t1) for cpu overuse adaptation. (Adapt up) + // Metric: encode usage (m1). m1 < t1 => underuse. Settable cpu_underuse_threshold; - // High threshold for cpu overuse adaptation in ms. (Adapt down) + // High threshold (t1) for cpu overuse adaptation. (Adapt down) + // Metric: encode usage (m1). m1 > t1 => overuse. Settable cpu_overuse_threshold; + // Low threshold (t2) for cpu overuse adaptation. (Adapt up) + // Metric: relative standard deviation of encode time (m2). + // Optional threshold. If set, (m1 < t1 && m2 < t2) => underuse. + // Note: t2 will have no effect if t1 is not set. + Settable cpu_underuse_encode_rsd_threshold; + // High threshold (t2) for cpu overuse adaptation. (Adapt down) + // Metric: relative standard deviation of encode time (m2). + // Optional threshold. If set, (m1 > t1 || m2 > t2) => overuse. + // Note: t2 will have no effect if t1 is not set. + Settable cpu_overuse_encode_rsd_threshold; // Use encode usage for cpu detection. Settable cpu_overuse_encode_usage; // Use conference mode? @@ -481,6 +509,8 @@ struct VideoOptions { Settable screencast_min_bitrate; // Enable improved bandwidth estiamtor on wifi. Settable use_improved_wifi_bandwidth_estimator; + // Enable payload padding. + Settable use_payload_padding; }; // A class for playing out soundclips. @@ -791,6 +821,7 @@ struct MediaReceiverInfo { int packets_rcvd; int packets_lost; float fraction_lost; + std::string codec_name; std::vector local_stats; std::vector remote_stats; }; @@ -833,7 +864,8 @@ struct VoiceReceiverInfo : public MediaReceiverInfo { decoding_normal(0), decoding_plc(0), decoding_cng(0), - decoding_plc_cng(0) { + decoding_plc_cng(0), + capture_start_ntp_time_ms(-1) { } int ext_seqnum; @@ -850,6 +882,8 @@ struct VoiceReceiverInfo : public MediaReceiverInfo { int decoding_plc; int decoding_cng; int decoding_plc_cng; + // Estimated capture start time in NTP time in ms. + int64 capture_start_ntp_time_ms; }; struct VideoSenderInfo : public MediaSenderInfo { @@ -867,9 +901,11 @@ struct VideoSenderInfo : public MediaSenderInfo { nominal_bitrate(0), preferred_bitrate(0), adapt_reason(0), + adapt_changes(0), capture_jitter_ms(0), avg_encode_ms(0), encode_usage_percent(0), + encode_rsd(0), capture_queue_delay_ms_per_s(0) { } @@ -887,9 +923,11 @@ struct VideoSenderInfo : public MediaSenderInfo { int nominal_bitrate; int preferred_bitrate; int adapt_reason; + int adapt_changes; int capture_jitter_ms; int avg_encode_ms; int encode_usage_percent; + int encode_rsd; int capture_queue_delay_ms_per_s; VariableInfo adapt_frame_drops; VariableInfo effects_frame_drops; @@ -915,7 +953,8 @@ struct VideoReceiverInfo : public MediaReceiverInfo { min_playout_delay_ms(0), render_delay_ms(0), target_delay_ms(0), - current_delay_ms(0) { + current_delay_ms(0), + capture_start_ntp_time_ms(-1) { } std::vector ssrc_groups; @@ -952,6 +991,9 @@ struct VideoReceiverInfo : public MediaReceiverInfo { int target_delay_ms; // Current overall delay, possibly ramping towards target_delay_ms. int current_delay_ms; + + // Estimated capture start time in NTP time in ms. + int64 capture_start_ntp_time_ms; }; struct DataSenderInfo : public MediaSenderInfo { @@ -1277,6 +1319,8 @@ class DataMediaChannel : public MediaChannel { // Signal when the media channel is ready to send the stream. Arguments are: // writable(bool) sigslot::signal1 SignalReadyToSend; + // Signal for notifying that the remote side has closed the DataChannel. + sigslot::signal1 SignalStreamClosedRemotely; }; } // namespace cricket diff --git a/talk/media/base/rtputils.cc b/talk/media/base/rtputils.cc index 5215c3b76..221d94927 100644 --- a/talk/media/base/rtputils.cc +++ b/talk/media/base/rtputils.cc @@ -223,4 +223,15 @@ bool SetRtpHeader(void* data, size_t len, const RtpHeader& header) { SetRtpSsrc(data, len, header.ssrc)); } +bool IsRtpPacket(const void* data, size_t len) { + if (len < kMinRtpPacketLen) + return false; + + int version = 0; + if (!GetRtpVersion(data, len, &version)) + return false; + + return version == kRtpVersion; +} + } // namespace cricket diff --git a/talk/media/base/rtputils.h b/talk/media/base/rtputils.h index 6f76866ab..f653e4230 100644 --- a/talk/media/base/rtputils.h +++ b/talk/media/base/rtputils.h @@ -74,6 +74,7 @@ bool SetRtpSsrc(void* data, size_t len, uint32 value); // Assumes version 2, no padding, no extensions, no csrcs. bool SetRtpHeader(void* data, size_t len, const RtpHeader& header); +bool IsRtpPacket(const void* data, size_t len); } // namespace cricket #endif // TALK_MEDIA_BASE_RTPUTILS_H_ diff --git a/talk/media/base/testutils.cc b/talk/media/base/testutils.cc index 9b1b16d21..732061384 100644 --- a/talk/media/base/testutils.cc +++ b/talk/media/base/testutils.cc @@ -35,6 +35,7 @@ #include "talk/base/pathutils.h" #include "talk/base/stream.h" #include "talk/base/stringutils.h" +#include "talk/base/testutils.h" #include "talk/media/base/rtpdump.h" #include "talk/media/base/videocapturer.h" #include "talk/media/base/videoframe.h" @@ -254,7 +255,7 @@ void VideoCapturerListener::OnFrameCaptured(VideoCapturer* capturer, // Returns the absolute path to a file in the testdata/ directory. std::string GetTestFilePath(const std::string& filename) { // Locate test data directory. - talk_base::Pathname path = GetTalkDirectory(); + talk_base::Pathname path = testing::GetTalkDirectory(); EXPECT_FALSE(path.empty()); // must be run from inside "talk" path.AppendFolder("media"); path.AppendFolder("testdata"); diff --git a/talk/media/base/videoadapter.cc b/talk/media/base/videoadapter.cc index bcf89cbdb..76ec52775 100644 --- a/talk/media/base/videoadapter.cc +++ b/talk/media/base/videoadapter.cc @@ -261,7 +261,7 @@ int VideoAdapter::GetOutputNumPixels() const { // TODO(fbarchard): Add AdaptFrameRate function that only drops frames but // not resolution. -bool VideoAdapter::AdaptFrame(const VideoFrame* in_frame, +bool VideoAdapter::AdaptFrame(VideoFrame* in_frame, VideoFrame** out_frame) { talk_base::CritScope cs(&critical_section_); if (!in_frame || !out_frame) { @@ -326,12 +326,19 @@ bool VideoAdapter::AdaptFrame(const VideoFrame* in_frame, output_format_.height = static_cast(in_frame->GetHeight()); } - if (!StretchToOutputFrame(in_frame)) { - LOG(LS_VERBOSE) << "VAdapt Stretch Failed."; - return false; - } + if (!black_output_ && + in_frame->GetWidth() == static_cast(output_format_.width) && + in_frame->GetHeight() == static_cast(output_format_.height)) { + // The dimensions are correct and we aren't muting, so use the input frame. + *out_frame = in_frame; + } else { + if (!StretchToOutputFrame(in_frame)) { + LOG(LS_VERBOSE) << "VAdapt Stretch Failed."; + return false; + } - *out_frame = output_frame_.get(); + *out_frame = output_frame_.get(); + } ++frames_out_; if (in_frame->GetWidth() != (*out_frame)->GetWidth() || diff --git a/talk/media/base/videoadapter.h b/talk/media/base/videoadapter.h index 64a850f98..888183790 100644 --- a/talk/media/base/videoadapter.h +++ b/talk/media/base/videoadapter.h @@ -61,13 +61,19 @@ class VideoAdapter { // true and set the output frame to NULL if the input frame is dropped. Return // true and set the out frame to output_frame_ if the input frame is adapted // successfully. Return false otherwise. - // output_frame_ is owned by the VideoAdapter that has the best knowledge on - // the output frame. - bool AdaptFrame(const VideoFrame* in_frame, VideoFrame** out_frame); + // Note that, if no adaptation is required, |out_frame| will refer directly + // in_frame. If a copy is always required, the caller must do an explicit + // copy. + // If a copy has taken place, |output_frame_| is owned by the VideoAdapter + // and will remain usable until the adapter is destroyed or AdaptFrame is + // called again. + bool AdaptFrame(VideoFrame* in_frame, VideoFrame** out_frame); void set_scale_third(bool enable); bool scale_third() const { return scale_third_; } + int adaptation_changes() const { return adaption_changes_; } + protected: float FindClosestScale(int width, int height, int target_num_pixels); float FindClosestViewScale(int width, int height, int target_num_pixels); diff --git a/talk/media/base/videocapturer.cc b/talk/media/base/videocapturer.cc index 877c41030..59860a40c 100644 --- a/talk/media/base/videocapturer.cc +++ b/talk/media/base/videocapturer.cc @@ -326,11 +326,13 @@ std::string VideoCapturer::ToString(const CapturedFrame* captured_frame) const { void VideoCapturer::GetStats(VariableInfo* adapt_drops_stats, VariableInfo* effect_drops_stats, - VariableInfo* frame_time_stats) { + VariableInfo* frame_time_stats, + VideoFormat* last_captured_frame_format) { talk_base::CritScope cs(&frame_stats_crit_); GetVariableSnapshot(adapt_frame_drops_data_, adapt_drops_stats); GetVariableSnapshot(effect_frame_drops_data_, effect_drops_stats); GetVariableSnapshot(frame_time_data_, frame_time_stats); + *last_captured_frame_format = last_captured_frame_format_; adapt_frame_drops_data_.Reset(); effect_frame_drops_data_.Reset(); @@ -530,18 +532,7 @@ void VideoCapturer::OnFrameCaptured(VideoCapturer*, } SignalVideoFrame(this, adapted_frame); - double time_now = frame_length_time_reporter_.TimerNow(); - if (previous_frame_time_ != 0.0) { - // Update stats protected from jmi data fetches. - talk_base::CritScope cs(&frame_stats_crit_); - - adapt_frame_drops_data_.AddSample(adapt_frame_drops_); - effect_frame_drops_data_.AddSample(effect_frame_drops_); - frame_time_data_.AddSample(time_now - previous_frame_time_); - } - previous_frame_time_ = time_now; - effect_frame_drops_ = 0; - adapt_frame_drops_ = 0; + UpdateStats(captured_frame); #endif // VIDEO_FRAME_NAME } @@ -717,6 +708,27 @@ bool VideoCapturer::ShouldFilterFormat(const VideoFormat& format) const { format.height > max_format_->height; } +void VideoCapturer::UpdateStats(const CapturedFrame* captured_frame) { + // Update stats protected from fetches from different thread. + talk_base::CritScope cs(&frame_stats_crit_); + + last_captured_frame_format_.width = captured_frame->width; + last_captured_frame_format_.height = captured_frame->height; + // TODO(ronghuawu): Useful to report interval as well? + last_captured_frame_format_.interval = 0; + last_captured_frame_format_.fourcc = captured_frame->fourcc; + + double time_now = frame_length_time_reporter_.TimerNow(); + if (previous_frame_time_ != 0.0) { + adapt_frame_drops_data_.AddSample(adapt_frame_drops_); + effect_frame_drops_data_.AddSample(effect_frame_drops_); + frame_time_data_.AddSample(time_now - previous_frame_time_); + } + previous_frame_time_ = time_now; + effect_frame_drops_ = 0; + adapt_frame_drops_ = 0; +} + template void VideoCapturer::GetVariableSnapshot( const talk_base::RollingAccumulator& data, diff --git a/talk/media/base/videocapturer.h b/talk/media/base/videocapturer.h index c45ad78f8..6b1c46ddd 100644 --- a/talk/media/base/videocapturer.h +++ b/talk/media/base/videocapturer.h @@ -294,7 +294,8 @@ class VideoCapturer // should be called only periodically to log statistics. void GetStats(VariableInfo* adapt_drop_stats, VariableInfo* effect_drop_stats, - VariableInfo* frame_time_stats); + VariableInfo* frame_time_stats, + VideoFormat* last_captured_frame_format); protected: // Callback attached to SignalFrameCaptured where SignalVideoFrames is called. @@ -348,6 +349,8 @@ class VideoCapturer // Returns true if format doesn't fulfill all applied restrictions. bool ShouldFilterFormat(const VideoFormat& format) const; + void UpdateStats(const CapturedFrame* captured_frame); + // Helper function to save statistics on the current data from a // RollingAccumulator into stats. template @@ -385,6 +388,8 @@ class VideoCapturer talk_base::RollingAccumulator effect_frame_drops_data_; double previous_frame_time_; talk_base::RollingAccumulator frame_time_data_; + // The captured frame format before potential adapation. + VideoFormat last_captured_frame_format_; talk_base::CriticalSection crit_; VideoProcessors video_processors_; diff --git a/talk/media/base/videocapturer_unittest.cc b/talk/media/base/videocapturer_unittest.cc index 75da2360e..9f025e37b 100644 --- a/talk/media/base/videocapturer_unittest.cc +++ b/talk/media/base/videocapturer_unittest.cc @@ -31,6 +31,12 @@ const int kMsCallbackWait = 500; const int kMinHdHeight = 720; const uint32 kTimeout = 5000U; +void NormalizeVideoSize(int* expected_width, int* expected_height) { + // WebRtcVideoFrame truncates the frame size to a multiple of 4. + *expected_width = *expected_width & ~3; + *expected_height = *expected_height & ~3; +} + } // namespace // Sets the elapsed time in the video frame to 0. @@ -228,6 +234,59 @@ TEST_F(VideoCapturerTest, ScreencastScaledMaxPixels) { EXPECT_EQ(2, renderer_.num_rendered_frames()); } +TEST_F(VideoCapturerTest, ScreencastScaledOddWidth) { + capturer_.SetScreencast(true); + + int kWidth = 1281; + int kHeight = 720; + + std::vector formats; + formats.push_back(cricket::VideoFormat(kWidth, kHeight, + cricket::VideoFormat::FpsToInterval(5), cricket::FOURCC_ARGB)); + capturer_.ResetSupportedFormats(formats); + + EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat( + kWidth, + kHeight, + cricket::VideoFormat::FpsToInterval(30), + cricket::FOURCC_ARGB))); + EXPECT_TRUE(capturer_.IsRunning()); + EXPECT_EQ(0, renderer_.num_rendered_frames()); + int expected_width = kWidth; + int expected_height = kHeight; + NormalizeVideoSize(&expected_width, &expected_height); + renderer_.SetSize(expected_width, expected_height, 0); + EXPECT_TRUE(capturer_.CaptureFrame()); + EXPECT_EQ(1, renderer_.num_rendered_frames()); +} + +TEST_F(VideoCapturerTest, ScreencastScaledSuperLarge) { + capturer_.SetScreencast(true); + + const int kMaxWidth = 4096; + const int kMaxHeight = 3072; + int kWidth = kMaxWidth + 4; + int kHeight = kMaxHeight + 4; + + std::vector formats; + formats.push_back(cricket::VideoFormat(kWidth, kHeight, + cricket::VideoFormat::FpsToInterval(5), cricket::FOURCC_ARGB)); + capturer_.ResetSupportedFormats(formats); + + EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat( + kWidth, + kHeight, + cricket::VideoFormat::FpsToInterval(30), + cricket::FOURCC_ARGB))); + EXPECT_TRUE(capturer_.IsRunning()); + EXPECT_EQ(0, renderer_.num_rendered_frames()); + int expected_width = 2050; + int expected_height = 1538; + NormalizeVideoSize(&expected_width, &expected_height); + renderer_.SetSize(expected_width, expected_height, 0); + EXPECT_TRUE(capturer_.CaptureFrame()); + EXPECT_EQ(1, renderer_.num_rendered_frames()); +} TEST_F(VideoCapturerTest, TestFourccMatch) { cricket::VideoFormat desired(640, 480, diff --git a/talk/media/base/videoengine_unittest.h b/talk/media/base/videoengine_unittest.h index 54b55005a..382fb775e 100644 --- a/talk/media/base/videoengine_unittest.h +++ b/talk/media/base/videoengine_unittest.h @@ -792,14 +792,45 @@ class VideoMediaChannelTest : public testing::Test, EXPECT_FRAME_WAIT(3, codec.width, codec.height, kTimeout); EXPECT_EQ(2, renderer_.num_set_sizes()); } + void SendReceiveManyAndGetStats(const cricket::VideoCodec& codec, + int duration_sec, int fps) { + EXPECT_TRUE(SetOneCodec(codec)); + EXPECT_TRUE(SetSend(true)); + EXPECT_TRUE(channel_->SetRender(true)); + EXPECT_EQ(0, renderer_.num_rendered_frames()); + for (int i = 0; i < duration_sec; ++i) { + for (int frame = 1; frame <= fps; ++frame) { + EXPECT_TRUE(WaitAndSendFrame(1000 / fps)); + EXPECT_FRAME_WAIT(frame + i * fps, codec.width, codec.height, kTimeout); + } + cricket::VideoMediaInfo info; + EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); + // For webrtc, |framerate_sent| and |framerate_rcvd| depend on periodic + // callbacks (1 sec). + // Received |fraction_lost| and |packets_lost| are from sent RTCP packet. + // One sent packet needed (sent about once per second). + // |framerate_input|, |framerate_decoded| and |framerate_output| are using + // RateTracker. RateTracker needs to be called twice (with >1 second in + // b/w calls) before a framerate is calculated. + // Therefore insert frames (and call GetStats each sec) for a few seconds + // before testing stats. + } + talk_base::scoped_ptr p(GetRtpPacket(0)); + EXPECT_EQ(codec.id, GetPayloadType(p.get())); + } + // Test that stats work properly for a 1-1 call. void GetStats() { - SendAndReceive(DefaultCodec()); + const int kDurationSec = 3; + const int kFps = 10; + SendReceiveManyAndGetStats(DefaultCodec(), kDurationSec, kFps); + cricket::VideoMediaInfo info; EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); ASSERT_EQ(1U, info.senders.size()); // TODO(whyuan): bytes_sent and bytes_rcvd are different. Are both payload? + // For webrtc, bytes_sent does not include the RTP header length. EXPECT_GT(info.senders[0].bytes_sent, 0); EXPECT_EQ(NumRtpPackets(), info.senders[0].packets_sent); EXPECT_EQ(0.0, info.senders[0].fraction_lost); @@ -819,7 +850,8 @@ class VideoMediaChannelTest : public testing::Test, EXPECT_EQ(NumRtpPackets(), info.receivers[0].packets_rcvd); EXPECT_EQ(0.0, info.receivers[0].fraction_lost); EXPECT_EQ(0, info.receivers[0].packets_lost); - EXPECT_EQ(0, info.receivers[0].packets_concealed); + // TODO(asapersson): Not set for webrtc. Handle missing stats. + // EXPECT_EQ(0, info.receivers[0].packets_concealed); EXPECT_EQ(0, info.receivers[0].firs_sent); EXPECT_EQ(0, info.receivers[0].plis_sent); EXPECT_EQ(0, info.receivers[0].nacks_sent); @@ -860,16 +892,11 @@ class VideoMediaChannelTest : public testing::Test, ASSERT_EQ(1U, info.senders.size()); // TODO(whyuan): bytes_sent and bytes_rcvd are different. Are both payload? + // For webrtc, bytes_sent does not include the RTP header length. EXPECT_GT(info.senders[0].bytes_sent, 0); EXPECT_EQ(NumRtpPackets(), info.senders[0].packets_sent); - EXPECT_EQ(0.0, info.senders[0].fraction_lost); - EXPECT_EQ(0, info.senders[0].firs_rcvd); - EXPECT_EQ(0, info.senders[0].plis_rcvd); - EXPECT_EQ(0, info.senders[0].nacks_rcvd); EXPECT_EQ(DefaultCodec().width, info.senders[0].send_frame_width); EXPECT_EQ(DefaultCodec().height, info.senders[0].send_frame_height); - EXPECT_GT(info.senders[0].framerate_input, 0); - EXPECT_GT(info.senders[0].framerate_sent, 0); ASSERT_EQ(2U, info.receivers.size()); for (size_t i = 0; i < info.receivers.size(); ++i) { @@ -877,17 +904,8 @@ class VideoMediaChannelTest : public testing::Test, EXPECT_EQ(i + 1, info.receivers[i].ssrcs()[0]); EXPECT_EQ(NumRtpBytes(), info.receivers[i].bytes_rcvd); EXPECT_EQ(NumRtpPackets(), info.receivers[i].packets_rcvd); - EXPECT_EQ(0.0, info.receivers[i].fraction_lost); - EXPECT_EQ(0, info.receivers[i].packets_lost); - EXPECT_EQ(0, info.receivers[i].packets_concealed); - EXPECT_EQ(0, info.receivers[i].firs_sent); - EXPECT_EQ(0, info.receivers[i].plis_sent); - EXPECT_EQ(0, info.receivers[i].nacks_sent); EXPECT_EQ(DefaultCodec().width, info.receivers[i].frame_width); EXPECT_EQ(DefaultCodec().height, info.receivers[i].frame_height); - EXPECT_GT(info.receivers[i].framerate_rcvd, 0); - EXPECT_GT(info.receivers[i].framerate_decoded, 0); - EXPECT_GT(info.receivers[i].framerate_output, 0); } } // Test that stats work properly for a conf call with multiple send streams. diff --git a/talk/media/sctp/sctpdataengine.cc b/talk/media/sctp/sctpdataengine.cc index 59e252aae..3647d2129 100644 --- a/talk/media/sctp/sctpdataengine.cc +++ b/talk/media/sctp/sctpdataengine.cc @@ -105,12 +105,6 @@ namespace cricket { typedef talk_base::ScopedMessageData InboundPacketMessage; typedef talk_base::ScopedMessageData OutboundPacketMessage; -// This is the SCTP port to use. It is passed along the wire and the listener -// and connector must be using the same port. It is not related to the ports at -// the IP level. (Corresponds to: sockaddr_conn.sconn_port in usrsctp.h) -// -// TODO(ldixon): Allow port to be set from higher level code. -static const int kSctpDefaultPort = 5001; // TODO(ldixon): Find where this is defined, and also check is Sctp really // respects this. static const size_t kSctpMtu = 1280; @@ -277,25 +271,26 @@ SctpDataEngine::SctpDataEngine() { } usrsctp_engines_count++; - // We don't put in a codec because we don't want one offered when we - // use the hybrid data engine. - // codecs_.push_back(cricket::DataCodec( kGoogleSctpDataCodecId, - // kGoogleSctpDataCodecName, 0)); + cricket::DataCodec codec(kGoogleSctpDataCodecId, kGoogleSctpDataCodecName, 0); + codec.SetParam(kCodecParamPort, kSctpDefaultPort); + codecs_.push_back(codec); } SctpDataEngine::~SctpDataEngine() { - // TODO(ldixon): There is currently a bug in teardown of usrsctp that blocks - // indefintely if a finish call made too soon after close calls. So teardown - // has been skipped. Once the bug is fixed, retest and enable teardown. - // Tracked in webrtc issue 2749. - // - // usrsctp_engines_count--; - // LOG(LS_VERBOSE) << "usrsctp_engines_count:" << usrsctp_engines_count; - // if (usrsctp_engines_count == 0) { - // if (usrsctp_finish() != 0) { - // LOG(LS_WARNING) << "usrsctp_finish."; - // } - // } + usrsctp_engines_count--; + LOG(LS_VERBOSE) << "usrsctp_engines_count:" << usrsctp_engines_count; + + if (usrsctp_engines_count == 0) { + // usrsctp_finish() may fail if it's called too soon after the channels are + // closed. Wait and try again until it succeeds for up to 3 seconds. + for (size_t i = 0; i < 300; ++i) { + if (usrsctp_finish() == 0) + return; + + talk_base::Thread::SleepMs(10); + } + LOG(LS_ERROR) << "Failed to shutdown usrsctp."; + } } DataMediaChannel* SctpDataEngine::CreateChannel( @@ -308,8 +303,8 @@ DataMediaChannel* SctpDataEngine::CreateChannel( SctpDataMediaChannel::SctpDataMediaChannel(talk_base::Thread* thread) : worker_thread_(thread), - local_port_(-1), - remote_port_(-1), + local_port_(kSctpDefaultPort), + remote_port_(kSctpDefaultPort), sock_(NULL), sending_(false), receiving_(false), @@ -423,12 +418,6 @@ void SctpDataMediaChannel::CloseSctpSocket() { bool SctpDataMediaChannel::Connect() { LOG(LS_VERBOSE) << debug_name_ << "->Connect()."; - if (remote_port_ < 0) { - remote_port_ = kSctpDefaultPort; - } - if (local_port_ < 0) { - local_port_ = kSctpDefaultPort; - } // If we already have a socket connection, just return. if (sock_) { @@ -565,7 +554,7 @@ bool SctpDataMediaChannel::SendData( talk_base::checked_cast(sizeof(spa)), SCTP_SENDV_SPA, 0); if (send_res < 0) { - if (errno == EWOULDBLOCK) { + if (errno == SCTP_EWOULDBLOCK) { *result = SDR_BLOCK; LOG(LS_INFO) << debug_name_ << "->SendData(...): EWOULDBLOCK returned"; } else { @@ -794,7 +783,6 @@ void SctpDataMediaChannel::OnStreamResetEvent( << ListStreams(open_streams_) << "], Q'd: [" << ListStreams(queued_reset_streams_) << "], Sent: [" << ListStreams(sent_reset_streams_) << "]"; - bool local_stream_reset_acknowledged = false; // If both sides try to reset some streams at the same time (even if they're // disjoint sets), we can get reset failures. @@ -805,7 +793,6 @@ void SctpDataMediaChannel::OnStreamResetEvent( sent_reset_streams_.begin(), sent_reset_streams_.end()); sent_reset_streams_.clear(); - local_stream_reset_acknowledged = true; } else if (evt->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) { // Each side gets an event for each direction of a stream. That is, @@ -822,7 +809,6 @@ void SctpDataMediaChannel::OnStreamResetEvent( if (it != sent_reset_streams_.end()) { LOG(LS_VERBOSE) << "SCTP_STREAM_RESET_EVENT(" << debug_name_ << "): local sid " << stream_id << " acknowledged."; - local_stream_reset_acknowledged = true; sent_reset_streams_.erase(it); } else if ((it = open_streams_.find(stream_id)) @@ -831,7 +817,7 @@ void SctpDataMediaChannel::OnStreamResetEvent( LOG(LS_VERBOSE) << "SCTP_STREAM_RESET_EVENT(" << debug_name_ << "): closing sid " << stream_id; open_streams_.erase(it); - SignalStreamClosed(stream_id); + SignalStreamClosedRemotely(stream_id); } else if ((it = queued_reset_streams_.find(stream_id)) != queued_reset_streams_.end()) { @@ -853,11 +839,9 @@ void SctpDataMediaChannel::OnStreamResetEvent( } } - if (local_stream_reset_acknowledged) { - // This message acknowledges the last stream-reset request we sent out - // (only one can be outstanding at a time). Send out the next one. - SendQueuedStreamResets(); - } + // Always try to send the queued RESET because this call indicates that the + // last local RESET or remote RESET has made some progress. + SendQueuedStreamResets(); } // Puts the specified |param| from the codec identified by |id| into |dest| diff --git a/talk/media/sctp/sctpdataengine.h b/talk/media/sctp/sctpdataengine.h index f2322ab27..2e8beecd1 100644 --- a/talk/media/sctp/sctpdataengine.h +++ b/talk/media/sctp/sctpdataengine.h @@ -58,6 +58,12 @@ namespace cricket { // tell SCTP we're going to use. const uint32 kMaxSctpSid = 1023; +// This is the default SCTP port to use. It is passed along the wire and the +// connectee and connector must be using the same port. It is not related to the +// ports at the IP level. (Corresponds to: sockaddr_conn.sconn_port in +// usrsctp.h) +const int kSctpDefaultPort = 5000; + // A DataEngine that interacts with usrsctp. // // From channel calls, data flows like this: @@ -184,9 +190,6 @@ class SctpDataMediaChannel : public DataMediaChannel, } const std::string& debug_name() const { return debug_name_; } - // Called with the SSID of a remote stream that's been closed. - sigslot::signal1 SignalStreamClosed; - private: sockaddr_conn GetSctpSockAddr(int port); diff --git a/talk/media/sctp/sctpdataengine_unittest.cc b/talk/media/sctp/sctpdataengine_unittest.cc index 650c785cb..cf410e5ac 100644 --- a/talk/media/sctp/sctpdataengine_unittest.cc +++ b/talk/media/sctp/sctpdataengine_unittest.cc @@ -81,13 +81,13 @@ class SctpFakeNetworkInterface : public cricket::MediaChannel::NetworkInterface, // an SCTP packet. virtual void OnMessage(talk_base::Message* msg) { LOG(LS_VERBOSE) << "SctpFakeNetworkInterface::OnMessage"; - talk_base::Buffer* buffer = + talk_base::scoped_ptr buffer( static_cast*>( - msg->pdata)->data(); + msg->pdata)->data()); if (dest_) { - dest_->OnPacketReceived(buffer, talk_base::PacketTime()); + dest_->OnPacketReceived(buffer.get(), talk_base::PacketTime()); } - delete buffer; + delete msg->pdata; } // Unsupported functions required to exist by NetworkInterface. @@ -167,24 +167,24 @@ class SignalChannelClosedObserver : public sigslot::has_slots<> { public: SignalChannelClosedObserver() {} void BindSelf(cricket::SctpDataMediaChannel* channel) { - channel->SignalStreamClosed.connect( + channel->SignalStreamClosedRemotely.connect( this, &SignalChannelClosedObserver::OnStreamClosed); } - void OnStreamClosed(int stream) { + void OnStreamClosed(uint32 stream) { streams_.push_back(stream); } - int StreamCloseCount(int stream) { + int StreamCloseCount(uint32 stream) { return std::count(streams_.begin(), streams_.end(), stream); } - bool WasStreamClosed(int stream) { + bool WasStreamClosed(uint32 stream) { return std::find(streams_.begin(), streams_.end(), stream) != streams_.end(); } private: - std::vector streams_; + std::vector streams_; }; class SignalChannelClosedReopener : public sigslot::has_slots<> { @@ -295,7 +295,7 @@ class SctpDataMediaChannelTest : public testing::Test, params.ssrc = ssrc; return chan->SendData(params, talk_base::Buffer( - msg.data(), msg.length()), result); + &msg[0], msg.length()), result); } bool ReceivedData(const SctpFakeDataReceiver* recv, uint32 ssrc, @@ -364,26 +364,46 @@ TEST_F(SctpDataMediaChannelTest, SendData) { EXPECT_EQ(cricket::SDR_SUCCESS, result); EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hello?"), 1000); LOG(LS_VERBOSE) << "recv2.received=" << receiver2()->received() - << "recv2.last_params.ssrc=" + << ", recv2.last_params.ssrc=" << receiver2()->last_params().ssrc - << "recv2.last_params.timestamp=" + << ", recv2.last_params.timestamp=" << receiver2()->last_params().ssrc - << "recv2.last_params.seq_num=" + << ", recv2.last_params.seq_num=" << receiver2()->last_params().seq_num - << "recv2.last_data=" << receiver2()->last_data(); + << ", recv2.last_data=" << receiver2()->last_data(); LOG(LS_VERBOSE) << "chan2 sending: 'hi chan1' -----------------------------"; ASSERT_TRUE(SendData(channel2(), 2, "hi chan1", &result)); EXPECT_EQ(cricket::SDR_SUCCESS, result); EXPECT_TRUE_WAIT(ReceivedData(receiver1(), 2, "hi chan1"), 1000); LOG(LS_VERBOSE) << "recv1.received=" << receiver1()->received() - << "recv1.last_params.ssrc=" + << ", recv1.last_params.ssrc=" << receiver1()->last_params().ssrc - << "recv1.last_params.timestamp=" + << ", recv1.last_params.timestamp=" << receiver1()->last_params().ssrc - << "recv1.last_params.seq_num=" + << ", recv1.last_params.seq_num=" << receiver1()->last_params().seq_num - << "recv1.last_data=" << receiver1()->last_data(); + << ", recv1.last_data=" << receiver1()->last_data(); +} + +// Sends a lot of large messages at once and verifies SDR_BLOCK is returned. +TEST_F(SctpDataMediaChannelTest, SendDataBlocked) { + SetupConnectedChannels(); + + cricket::SendDataResult result; + cricket::SendDataParams params; + params.ssrc = 1; + + std::vector buffer(1024 * 64, 0); + + for (size_t i = 0; i < 100; ++i) { + channel1()->SendData( + params, talk_base::Buffer(&buffer[0], buffer.size()), &result); + if (result == cricket::SDR_BLOCK) + break; + } + + EXPECT_EQ(cricket::SDR_BLOCK, result); } TEST_F(SctpDataMediaChannelTest, ClosesRemoteStream) { diff --git a/talk/media/webrtc/OWNERS b/talk/media/webrtc/OWNERS new file mode 100644 index 000000000..9a3546ef8 --- /dev/null +++ b/talk/media/webrtc/OWNERS @@ -0,0 +1,3 @@ +mflodman@webrtc.org +pthatcher@webrtc.org +wu@webrtc.org diff --git a/talk/media/webrtc/constants.h b/talk/media/webrtc/constants.h new file mode 100755 index 000000000..68f664bd9 --- /dev/null +++ b/talk/media/webrtc/constants.h @@ -0,0 +1,46 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef TALK_MEDIA_WEBRTC_CONSTANTS_H_ +#define TALK_MEDIA_WEBRTC_CONSTANTS_H_ + +namespace cricket { + +extern const int kVideoMtu; +extern const int kVideoRtpBufferSize; + +extern const char kVp8CodecName[]; + +extern const int kDefaultFramerate; +extern const int kMinVideoBitrate; +extern const int kStartVideoBitrate; +extern const int kMaxVideoBitrate; + +extern const int kCpuMonitorPeriodMs; + +} // namespace cricket + +#endif // TALK_MEDIA_WEBRTC_CONSTANTS_H_ diff --git a/talk/media/webrtc/fakewebrtcvideocapturemodule.h b/talk/media/webrtc/fakewebrtcvideocapturemodule.h index d63e710a7..347e4b713 100644 --- a/talk/media/webrtc/fakewebrtcvideocapturemodule.h +++ b/talk/media/webrtc/fakewebrtcvideocapturemodule.h @@ -59,7 +59,6 @@ class FakeWebRtcVideoCaptureModule : public webrtc::VideoCaptureModule { id_ = id; return 0; } -#if defined(USE_WEBRTC_DEV_BRANCH) virtual void RegisterCaptureDataCallback( webrtc::VideoCaptureDataCallback& callback) { callback_ = &callback; @@ -79,37 +78,6 @@ class FakeWebRtcVideoCaptureModule : public webrtc::VideoCaptureModule { virtual void EnableNoPictureAlarm(const bool enable) { // not implemented } -#else - virtual int32_t RegisterCaptureDataCallback( - webrtc::VideoCaptureDataCallback& callback) { - callback_ = &callback; - return 0; - } - virtual int32_t DeRegisterCaptureDataCallback() { - callback_ = NULL; - return 0; - } - virtual int32_t RegisterCaptureCallback( - webrtc::VideoCaptureFeedBack& callback) { - return -1; // not implemented - } - virtual int32_t DeRegisterCaptureCallback() { - return 0; - } - virtual int32_t SetCaptureDelay(int32_t delay) { - delay_ = delay; - return 0; - } - virtual int32_t CaptureDelay() { - return delay_; - } - virtual int32_t EnableFrameRateCallback(const bool enable) { - return -1; // not implemented - } - virtual int32_t EnableNoPictureAlarm(const bool enable) { - return -1; // not implemented - } -#endif virtual int32_t StartCapture( const webrtc::VideoCaptureCapability& cap) { if (running_) return -1; diff --git a/talk/media/webrtc/fakewebrtcvideoengine.h b/talk/media/webrtc/fakewebrtcvideoengine.h index 1b6031a88..85c59d838 100644 --- a/talk/media/webrtc/fakewebrtcvideoengine.h +++ b/talk/media/webrtc/fakewebrtcvideoengine.h @@ -41,16 +41,6 @@ #include "talk/media/webrtc/webrtcvideoencoderfactory.h" #include "talk/media/webrtc/webrtcvie.h" -#if !defined(USE_WEBRTC_DEV_BRANCH) -namespace webrtc { - -bool operator==(const webrtc::VideoCodec& c1, const webrtc::VideoCodec& c2) { - return memcmp(&c1, &c2, sizeof(c1)) == 0; -} - -} -#endif - namespace cricket { #define WEBRTC_CHECK_CAPTURER(capturer) \ @@ -244,6 +234,10 @@ class FakeWebRtcVideoEncoderFactory : public WebRtcVideoEncoderFactory { (*it)->OnCodecsAvailable(); } + int GetNumCreatedEncoders() { + return num_created_encoders_; + } + const std::vector& encoders() { return encoders_; } @@ -277,15 +271,16 @@ class FakeWebRtcVideoEngine can_transmit_(true), remote_rtx_ssrc_(-1), rtx_send_payload_type(-1), + rtx_recv_payload_type(-1), rtcp_status_(webrtc::kRtcpNone), key_frame_request_method_(webrtc::kViEKeyFrameRequestNone), tmmbr_(false), remb_contribute_(false), remb_bw_partition_(false), - rtp_offset_send_id_(0), - rtp_offset_receive_id_(0), - rtp_absolute_send_time_send_id_(0), - rtp_absolute_send_time_receive_id_(0), + rtp_offset_send_id_(-1), + rtp_offset_receive_id_(-1), + rtp_absolute_send_time_send_id_(-1), + rtp_absolute_send_time_receive_id_(-1), sender_target_delay_(0), receiver_target_delay_(0), transmission_smoothing_(false), @@ -296,13 +291,13 @@ class FakeWebRtcVideoEngine send_nack_bitrate_(0), send_bandwidth_(0), receive_bandwidth_(0), + reserved_transmit_bitrate_bps_(0), suspend_below_min_bitrate_(false), - overuse_observer_(NULL) { + overuse_observer_(NULL), + last_recvd_payload_type_(-1) { ssrcs_[0] = 0; // default ssrc. memset(&send_codec, 0, sizeof(send_codec)); -#ifdef USE_WEBRTC_DEV_BRANCH memset(&overuse_options_, 0, sizeof(overuse_options_)); -#endif } int capture_id_; int original_channel_id_; @@ -315,6 +310,7 @@ class FakeWebRtcVideoEngine std::map rtx_ssrcs_; int remote_rtx_ssrc_; int rtx_send_payload_type; + int rtx_recv_payload_type; std::string cname_; webrtc::ViERTCPMode rtcp_status_; webrtc::ViEKeyFrameRequestMethod key_frame_request_method_; @@ -339,11 +335,11 @@ class FakeWebRtcVideoEngine unsigned int send_nack_bitrate_; unsigned int send_bandwidth_; unsigned int receive_bandwidth_; + unsigned int reserved_transmit_bitrate_bps_; bool suspend_below_min_bitrate_; webrtc::CpuOveruseObserver* overuse_observer_; -#ifdef USE_WEBRTC_DEV_BRANCH webrtc::CpuOveruseOptions overuse_options_; -#endif + int last_recvd_payload_type_; }; class Capturer : public webrtc::ViEExternalCapture { public: @@ -491,21 +487,24 @@ class FakeWebRtcVideoEngine WEBRTC_ASSERT_CHANNEL(channel); return channels_.find(channel)->second->remb_contribute_; } - int GetSendRtpTimestampOffsetExtensionId(int channel) { - WEBRTC_ASSERT_CHANNEL(channel); - return channels_.find(channel)->second->rtp_offset_send_id_; - } - int GetReceiveRtpTimestampOffsetExtensionId(int channel) { + int GetSendRtpExtensionId(int channel, const std::string& extension) { WEBRTC_ASSERT_CHANNEL(channel); - return channels_.find(channel)->second->rtp_offset_receive_id_; - } - int GetSendAbsoluteSendTimeExtensionId(int channel) { - WEBRTC_ASSERT_CHANNEL(channel); - return channels_.find(channel)->second->rtp_absolute_send_time_send_id_; + if (extension == kRtpTimestampOffsetHeaderExtension) { + return channels_.find(channel)->second->rtp_offset_send_id_; + } else if (extension == kRtpAbsoluteSenderTimeHeaderExtension) { + return channels_.find(channel)->second->rtp_absolute_send_time_send_id_; + } + return -1; } - int GetReceiveAbsoluteSendTimeExtensionId(int channel) { + int GetReceiveRtpExtensionId(int channel, const std::string& extension) { WEBRTC_ASSERT_CHANNEL(channel); - return channels_.find(channel)->second->rtp_absolute_send_time_receive_id_; + if (extension == kRtpTimestampOffsetHeaderExtension) { + return channels_.find(channel)->second->rtp_offset_receive_id_; + } else if (extension == kRtpAbsoluteSenderTimeHeaderExtension) { + return + channels_.find(channel)->second->rtp_absolute_send_time_receive_id_; + } + return -1; } bool GetTransmissionSmoothingStatus(int channel) { WEBRTC_ASSERT_CHANNEL(channel); @@ -545,12 +544,10 @@ class FakeWebRtcVideoEngine WEBRTC_ASSERT_CHANNEL(channel); return channels_.find(channel)->second->overuse_observer_; } -#ifdef USE_WEBRTC_DEV_BRANCH webrtc::CpuOveruseOptions GetCpuOveruseOptions(int channel) const { WEBRTC_ASSERT_CHANNEL(channel); return channels_.find(channel)->second->overuse_options_; } -#endif int GetRtxSsrc(int channel, int simulcast_idx) const { WEBRTC_ASSERT_CHANNEL(channel); if (channels_.find(channel)->second->rtx_ssrcs_.find(simulcast_idx) == @@ -562,16 +559,9 @@ class FakeWebRtcVideoEngine bool ReceiveCodecRegistered(int channel, const webrtc::VideoCodec& codec) const { WEBRTC_ASSERT_CHANNEL(channel); -#if !defined(USE_WEBRTC_DEV_BRANCH) const std::vector& codecs = channels_.find(channel)->second->recv_codecs; return std::find(codecs.begin(), codecs.end(), codec) != codecs.end(); -#else - // TODO(mallinath) - Remove this specilization after this change is pushed - // to googlecode and operator== from VideoCodecDerived moved inside - // VideoCodec. - return true; -#endif }; bool ExternalDecoderRegistered(int channel, unsigned int pl_type) const { @@ -612,17 +602,22 @@ class FakeWebRtcVideoEngine } void SetSendBandwidthEstimate(int channel, unsigned int send_bandwidth) { WEBRTC_ASSERT_CHANNEL(channel); - channels_[channel]->send_bandwidth_ = send_bandwidth; + channels_[GetOriginalChannelId(channel)]->send_bandwidth_ = send_bandwidth; } void SetReceiveBandwidthEstimate(int channel, unsigned int receive_bandwidth) { WEBRTC_ASSERT_CHANNEL(channel); - channels_[channel]->receive_bandwidth_ = receive_bandwidth; + channels_[GetOriginalChannelId(channel)]->receive_bandwidth_ = + receive_bandwidth; }; int GetRtxSendPayloadType(int channel) { WEBRTC_CHECK_CHANNEL(channel); return channels_[channel]->rtx_send_payload_type; } + int GetRtxRecvPayloadType(int channel) { + WEBRTC_CHECK_CHANNEL(channel); + return channels_[channel]->rtx_recv_payload_type; + } int GetRemoteRtxSsrc(int channel) { WEBRTC_CHECK_CHANNEL(channel); return channels_.find(channel)->second->remote_rtx_ssrc_; @@ -631,6 +626,14 @@ class FakeWebRtcVideoEngine WEBRTC_ASSERT_CHANNEL(channel); return channels_.find(channel)->second->suspend_below_min_bitrate_; } + int GetLastRecvdPayloadType(int channel) const { + WEBRTC_CHECK_CHANNEL(channel); + return channels_.find(channel)->second->last_recvd_payload_type_; + } + unsigned int GetReservedTransmitBitrate(int channel) { + WEBRTC_ASSERT_CHANNEL(channel); + return channels_.find(channel)->second->reserved_transmit_bitrate_bps_; + } WEBRTC_STUB(Release, ()); @@ -648,7 +651,11 @@ class FakeWebRtcVideoEngine return -1; } Channel* ch = new Channel(); - channels_[++last_channel_] = ch; + ++last_channel_; + // The original channel of the first channel in a group refers to itself + // for code simplicity. + ch->original_channel_id_ = last_channel_; + channels_[last_channel_] = ch; channel = last_channel_; return 0; }; @@ -678,14 +685,12 @@ class FakeWebRtcVideoEngine return 0; } WEBRTC_STUB(CpuOveruseMeasures, (int, int*, int*, int*, int*)); -#ifdef USE_WEBRTC_DEV_BRANCH WEBRTC_FUNC(SetCpuOveruseOptions, (int channel, const webrtc::CpuOveruseOptions& options)) { WEBRTC_CHECK_CHANNEL(channel); channels_[channel]->overuse_options_ = options; return 0; } -#endif WEBRTC_STUB(ConnectAudioChannel, (const int, const int)); WEBRTC_STUB(DisconnectAudioChannel, (const int)); WEBRTC_FUNC(StartSend, (const int channel)) { @@ -873,12 +878,24 @@ class FakeWebRtcVideoEngine } WEBRTC_STUB(RegisterSendTransport, (const int, webrtc::Transport&)); WEBRTC_STUB(DeregisterSendTransport, (const int)); - WEBRTC_STUB(ReceivedRTPPacket, (const int, const void*, const int, - const webrtc::PacketTime&)); + + WEBRTC_FUNC(ReceivedRTPPacket, (const int channel, + const void* packet, + const int length, + const webrtc::PacketTime& packet_time)) { + WEBRTC_ASSERT_CHANNEL(channel); + ASSERT(length > 1); + uint8_t payload_type = static_cast(packet)[1] & 0x7F; + channels_[channel]->last_recvd_payload_type_ = payload_type; + return 0; + } + WEBRTC_STUB(ReceivedRTCPPacket, (const int, const void*, const int)); // Not using WEBRTC_STUB due to bool return value virtual bool IsIPv6Enabled(int channel) { return true; } WEBRTC_STUB(SetMTU, (int, unsigned int)); + WEBRTC_STUB(ReceivedBWEPacket, (const int, int64_t, int, + const webrtc::RTPHeader&)); // webrtc::ViERender WEBRTC_STUB(RegisterVideoRenderModule, (webrtc::VideoRender&)); @@ -982,7 +999,17 @@ class FakeWebRtcVideoEngine channels_[channel]->rtx_send_payload_type = payload_type; return 0; } - WEBRTC_STUB(SetRtxReceivePayloadType, (const int, const uint8)); + +#ifdef USE_WEBRTC_DEV_BRANCH + WEBRTC_STUB(SetPadWithRedundantPayloads, (int, bool)); +#endif + + WEBRTC_FUNC(SetRtxReceivePayloadType, (const int channel, + const uint8 payload_type)) { + WEBRTC_CHECK_CHANNEL(channel); + channels_[channel]->rtx_recv_payload_type = payload_type; + return 0; + } WEBRTC_STUB(SetStartSequenceNumber, (const int, unsigned short)); WEBRTC_FUNC(SetRTCPStatus, @@ -1061,25 +1088,25 @@ class FakeWebRtcVideoEngine WEBRTC_FUNC(SetSendTimestampOffsetStatus, (int channel, bool enable, int id)) { WEBRTC_CHECK_CHANNEL(channel); - channels_[channel]->rtp_offset_send_id_ = (enable) ? id : 0; + channels_[channel]->rtp_offset_send_id_ = (enable) ? id : -1; return 0; } WEBRTC_FUNC(SetReceiveTimestampOffsetStatus, (int channel, bool enable, int id)) { WEBRTC_CHECK_CHANNEL(channel); - channels_[channel]->rtp_offset_receive_id_ = (enable) ? id : 0; + channels_[channel]->rtp_offset_receive_id_ = (enable) ? id : -1; return 0; } WEBRTC_FUNC(SetSendAbsoluteSendTimeStatus, (int channel, bool enable, int id)) { WEBRTC_CHECK_CHANNEL(channel); - channels_[channel]->rtp_absolute_send_time_send_id_ = (enable) ? id : 0; + channels_[channel]->rtp_absolute_send_time_send_id_ = (enable) ? id : -1; return 0; } WEBRTC_FUNC(SetReceiveAbsoluteSendTimeStatus, (int channel, bool enable, int id)) { WEBRTC_CHECK_CHANNEL(channel); - channels_[channel]->rtp_absolute_send_time_receive_id_ = (enable) ? id : 0; + channels_[channel]->rtp_absolute_send_time_receive_id_ = (enable) ? id : -1; return 0; } WEBRTC_STUB(SetRtcpXrRrtrStatus, (int, bool)); @@ -1088,10 +1115,15 @@ class FakeWebRtcVideoEngine channels_[channel]->transmission_smoothing_ = enable; return 0; } -#ifdef USE_WEBRTC_DEV_BRANCH + WEBRTC_FUNC(SetReservedTransmitBitrate, (int channel, + unsigned int reserved_transmit_bitrate_bps)) { + WEBRTC_CHECK_CHANNEL(channel); + channels_[channel]->reserved_transmit_bitrate_bps_ = + reserved_transmit_bitrate_bps; + return 0; + } WEBRTC_STUB_CONST(GetRtcpPacketTypeCounters, (int, webrtc::RtcpPacketTypeCounter*, webrtc::RtcpPacketTypeCounter*)); -#endif WEBRTC_STUB_CONST(GetReceivedRTCPStatistics, (const int, unsigned short&, unsigned int&, unsigned int&, unsigned int&, int&)); WEBRTC_STUB_CONST(GetSentRTCPStatistics, (const int, unsigned short&, @@ -1128,6 +1160,7 @@ class FakeWebRtcVideoEngine std::map::const_iterator it = channels_.find(channel); // Assume the current video, fec and nack bitrate sums up to our estimate. if (it->second->send) { + it = channels_.find(GetOriginalChannelId(channel)); *send_bandwidth_estimate = it->second->send_bandwidth_; } else { *send_bandwidth_estimate = 0; @@ -1139,7 +1172,7 @@ class FakeWebRtcVideoEngine WEBRTC_CHECK_CHANNEL(channel); std::map::const_iterator it = channels_.find(channel); if (it->second->receive_) { - // For simplicity, assume all channels receive half of max send rate. + it = channels_.find(GetOriginalChannelId(channel)); *receive_bandwidth_estimate = it->second->receive_bandwidth_; } else { *receive_bandwidth_estimate = 0; diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h index b552b49aa..25c952d33 100644 --- a/talk/media/webrtc/fakewebrtcvoiceengine.h +++ b/talk/media/webrtc/fakewebrtcvoiceengine.h @@ -32,16 +32,18 @@ #include #include - #include "talk/base/basictypes.h" #include "talk/base/gunit.h" #include "talk/base/stringutils.h" #include "talk/media/base/codec.h" +#include "talk/media/base/rtputils.h" #include "talk/media/base/voiceprocessor.h" #include "talk/media/webrtc/fakewebrtccommon.h" #include "talk/media/webrtc/webrtcvoe.h" -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" -#include "webrtc/common.h" + +namespace webrtc { +class ViENetwork; +} namespace cricket { @@ -59,7 +61,6 @@ static const int kFakeDeviceId = 0; static const int kFakeDeviceId = 1; #endif - // Verify the header extension ID, if enabled, is within the bounds specified in // [RFC5285]: 1-14 inclusive. #define WEBRTC_CHECK_HEADER_EXTENSION_ID(enable, id) \ @@ -87,7 +88,7 @@ class FakeWebRtcVoiceEngine int dtmf_length_ms; }; struct Channel { - explicit Channel(bool use_experimental_acm) + explicit Channel() : external_transport(false), send(false), playout(false), @@ -96,7 +97,8 @@ class FakeWebRtcVoiceEngine volume_pan_right(1.0), file(false), vad(false), - fec(false), + codec_fec(false), + red(false), nack(false), media_processor_registered(false), rx_agc_enabled(false), @@ -104,13 +106,15 @@ class FakeWebRtcVoiceEngine cn8_type(13), cn16_type(105), dtmf_type(106), - fec_type(117), + red_type(117), nack_max_packets(0), + vie_network(NULL), + video_channel(-1), send_ssrc(0), send_audio_level_ext_(-1), + receive_audio_level_ext_(-1), send_absolute_sender_time_ext_(-1), - receive_absolute_sender_time_ext_(-1), - using_experimental_acm(use_experimental_acm) { + receive_absolute_sender_time_ext_(-1) { memset(&send_codec, 0, sizeof(send_codec)); memset(&rx_agc_config, 0, sizeof(rx_agc_config)); } @@ -122,7 +126,8 @@ class FakeWebRtcVoiceEngine float volume_pan_right; bool file; bool vad; - bool fec; + bool codec_fec; + bool red; bool nack; bool media_processor_registered; bool rx_agc_enabled; @@ -131,17 +136,20 @@ class FakeWebRtcVoiceEngine int cn8_type; int cn16_type; int dtmf_type; - int fec_type; + int red_type; int nack_max_packets; + webrtc::ViENetwork* vie_network; + int video_channel; uint32 send_ssrc; int send_audio_level_ext_; + int receive_audio_level_ext_; int send_absolute_sender_time_ext_; int receive_absolute_sender_time_ext_; DtmfInfo dtmf_info; std::vector recv_codecs; webrtc::CodecInst send_codec; + webrtc::PacketTime last_rtp_packet_time; std::list packets; - bool using_experimental_acm; }; FakeWebRtcVoiceEngine(const cricket::AudioCodec* const* codecs, @@ -209,8 +217,11 @@ class FakeWebRtcVoiceEngine bool GetVAD(int channel) { return channels_[channel]->vad; } - bool GetFEC(int channel) { - return channels_[channel]->fec; + bool GetRED(int channel) { + return channels_[channel]->red; + } + bool GetCodecFEC(int channel) { + return channels_[channel]->codec_fec; } bool GetNACK(int channel) { return channels_[channel]->nack; @@ -218,9 +229,17 @@ class FakeWebRtcVoiceEngine int GetNACKMaxPackets(int channel) { return channels_[channel]->nack_max_packets; } - bool IsUsingExperimentalAcm(int channel) { + webrtc::ViENetwork* GetViENetwork(int channel) { WEBRTC_ASSERT_CHANNEL(channel); - return channels_[channel]->using_experimental_acm; + return channels_[channel]->vie_network; + } + int GetVideoChannel(int channel) { + WEBRTC_ASSERT_CHANNEL(channel); + return channels_[channel]->video_channel; + } + const webrtc::PacketTime& GetLastRtpPacketTime(int channel) { + WEBRTC_ASSERT_CHANNEL(channel); + return channels_[channel]->last_rtp_packet_time; } int GetSendCNPayloadType(int channel, bool wideband) { return (wideband) ? @@ -230,8 +249,8 @@ class FakeWebRtcVoiceEngine int GetSendTelephoneEventPayloadType(int channel) { return channels_[channel]->dtmf_type; } - int GetSendFECPayloadType(int channel) { - return channels_[channel]->fec_type; + int GetSendREDPayloadType(int channel) { + return channels_[channel]->red_type; } bool CheckPacket(int channel, const void* data, size_t len) { bool result = !CheckNoPacket(channel); @@ -275,11 +294,11 @@ class FakeWebRtcVoiceEngine true); } } - int AddChannel(bool use_experimental_acm) { + int AddChannel() { if (fail_create_channel_) { return -1; } - Channel* ch = new Channel(use_experimental_acm); + Channel* ch = new Channel(); for (int i = 0; i < NumOfCodecs(); ++i) { webrtc::CodecInst codec; GetCodec(i, codec); @@ -288,14 +307,23 @@ class FakeWebRtcVoiceEngine channels_[++last_channel_] = ch; return last_channel_; } - int GetSendAudioLevelId(int channel) { - return channels_[channel]->send_audio_level_ext_; - } - int GetSendAbsoluteSenderTimeId(int channel) { - return channels_[channel]->send_absolute_sender_time_ext_; + int GetSendRtpExtensionId(int channel, const std::string& extension) { + WEBRTC_ASSERT_CHANNEL(channel); + if (extension == kRtpAudioLevelHeaderExtension) { + return channels_[channel]->send_audio_level_ext_; + } else if (extension == kRtpAbsoluteSenderTimeHeaderExtension) { + return channels_[channel]->send_absolute_sender_time_ext_; + } + return -1; } - int GetReceiveAbsoluteSenderTimeId(int channel) { - return channels_[channel]->receive_absolute_sender_time_ext_; + int GetReceiveRtpExtensionId(int channel, const std::string& extension) { + WEBRTC_ASSERT_CHANNEL(channel); + if (extension == kRtpAudioLevelHeaderExtension) { + return channels_[channel]->receive_audio_level_ext_; + } else if (extension == kRtpAbsoluteSenderTimeHeaderExtension) { + return channels_[channel]->receive_absolute_sender_time_ext_; + } + return -1; } int GetNumSetSendCodecs() const { return num_set_send_codecs_; } @@ -322,13 +350,10 @@ class FakeWebRtcVoiceEngine return NULL; } WEBRTC_FUNC(CreateChannel, ()) { - return AddChannel(false); + return AddChannel(); } - WEBRTC_FUNC(CreateChannel, (const webrtc::Config& config)) { - talk_base::scoped_ptr acm( - config.Get().Create(0)); - return AddChannel(strcmp(acm->Version(), webrtc::kExperimentalAcmVersion) - == 0); + WEBRTC_FUNC(CreateChannel, (const webrtc::Config& /*config*/)) { + return AddChannel(); } WEBRTC_FUNC(DeleteChannel, (int channel)) { WEBRTC_CHECK_CHANNEL(channel); @@ -418,7 +443,26 @@ class FakeWebRtcVoiceEngine WEBRTC_STUB(RemoveSecondarySendCodec, (int channel)); WEBRTC_STUB(GetSecondarySendCodec, (int channel, webrtc::CodecInst& codec)); - WEBRTC_STUB(GetRecCodec, (int channel, webrtc::CodecInst& codec)); + WEBRTC_FUNC(GetRecCodec, (int channel, webrtc::CodecInst& codec)) { + WEBRTC_CHECK_CHANNEL(channel); + const Channel* c = channels_[channel]; + for (std::list::const_iterator it_packet = c->packets.begin(); + it_packet != c->packets.end(); ++it_packet) { + int pltype; + if (!GetRtpPayloadType(it_packet->data(), it_packet->length(), &pltype)) { + continue; + } + for (std::vector::const_iterator it_codec = + c->recv_codecs.begin(); it_codec != c->recv_codecs.end(); + ++it_codec) { + if (it_codec->pltype == pltype) { + codec = *it_codec; + return 0; + } + } + } + return -1; + } WEBRTC_STUB(SetAMREncFormat, (int channel, webrtc::AmrMode mode)); WEBRTC_STUB(SetAMRDecFormat, (int channel, webrtc::AmrMode mode)); WEBRTC_STUB(SetAMRWbEncFormat, (int channel, webrtc::AmrMode mode)); @@ -492,6 +536,18 @@ class FakeWebRtcVoiceEngine } WEBRTC_STUB(GetVADStatus, (int channel, bool& enabled, webrtc::VadModes& mode, bool& disabledDTX)); +#ifdef USE_WEBRTC_DEV_BRANCH + WEBRTC_FUNC(SetFECStatus, (int channel, bool enable)) { + WEBRTC_CHECK_CHANNEL(channel); + channels_[channel]->codec_fec = enable; + return 0; + } + WEBRTC_FUNC(GetFECStatus, (int channel, bool& enable)) { + WEBRTC_CHECK_CHANNEL(channel); + enable = channels_[channel]->codec_fec; + return 0; + } +#endif // USE_WEBRTC_DEV_BRANCH // webrtc::VoEDtmf WEBRTC_FUNC(SendTelephoneEvent, (int channel, int event_code, @@ -689,6 +745,17 @@ class FakeWebRtcVoiceEngine std::string(static_cast(data), length)); return 0; } + WEBRTC_FUNC(ReceivedRTPPacket, (int channel, const void* data, + unsigned int length, + const webrtc::PacketTime& packet_time)) { + WEBRTC_CHECK_CHANNEL(channel); + if (ReceivedRTPPacket(channel, data, length) == -1) { + return -1; + } + channels_[channel]->last_rtp_packet_time = packet_time; + return 0; + } + WEBRTC_STUB(ReceivedRTCPPacket, (int channel, const void* data, unsigned int length)); @@ -710,25 +777,22 @@ class FakeWebRtcVoiceEngine return 0; } WEBRTC_STUB(GetRemoteSSRC, (int channel, unsigned int& ssrc)); -#ifndef USE_WEBRTC_DEV_BRANCH - WEBRTC_FUNC(SetRTPAudioLevelIndicationStatus, (int channel, bool enable, + WEBRTC_FUNC(SetSendAudioLevelIndicationStatus, (int channel, bool enable, unsigned char id)) { WEBRTC_CHECK_CHANNEL(channel); WEBRTC_CHECK_HEADER_EXTENSION_ID(enable, id); channels_[channel]->send_audio_level_ext_ = (enable) ? id : -1; return 0; } - WEBRTC_STUB(GetRTPAudioLevelIndicationStatus, (int channel, bool& enable, - unsigned char& id)); -#endif #ifdef USE_WEBRTC_DEV_BRANCH - WEBRTC_FUNC(SetSendAudioLevelIndicationStatus, (int channel, bool enable, + WEBRTC_FUNC(SetReceiveAudioLevelIndicationStatus, (int channel, bool enable, unsigned char id)) { WEBRTC_CHECK_CHANNEL(channel); WEBRTC_CHECK_HEADER_EXTENSION_ID(enable, id); - channels_[channel]->send_audio_level_ext_ = (enable) ? id : -1; - return 0; + channels_[channel]->receive_audio_level_ext_ = (enable) ? id : -1; + return 0; } +#endif // USE_WEBRTC_DEV_BRANCH WEBRTC_FUNC(SetSendAbsoluteSenderTimeStatus, (int channel, bool enable, unsigned char id)) { WEBRTC_CHECK_CHANNEL(channel); @@ -743,7 +807,6 @@ class FakeWebRtcVoiceEngine channels_[channel]->receive_absolute_sender_time_ext_ = (enable) ? id : -1; return 0; } -#endif WEBRTC_STUB(GetRemoteCSRCs, (int channel, unsigned int arrCSRC[15])); WEBRTC_STUB(SetRTCPStatus, (int channel, bool enable)); @@ -797,16 +860,24 @@ class FakeWebRtcVoiceEngine stats.packetsReceived = kIntStatValue; return 0; } +#ifdef USE_WEBRTC_DEV_BRANCH + WEBRTC_FUNC(SetREDStatus, (int channel, bool enable, int redPayloadtype)) { +#else WEBRTC_FUNC(SetFECStatus, (int channel, bool enable, int redPayloadtype)) { +#endif // USE_WEBRTC_DEV_BRANCH WEBRTC_CHECK_CHANNEL(channel); - channels_[channel]->fec = enable; - channels_[channel]->fec_type = redPayloadtype; + channels_[channel]->red = enable; + channels_[channel]->red_type = redPayloadtype; return 0; } +#ifdef USE_WEBRTC_DEV_BRANCH + WEBRTC_FUNC(GetREDStatus, (int channel, bool& enable, int& redPayloadtype)) { +#else WEBRTC_FUNC(GetFECStatus, (int channel, bool& enable, int& redPayloadtype)) { +#endif // USE_WEBRTC_DEV_BRANCH WEBRTC_CHECK_CHANNEL(channel); - enable = channels_[channel]->fec; - redPayloadtype = channels_[channel]->fec_type; + enable = channels_[channel]->red; + redPayloadtype = channels_[channel]->red_type; return 0; } WEBRTC_FUNC(SetNACKStatus, (int channel, bool enable, int maxNoPackets)) { @@ -824,6 +895,14 @@ class FakeWebRtcVoiceEngine unsigned short payloadSize)); WEBRTC_STUB(GetLastRemoteTimeStamp, (int channel, uint32_t* lastRemoteTimeStamp)); + WEBRTC_FUNC(SetVideoEngineBWETarget, (int channel, + webrtc::ViENetwork* vie_network, + int video_channel)) { + WEBRTC_CHECK_CHANNEL(channel); + channels_[channel]->vie_network = vie_network; + channels_[channel]->video_channel = video_channel; + return 0; + } // webrtc::VoEVideoSync WEBRTC_STUB(GetPlayoutBufferSize, (int& bufferMs)); diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h b/talk/media/webrtc/webrtcmediaengine.cc similarity index 54% rename from talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h rename to talk/media/webrtc/webrtcmediaengine.cc index 3e6d600bb..445564c82 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h +++ b/talk/media/webrtc/webrtcmediaengine.cc @@ -1,6 +1,6 @@ /* * libjingle - * Copyright 2013, Google Inc. + * Copyright 2014 Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -25,36 +25,34 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import - -#import "GAEChannelClient.h" -#import "APPRTCAppClient.h" -#import "RTCSessionDescriptonDelegate.h" -#import "RTCVideoSource.h" -// Used to send a message to an apprtc.appspot.com "room". -@protocol APPRTCSendMessage - -- (void)sendData:(NSData*)data; -// Logging helper. -- (void)displayLogMessage:(NSString*)message; -@end - -@class APPRTCViewController; -@class RTCVideoTrack; - -// The main application class of the AppRTCDemo iOS app demonstrating -// interoperability between the Objective C implementation of PeerConnection -// and the apprtc.appspot.com demo webapp. -@interface APPRTCAppDelegate : UIResponder - -@property(strong, nonatomic) UIWindow* window; -@property(strong, nonatomic) APPRTCViewController* viewController; -@property (strong, nonatomic) RTCVideoSource* videoSource; - -- (void)closeVideoUI; - -@end +#include "talk/media/webrtc/webrtcmediaengine.h" +#include "webrtc/system_wrappers/interface/field_trial.h" + +WRME_EXPORT +cricket::MediaEngineInterface* CreateWebRtcMediaEngine( + webrtc::AudioDeviceModule* adm, + webrtc::AudioDeviceModule* adm_sc, + cricket::WebRtcVideoEncoderFactory* encoder_factory, + cricket::WebRtcVideoDecoderFactory* decoder_factory) { +#ifdef WEBRTC_CHROMIUM_BUILD + if (webrtc::field_trial::FindFullName("WebRTC-NewVideoAPI") == "Enabled") { + return new cricket::WebRtcMediaEngine2( + adm, adm_sc, encoder_factory, decoder_factory); + } +#endif // WEBRTC_CHROMIUM_BUILD + return new cricket::WebRtcMediaEngine( + adm, adm_sc, encoder_factory, decoder_factory); +} + +WRME_EXPORT +void DestroyWebRtcMediaEngine(cricket::MediaEngineInterface* media_engine) { +#ifdef WEBRTC_CHROMIUM_BUILD + if (webrtc::field_trial::FindFullName("WebRTC-NewVideoAPI") == "Enabled") { + delete static_cast(media_engine); + } else { +#endif // WEBRTC_CHROMIUM_BUILD + delete static_cast(media_engine); +#ifdef WEBRTC_CHROMIUM_BUILD + } +#endif // WEBRTC_CHROMIUM_BUILD +} diff --git a/talk/media/webrtc/webrtcmediaengine.h b/talk/media/webrtc/webrtcmediaengine.h index 84319a11f..6ca39e7dc 100644 --- a/talk/media/webrtc/webrtcmediaengine.h +++ b/talk/media/webrtc/webrtcmediaengine.h @@ -175,6 +175,9 @@ class WebRtcMediaEngine : public cricket::MediaEngineInterface { #else #include "talk/media/webrtc/webrtcvideoengine.h" +#ifdef WEBRTC_CHROMIUM_BUILD +#include "talk/media/webrtc/webrtcvideoengine2.h" +#endif #include "talk/media/webrtc/webrtcvoiceengine.h" namespace cricket { @@ -195,6 +198,23 @@ class WebRtcMediaEngine : public WebRtcCompositeMediaEngine { } }; +#ifdef WEBRTC_CHROMIUM_BUILD +typedef CompositeMediaEngine + WebRtcCompositeMediaEngine2; + +class WebRtcMediaEngine2 : public WebRtcCompositeMediaEngine2 { + public: + WebRtcMediaEngine2(webrtc::AudioDeviceModule* adm, + webrtc::AudioDeviceModule* adm_sc, + WebRtcVideoEncoderFactory* encoder_factory, + WebRtcVideoDecoderFactory* decoder_factory) { + voice_.SetAudioDeviceModule(adm, adm_sc); + video_.SetVoiceEngine(&voice_); + video_.EnableTimedRender(); + } +}; +#endif + } // namespace cricket #endif // !defined(LIBPEERCONNECTION_LIB) && diff --git a/talk/media/webrtc/webrtcvideocapturer.cc b/talk/media/webrtc/webrtcvideocapturer.cc index 0048cf40d..cb99a2a9c 100644 --- a/talk/media/webrtc/webrtcvideocapturer.cc +++ b/talk/media/webrtc/webrtcvideocapturer.cc @@ -271,13 +271,8 @@ CaptureState WebRtcVideoCapturer::Start(const VideoFormat& capture_format) { std::string camera_id(GetId()); uint32 start = talk_base::Time(); -#if defined(USE_WEBRTC_DEV_BRANCH) module_->RegisterCaptureDataCallback(*this); if (module_->StartCapture(cap) != 0) { -#else - if (module_->RegisterCaptureDataCallback(*this) != 0 || - module_->StartCapture(cap) != 0) { -#endif LOG(LS_ERROR) << "Camera '" << camera_id << "' failed to start"; return CS_FAILED; } diff --git a/talk/media/webrtc/webrtcvideochannelfactory.h b/talk/media/webrtc/webrtcvideochannelfactory.h new file mode 100644 index 000000000..646348cd5 --- /dev/null +++ b/talk/media/webrtc/webrtcvideochannelfactory.h @@ -0,0 +1,44 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_WEBRTC_WEBRTCVIDEOCHANNEL_H_ +#define TALK_MEDIA_WEBRTC_WEBRTCVIDEOCHANNEL_H_ + +namespace cricket { +class VoiceMediaChannel; +class WebRtcVideoEngine2; +class WebRtcVideoChannel2; + +class WebRtcVideoChannelFactory { + public: + virtual ~WebRtcVideoChannelFactory() {} + virtual WebRtcVideoChannel2* Create(WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel) = 0; +}; +} // namespace cricket + +#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOCHANNEL_H_ diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc index ceec3121f..3815aaa32 100644 --- a/talk/media/webrtc/webrtcvideoengine.cc +++ b/talk/media/webrtc/webrtcvideoengine.cc @@ -51,6 +51,7 @@ #include "talk/media/base/videocapturer.h" #include "talk/media/base/videorenderer.h" #include "talk/media/devices/filevideocapturer.h" +#include "talk/media/webrtc/constants.h" #include "talk/media/webrtc/webrtcpassthroughrender.h" #include "talk/media/webrtc/webrtctexturevideoframe.h" #include "talk/media/webrtc/webrtcvideocapturer.h" @@ -63,57 +64,47 @@ #include "webrtc/experiments.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" -#if !defined(LIBPEERCONNECTION_LIB) -#ifndef HAVE_WEBRTC_VIDEO -#error Need webrtc video -#endif -#include "talk/media/webrtc/webrtcmediaengine.h" - -WRME_EXPORT -cricket::MediaEngineInterface* CreateWebRtcMediaEngine( - webrtc::AudioDeviceModule* adm, webrtc::AudioDeviceModule* adm_sc, - cricket::WebRtcVideoEncoderFactory* encoder_factory, - cricket::WebRtcVideoDecoderFactory* decoder_factory) { - return new cricket::WebRtcMediaEngine(adm, adm_sc, encoder_factory, - decoder_factory); -} - -WRME_EXPORT -void DestroyWebRtcMediaEngine(cricket::MediaEngineInterface* media_engine) { - delete static_cast(media_engine); -} -#endif - namespace cricket { +// Constants defined in talk/media/webrtc/constants.h +// TODO(pbos): Move these to a separate constants.cc file. +const int kVideoMtu = 1200; +const int kVideoRtpBufferSize = 65536; -static const int kDefaultLogSeverity = talk_base::LS_WARNING; +const char kVp8CodecName[] = "VP8"; -static const int kMinVideoBitrate = 50; -static const int kStartVideoBitrate = 300; -static const int kMaxVideoBitrate = 2000; -static const int kDefaultConferenceModeMaxVideoBitrate = 500; +const int kDefaultFramerate = 30; +const int kMinVideoBitrate = 50; +const int kStartVideoBitrate = 300; +const int kMaxVideoBitrate = 2000; -// Controlled by exp, try a super low minimum bitrate for poor connections. -static const int kLowerMinBitrate = 30; +const int kCpuMonitorPeriodMs = 2000; // 2 seconds. -static const int kVideoMtu = 1200; -static const int kVideoRtpBufferSize = 65536; +static const int kDefaultLogSeverity = talk_base::LS_WARNING; -static const char kVp8PayloadName[] = "VP8"; -static const char kRedPayloadName[] = "red"; -static const char kFecPayloadName[] = "ulpfec"; +// Controlled by exp, try a super low minimum bitrate for poor connections. +static const int kLowerMinBitrate = 30; static const int kDefaultNumberOfTemporalLayers = 1; // 1:1 -static const int kMaxExternalVideoCodecs = 8; static const int kExternalVideoPayloadTypeBase = 120; +static bool BitrateIsSet(int value) { + return value > kAutoBandwidth; +} + +static int GetBitrate(int value, int deflt) { + return BitrateIsSet(value) ? value : deflt; +} + // Static allocation of payload type values for external video codec. static int GetExternalVideoPayloadType(int index) { +#if ENABLE_DEBUG + static const int kMaxExternalVideoCodecs = 8; ASSERT(index >= 0 && index < kMaxExternalVideoCodecs); +#endif return kExternalVideoPayloadTypeBase + index; } @@ -141,8 +132,6 @@ static int SeverityToFilter(int severity) { return filter; } -static const int kCpuMonitorPeriodMs = 2000; // 2 seconds. - static const bool kNotSending = false; // Default video dscp value. @@ -162,73 +151,6 @@ static bool IsRembEnabled(const VideoCodec& codec) { kParamValueEmpty)); } -// TODO(mallinath) - Remove this after trunk of webrtc is pushed to GTP. -#if !defined(USE_WEBRTC_DEV_BRANCH) -bool operator==(const webrtc::VideoCodecVP8& lhs, - const webrtc::VideoCodecVP8& rhs) { - return lhs.pictureLossIndicationOn == rhs.pictureLossIndicationOn && - lhs.feedbackModeOn == rhs.feedbackModeOn && - lhs.complexity == rhs.complexity && - lhs.resilience == rhs.resilience && - lhs.numberOfTemporalLayers == rhs.numberOfTemporalLayers && - lhs.denoisingOn == rhs.denoisingOn && - lhs.errorConcealmentOn == rhs.errorConcealmentOn && - lhs.automaticResizeOn == rhs.automaticResizeOn && - lhs.frameDroppingOn == rhs.frameDroppingOn && - lhs.keyFrameInterval == rhs.keyFrameInterval; -} - -bool operator!=(const webrtc::VideoCodecVP8& lhs, - const webrtc::VideoCodecVP8& rhs) { - return !(lhs == rhs); -} - -bool operator==(const webrtc::SimulcastStream& lhs, - const webrtc::SimulcastStream& rhs) { - return lhs.width == rhs.width && - lhs.height == rhs.height && - lhs.numberOfTemporalLayers == rhs.numberOfTemporalLayers && - lhs.maxBitrate == rhs.maxBitrate && - lhs.targetBitrate == rhs.targetBitrate && - lhs.minBitrate == rhs.minBitrate && - lhs.qpMax == rhs.qpMax; -} - -bool operator!=(const webrtc::SimulcastStream& lhs, - const webrtc::SimulcastStream& rhs) { - return !(lhs == rhs); -} - -bool operator==(const webrtc::VideoCodec& lhs, - const webrtc::VideoCodec& rhs) { - bool ret = lhs.codecType == rhs.codecType && - (_stricmp(lhs.plName, rhs.plName) == 0) && - lhs.plType == rhs.plType && - lhs.width == rhs.width && - lhs.height == rhs.height && - lhs.startBitrate == rhs.startBitrate && - lhs.maxBitrate == rhs.maxBitrate && - lhs.minBitrate == rhs.minBitrate && - lhs.maxFramerate == rhs.maxFramerate && - lhs.qpMax == rhs.qpMax && - lhs.numberOfSimulcastStreams == rhs.numberOfSimulcastStreams && - lhs.mode == rhs.mode; - if (ret && lhs.codecType == webrtc::kVideoCodecVP8) { - ret &= (lhs.codecSpecific.VP8 == rhs.codecSpecific.VP8); - } - - for (unsigned char i = 0; i < rhs.numberOfSimulcastStreams && ret; ++i) { - ret &= (lhs.simulcastStream[i] == rhs.simulcastStream[i]); - } - return ret; -} - -bool operator!=(const webrtc::VideoCodec& lhs, - const webrtc::VideoCodec& rhs) { - return !(lhs == rhs); -} -#endif - struct FlushBlackFrameData : public talk_base::MessageData { FlushBlackFrameData(uint32 s, int64 t) : ssrc(s), timestamp(t) { } @@ -238,8 +160,13 @@ struct FlushBlackFrameData : public talk_base::MessageData { class WebRtcRenderAdapter : public webrtc::ExternalRenderer { public: - explicit WebRtcRenderAdapter(VideoRenderer* renderer) - : renderer_(renderer), width_(0), height_(0) { + WebRtcRenderAdapter(VideoRenderer* renderer, int channel_id) + : renderer_(renderer), + channel_id_(channel_id), + width_(0), + height_(0), + capture_start_rtp_time_stamp_(-1), + capture_start_ntp_time_ms_(0) { } virtual ~WebRtcRenderAdapter() { @@ -256,7 +183,8 @@ class WebRtcRenderAdapter : public webrtc::ExternalRenderer { if (width_ > 0 && height_ > 0 && renderer_ != NULL) { if (!renderer_->SetSize(width_, height_, 0)) { LOG(LS_ERROR) - << "WebRtcRenderAdapter SetRenderer failed to SetSize to: " + << "WebRtcRenderAdapter (channel " << channel_id_ + << ") SetRenderer failed to SetSize to: " << width_ << "x" << height_; } } @@ -268,52 +196,75 @@ class WebRtcRenderAdapter : public webrtc::ExternalRenderer { talk_base::CritScope cs(&crit_); width_ = width; height_ = height; - LOG(LS_INFO) << "WebRtcRenderAdapter frame size changed to: " + LOG(LS_INFO) << "WebRtcRenderAdapter (channel " << channel_id_ + << ") frame size changed to: " << width << "x" << height; if (renderer_ == NULL) { - LOG(LS_VERBOSE) << "WebRtcRenderAdapter the renderer has not been set. " + LOG(LS_VERBOSE) << "WebRtcRenderAdapter (channel " << channel_id_ + << ") the renderer has not been set. " << "SetSize will be called later in SetRenderer."; return 0; } return renderer_->SetSize(width_, height_, 0) ? 0 : -1; } - virtual int DeliverFrame(unsigned char* buffer, int buffer_size, - uint32_t time_stamp, int64_t render_time, + virtual int DeliverFrame(unsigned char* buffer, + int buffer_size, + uint32_t rtp_time_stamp, +#ifdef USE_WEBRTC_DEV_BRANCH + int64_t ntp_time_ms, +#endif + int64_t render_time, void* handle) { talk_base::CritScope cs(&crit_); + if (capture_start_rtp_time_stamp_ < 0) { + capture_start_rtp_time_stamp_ = rtp_time_stamp; + } + + const int kVideoCodecClockratekHz = cricket::kVideoCodecClockrate / 1000; + + int64 elapsed_time_ms = + (rtp_ts_wraparound_handler_.Unwrap(rtp_time_stamp) - + capture_start_rtp_time_stamp_) / kVideoCodecClockratekHz; +#ifdef USE_WEBRTC_DEV_BRANCH + if (ntp_time_ms > 0) { + capture_start_ntp_time_ms_ = ntp_time_ms - elapsed_time_ms; + } +#endif frame_rate_tracker_.Update(1); if (renderer_ == NULL) { return 0; } - // Convert 90K rtp timestamp to ns timestamp. - int64 rtp_time_stamp_in_ns = (time_stamp / 90) * - talk_base::kNumNanosecsPerMillisec; + // Convert elapsed_time_ms to ns timestamp. + int64 elapsed_time_ns = + elapsed_time_ms * talk_base::kNumNanosecsPerMillisec; // Convert milisecond render time to ns timestamp. - int64 render_time_stamp_in_ns = render_time * + int64 render_time_ns = render_time * talk_base::kNumNanosecsPerMillisec; - // Send the rtp timestamp to renderer as the VideoFrame timestamp. - // and the render timestamp as the VideoFrame elapsed_time. + // Note that here we send the |elapsed_time_ns| to renderer as the + // cricket::VideoFrame's elapsed_time_ and the |render_time_ns| as the + // cricket::VideoFrame's time_stamp_. if (handle == NULL) { - return DeliverBufferFrame(buffer, buffer_size, render_time_stamp_in_ns, - rtp_time_stamp_in_ns); + return DeliverBufferFrame(buffer, buffer_size, render_time_ns, + elapsed_time_ns); } else { - return DeliverTextureFrame(handle, render_time_stamp_in_ns, - rtp_time_stamp_in_ns); + return DeliverTextureFrame(handle, render_time_ns, + elapsed_time_ns); } } virtual bool IsTextureSupported() { return true; } int DeliverBufferFrame(unsigned char* buffer, int buffer_size, - int64 elapsed_time, int64 time_stamp) { + int64 time_stamp, int64 elapsed_time) { WebRtcVideoFrame video_frame; video_frame.Alias(buffer, buffer_size, width_, height_, 1, 1, elapsed_time, time_stamp, 0); // Sanity check on decoded frame size. if (buffer_size != static_cast(VideoFrame::SizeOf(width_, height_))) { - LOG(LS_WARNING) << "WebRtcRenderAdapter received a strange frame size: " + LOG(LS_WARNING) << "WebRtcRenderAdapter (channel " << channel_id_ + << ") received a strange frame size: " << buffer_size; } @@ -321,7 +272,7 @@ class WebRtcRenderAdapter : public webrtc::ExternalRenderer { return ret; } - int DeliverTextureFrame(void* handle, int64 elapsed_time, int64 time_stamp) { + int DeliverTextureFrame(void* handle, int64 time_stamp, int64 elapsed_time) { WebRtcTextureVideoFrame video_frame( static_cast(handle), width_, height_, elapsed_time, time_stamp); @@ -348,12 +299,21 @@ class WebRtcRenderAdapter : public webrtc::ExternalRenderer { return renderer_; } + int64 capture_start_ntp_time_ms() { + talk_base::CritScope cs(&crit_); + return capture_start_ntp_time_ms_; + } + private: talk_base::CriticalSection crit_; VideoRenderer* renderer_; + int channel_id_; unsigned int width_; unsigned int height_; talk_base::RateTracker frame_rate_tracker_; + talk_base::TimestampWrapAroundHandler rtp_ts_wraparound_handler_; + int64 capture_start_rtp_time_stamp_; + int64 capture_start_ntp_time_ms_; }; class WebRtcDecoderObserver : public webrtc::ViEDecoderObserver { @@ -539,7 +499,7 @@ class WebRtcVideoChannelRecvInfo { typedef std::map DecoderMap; // key: payload type explicit WebRtcVideoChannelRecvInfo(int channel_id) : channel_id_(channel_id), - render_adapter_(NULL), + render_adapter_(NULL, channel_id), decoder_observer_(channel_id) { } int channel_id() { return channel_id_; } @@ -599,6 +559,7 @@ class WebRtcOveruseObserver : public webrtc::CpuOveruseObserver { } void Enable(bool enable) { + LOG(LS_INFO) << "WebRtcOveruseObserver enable: " << enable; talk_base::CritScope cs(&crit_); enabled_ = enable; } @@ -625,10 +586,9 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { video_capturer_(NULL), encoder_observer_(channel_id), external_capture_(external_capture), - capturer_updated_(false), interval_(0), cpu_monitor_(cpu_monitor), - overuse_observer_enabled_(false) { + old_adaptation_changes_(0) { } int channel_id() const { return channel_id_; } @@ -665,12 +625,17 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { int64 interval() { return interval_; } int CurrentAdaptReason() const { - const CoordinatedVideoAdapter* adapter = video_adapter(); - if (!adapter) { + if (!video_adapter()) { return CoordinatedVideoAdapter::ADAPTREASON_NONE; } return video_adapter()->adapt_reason(); } + int AdaptChanges() const { + if (!video_adapter()) { + return old_adaptation_changes_; + } + return old_adaptation_changes_ + video_adapter()->adaptation_changes(); + } StreamParams* stream_params() { return stream_params_.get(); } void set_stream_params(const StreamParams& sp) { @@ -695,6 +660,8 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { CoordinatedVideoAdapter* old_video_adapter = video_adapter(); if (old_video_adapter) { + // Get adaptation changes from old video adapter. + old_adaptation_changes_ += old_video_adapter->adaptation_changes(); // Disconnect signals from old video adapter. SignalCpuAdaptationUnable.disconnect(old_video_adapter); if (cpu_monitor_) { @@ -702,7 +669,6 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { } } - capturer_updated_ = true; video_capturer_ = video_capturer; vie_wrapper->base()->RegisterCpuOveruseObserver(channel_id_, NULL); @@ -720,7 +686,8 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { vie_wrapper->base()->RegisterCpuOveruseObserver(channel_id_, overuse_observer_.get()); // (Dis)connect the video adapter from the cpu monitor as appropriate. - SetCpuOveruseDetection(overuse_observer_enabled_); + SetCpuOveruseDetection( + video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false)); SignalCpuAdaptationUnable.repeat(adapter->SignalCpuAdaptationUnable); } @@ -739,10 +706,18 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { } void ApplyCpuOptions(const VideoOptions& video_options) { + bool cpu_overuse_detection_changed = + video_options.cpu_overuse_detection.IsSet() && + (video_options.cpu_overuse_detection.GetWithDefaultIfUnset(false) != + video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false)); // Use video_options_.SetAll() instead of assignment so that unset value in // video_options will not overwrite the previous option value. video_options_.SetAll(video_options); UpdateAdapterCpuOptions(); + if (cpu_overuse_detection_changed) { + SetCpuOveruseDetection( + video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false)); + } } void UpdateAdapterCpuOptions() { @@ -750,15 +725,19 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { return; } - bool cpu_adapt, cpu_smoothing, adapt_third; + bool cpu_smoothing, adapt_third; float low, med, high; + bool cpu_adapt = + video_options_.adapt_input_to_cpu_usage.GetWithDefaultIfUnset(false); + bool cpu_overuse_detection = + video_options_.cpu_overuse_detection.GetWithDefaultIfUnset(false); // TODO(thorcarpenter): Have VideoAdapter be responsible for setting // all these video options. CoordinatedVideoAdapter* video_adapter = video_capturer_->video_adapter(); - if (video_options_.adapt_input_to_cpu_usage.Get(&cpu_adapt) || - overuse_observer_enabled_) { - video_adapter->set_cpu_adaptation(cpu_adapt || overuse_observer_enabled_); + if (video_options_.adapt_input_to_cpu_usage.IsSet() || + video_options_.cpu_overuse_detection.IsSet()) { + video_adapter->set_cpu_adaptation(cpu_adapt || cpu_overuse_detection); } if (video_options_.adapt_cpu_with_smoothing.Get(&cpu_smoothing)) { video_adapter->set_cpu_smoothing(cpu_smoothing); @@ -778,8 +757,6 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { } void SetCpuOveruseDetection(bool enable) { - overuse_observer_enabled_ = enable; - if (overuse_observer_) { overuse_observer_->Enable(enable); } @@ -788,10 +765,6 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { // it will be signaled by cpu monitor. CoordinatedVideoAdapter* adapter = video_adapter(); if (adapter) { - bool cpu_adapt = false; - video_options_.adapt_input_to_cpu_usage.Get(&cpu_adapt); - adapter->set_cpu_adaptation( - adapter->cpu_adaptation() || cpu_adapt || enable); if (cpu_monitor_) { if (enable) { cpu_monitor_->SignalUpdate.disconnect(adapter); @@ -850,22 +823,21 @@ class WebRtcVideoChannelSendInfo : public sigslot::has_slots<> { WebRtcLocalStreamInfo local_stream_info_; - bool capturer_updated_; - int64 interval_; talk_base::CpuMonitor* cpu_monitor_; talk_base::scoped_ptr overuse_observer_; - bool overuse_observer_enabled_; + + int old_adaptation_changes_; VideoOptions video_options_; }; const WebRtcVideoEngine::VideoCodecPref WebRtcVideoEngine::kVideoCodecPrefs[] = { - {kVp8PayloadName, 100, -1, 0}, - {kRedPayloadName, 116, -1, 1}, - {kFecPayloadName, 117, -1, 2}, + {kVp8CodecName, 100, -1, 0}, + {kRedCodecName, 116, -1, 1}, + {kUlpfecCodecName, 117, -1, 2}, {kRtxCodecName, 96, 100, 3}, }; @@ -907,7 +879,6 @@ static void UpdateVideoCodec(const cricket::VideoFormat& video_format, video_format.interval); } -#ifdef USE_WEBRTC_DEV_BRANCH static bool GetCpuOveruseOptions(const VideoOptions& options, webrtc::CpuOveruseOptions* overuse_options) { int underuse_threshold = 0; @@ -928,6 +899,18 @@ static bool GetCpuOveruseOptions(const VideoOptions& options, // Use method based on encode usage. overuse_options->low_encode_usage_threshold_percent = underuse_threshold; overuse_options->high_encode_usage_threshold_percent = overuse_threshold; +#ifdef USE_WEBRTC_DEV_BRANCH + // Set optional thresholds, if configured. + int underuse_rsd_threshold = 0; + if (options.cpu_underuse_encode_rsd_threshold.Get( + &underuse_rsd_threshold)) { + overuse_options->low_encode_time_rsd_threshold = underuse_rsd_threshold; + } + int overuse_rsd_threshold = 0; + if (options.cpu_overuse_encode_rsd_threshold.Get(&overuse_rsd_threshold)) { + overuse_options->high_encode_time_rsd_threshold = overuse_rsd_threshold; + } +#endif } else { // Use default method based on capture jitter. overuse_options->low_capture_jitter_threshold_ms = @@ -937,7 +920,6 @@ static bool GetCpuOveruseOptions(const VideoOptions& options, } return true; } -#endif WebRtcVideoEngine::WebRtcVideoEngine() { Construct(new ViEWrapper(), new ViETraceWrapper(), NULL, @@ -1300,8 +1282,15 @@ static void ConvertToCricketVideoCodec( out_codec->width = in_codec.width; out_codec->height = in_codec.height; out_codec->framerate = in_codec.maxFramerate; - out_codec->SetParam(kCodecParamMinBitrate, in_codec.minBitrate); - out_codec->SetParam(kCodecParamMaxBitrate, in_codec.maxBitrate); + if (BitrateIsSet(in_codec.minBitrate)) { + out_codec->SetParam(kCodecParamMinBitrate, in_codec.minBitrate); + } + if (BitrateIsSet(in_codec.maxBitrate)) { + out_codec->SetParam(kCodecParamMaxBitrate, in_codec.maxBitrate); + } + if (BitrateIsSet(in_codec.startBitrate)) { + out_codec->SetParam(kCodecParamStartBitrate, in_codec.startBitrate); + } if (in_codec.qpMax) { out_codec->SetParam(kCodecParamMaxQuantization, in_codec.qpMax); } @@ -1335,6 +1324,15 @@ bool WebRtcVideoEngine::ConvertFromCricketVideoCodec( } } + // Is this an RTX codec? Handled separately here since webrtc doesn't handle + // them as webrtc::VideoCodec internally. + if (!found && _stricmp(in_codec.name.c_str(), kRtxCodecName) == 0) { + talk_base::strcpyn(out_codec->plName, sizeof(out_codec->plName), + in_codec.name.c_str(), in_codec.name.length()); + out_codec->plType = in_codec.id; + found = true; + } + if (!found) { LOG(LS_ERROR) << "invalid codec type"; return false; @@ -1353,18 +1351,14 @@ bool WebRtcVideoEngine::ConvertFromCricketVideoCodec( out_codec->maxFramerate = in_codec.framerate; // Convert bitrate parameters. - int max_bitrate = kMaxVideoBitrate; - int min_bitrate = kMinVideoBitrate; - int start_bitrate = kStartVideoBitrate; + int max_bitrate = -1; + int min_bitrate = -1; + int start_bitrate = -1; in_codec.GetParam(kCodecParamMinBitrate, &min_bitrate); in_codec.GetParam(kCodecParamMaxBitrate, &max_bitrate); + in_codec.GetParam(kCodecParamStartBitrate, &start_bitrate); - if (max_bitrate < min_bitrate) { - return false; - } - start_bitrate = talk_base::_max(start_bitrate, min_bitrate); - start_bitrate = talk_base::_min(start_bitrate, max_bitrate); out_codec->minBitrate = min_bitrate; out_codec->startBitrate = start_bitrate; @@ -1466,7 +1460,7 @@ bool WebRtcVideoEngine::RebuildCodecList(const VideoCodec& in_codec) { VideoCodec codec(pref.payload_type, pref.name, in_codec.width, in_codec.height, in_codec.framerate, static_cast(ARRAY_SIZE(kVideoCodecPrefs) - i)); - if (_stricmp(kVp8PayloadName, codec.name.c_str()) == 0) { + if (_stricmp(kVp8CodecName, codec.name.c_str()) == 0) { AddDefaultFeedbackParams(&codec); } if (pref.associated_payload_type != -1) { @@ -1646,9 +1640,6 @@ WebRtcVideoMediaChannel::WebRtcVideoMediaChannel( send_rtx_type_(-1), send_red_type_(-1), send_fec_type_(-1), - send_min_bitrate_(kMinVideoBitrate), - send_start_bitrate_(kStartVideoBitrate), - send_max_bitrate_(kMaxVideoBitrate), sending_(false), ratio_w_(0), ratio_h_(0) { @@ -1690,12 +1681,17 @@ WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() { bool WebRtcVideoMediaChannel::SetRecvCodecs( const std::vector& codecs) { receive_codecs_.clear(); + associated_payload_types_.clear(); for (std::vector::const_iterator iter = codecs.begin(); iter != codecs.end(); ++iter) { if (engine()->FindCodec(*iter)) { webrtc::VideoCodec wcodec; if (engine()->ConvertFromCricketVideoCodec(*iter, &wcodec)) { receive_codecs_.push_back(wcodec); + int apt; + if (iter->GetParam(cricket::kCodecParamAssociatedPayloadType, &apt)) { + associated_payload_types_[wcodec.plType] = apt; + } } } else { LOG(LS_INFO) << "Unknown codec " << iter->name; @@ -1725,9 +1721,9 @@ bool WebRtcVideoMediaChannel::SetSendCodecs( bool remb_enabled = remb_enabled_; for (std::vector::const_iterator iter = codecs.begin(); iter != codecs.end(); ++iter) { - if (_stricmp(iter->name.c_str(), kRedPayloadName) == 0) { + if (_stricmp(iter->name.c_str(), kRedCodecName) == 0) { send_red_type_ = iter->id; - } else if (_stricmp(iter->name.c_str(), kFecPayloadName) == 0) { + } else if (_stricmp(iter->name.c_str(), kUlpfecCodecName) == 0) { send_fec_type_ = iter->id; } else if (_stricmp(iter->name.c_str(), kRtxCodecName) == 0) { int rtx_type = iter->id; @@ -1806,8 +1802,19 @@ bool WebRtcVideoMediaChannel::SetSendCodecs( send_rtx_type_ = rtx_it->second; } - if (!SetSendCodec( - codec, codec.minBitrate, codec.startBitrate, codec.maxBitrate)) { + if (BitrateIsSet(codec.minBitrate) && BitrateIsSet(codec.maxBitrate) && + codec.minBitrate > codec.maxBitrate) { + // TODO(pthatcher): This behavior contradicts other behavior in + // this file which will cause min > max to push the min down to + // the max. There are unit tests for both behaviors. We should + // pick one and do that. + LOG(LS_INFO) << "Rejecting codec with min bitrate (" + << codec.minBitrate << ") larger than max (" + << codec.maxBitrate << "). "; + return false; + } + + if (!SetSendCodec(codec)) { return false; } @@ -1967,8 +1974,7 @@ bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) { // Reset send codec after stream parameters changed. if (send_codec_) { - if (!SetSendCodec(send_channel, *send_codec_, send_min_bitrate_, - send_start_bitrate_, send_max_bitrate_)) { + if (!SetSendCodec(send_channel, *send_codec_)) { return false; } LogSendCodecChange("SetSendStreamFormat()"); @@ -2039,6 +2045,9 @@ bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) { << " reuse default channel #" << vie_channel_; first_receive_ssrc_ = sp.first_ssrc(); + if (!MaybeSetRtxSsrc(sp, vie_channel_)) { + return false; + } if (render_started_) { if (engine()->vie()->render()->StartRender(vie_channel_) !=0) { LOG_RTCERR1(StartRender, vie_channel_); @@ -2075,14 +2084,9 @@ bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) { // Early receive added channel. channel_id = (*channel_iterator).second->channel_id(); } + channel_iterator = recv_channels_.find(sp.first_ssrc()); - // Set the corresponding RTX SSRC. - uint32 rtx_ssrc; - bool has_rtx = sp.GetFidSsrc(sp.first_ssrc(), &rtx_ssrc); - if (has_rtx && engine()->vie()->rtp()->SetRemoteSSRCType( - channel_id, webrtc::kViEStreamTypeRtx, rtx_ssrc) != 0) { - LOG_RTCERR3(SetRemoteSSRCType, channel_id, webrtc::kViEStreamTypeRtx, - rtx_ssrc); + if (!MaybeSetRtxSsrc(sp, channel_id)) { return false; } @@ -2112,6 +2116,24 @@ bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) { return true; } +bool WebRtcVideoMediaChannel::MaybeSetRtxSsrc(const StreamParams& sp, + int channel_id) { + uint32 rtx_ssrc; + bool has_rtx = sp.GetFidSsrc(sp.first_ssrc(), &rtx_ssrc); + if (has_rtx) { + LOG(LS_INFO) << "Setting rtx ssrc " << rtx_ssrc << " for stream " + << sp.first_ssrc(); + if (engine()->vie()->rtp()->SetRemoteSSRCType( + channel_id, webrtc::kViEStreamTypeRtx, rtx_ssrc) != 0) { + LOG_RTCERR3(SetRemoteSSRCType, channel_id, webrtc::kViEStreamTypeRtx, + rtx_ssrc); + return false; + } + rtx_to_primary_ssrc_[rtx_ssrc] = sp.first_ssrc(); + } + return true; +} + bool WebRtcVideoMediaChannel::RemoveRecvStream(uint32 ssrc) { if (ssrc == 0) { LOG(LS_ERROR) << "RemoveRecvStream with 0 ssrc is not supported."; @@ -2141,6 +2163,17 @@ bool WebRtcVideoMediaChannel::RemoveRecvStreamInternal(uint32 ssrc) { return false; } WebRtcVideoChannelRecvInfo* info = it->second; + + // Remove any RTX SSRC mappings to this stream. + SsrcMap::iterator rtx_it = rtx_to_primary_ssrc_.begin(); + while (rtx_it != rtx_to_primary_ssrc_.end()) { + if (rtx_it->second == ssrc) { + rtx_to_primary_ssrc_.erase(rtx_it++); + } else { + ++rtx_it; + } + } + int channel_id = info->channel_id(); if (engine()->vie()->render()->RemoveRenderer(channel_id) != 0) { LOG_RTCERR1(RemoveRenderer, channel_id); @@ -2452,20 +2485,22 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, sinfo.packets_lost = -1; sinfo.fraction_lost = -1; sinfo.rtt_ms = -1; - sinfo.input_frame_width = static_cast(channel_stream_info->width()); - sinfo.input_frame_height = - static_cast(channel_stream_info->height()); VideoCapturer* video_capturer = send_channel->video_capturer(); if (video_capturer) { + VideoFormat last_captured_frame_format; video_capturer->GetStats(&sinfo.adapt_frame_drops, &sinfo.effects_frame_drops, - &sinfo.capturer_frame_time); + &sinfo.capturer_frame_time, + &last_captured_frame_format); + sinfo.input_frame_width = last_captured_frame_format.width; + sinfo.input_frame_height = last_captured_frame_format.height; + } else { + sinfo.input_frame_width = 0; + sinfo.input_frame_height = 0; } webrtc::VideoCodec vie_codec; - // TODO(ronghuawu): Add unit tests to cover the new send stats: - // send_frame_width/height. if (!video_capturer || video_capturer->IsMuted()) { sinfo.send_frame_width = 0; sinfo.send_frame_height = 0; @@ -2481,8 +2516,22 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, sinfo.framerate_input = channel_stream_info->framerate(); sinfo.framerate_sent = send_channel->encoder_observer()->framerate(); sinfo.nominal_bitrate = send_channel->encoder_observer()->bitrate(); - sinfo.preferred_bitrate = send_max_bitrate_; + if (send_codec_) { + sinfo.preferred_bitrate = GetBitrate( + send_codec_->maxBitrate, kMaxVideoBitrate); + } sinfo.adapt_reason = send_channel->CurrentAdaptReason(); + sinfo.adapt_changes = send_channel->AdaptChanges(); + +#ifdef USE_WEBRTC_DEV_BRANCH + webrtc::CpuOveruseMetrics metrics; + engine()->vie()->base()->GetCpuOveruseMetrics(channel_id, &metrics); + sinfo.capture_jitter_ms = metrics.capture_jitter_ms; + sinfo.avg_encode_ms = metrics.avg_encode_time_ms; + sinfo.encode_usage_percent = metrics.encode_usage_percent; + sinfo.encode_rsd = metrics.encode_rsd; + sinfo.capture_queue_delay_ms_per_s = metrics.capture_queue_delay_ms_per_s; +#else sinfo.capture_jitter_ms = -1; sinfo.avg_encode_ms = -1; sinfo.encode_usage_percent = -1; @@ -2503,8 +2552,8 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, sinfo.encode_usage_percent = encode_usage_percent; sinfo.capture_queue_delay_ms_per_s = capture_queue_delay_ms_per_s; } +#endif -#ifdef USE_WEBRTC_DEV_BRANCH webrtc::RtcpPacketTypeCounter rtcp_sent; webrtc::RtcpPacketTypeCounter rtcp_received; if (engine()->vie()->rtp()->GetRtcpPacketTypeCounters( @@ -2518,11 +2567,6 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, sinfo.nacks_rcvd = -1; LOG_RTCERR1(GetRtcpPacketTypeCounters, channel_id); } -#else - sinfo.firs_rcvd = -1; - sinfo.plis_rcvd = -1; - sinfo.nacks_rcvd = -1; -#endif // Get received RTCP statistics for the sender (reported by the remote // client in a RTCP packet), if available. @@ -2558,13 +2602,6 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, LOG_RTCERR1(GetBandwidthUsage, channel_id); } - unsigned int estimated_stream_send_bandwidth = 0; - if (engine_->vie()->rtp()->GetEstimatedSendBandwidth( - channel_id, &estimated_stream_send_bandwidth) == 0) { - estimated_send_bandwidth += estimated_stream_send_bandwidth; - } else { - LOG_RTCERR1(GetEstimatedSendBandwidth, channel_id); - } unsigned int target_enc_stream_bitrate = 0; if (engine_->vie()->codec()->GetCodecTargetBitrate( channel_id, &target_enc_stream_bitrate) == 0) { @@ -2573,17 +2610,27 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, LOG_RTCERR1(GetCodecTargetBitrate, channel_id); } } + if (!send_channels_.empty()) { + // GetEstimatedSendBandwidth returns the estimated bandwidth for all video + // engine channels in a channel group. Any valid channel id will do as it + // is only used to access the right group of channels. + const int channel_id = send_channels_.begin()->second->channel_id(); + // Get the send bandwidth available for this MediaChannel. + if (engine_->vie()->rtp()->GetEstimatedSendBandwidth( + channel_id, &estimated_send_bandwidth) != 0) { + LOG_RTCERR1(GetEstimatedSendBandwidth, channel_id); + } + } } else { LOG(LS_WARNING) << "GetStats: sender information not ready."; } // Get the SSRC and stats for each receiver, based on our own calculations. - unsigned int estimated_recv_bandwidth = 0; for (RecvChannelMap::const_iterator it = recv_channels_.begin(); it != recv_channels_.end(); ++it) { WebRtcVideoChannelRecvInfo* channel = it->second; - unsigned int ssrc; + unsigned int ssrc = 0; // Get receiver statistics and build VideoReceiverInfo, if we have data. // Skip the default channel (ssrc == 0). if (engine_->vie()->rtp()->GetRemoteSSRC( @@ -2610,9 +2657,10 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, int fps = channel->render_adapter()->framerate(); rinfo.framerate_decoded = fps; rinfo.framerate_output = fps; + rinfo.capture_start_ntp_time_ms = + channel->render_adapter()->capture_start_ntp_time_ms(); channel->decoder_observer()->ExportTo(&rinfo); -#ifdef USE_WEBRTC_DEV_BRANCH webrtc::RtcpPacketTypeCounter rtcp_sent; webrtc::RtcpPacketTypeCounter rtcp_received; if (engine()->vie()->rtp()->GetRtcpPacketTypeCounters( @@ -2626,11 +2674,6 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, rinfo.nacks_sent = -1; LOG_RTCERR1(GetRtcpPacketTypeCounters, channel->channel_id()); } -#else - rinfo.firs_sent = -1; - rinfo.plis_sent = -1; - rinfo.nacks_sent = -1; -#endif // Get our locally created statistics of the received RTP stream. webrtc::RtcpStatistics incoming_stream_rtcp_stats; @@ -2645,22 +2688,26 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, incoming_stream_rtcp_stats.fraction_lost) / (1 << 8); } info->receivers.push_back(rinfo); - - unsigned int estimated_recv_stream_bandwidth = 0; + } + unsigned int estimated_recv_bandwidth = 0; + if (!recv_channels_.empty()) { + // GetEstimatedReceiveBandwidth returns the estimated bandwidth for all + // video engine channels in a channel group. Any valid channel id will do as + // it is only used to access the right group of channels. + const int channel_id = recv_channels_.begin()->second->channel_id(); + // Gets the estimated receive bandwidth for the MediaChannel. if (engine_->vie()->rtp()->GetEstimatedReceiveBandwidth( - channel->channel_id(), &estimated_recv_stream_bandwidth) == 0) { - estimated_recv_bandwidth += estimated_recv_stream_bandwidth; - } else { - LOG_RTCERR1(GetEstimatedReceiveBandwidth, channel->channel_id()); + channel_id, &estimated_recv_bandwidth) != 0) { + LOG_RTCERR1(GetEstimatedReceiveBandwidth, channel_id); } } + // Build BandwidthEstimationInfo. // TODO(zhurunz): Add real unittest for this. BandwidthEstimationInfo bwe; // TODO(jiayl): remove the condition when the necessary changes are available // outside the dev branch. -#ifdef USE_WEBRTC_DEV_BRANCH if (options.include_received_propagation_stats) { webrtc::ReceiveBandwidthEstimatorStats additional_stats; // Only call for the default channel because the returned stats are @@ -2678,7 +2725,6 @@ bool WebRtcVideoMediaChannel::GetStats(const StatsOptions& options, engine_->vie()->rtp()->GetPacerQueuingDelayMs( recv_channels_[0]->channel_id(), &bwe.bucket_delay); -#endif // Calculations done above per send/receive stream. bwe.actual_enc_bitrate = video_bitrate_sent; @@ -2736,10 +2782,10 @@ void WebRtcVideoMediaChannel::OnPacketReceived( if (processing_channel == -1) { // Allocate an unsignalled recv channel for processing in conference mode. if (!InConferenceMode()) { - // If we cant find or allocate one, use the default. + // If we can't find or allocate one, use the default. processing_channel = video_channel(); } else if (!CreateUnsignalledRecvChannel(ssrc, &processing_channel)) { - // If we cant create an unsignalled recv channel, drop the packet in + // If we can't create an unsignalled recv channel, drop the packet in // conference mode. return; } @@ -2813,7 +2859,6 @@ bool WebRtcVideoMediaChannel::SetRecvRtpHeaderExtensions( if (receive_extensions_ == extensions) { return true; } - receive_extensions_ = extensions; const RtpHeaderExtension* offset_extension = FindHeaderExtension(extensions, kRtpTimestampOffsetHeaderExtension); @@ -2835,12 +2880,16 @@ bool WebRtcVideoMediaChannel::SetRecvRtpHeaderExtensions( return false; } } + + receive_extensions_ = extensions; return true; } bool WebRtcVideoMediaChannel::SetSendRtpHeaderExtensions( const std::vector& extensions) { - send_extensions_ = extensions; + if (send_extensions_ == extensions) { + return true; + } const RtpHeaderExtension* offset_extension = FindHeaderExtension(extensions, kRtpTimestampOffsetHeaderExtension); @@ -2871,6 +2920,8 @@ bool WebRtcVideoMediaChannel::SetSendRtpHeaderExtensions( talk_base::Socket::OPT_RTP_SENDTIME_EXTN_ID, send_time_extension->id); } + + send_extensions_ = extensions; return true; } @@ -2892,34 +2943,29 @@ bool WebRtcVideoMediaChannel::SetStartSendBandwidth(int bps) { } // On success, SetSendCodec() will reset |send_start_bitrate_| to |bps/1000|, - // by calling MaybeChangeStartBitrate. That method will also clamp the + // by calling MaybeChangeBitrates. That method will also clamp the // start bitrate between min and max, consistent with the override behavior // in SetMaxSendBandwidth. - return SetSendCodec(*send_codec_, - send_min_bitrate_, bps / 1000, send_max_bitrate_); + webrtc::VideoCodec new_codec = *send_codec_; + if (BitrateIsSet(bps)) { + new_codec.startBitrate = bps / 1000; + } + return SetSendCodec(new_codec); } bool WebRtcVideoMediaChannel::SetMaxSendBandwidth(int bps) { LOG(LS_INFO) << "WebRtcVideoMediaChannel::SetMaxSendBandwidth"; - if (InConferenceMode()) { - LOG(LS_INFO) << "Conference mode ignores SetMaxSendBandwidth"; - return true; - } - if (!send_codec_) { LOG(LS_INFO) << "The send codec has not been set up yet"; return true; } - // Use the default value or the bps for the max - int max_bitrate = (bps <= 0) ? send_max_bitrate_ : (bps / 1000); - - // Reduce the current minimum and start bitrates if necessary. - int min_bitrate = talk_base::_min(send_min_bitrate_, max_bitrate); - int start_bitrate = talk_base::_min(send_start_bitrate_, max_bitrate); - - if (!SetSendCodec(*send_codec_, min_bitrate, start_bitrate, max_bitrate)) { + webrtc::VideoCodec new_codec = *send_codec_; + if (BitrateIsSet(bps)) { + new_codec.maxBitrate = bps / 1000; + } + if (!SetSendCodec(new_codec)) { return false; } LogSendCodecChange("SetMaxSendBandwidth()"); @@ -2944,9 +2990,6 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { bool buffer_latency_changed = options.buffered_mode_latency.IsSet() && (options_.buffered_mode_latency != options.buffered_mode_latency); - bool cpu_overuse_detection_changed = options.cpu_overuse_detection.IsSet() && - (options_.cpu_overuse_detection != options.cpu_overuse_detection); - bool dscp_option_changed = (options_.dscp != options.dscp); bool suspend_below_min_bitrate_changed = @@ -2960,14 +3003,17 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { conference_mode_turned_off = true; } -#ifdef USE_WEBRTC_DEV_BRANCH bool improved_wifi_bwe_changed = options.use_improved_wifi_bandwidth_estimator.IsSet() && options_.use_improved_wifi_bandwidth_estimator != options.use_improved_wifi_bandwidth_estimator; +#ifdef USE_WEBRTC_DEV_BRANCH + bool payload_padding_changed = options.use_payload_padding.IsSet() && + options_.use_payload_padding != options.use_payload_padding; #endif + // Save the options, to be interpreted where appropriate. // Use options_.SetAll() instead of assignment so that unset value in options // will not overwrite the previous option value. @@ -2980,61 +3026,52 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { send_channel->ApplyCpuOptions(options_); } - // Adjust send codec bitrate if needed. - int conf_max_bitrate = kDefaultConferenceModeMaxVideoBitrate; + if (send_codec_) { + bool reset_send_codec_needed = denoiser_changed; + webrtc::VideoCodec new_codec = *send_codec_; + + // TODO(pthatcher): Remove this. We don't need 4 ways to set bitrates. + bool lower_min_bitrate; + if (options.lower_min_bitrate.Get(&lower_min_bitrate)) { + new_codec.minBitrate = kLowerMinBitrate; + reset_send_codec_needed = true; + } - // Save altered min_bitrate level and apply if necessary. - bool adjusted_min_bitrate = false; - if (options.lower_min_bitrate.IsSet()) { - bool lower; - options.lower_min_bitrate.Get(&lower); + if (conference_mode_turned_off) { + // This is a special case for turning conference mode off. + // Max bitrate should go back to the default maximum value instead + // of the current maximum. + new_codec.maxBitrate = kAutoBandwidth; + reset_send_codec_needed = true; + } - int new_send_min_bitrate = lower ? kLowerMinBitrate : kMinVideoBitrate; - adjusted_min_bitrate = (new_send_min_bitrate != send_min_bitrate_); - send_min_bitrate_ = new_send_min_bitrate; - } + // TODO(pthatcher): Remove this. We don't need 4 ways to set bitrates. + int new_start_bitrate; + if (options.video_start_bitrate.Get(&new_start_bitrate)) { + new_codec.startBitrate = new_start_bitrate; + reset_send_codec_needed = true; + } - int expected_bitrate = send_max_bitrate_; - if (InConferenceMode()) { - expected_bitrate = conf_max_bitrate; - } else if (conference_mode_turned_off) { - // This is a special case for turning conference mode off. - // Max bitrate should go back to the default maximum value instead - // of the current maximum. - expected_bitrate = kMaxVideoBitrate; - } - - int options_start_bitrate; - bool start_bitrate_changed = false; - if (options.video_start_bitrate.Get(&options_start_bitrate) && - options_start_bitrate != send_start_bitrate_) { - send_start_bitrate_ = options_start_bitrate; - start_bitrate_changed = true; - } - - bool reset_send_codec_needed = send_codec_ && - (send_max_bitrate_ != expected_bitrate || denoiser_changed || - adjusted_min_bitrate || start_bitrate_changed); - - - if (reset_send_codec_needed) { - // On success, SetSendCodec() will reset send_max_bitrate_ to - // expected_bitrate. - if (!SetSendCodec(*send_codec_, - send_min_bitrate_, - send_start_bitrate_, - expected_bitrate)) { - return false; + + LOG(LS_INFO) << "Reset send codec needed is enabled? " + << reset_send_codec_needed; + if (reset_send_codec_needed) { + if (!SetSendCodec(new_codec)) { + return false; + } + LogSendCodecChange("SetOptions()"); } - LogSendCodecChange("SetOptions()"); } if (leaky_bucket_changed) { bool enable_leaky_bucket = - options_.video_leaky_bucket.GetWithDefaultIfUnset(false); - LOG(LS_INFO) << "Leaky bucket is enabled : " << enable_leaky_bucket; + options_.video_leaky_bucket.GetWithDefaultIfUnset(true); + LOG(LS_INFO) << "Leaky bucket is enabled? " << enable_leaky_bucket; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { + // TODO(holmer): This API will be removed as we move to the new + // webrtc::Call API. We should clean up this experiment when that is + // happening. if (engine()->vie()->rtp()->SetTransmissionSmoothingStatus( it->second->channel_id(), enable_leaky_bucket) != 0) { LOG_RTCERR2(SetTransmissionSmoothingStatus, it->second->channel_id(), @@ -3046,6 +3083,7 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { int buffer_latency = options_.buffered_mode_latency.GetWithDefaultIfUnset( cricket::kBufferedModeDisabled); + LOG(LS_INFO) << "Buffer latency is " << buffer_latency; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { if (engine()->vie()->rtp()->SetSenderBufferingMode( @@ -3063,25 +3101,18 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } } } - if (cpu_overuse_detection_changed) { - bool cpu_overuse_detection = - options_.cpu_overuse_detection.GetWithDefaultIfUnset(false); - for (SendChannelMap::iterator iter = send_channels_.begin(); - iter != send_channels_.end(); ++iter) { - WebRtcVideoChannelSendInfo* send_channel = iter->second; - send_channel->SetCpuOveruseDetection(cpu_overuse_detection); - } - } if (dscp_option_changed) { talk_base::DiffServCodePoint dscp = talk_base::DSCP_DEFAULT; if (options_.dscp.GetWithDefaultIfUnset(false)) dscp = kVideoDscpValue; + LOG(LS_INFO) << "DSCP is " << dscp; if (MediaChannel::SetDscp(dscp) != 0) { LOG(LS_WARNING) << "Failed to set DSCP settings for video channel"; } } if (suspend_below_min_bitrate_changed) { if (options_.suspend_below_min_bitrate.GetWithDefaultIfUnset(false)) { + LOG(LS_INFO) << "Suspend below min bitrate enabled."; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { engine()->vie()->codec()->SuspendBelowMinBitrate( @@ -3091,8 +3122,8 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { LOG(LS_WARNING) << "Cannot disable video suspension once it is enabled"; } } -#ifdef USE_WEBRTC_DEV_BRANCH if (improved_wifi_bwe_changed) { + LOG(LS_INFO) << "Improved WIFI BWE called."; webrtc::Config config; config.Set(new webrtc::AimdRemoteRateControl( options_.use_improved_wifi_bandwidth_estimator @@ -3103,6 +3134,17 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { it->second->channel_id(), config); } } +#ifdef USE_WEBRTC_DEV_BRANCH + if (payload_padding_changed) { + LOG(LS_INFO) << "Payload-based padding called."; + for (SendChannelMap::iterator it = send_channels_.begin(); + it != send_channels_.end(); ++it) { + engine()->vie()->rtp()->SetPadWithRedundantPayloads( + it->second->channel_id(), + options_.use_payload_padding.GetWithDefaultIfUnset(false)); + } + } +#endif webrtc::CpuOveruseOptions overuse_options; if (GetCpuOveruseOptions(options_, &overuse_options)) { for (SendChannelMap::iterator it = send_channels_.begin(); @@ -3113,7 +3155,6 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } } } -#endif return true; } @@ -3236,7 +3277,7 @@ bool WebRtcVideoMediaChannel::SendFrame( } const VideoFrame* frame_out = frame; talk_base::scoped_ptr processed_frame; - // Disable muting for screencast. + // TODO(hellner): Remove the need for disabling mute when screencasting. const bool mute = (send_channel->muted() && !is_screencast); send_channel->ProcessFrame(*frame_out, mute, processed_frame.use()); if (processed_frame) { @@ -3544,11 +3585,6 @@ bool WebRtcVideoMediaChannel::ConfigureSending(int channel_id, send_channel->SignalCpuAdaptationUnable.connect(this, &WebRtcVideoMediaChannel::OnCpuAdaptationUnable); - if (options_.cpu_overuse_detection.GetWithDefaultIfUnset(false)) { - send_channel->SetCpuOveruseDetection(true); - } - -#ifdef USE_WEBRTC_DEV_BRANCH webrtc::CpuOveruseOptions overuse_options; if (GetCpuOveruseOptions(options_, &overuse_options)) { if (engine()->vie()->base()->SetCpuOveruseOptions(channel_id, @@ -3556,7 +3592,6 @@ bool WebRtcVideoMediaChannel::ConfigureSending(int channel_id, LOG_RTCERR1(SetCpuOveruseOptions, channel_id); } } -#endif // Register encoder observer for outgoing framerate and bitrate. if (engine()->vie()->codec()->RegisterEncoderObserver( @@ -3575,7 +3610,7 @@ bool WebRtcVideoMediaChannel::ConfigureSending(int channel_id, return false; } - if (options_.video_leaky_bucket.GetWithDefaultIfUnset(false)) { + if (options_.video_leaky_bucket.GetWithDefaultIfUnset(true)) { if (engine()->vie()->rtp()->SetTransmissionSmoothingStatus(channel_id, true) != 0) { LOG_RTCERR2(SetTransmissionSmoothingStatus, channel_id, true); @@ -3645,32 +3680,24 @@ bool WebRtcVideoMediaChannel::SetNackFec(int channel_id, return true; } -bool WebRtcVideoMediaChannel::SetSendCodec(const webrtc::VideoCodec& codec, - int min_bitrate, - int start_bitrate, - int max_bitrate) { +bool WebRtcVideoMediaChannel::SetSendCodec(const webrtc::VideoCodec& codec) { bool ret_val = true; for (SendChannelMap::iterator iter = send_channels_.begin(); iter != send_channels_.end(); ++iter) { WebRtcVideoChannelSendInfo* send_channel = iter->second; - ret_val = SetSendCodec(send_channel, codec, min_bitrate, start_bitrate, - max_bitrate) && ret_val; + ret_val = SetSendCodec(send_channel, codec) && ret_val; } if (ret_val) { // All SetSendCodec calls were successful. Update the global state // accordingly. send_codec_.reset(new webrtc::VideoCodec(codec)); - send_min_bitrate_ = min_bitrate; - send_start_bitrate_ = start_bitrate; - send_max_bitrate_ = max_bitrate; } else { // At least one SetSendCodec call failed, rollback. for (SendChannelMap::iterator iter = send_channels_.begin(); iter != send_channels_.end(); ++iter) { WebRtcVideoChannelSendInfo* send_channel = iter->second; if (send_codec_) { - SetSendCodec(send_channel, *send_codec_.get(), send_min_bitrate_, - send_start_bitrate_, send_max_bitrate_); + SetSendCodec(send_channel, *send_codec_); } } } @@ -3679,19 +3706,14 @@ bool WebRtcVideoMediaChannel::SetSendCodec(const webrtc::VideoCodec& codec, bool WebRtcVideoMediaChannel::SetSendCodec( WebRtcVideoChannelSendInfo* send_channel, - const webrtc::VideoCodec& codec, - int min_bitrate, - int start_bitrate, - int max_bitrate) { + const webrtc::VideoCodec& codec) { if (!send_channel) { return false; } + const int channel_id = send_channel->channel_id(); // Make a copy of the codec webrtc::VideoCodec target_codec = codec; - target_codec.startBitrate = start_bitrate; - target_codec.minBitrate = min_bitrate; - target_codec.maxBitrate = max_bitrate; // Set the default number of temporal layers for VP8. if (webrtc::kVideoCodecVP8 == codec.codecType) { @@ -3702,7 +3724,7 @@ bool WebRtcVideoMediaChannel::SetSendCodec( target_codec.codecSpecific.VP8.resilience = webrtc::kResilienceOff; bool enable_denoising = - options_.video_noise_reduction.GetWithDefaultIfUnset(false); + options_.video_noise_reduction.GetWithDefaultIfUnset(true); target_codec.codecSpecific.VP8.denoisingOn = enable_denoising; } @@ -3731,7 +3753,7 @@ bool WebRtcVideoMediaChannel::SetSendCodec( LOG(LS_INFO) << "0x0 resolution selected. Captured frames will be dropped " << "for ssrc: " << ssrc << "."; } else { - MaybeChangeStartBitrate(channel_id, &target_codec); + MaybeChangeBitrates(channel_id, &target_codec); webrtc::VideoCodec current_codec; if (!engine()->vie()->codec()->GetSendCodec(channel_id, current_codec)) { // Compare against existing configured send codec. @@ -3761,7 +3783,6 @@ bool WebRtcVideoMediaChannel::SetSendCodec( return true; } - static std::string ToString(webrtc::VideoCodecComplexity complexity) { switch (complexity) { case webrtc::kComplexityNormal: @@ -3840,6 +3861,13 @@ bool WebRtcVideoMediaChannel::SetReceiveCodecs( int red_type = -1; int fec_type = -1; int channel_id = info->channel_id(); + // Build a map from payload types to video codecs so that we easily can find + // out if associated payload types are referring to valid codecs. + std::map pt_to_codec; + for (std::vector::iterator it = receive_codecs_.begin(); + it != receive_codecs_.end(); ++it) { + pt_to_codec[it->plType] = &(*it); + } for (std::vector::iterator it = receive_codecs_.begin(); it != receive_codecs_.end(); ++it) { if (it->codecType == webrtc::kVideoCodecRED) { @@ -3847,6 +3875,32 @@ bool WebRtcVideoMediaChannel::SetReceiveCodecs( } else if (it->codecType == webrtc::kVideoCodecULPFEC) { fec_type = it->plType; } + // If this is an RTX codec we have to verify that it is associated with + // a valid video codec which we have RTX support for. + if (_stricmp(it->plName, kRtxCodecName) == 0) { + std::map::iterator apt_it = associated_payload_types_.find( + it->plType); + bool valid_apt = false; + if (apt_it != associated_payload_types_.end()) { + std::map::iterator codec_it = + pt_to_codec.find(apt_it->second); + // We currently only support RTX associated with VP8 due to limitations + // in webrtc where only one RTX payload type can be registered. + valid_apt = codec_it != pt_to_codec.end() && + _stricmp(codec_it->second->plName, kVp8CodecName) == 0; + } + if (!valid_apt) { + LOG(LS_ERROR) << "The RTX codec isn't associated with a known and " + "supported payload type"; + return false; + } + if (engine()->vie()->rtp()->SetRtxReceivePayloadType( + channel_id, it->plType) != 0) { + LOG_RTCERR2(SetRtxReceivePayloadType, channel_id, it->plType); + return false; + } + continue; + } if (engine()->vie()->codec()->SetReceiveCodec(channel_id, *it) != 0) { LOG_RTCERR2(SetReceiveCodec, channel_id, it->plName); return false; @@ -3874,14 +3928,32 @@ int WebRtcVideoMediaChannel::GetRecvChannelNum(uint32 ssrc) { if (ssrc == first_receive_ssrc_) { return vie_channel_; } + int recv_channel = -1; RecvChannelMap::iterator it = recv_channels_.find(ssrc); - return (it != recv_channels_.end()) ? it->second->channel_id() : -1; + if (it == recv_channels_.end()) { + // Check if we have an RTX stream registered on this SSRC. + SsrcMap::iterator rtx_it = rtx_to_primary_ssrc_.find(ssrc); + if (rtx_it != rtx_to_primary_ssrc_.end()) { + if (rtx_it->second == first_receive_ssrc_) { + recv_channel = vie_channel_; + } else { + it = recv_channels_.find(rtx_it->second); + assert(it != recv_channels_.end()); + recv_channel = it->second->channel_id(); + } + } + } else { + recv_channel = it->second->channel_id(); + } + return recv_channel; } // If the new frame size is different from the send codec size we set on vie, // we need to reset the send codec on vie. // The new send codec size should not exceed send_codec_ which is controlled // only by the 'jec' logic. +// TODO(pthatcher): Get rid of this function, so we only ever set up +// codecs in a single place. bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( WebRtcVideoChannelSendInfo* send_channel, int new_width, @@ -3893,7 +3965,7 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( } ASSERT(send_codec_.get() != NULL); - webrtc::VideoCodec target_codec = *send_codec_.get(); + webrtc::VideoCodec target_codec = *send_codec_; const VideoFormat& video_format = send_channel->video_format(); UpdateVideoCodec(video_format, &target_codec); @@ -3924,19 +3996,21 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( // Turn off VP8 frame dropping when screensharing as the current model does // not work well at low fps. bool vp8_frame_dropping = !is_screencast; - // Disable denoising for screencasting. + // TODO(pbos): Remove |video_noise_reduction| and enable it for all + // non-screencast. bool enable_denoising = - options_.video_noise_reduction.GetWithDefaultIfUnset(false); -#ifdef USE_WEBRTC_DEV_BRANCH + options_.video_noise_reduction.GetWithDefaultIfUnset(true); + // Disable denoising for screencasting. + if (is_screencast) { + enable_denoising = false; + } int screencast_min_bitrate = options_.screencast_min_bitrate.GetWithDefaultIfUnset(0); - bool leaky_bucket = options_.video_leaky_bucket.GetWithDefaultIfUnset(false); -#endif - bool denoising = !is_screencast && enable_denoising; + bool leaky_bucket = options_.video_leaky_bucket.GetWithDefaultIfUnset(true); bool reset_send_codec = target_width != cur_width || target_height != cur_height || automatic_resize != vie_codec.codecSpecific.VP8.automaticResizeOn || - denoising != vie_codec.codecSpecific.VP8.denoisingOn || + enable_denoising != vie_codec.codecSpecific.VP8.denoisingOn || vp8_frame_dropping != vie_codec.codecSpecific.VP8.frameDroppingOn; if (reset_send_codec) { @@ -3945,28 +4019,19 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( vie_codec.height = target_height; vie_codec.maxFramerate = target_codec.maxFramerate; vie_codec.startBitrate = target_codec.startBitrate; -#ifdef USE_WEBRTC_DEV_BRANCH + vie_codec.minBitrate = target_codec.minBitrate; + vie_codec.maxBitrate = target_codec.maxBitrate; vie_codec.targetBitrate = 0; -#endif vie_codec.codecSpecific.VP8.automaticResizeOn = automatic_resize; - vie_codec.codecSpecific.VP8.denoisingOn = denoising; + vie_codec.codecSpecific.VP8.denoisingOn = enable_denoising; vie_codec.codecSpecific.VP8.frameDroppingOn = vp8_frame_dropping; - bool maybe_change_start_bitrate = !is_screencast; -#ifdef USE_WEBRTC_DEV_BRANCH - // TODO(pbos): When USE_WEBRTC_DEV_BRANCH is removed, remove - // maybe_change_start_bitrate as well. MaybeChangeStartBitrate should be - // called for all content. - maybe_change_start_bitrate = true; -#endif - if (maybe_change_start_bitrate) - MaybeChangeStartBitrate(channel_id, &vie_codec); + MaybeChangeBitrates(channel_id, &vie_codec); if (engine()->vie()->codec()->SetSendCodec(channel_id, vie_codec) != 0) { LOG_RTCERR1(SetSendCodec, channel_id); return false; } -#ifdef USE_WEBRTC_DEV_BRANCH if (is_screencast) { engine()->vie()->rtp()->SetMinTransmitBitrate(channel_id, screencast_min_bitrate); @@ -3982,7 +4047,6 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( engine()->vie()->rtp()->SetTransmissionSmoothingStatus(channel_id, leaky_bucket); } -#endif if (reset) { *reset = true; } @@ -3992,12 +4056,28 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( return true; } -void WebRtcVideoMediaChannel::MaybeChangeStartBitrate( - int channel_id, webrtc::VideoCodec* video_codec) { - if (video_codec->startBitrate < video_codec->minBitrate) { - video_codec->startBitrate = video_codec->minBitrate; - } else if (video_codec->startBitrate > video_codec->maxBitrate) { - video_codec->startBitrate = video_codec->maxBitrate; +void WebRtcVideoMediaChannel::MaybeChangeBitrates( + int channel_id, webrtc::VideoCodec* codec) { + codec->minBitrate = GetBitrate(codec->minBitrate, kMinVideoBitrate); + codec->startBitrate = GetBitrate(codec->startBitrate, kStartVideoBitrate); + codec->maxBitrate = GetBitrate(codec->maxBitrate, kMaxVideoBitrate); + + if (codec->minBitrate > codec->maxBitrate) { + LOG(LS_INFO) << "Decreasing codec min bitrate to the max (" + << codec->maxBitrate << ") because the min (" + << codec->minBitrate << ") exceeds the max."; + codec->minBitrate = codec->maxBitrate; + } + if (codec->startBitrate < codec->minBitrate) { + LOG(LS_INFO) << "Increasing codec start bitrate to the min (" + << codec->minBitrate << ") because the start (" + << codec->startBitrate << ") is less than the min."; + codec->startBitrate = codec->minBitrate; + } else if (codec->startBitrate > codec->maxBitrate) { + LOG(LS_INFO) << "Decreasing codec start bitrate to the max (" + << codec->maxBitrate << ") because the start (" + << codec->startBitrate << ") exceeds the max."; + codec->startBitrate = codec->maxBitrate; } // Use a previous target bitrate, if there is one. @@ -4006,11 +4086,11 @@ void WebRtcVideoMediaChannel::MaybeChangeStartBitrate( channel_id, ¤t_target_bitrate) == 0) { // Convert to kbps. current_target_bitrate /= 1000; - if (current_target_bitrate > video_codec->maxBitrate) { - current_target_bitrate = video_codec->maxBitrate; + if (current_target_bitrate > codec->maxBitrate) { + current_target_bitrate = codec->maxBitrate; } - if (current_target_bitrate > video_codec->startBitrate) { - video_codec->startBitrate = current_target_bitrate; + if (current_target_bitrate > codec->startBitrate) { + codec->startBitrate = current_target_bitrate; } } } diff --git a/talk/media/webrtc/webrtcvideoengine.h b/talk/media/webrtc/webrtcvideoengine.h index 1d36ab19a..f9b97710c 100644 --- a/talk/media/webrtc/webrtcvideoengine.h +++ b/talk/media/webrtc/webrtcvideoengine.h @@ -45,7 +45,6 @@ #error "Bogus include." #endif - namespace webrtc { class VideoCaptureModule; class VideoDecoder; @@ -201,6 +200,7 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, void SetTraceFilter(int filter); void SetTraceOptions(const std::string& options); bool InitVideoEngine(); + bool VerifyApt(const VideoCodec& in, int expected_apt) const; // webrtc::TraceCallback implementation. virtual void Print(webrtc::TraceLevel level, const char* trace, int length); @@ -313,6 +313,7 @@ class WebRtcVideoMediaChannel : public talk_base::MessageHandler, private: typedef std::map RecvChannelMap; typedef std::map SendChannelMap; + typedef std::map SsrcMap; typedef int (webrtc::ViERTP_RTCP::* ExtensionSetterFunction)(int, bool, int); enum MediaDirection { MD_RECV, MD_SEND, MD_SENDRECV }; @@ -334,26 +335,25 @@ class WebRtcVideoMediaChannel : public talk_base::MessageHandler, bool ConfigureSending(int channel_id, uint32 local_ssrc_key); bool SetNackFec(int channel_id, int red_payload_type, int fec_payload_type, bool nack_enabled); - bool SetSendCodec(const webrtc::VideoCodec& codec, int min_bitrate, - int start_bitrate, int max_bitrate); + bool SetSendCodec(const webrtc::VideoCodec& codec); bool SetSendCodec(WebRtcVideoChannelSendInfo* send_channel, - const webrtc::VideoCodec& codec, int min_bitrate, - int start_bitrate, int max_bitrate); + const webrtc::VideoCodec& codec); void LogSendCodecChange(const std::string& reason); // Prepares the channel with channel id |info->channel_id()| to receive all // codecs in |receive_codecs_| and start receive packets. bool SetReceiveCodecs(WebRtcVideoChannelRecvInfo* info); // Returns the channel number that receives the stream with SSRC |ssrc|. int GetRecvChannelNum(uint32 ssrc); + bool MaybeSetRtxSsrc(const StreamParams& sp, int channel_id); // Given captured video frame size, checks if we need to reset vie send codec. // |reset| is set to whether resetting has happened on vie or not. // Returns false on error. bool MaybeResetVieSendCodec(WebRtcVideoChannelSendInfo* send_channel, int new_width, int new_height, bool is_screencast, bool* reset); - // Checks the current bitrate estimate and modifies the start bitrate - // accordingly. - void MaybeChangeStartBitrate(int channel_id, webrtc::VideoCodec* video_codec); + // Checks the current bitrate estimate and modifies the bitrates + // accordingly, including converting kAutoBandwidth to the correct defaults. + void MaybeChangeBitrates(int channel_id, webrtc::VideoCodec* video_codec); // Helper function for starting the sending of media on all channels or // |channel_id|. Note that these two function do not change |sending_|. bool StartSend(); @@ -389,7 +389,6 @@ class WebRtcVideoMediaChannel : public talk_base::MessageHandler, } bool RemoveCapturer(uint32 ssrc); - talk_base::MessageQueue* worker_thread() { return engine_->worker_thread(); } void QueueBlackFrame(uint32 ssrc, int64 timestamp, int framerate); void FlushBlackFrame(uint32 ssrc, int64 timestamp); @@ -433,7 +432,16 @@ class WebRtcVideoMediaChannel : public talk_base::MessageHandler, // work properly), resides in both recv_channels_ and send_channels_ with the // ssrc key 0. RecvChannelMap recv_channels_; // Contains all receive channels. + // A map from the SSRCs on which RTX packets are received to the media SSRCs + // the RTX packets are associated with. RTX packets will be delivered to the + // streams matching the primary SSRC. + SsrcMap rtx_to_primary_ssrc_; std::vector receive_codecs_; + // A map from codec payload types to their associated payload types, if any. + // TODO(holmer): This is a temporary solution until webrtc::VideoCodec has + // an associated payload type member, when it does we can rely on + // receive_codecs_. + std::map associated_payload_types_; bool render_started_; uint32 first_receive_ssrc_; std::vector receive_extensions_; @@ -445,9 +453,6 @@ class WebRtcVideoMediaChannel : public talk_base::MessageHandler, int send_rtx_type_; int send_red_type_; int send_fec_type_; - int send_min_bitrate_; - int send_start_bitrate_; - int send_max_bitrate_; bool sending_; std::vector send_extensions_; diff --git a/talk/media/webrtc/webrtcvideoengine2.cc b/talk/media/webrtc/webrtcvideoengine2.cc new file mode 100644 index 000000000..832f9a911 --- /dev/null +++ b/talk/media/webrtc/webrtcvideoengine2.cc @@ -0,0 +1,1768 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_WEBRTC_VIDEO +#include "talk/media/webrtc/webrtcvideoengine2.h" + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "libyuv/convert_from.h" +#include "talk/base/buffer.h" +#include "talk/base/logging.h" +#include "talk/base/stringutils.h" +#include "talk/media/base/videocapturer.h" +#include "talk/media/base/videorenderer.h" +#include "talk/media/webrtc/constants.h" +#include "talk/media/webrtc/webrtcvideocapturer.h" +#include "talk/media/webrtc/webrtcvideoframe.h" +#include "talk/media/webrtc/webrtcvoiceengine.h" +#include "webrtc/call.h" +// TODO(pbos): Move codecs out of modules (webrtc:3070). +#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" + +#define UNIMPLEMENTED \ + LOG(LS_ERROR) << "Call to unimplemented function " << __FUNCTION__; \ + ASSERT(false) + +namespace cricket { + +// This constant is really an on/off, lower-level configurable NACK history +// duration hasn't been implemented. +static const int kNackHistoryMs = 1000; + +static const int kDefaultRtcpReceiverReportSsrc = 1; + +struct VideoCodecPref { + int payload_type; + const char* name; + int rtx_payload_type; +} kDefaultVideoCodecPref = {100, kVp8CodecName, 96}; + +VideoCodecPref kRedPref = {116, kRedCodecName, -1}; +VideoCodecPref kUlpfecPref = {117, kUlpfecCodecName, -1}; + +// The formats are sorted by the descending order of width. We use the order to +// find the next format for CPU and bandwidth adaptation. +const VideoFormatPod kDefaultVideoFormat = { + 640, 400, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}; +const VideoFormatPod kVideoFormats[] = { + {1280, 800, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {1280, 720, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {960, 600, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {960, 540, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + kDefaultVideoFormat, + {640, 360, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {640, 480, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {480, 300, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {480, 270, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {480, 360, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {320, 200, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {320, 180, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {320, 240, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {240, 150, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {240, 135, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {240, 180, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {160, 100, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {160, 90, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {160, 120, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, }; + +static bool FindFirstMatchingCodec(const std::vector& codecs, + const VideoCodec& requested_codec, + VideoCodec* matching_codec) { + for (size_t i = 0; i < codecs.size(); ++i) { + if (requested_codec.Matches(codecs[i])) { + *matching_codec = codecs[i]; + return true; + } + } + return false; +} +static bool FindBestVideoFormat(int max_width, + int max_height, + int aspect_width, + int aspect_height, + VideoFormat* video_format) { + assert(max_width > 0); + assert(max_height > 0); + assert(aspect_width > 0); + assert(aspect_height > 0); + VideoFormat best_format; + for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) { + const VideoFormat format(kVideoFormats[i]); + + // Skip any format that is larger than the local or remote maximums, or + // smaller than the current best match + if (format.width > max_width || format.height > max_height || + (format.width < best_format.width && + format.height < best_format.height)) { + continue; + } + + // If we don't have any matches yet, this is the best so far. + if (best_format.width == 0) { + best_format = format; + continue; + } + + // Prefer closer aspect ratios i.e: + // |format| aspect - requested aspect < + // |best_format| aspect - requested aspect + if (abs(format.width * aspect_height * best_format.height - + aspect_width * format.height * best_format.height) < + abs(best_format.width * aspect_height * format.height - + aspect_width * format.height * best_format.height)) { + best_format = format; + } + } + if (best_format.width != 0) { + *video_format = best_format; + return true; + } + return false; +} + +static void AddDefaultFeedbackParams(VideoCodec* codec) { + const FeedbackParam kFir(kRtcpFbParamCcm, kRtcpFbCcmParamFir); + codec->AddFeedbackParam(kFir); + const FeedbackParam kNack(kRtcpFbParamNack, kParamValueEmpty); + codec->AddFeedbackParam(kNack); + const FeedbackParam kPli(kRtcpFbParamNack, kRtcpFbNackParamPli); + codec->AddFeedbackParam(kPli); + const FeedbackParam kRemb(kRtcpFbParamRemb, kParamValueEmpty); + codec->AddFeedbackParam(kRemb); +} + +static bool IsNackEnabled(const VideoCodec& codec) { + return codec.HasFeedbackParam( + FeedbackParam(kRtcpFbParamNack, kParamValueEmpty)); +} + +static VideoCodec DefaultVideoCodec() { + VideoCodec default_codec(kDefaultVideoCodecPref.payload_type, + kDefaultVideoCodecPref.name, + kDefaultVideoFormat.width, + kDefaultVideoFormat.height, + kDefaultFramerate, + 0); + AddDefaultFeedbackParams(&default_codec); + return default_codec; +} + +static VideoCodec DefaultRedCodec() { + return VideoCodec(kRedPref.payload_type, kRedPref.name, 0, 0, 0, 0); +} + +static VideoCodec DefaultUlpfecCodec() { + return VideoCodec(kUlpfecPref.payload_type, kUlpfecPref.name, 0, 0, 0, 0); +} + +static std::vector DefaultVideoCodecs() { + std::vector codecs; + codecs.push_back(DefaultVideoCodec()); + codecs.push_back(DefaultRedCodec()); + codecs.push_back(DefaultUlpfecCodec()); + if (kDefaultVideoCodecPref.rtx_payload_type != -1) { + codecs.push_back( + VideoCodec::CreateRtxCodec(kDefaultVideoCodecPref.rtx_payload_type, + kDefaultVideoCodecPref.payload_type)); + } + return codecs; +} + +WebRtcVideoEncoderFactory2::~WebRtcVideoEncoderFactory2() { +} + +std::vector WebRtcVideoEncoderFactory2::CreateVideoStreams( + const VideoCodec& codec, + const VideoOptions& options, + size_t num_streams) { + assert(SupportsCodec(codec)); + if (num_streams != 1) { + LOG(LS_ERROR) << "Unsupported number of streams: " << num_streams; + return std::vector(); + } + + webrtc::VideoStream stream; + stream.width = codec.width; + stream.height = codec.height; + stream.max_framerate = + codec.framerate != 0 ? codec.framerate : kDefaultFramerate; + + int min_bitrate = kMinVideoBitrate; + codec.GetParam(kCodecParamMinBitrate, &min_bitrate); + int max_bitrate = kMaxVideoBitrate; + codec.GetParam(kCodecParamMaxBitrate, &max_bitrate); + stream.min_bitrate_bps = min_bitrate * 1000; + stream.target_bitrate_bps = stream.max_bitrate_bps = max_bitrate * 1000; + + int max_qp = 56; + codec.GetParam(kCodecParamMaxQuantization, &max_qp); + stream.max_qp = max_qp; + std::vector streams; + streams.push_back(stream); + return streams; +} + +webrtc::VideoEncoder* WebRtcVideoEncoderFactory2::CreateVideoEncoder( + const VideoCodec& codec, + const VideoOptions& options) { + assert(SupportsCodec(codec)); + return webrtc::VP8Encoder::Create(); +} + +bool WebRtcVideoEncoderFactory2::SupportsCodec(const VideoCodec& codec) { + return _stricmp(codec.name.c_str(), kVp8CodecName) == 0; +} + +WebRtcVideoEngine2::WebRtcVideoEngine2() { + // Construct without a factory or voice engine. + Construct(NULL, NULL, new talk_base::CpuMonitor(NULL)); +} + +WebRtcVideoEngine2::WebRtcVideoEngine2( + WebRtcVideoChannelFactory* channel_factory) { + // Construct without a voice engine. + Construct(channel_factory, NULL, new talk_base::CpuMonitor(NULL)); +} + +void WebRtcVideoEngine2::Construct(WebRtcVideoChannelFactory* channel_factory, + WebRtcVoiceEngine* voice_engine, + talk_base::CpuMonitor* cpu_monitor) { + LOG(LS_INFO) << "WebRtcVideoEngine2::WebRtcVideoEngine2"; + worker_thread_ = NULL; + voice_engine_ = voice_engine; + initialized_ = false; + capture_started_ = false; + cpu_monitor_.reset(cpu_monitor); + channel_factory_ = channel_factory; + + video_codecs_ = DefaultVideoCodecs(); + default_codec_format_ = VideoFormat(kDefaultVideoFormat); + + rtp_header_extensions_.push_back( + RtpHeaderExtension(kRtpTimestampOffsetHeaderExtension, + kRtpTimestampOffsetHeaderExtensionDefaultId)); + rtp_header_extensions_.push_back( + RtpHeaderExtension(kRtpAbsoluteSenderTimeHeaderExtension, + kRtpAbsoluteSenderTimeHeaderExtensionDefaultId)); +} + +WebRtcVideoEngine2::~WebRtcVideoEngine2() { + LOG(LS_INFO) << "WebRtcVideoEngine2::~WebRtcVideoEngine2"; + + if (initialized_) { + Terminate(); + } +} + +bool WebRtcVideoEngine2::Init(talk_base::Thread* worker_thread) { + LOG(LS_INFO) << "WebRtcVideoEngine2::Init"; + worker_thread_ = worker_thread; + ASSERT(worker_thread_ != NULL); + + cpu_monitor_->set_thread(worker_thread_); + if (!cpu_monitor_->Start(kCpuMonitorPeriodMs)) { + LOG(LS_ERROR) << "Failed to start CPU monitor."; + cpu_monitor_.reset(); + } + + initialized_ = true; + return true; +} + +void WebRtcVideoEngine2::Terminate() { + LOG(LS_INFO) << "WebRtcVideoEngine2::Terminate"; + + cpu_monitor_->Stop(); + + initialized_ = false; +} + +int WebRtcVideoEngine2::GetCapabilities() { return VIDEO_RECV | VIDEO_SEND; } + +bool WebRtcVideoEngine2::SetOptions(const VideoOptions& options) { + // TODO(pbos): Do we need this? This is a no-op in the existing + // WebRtcVideoEngine implementation. + LOG(LS_VERBOSE) << "SetOptions: " << options.ToString(); + // options_ = options; + return true; +} + +bool WebRtcVideoEngine2::SetDefaultEncoderConfig( + const VideoEncoderConfig& config) { + // TODO(pbos): Implement. Should be covered by corresponding unit tests. + LOG(LS_VERBOSE) << "SetDefaultEncoderConfig()"; + return true; +} + +VideoEncoderConfig WebRtcVideoEngine2::GetDefaultEncoderConfig() const { + return VideoEncoderConfig(DefaultVideoCodec()); +} + +WebRtcVideoChannel2* WebRtcVideoEngine2::CreateChannel( + VoiceMediaChannel* voice_channel) { + LOG(LS_INFO) << "CreateChannel: " + << (voice_channel != NULL ? "With" : "Without") + << " voice channel."; + WebRtcVideoChannel2* channel = + channel_factory_ != NULL + ? channel_factory_->Create(this, voice_channel) + : new WebRtcVideoChannel2( + this, voice_channel, GetVideoEncoderFactory()); + if (!channel->Init()) { + delete channel; + return NULL; + } + channel->SetRecvCodecs(video_codecs_); + return channel; +} + +const std::vector& WebRtcVideoEngine2::codecs() const { + return video_codecs_; +} + +const std::vector& +WebRtcVideoEngine2::rtp_header_extensions() const { + return rtp_header_extensions_; +} + +void WebRtcVideoEngine2::SetLogging(int min_sev, const char* filter) { + // TODO(pbos): Set up logging. + LOG(LS_VERBOSE) << "SetLogging: " << min_sev << '"' << filter << '"'; + // if min_sev == -1, we keep the current log level. + if (min_sev < 0) { + assert(min_sev == -1); + return; + } +} + +bool WebRtcVideoEngine2::EnableTimedRender() { + // TODO(pbos): Figure out whether this can be removed. + return true; +} + +bool WebRtcVideoEngine2::SetLocalRenderer(VideoRenderer* renderer) { + // TODO(pbos): Implement or remove. Unclear which stream should be rendered + // locally even. + return true; +} + +// Checks to see whether we comprehend and could receive a particular codec +bool WebRtcVideoEngine2::FindCodec(const VideoCodec& in) { + // TODO(pbos): Probe encoder factory to figure out that the codec is supported + // if supported by the encoder factory. Add a corresponding test that fails + // with this code (that doesn't ask the factory). + for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) { + const VideoFormat fmt(kVideoFormats[i]); + if ((in.width != 0 || in.height != 0) && + (fmt.width != in.width || fmt.height != in.height)) { + continue; + } + for (size_t j = 0; j < video_codecs_.size(); ++j) { + VideoCodec codec(video_codecs_[j].id, video_codecs_[j].name, 0, 0, 0, 0); + if (codec.Matches(in)) { + return true; + } + } + } + return false; +} + +// Tells whether the |requested| codec can be transmitted or not. If it can be +// transmitted |out| is set with the best settings supported. Aspect ratio will +// be set as close to |current|'s as possible. If not set |requested|'s +// dimensions will be used for aspect ratio matching. +bool WebRtcVideoEngine2::CanSendCodec(const VideoCodec& requested, + const VideoCodec& current, + VideoCodec* out) { + assert(out != NULL); + // TODO(pbos): Implement. + + if (requested.width != requested.height && + (requested.height == 0 || requested.width == 0)) { + // 0xn and nx0 are invalid resolutions. + return false; + } + + VideoCodec matching_codec; + if (!FindFirstMatchingCodec(video_codecs_, requested, &matching_codec)) { + // Codec not supported. + return false; + } + + // Pick the best quality that is within their and our bounds and has the + // correct aspect ratio. + VideoFormat format; + if (requested.width == 0 && requested.height == 0) { + // Special case with resolution 0. The channel should not send frames. + } else { + int max_width = talk_base::_min(requested.width, matching_codec.width); + int max_height = talk_base::_min(requested.height, matching_codec.height); + int aspect_width = max_width; + int aspect_height = max_height; + if (current.width > 0 && current.height > 0) { + aspect_width = current.width; + aspect_height = current.height; + } + if (!FindBestVideoFormat( + max_width, max_height, aspect_width, aspect_height, &format)) { + return false; + } + } + + out->id = requested.id; + out->name = requested.name; + out->preference = requested.preference; + out->params = requested.params; + out->framerate = + talk_base::_min(requested.framerate, matching_codec.framerate); + out->width = format.width; + out->height = format.height; + out->params = requested.params; + out->feedback_params = requested.feedback_params; + return true; +} + +bool WebRtcVideoEngine2::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) { + if (initialized_) { + LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init"; + return false; + } + voice_engine_ = voice_engine; + return true; +} + +// Ignore spammy trace messages, mostly from the stats API when we haven't +// gotten RTCP info yet from the remote side. +bool WebRtcVideoEngine2::ShouldIgnoreTrace(const std::string& trace) { + static const char* const kTracesToIgnore[] = {NULL}; + for (const char* const* p = kTracesToIgnore; *p; ++p) { + if (trace.find(*p) == 0) { + return true; + } + } + return false; +} + +WebRtcVideoEncoderFactory2* WebRtcVideoEngine2::GetVideoEncoderFactory() { + return &default_video_encoder_factory_; +} + +// Thin map between VideoFrame and an existing webrtc::I420VideoFrame +// to avoid having to copy the rendered VideoFrame prematurely. +// This implementation is only safe to use in a const context and should never +// be written to. +class WebRtcVideoRenderFrame : public VideoFrame { + public: + explicit WebRtcVideoRenderFrame(const webrtc::I420VideoFrame* frame) + : frame_(frame) {} + + virtual bool InitToBlack(int w, + int h, + size_t pixel_width, + size_t pixel_height, + int64 elapsed_time, + int64 time_stamp) OVERRIDE { + UNIMPLEMENTED; + return false; + } + + virtual bool Reset(uint32 fourcc, + int w, + int h, + int dw, + int dh, + uint8* sample, + size_t sample_size, + size_t pixel_width, + size_t pixel_height, + int64 elapsed_time, + int64 time_stamp, + int rotation) OVERRIDE { + UNIMPLEMENTED; + return false; + } + + virtual size_t GetWidth() const OVERRIDE { + return static_cast(frame_->width()); + } + virtual size_t GetHeight() const OVERRIDE { + return static_cast(frame_->height()); + } + + virtual const uint8* GetYPlane() const OVERRIDE { + return frame_->buffer(webrtc::kYPlane); + } + virtual const uint8* GetUPlane() const OVERRIDE { + return frame_->buffer(webrtc::kUPlane); + } + virtual const uint8* GetVPlane() const OVERRIDE { + return frame_->buffer(webrtc::kVPlane); + } + + virtual uint8* GetYPlane() OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + virtual uint8* GetUPlane() OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + virtual uint8* GetVPlane() OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + + virtual int32 GetYPitch() const OVERRIDE { + return frame_->stride(webrtc::kYPlane); + } + virtual int32 GetUPitch() const OVERRIDE { + return frame_->stride(webrtc::kUPlane); + } + virtual int32 GetVPitch() const OVERRIDE { + return frame_->stride(webrtc::kVPlane); + } + + virtual void* GetNativeHandle() const OVERRIDE { return NULL; } + + virtual size_t GetPixelWidth() const OVERRIDE { return 1; } + virtual size_t GetPixelHeight() const OVERRIDE { return 1; } + + virtual int64 GetElapsedTime() const OVERRIDE { + // Convert millisecond render time to ns timestamp. + return frame_->render_time_ms() * talk_base::kNumNanosecsPerMillisec; + } + virtual int64 GetTimeStamp() const OVERRIDE { + // Convert 90K rtp timestamp to ns timestamp. + return (frame_->timestamp() / 90) * talk_base::kNumNanosecsPerMillisec; + } + virtual void SetElapsedTime(int64 elapsed_time) OVERRIDE { UNIMPLEMENTED; } + virtual void SetTimeStamp(int64 time_stamp) OVERRIDE { UNIMPLEMENTED; } + + virtual int GetRotation() const OVERRIDE { + UNIMPLEMENTED; + return ROTATION_0; + } + + virtual VideoFrame* Copy() const OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + + virtual bool MakeExclusive() OVERRIDE { + UNIMPLEMENTED; + return false; + } + + virtual size_t CopyToBuffer(uint8* buffer, size_t size) const { + UNIMPLEMENTED; + return 0; + } + + // TODO(fbarchard): Refactor into base class and share with LMI + virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, + uint8* buffer, + size_t size, + int stride_rgb) const OVERRIDE { + size_t width = GetWidth(); + size_t height = GetHeight(); + size_t needed = (stride_rgb >= 0 ? stride_rgb : -stride_rgb) * height; + if (size < needed) { + LOG(LS_WARNING) << "RGB buffer is not large enough"; + return needed; + } + + if (libyuv::ConvertFromI420(GetYPlane(), + GetYPitch(), + GetUPlane(), + GetUPitch(), + GetVPlane(), + GetVPitch(), + buffer, + stride_rgb, + static_cast(width), + static_cast(height), + to_fourcc)) { + LOG(LS_ERROR) << "RGB type not supported: " << to_fourcc; + return 0; // 0 indicates error + } + return needed; + } + + protected: + virtual VideoFrame* CreateEmptyFrame(int w, + int h, + size_t pixel_width, + size_t pixel_height, + int64 elapsed_time, + int64 time_stamp) const OVERRIDE { + // TODO(pbos): Remove WebRtcVideoFrame dependency, and have a non-const + // version of I420VideoFrame wrapped. + WebRtcVideoFrame* frame = new WebRtcVideoFrame(); + frame->InitToBlack( + w, h, pixel_width, pixel_height, elapsed_time, time_stamp); + return frame; + } + + private: + const webrtc::I420VideoFrame* const frame_; +}; + +WebRtcVideoRenderer::WebRtcVideoRenderer() + : last_width_(-1), last_height_(-1), renderer_(NULL) {} + +void WebRtcVideoRenderer::RenderFrame(const webrtc::I420VideoFrame& frame, + int time_to_render_ms) { + talk_base::CritScope crit(&lock_); + if (renderer_ == NULL) { + LOG(LS_WARNING) << "VideoReceiveStream not connected to a VideoRenderer."; + return; + } + + if (frame.width() != last_width_ || frame.height() != last_height_) { + SetSize(frame.width(), frame.height()); + } + + LOG(LS_VERBOSE) << "RenderFrame: (" << frame.width() << "x" << frame.height() + << ")"; + + const WebRtcVideoRenderFrame render_frame(&frame); + renderer_->RenderFrame(&render_frame); +} + +void WebRtcVideoRenderer::SetRenderer(cricket::VideoRenderer* renderer) { + talk_base::CritScope crit(&lock_); + renderer_ = renderer; + if (renderer_ != NULL && last_width_ != -1) { + SetSize(last_width_, last_height_); + } +} + +VideoRenderer* WebRtcVideoRenderer::GetRenderer() { + talk_base::CritScope crit(&lock_); + return renderer_; +} + +void WebRtcVideoRenderer::SetSize(int width, int height) { + talk_base::CritScope crit(&lock_); + if (!renderer_->SetSize(width, height, 0)) { + LOG(LS_ERROR) << "Could not set renderer size."; + } + last_width_ = width; + last_height_ = height; +} + +// WebRtcVideoChannel2 + +WebRtcVideoChannel2::WebRtcVideoChannel2( + WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel, + WebRtcVideoEncoderFactory2* encoder_factory) + : encoder_factory_(encoder_factory) { + // TODO(pbos): Connect the video and audio with |voice_channel|. + webrtc::Call::Config config(this); + Construct(webrtc::Call::Create(config), engine); +} + +WebRtcVideoChannel2::WebRtcVideoChannel2( + webrtc::Call* call, + WebRtcVideoEngine2* engine, + WebRtcVideoEncoderFactory2* encoder_factory) + : encoder_factory_(encoder_factory) { + Construct(call, engine); +} + +void WebRtcVideoChannel2::Construct(webrtc::Call* call, + WebRtcVideoEngine2* engine) { + rtcp_receiver_report_ssrc_ = kDefaultRtcpReceiverReportSsrc; + sending_ = false; + call_.reset(call); + default_renderer_ = NULL; + default_send_ssrc_ = 0; + default_recv_ssrc_ = 0; +} + +WebRtcVideoChannel2::~WebRtcVideoChannel2() { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + delete it->second; + } + + for (std::map::iterator it = + receive_streams_.begin(); + it != receive_streams_.end(); + ++it) { + assert(it->second != NULL); + call_->DestroyVideoReceiveStream(it->second); + } + + for (std::map::iterator it = renderers_.begin(); + it != renderers_.end(); + ++it) { + assert(it->second != NULL); + delete it->second; + } +} + +bool WebRtcVideoChannel2::Init() { return true; } + +namespace { + +static std::string CodecVectorToString(const std::vector& codecs) { + std::stringstream out; + out << '{'; + for (size_t i = 0; i < codecs.size(); ++i) { + out << codecs[i].ToString(); + if (i != codecs.size() - 1) { + out << ", "; + } + } + out << '}'; + return out.str(); +} + +static bool ValidateCodecFormats(const std::vector& codecs) { + bool has_video = false; + for (size_t i = 0; i < codecs.size(); ++i) { + if (!codecs[i].ValidateCodecFormat()) { + return false; + } + if (codecs[i].GetCodecType() == VideoCodec::CODEC_VIDEO) { + has_video = true; + } + } + if (!has_video) { + LOG(LS_ERROR) << "Setting codecs without a video codec is invalid: " + << CodecVectorToString(codecs); + return false; + } + return true; +} + +static std::string RtpExtensionsToString( + const std::vector& extensions) { + std::stringstream out; + out << '{'; + for (size_t i = 0; i < extensions.size(); ++i) { + out << "{" << extensions[i].uri << ": " << extensions[i].id << "}"; + if (i != extensions.size() - 1) { + out << ", "; + } + } + out << '}'; + return out.str(); +} + +} // namespace + +bool WebRtcVideoChannel2::SetRecvCodecs(const std::vector& codecs) { + // TODO(pbos): Must these receive codecs propagate to existing receive + // streams? + LOG(LS_INFO) << "SetRecvCodecs: " << CodecVectorToString(codecs); + if (!ValidateCodecFormats(codecs)) { + return false; + } + + const std::vector mapped_codecs = MapCodecs(codecs); + if (mapped_codecs.empty()) { + LOG(LS_ERROR) << "SetRecvCodecs called without video codec payloads."; + return false; + } + + // TODO(pbos): Add a decoder factory which controls supported codecs. + // Blocked on webrtc:2854. + for (size_t i = 0; i < mapped_codecs.size(); ++i) { + if (_stricmp(mapped_codecs[i].codec.name.c_str(), kVp8CodecName) != 0) { + LOG(LS_ERROR) << "SetRecvCodecs called with unsupported codec: '" + << mapped_codecs[i].codec.name << "'"; + return false; + } + } + + recv_codecs_ = mapped_codecs; + return true; +} + +bool WebRtcVideoChannel2::SetSendCodecs(const std::vector& codecs) { + LOG(LS_INFO) << "SetSendCodecs: " << CodecVectorToString(codecs); + if (!ValidateCodecFormats(codecs)) { + return false; + } + + const std::vector supported_codecs = + FilterSupportedCodecs(MapCodecs(codecs)); + + if (supported_codecs.empty()) { + LOG(LS_ERROR) << "No video codecs supported by encoder factory."; + return false; + } + + send_codec_.Set(supported_codecs.front()); + LOG(LS_INFO) << "Using codec: " << supported_codecs.front().codec.ToString(); + + SetCodecForAllSendStreams(supported_codecs.front()); + + return true; +} + +bool WebRtcVideoChannel2::GetSendCodec(VideoCodec* codec) { + VideoCodecSettings codec_settings; + if (!send_codec_.Get(&codec_settings)) { + LOG(LS_VERBOSE) << "GetSendCodec: No send codec set."; + return false; + } + *codec = codec_settings.codec; + return true; +} + +bool WebRtcVideoChannel2::SetSendStreamFormat(uint32 ssrc, + const VideoFormat& format) { + LOG(LS_VERBOSE) << "SetSendStreamFormat:" << ssrc << " -> " + << format.ToString(); + if (send_streams_.find(ssrc) == send_streams_.end()) { + return false; + } + return send_streams_[ssrc]->SetVideoFormat(format); +} + +bool WebRtcVideoChannel2::SetRender(bool render) { + // TODO(pbos): Implement. Or refactor away as it shouldn't be needed. + LOG(LS_VERBOSE) << "SetRender: " << (render ? "true" : "false"); + return true; +} + +bool WebRtcVideoChannel2::SetSend(bool send) { + LOG(LS_VERBOSE) << "SetSend: " << (send ? "true" : "false"); + if (send && !send_codec_.IsSet()) { + LOG(LS_ERROR) << "SetSend(true) called before setting codec."; + return false; + } + if (send) { + StartAllSendStreams(); + } else { + StopAllSendStreams(); + } + sending_ = send; + return true; +} + +static bool ConfigureSendSsrcs(webrtc::VideoSendStream::Config* config, + const StreamParams& sp) { + if (!sp.has_ssrc_groups()) { + config->rtp.ssrcs = sp.ssrcs; + return true; + } + + if (sp.get_ssrc_group(kFecSsrcGroupSemantics) != NULL) { + LOG(LS_ERROR) << "Standalone FEC SSRCs not supported."; + return false; + } + + // Map RTX SSRCs. + std::vector ssrcs; + std::vector rtx_ssrcs; + const SsrcGroup* sim_group = sp.get_ssrc_group(kSimSsrcGroupSemantics); + if (sim_group == NULL) { + ssrcs.push_back(sp.first_ssrc()); + uint32_t rtx_ssrc; + if (!sp.GetFidSsrc(sp.first_ssrc(), &rtx_ssrc)) { + LOG(LS_ERROR) << "Could not find FID ssrc for primary SSRC '" + << sp.first_ssrc() << "':" << sp.ToString(); + return false; + } + rtx_ssrcs.push_back(rtx_ssrc); + } else { + ssrcs = sim_group->ssrcs; + for (size_t i = 0; i < sim_group->ssrcs.size(); ++i) { + uint32_t rtx_ssrc; + if (!sp.GetFidSsrc(sim_group->ssrcs[i], &rtx_ssrc)) { + continue; + } + rtx_ssrcs.push_back(rtx_ssrc); + } + } + if (!rtx_ssrcs.empty() && ssrcs.size() != rtx_ssrcs.size()) { + LOG(LS_ERROR) + << "RTX SSRCs exist, but don't cover all SSRCs (unsupported): " + << sp.ToString(); + return false; + } + config->rtp.rtx.ssrcs = rtx_ssrcs; + config->rtp.ssrcs = ssrcs; + return true; +} + +bool WebRtcVideoChannel2::AddSendStream(const StreamParams& sp) { + LOG(LS_INFO) << "AddSendStream: " << sp.ToString(); + if (sp.ssrcs.empty()) { + LOG(LS_ERROR) << "No SSRCs in stream parameters."; + return false; + } + + uint32 ssrc = sp.first_ssrc(); + assert(ssrc != 0); + // TODO(pbos): Make sure none of sp.ssrcs are used, not just the identifying + // ssrc. + if (send_streams_.find(ssrc) != send_streams_.end()) { + LOG(LS_ERROR) << "Send stream with ssrc '" << ssrc << "' already exists."; + return false; + } + + webrtc::VideoSendStream::Config config = call_->GetDefaultSendConfig(); + + if (!ConfigureSendSsrcs(&config, sp)) { + return false; + } + + VideoCodecSettings codec_settings; + if (!send_codec_.Get(&codec_settings)) { + // TODO(pbos): Set up a temporary fake encoder for VideoSendStream instead + // of setting default codecs not to break CreateEncoderSettings. + SetSendCodecs(DefaultVideoCodecs()); + assert(send_codec_.IsSet()); + send_codec_.Get(&codec_settings); + // This is only to bring up defaults to make VideoSendStream setup easier + // and avoid complexity. We still don't want to allow sending with the + // default codec. + send_codec_.Clear(); + } + + // CreateEncoderSettings will allocate a suitable VideoEncoder instance + // matching current settings. + std::vector video_streams = + encoder_factory_->CreateVideoStreams( + codec_settings.codec, options_, config.rtp.ssrcs.size()); + if (video_streams.empty()) { + return false; + } + + config.encoder_settings.encoder = + encoder_factory_->CreateVideoEncoder(codec_settings.codec, options_); + config.encoder_settings.payload_name = codec_settings.codec.name; + config.encoder_settings.payload_type = codec_settings.codec.id; + config.rtp.c_name = sp.cname; + config.rtp.fec = codec_settings.fec; + if (!config.rtp.rtx.ssrcs.empty()) { + config.rtp.rtx.payload_type = codec_settings.rtx_payload_type; + } + + config.rtp.extensions = send_rtp_extensions_; + + if (IsNackEnabled(codec_settings.codec)) { + config.rtp.nack.rtp_history_ms = kNackHistoryMs; + } + config.rtp.max_packet_size = kVideoMtu; + + WebRtcVideoSendStream* stream = + new WebRtcVideoSendStream(call_.get(), + config, + options_, + codec_settings.codec, + video_streams, + encoder_factory_); + send_streams_[ssrc] = stream; + + if (rtcp_receiver_report_ssrc_ == kDefaultRtcpReceiverReportSsrc) { + rtcp_receiver_report_ssrc_ = ssrc; + } + if (default_send_ssrc_ == 0) { + default_send_ssrc_ = ssrc; + } + if (sending_) { + stream->Start(); + } + + return true; +} + +bool WebRtcVideoChannel2::RemoveSendStream(uint32 ssrc) { + LOG(LS_INFO) << "RemoveSendStream: " << ssrc; + + if (ssrc == 0) { + if (default_send_ssrc_ == 0) { + LOG(LS_ERROR) << "No default send stream active."; + return false; + } + + LOG(LS_VERBOSE) << "Removing default stream: " << default_send_ssrc_; + ssrc = default_send_ssrc_; + } + + std::map::iterator it = + send_streams_.find(ssrc); + if (it == send_streams_.end()) { + return false; + } + + delete it->second; + send_streams_.erase(it); + + if (ssrc == default_send_ssrc_) { + default_send_ssrc_ = 0; + } + + return true; +} + +bool WebRtcVideoChannel2::AddRecvStream(const StreamParams& sp) { + LOG(LS_INFO) << "AddRecvStream: " << sp.ToString(); + assert(sp.ssrcs.size() > 0); + + uint32 ssrc = sp.first_ssrc(); + assert(ssrc != 0); // TODO(pbos): Is this ever valid? + if (default_recv_ssrc_ == 0) { + default_recv_ssrc_ = ssrc; + } + + // TODO(pbos): Check if any of the SSRCs overlap. + if (receive_streams_.find(ssrc) != receive_streams_.end()) { + LOG(LS_ERROR) << "Receive stream for SSRC " << ssrc << "already exists."; + return false; + } + + webrtc::VideoReceiveStream::Config config = call_->GetDefaultReceiveConfig(); + config.rtp.remote_ssrc = ssrc; + config.rtp.local_ssrc = rtcp_receiver_report_ssrc_; + + if (IsNackEnabled(recv_codecs_.begin()->codec)) { + config.rtp.nack.rtp_history_ms = kNackHistoryMs; + } + config.rtp.remb = true; + config.rtp.extensions = recv_rtp_extensions_; + // TODO(pbos): This protection is against setting the same local ssrc as + // remote which is not permitted by the lower-level API. RTCP requires a + // corresponding sender SSRC. Figure out what to do when we don't have + // (receive-only) or know a good local SSRC. + if (config.rtp.remote_ssrc == config.rtp.local_ssrc) { + if (config.rtp.local_ssrc != kDefaultRtcpReceiverReportSsrc) { + config.rtp.local_ssrc = kDefaultRtcpReceiverReportSsrc; + } else { + config.rtp.local_ssrc = kDefaultRtcpReceiverReportSsrc + 1; + } + } + bool default_renderer_used = false; + for (std::map::iterator it = renderers_.begin(); + it != renderers_.end(); + ++it) { + if (it->second->GetRenderer() == default_renderer_) { + default_renderer_used = true; + break; + } + } + + assert(renderers_[ssrc] == NULL); + renderers_[ssrc] = new WebRtcVideoRenderer(); + if (!default_renderer_used) { + renderers_[ssrc]->SetRenderer(default_renderer_); + } + config.renderer = renderers_[ssrc]; + + { + // TODO(pbos): Base receive codecs off recv_codecs_ and set up using a + // DecoderFactory similar to send side. Pending webrtc:2854. + // Also set up default codecs if there's nothing in recv_codecs_. + webrtc::VideoCodec codec; + memset(&codec, 0, sizeof(codec)); + + codec.plType = kDefaultVideoCodecPref.payload_type; + strcpy(codec.plName, kDefaultVideoCodecPref.name); + codec.codecType = webrtc::kVideoCodecVP8; + codec.codecSpecific.VP8.resilience = webrtc::kResilientStream; + codec.codecSpecific.VP8.numberOfTemporalLayers = 1; + codec.codecSpecific.VP8.denoisingOn = true; + codec.codecSpecific.VP8.errorConcealmentOn = false; + codec.codecSpecific.VP8.automaticResizeOn = false; + codec.codecSpecific.VP8.frameDroppingOn = true; + codec.codecSpecific.VP8.keyFrameInterval = 3000; + // Bitrates don't matter and are ignored for the receiver. This is put in to + // have the current underlying implementation accept the VideoCodec. + codec.minBitrate = codec.startBitrate = codec.maxBitrate = 300; + config.codecs.push_back(codec); + for (size_t i = 0; i < recv_codecs_.size(); ++i) { + if (recv_codecs_[i].codec.id == codec.plType) { + config.rtp.fec = recv_codecs_[i].fec; + uint32 rtx_ssrc; + if (recv_codecs_[i].rtx_payload_type != -1 && + sp.GetFidSsrc(ssrc, &rtx_ssrc)) { + config.rtp.rtx[codec.plType].ssrc = rtx_ssrc; + config.rtp.rtx[codec.plType].payload_type = + recv_codecs_[i].rtx_payload_type; + } + break; + } + } + } + + webrtc::VideoReceiveStream* receive_stream = + call_->CreateVideoReceiveStream(config); + assert(receive_stream != NULL); + + receive_streams_[ssrc] = receive_stream; + receive_stream->Start(); + + return true; +} + +bool WebRtcVideoChannel2::RemoveRecvStream(uint32 ssrc) { + LOG(LS_INFO) << "RemoveRecvStream: " << ssrc; + if (ssrc == 0) { + ssrc = default_recv_ssrc_; + } + + std::map::iterator stream = + receive_streams_.find(ssrc); + if (stream == receive_streams_.end()) { + LOG(LS_ERROR) << "Stream not found for ssrc: " << ssrc; + return false; + } + call_->DestroyVideoReceiveStream(stream->second); + receive_streams_.erase(stream); + + std::map::iterator renderer = + renderers_.find(ssrc); + assert(renderer != renderers_.end()); + delete renderer->second; + renderers_.erase(renderer); + + if (ssrc == default_recv_ssrc_) { + default_recv_ssrc_ = 0; + } + + return true; +} + +bool WebRtcVideoChannel2::SetRenderer(uint32 ssrc, VideoRenderer* renderer) { + LOG(LS_INFO) << "SetRenderer: ssrc:" << ssrc << " " + << (renderer ? "(ptr)" : "NULL"); + bool is_default_ssrc = false; + if (ssrc == 0) { + is_default_ssrc = true; + ssrc = default_recv_ssrc_; + default_renderer_ = renderer; + } + + std::map::iterator it = renderers_.find(ssrc); + if (it == renderers_.end()) { + return is_default_ssrc; + } + + it->second->SetRenderer(renderer); + return true; +} + +bool WebRtcVideoChannel2::GetRenderer(uint32 ssrc, VideoRenderer** renderer) { + if (ssrc == 0) { + if (default_renderer_ == NULL) { + return false; + } + *renderer = default_renderer_; + return true; + } + + std::map::iterator it = renderers_.find(ssrc); + if (it == renderers_.end()) { + return false; + } + *renderer = it->second->GetRenderer(); + return true; +} + +bool WebRtcVideoChannel2::GetStats(const StatsOptions& options, + VideoMediaInfo* info) { + // TODO(pbos): Implement. + return true; +} + +bool WebRtcVideoChannel2::SetCapturer(uint32 ssrc, VideoCapturer* capturer) { + LOG(LS_INFO) << "SetCapturer: " << ssrc << " -> " + << (capturer != NULL ? "(capturer)" : "NULL"); + assert(ssrc != 0); + if (send_streams_.find(ssrc) == send_streams_.end()) { + LOG(LS_ERROR) << "No sending stream on ssrc " << ssrc; + return false; + } + return send_streams_[ssrc]->SetCapturer(capturer); +} + +bool WebRtcVideoChannel2::SendIntraFrame() { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SendIntraFrame()."; + return true; +} + +bool WebRtcVideoChannel2::RequestIntraFrame() { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SendIntraFrame()."; + return true; +} + +void WebRtcVideoChannel2::OnPacketReceived( + talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) { + const webrtc::PacketReceiver::DeliveryStatus delivery_result = + call_->Receiver()->DeliverPacket( + reinterpret_cast(packet->data()), packet->length()); + switch (delivery_result) { + case webrtc::PacketReceiver::DELIVERY_OK: + return; + case webrtc::PacketReceiver::DELIVERY_PACKET_ERROR: + return; + case webrtc::PacketReceiver::DELIVERY_UNKNOWN_SSRC: + break; + } + + uint32 ssrc = 0; + if (default_recv_ssrc_ != 0) { // Already one default stream. + LOG(LS_WARNING) << "Unknown SSRC, but default receive stream already set."; + return; + } + + if (!GetRtpSsrc(packet->data(), packet->length(), &ssrc)) { + return; + } + + StreamParams sp; + sp.ssrcs.push_back(ssrc); + LOG(LS_INFO) << "Creating default receive stream for SSRC=" << ssrc << "."; + AddRecvStream(sp); + + if (call_->Receiver()->DeliverPacket( + reinterpret_cast(packet->data()), packet->length()) != + webrtc::PacketReceiver::DELIVERY_OK) { + LOG(LS_WARNING) << "Failed to deliver RTP packet after creating default " + "receiver."; + return; + } +} + +void WebRtcVideoChannel2::OnRtcpReceived( + talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) { + if (call_->Receiver()->DeliverPacket( + reinterpret_cast(packet->data()), packet->length()) != + webrtc::PacketReceiver::DELIVERY_OK) { + LOG(LS_WARNING) << "Failed to deliver RTCP packet."; + } +} + +void WebRtcVideoChannel2::OnReadyToSend(bool ready) { + LOG(LS_VERBOSE) << "OnReadySend: " << (ready ? "Ready." : "Not ready."); +} + +bool WebRtcVideoChannel2::MuteStream(uint32 ssrc, bool mute) { + LOG(LS_VERBOSE) << "MuteStream: " << ssrc << " -> " + << (mute ? "mute" : "unmute"); + assert(ssrc != 0); + if (send_streams_.find(ssrc) == send_streams_.end()) { + LOG(LS_ERROR) << "No sending stream on ssrc " << ssrc; + return false; + } + return send_streams_[ssrc]->MuteStream(mute); +} + +bool WebRtcVideoChannel2::SetRecvRtpHeaderExtensions( + const std::vector& extensions) { + LOG(LS_INFO) << "SetRecvRtpHeaderExtensions: " + << RtpExtensionsToString(extensions); + std::vector webrtc_extensions; + for (size_t i = 0; i < extensions.size(); ++i) { + // TODO(pbos): Make sure we don't pass unsupported extensions! + webrtc::RtpExtension webrtc_extension(extensions[i].uri.c_str(), + extensions[i].id); + webrtc_extensions.push_back(webrtc_extension); + } + recv_rtp_extensions_ = webrtc_extensions; + return true; +} + +bool WebRtcVideoChannel2::SetSendRtpHeaderExtensions( + const std::vector& extensions) { + LOG(LS_INFO) << "SetSendRtpHeaderExtensions: " + << RtpExtensionsToString(extensions); + std::vector webrtc_extensions; + for (size_t i = 0; i < extensions.size(); ++i) { + // TODO(pbos): Make sure we don't pass unsupported extensions! + webrtc::RtpExtension webrtc_extension(extensions[i].uri.c_str(), + extensions[i].id); + webrtc_extensions.push_back(webrtc_extension); + } + send_rtp_extensions_ = webrtc_extensions; + return true; +} + +bool WebRtcVideoChannel2::SetStartSendBandwidth(int bps) { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SetStartSendBandwidth: " << bps; + return true; +} + +bool WebRtcVideoChannel2::SetMaxSendBandwidth(int bps) { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SetMaxSendBandwidth: " << bps; + return true; +} + +bool WebRtcVideoChannel2::SetOptions(const VideoOptions& options) { + LOG(LS_VERBOSE) << "SetOptions: " << options.ToString(); + options_.SetAll(options); + return true; +} + +void WebRtcVideoChannel2::SetInterface(NetworkInterface* iface) { + MediaChannel::SetInterface(iface); + // Set the RTP recv/send buffer to a bigger size + MediaChannel::SetOption(NetworkInterface::ST_RTP, + talk_base::Socket::OPT_RCVBUF, + kVideoRtpBufferSize); + + // TODO(sriniv): Remove or re-enable this. + // As part of b/8030474, send-buffer is size now controlled through + // portallocator flags. + // network_interface_->SetOption(NetworkInterface::ST_RTP, + // talk_base::Socket::OPT_SNDBUF, + // kVideoRtpBufferSize); +} + +void WebRtcVideoChannel2::UpdateAspectRatio(int ratio_w, int ratio_h) { + // TODO(pbos): Implement. +} + +void WebRtcVideoChannel2::OnMessage(talk_base::Message* msg) { + // Ignored. +} + +bool WebRtcVideoChannel2::SendRtp(const uint8_t* data, size_t len) { + talk_base::Buffer packet(data, len, kMaxRtpPacketLen); + return MediaChannel::SendPacket(&packet); +} + +bool WebRtcVideoChannel2::SendRtcp(const uint8_t* data, size_t len) { + talk_base::Buffer packet(data, len, kMaxRtpPacketLen); + return MediaChannel::SendRtcp(&packet); +} + +void WebRtcVideoChannel2::StartAllSendStreams() { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + it->second->Start(); + } +} + +void WebRtcVideoChannel2::StopAllSendStreams() { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + it->second->Stop(); + } +} + +void WebRtcVideoChannel2::SetCodecForAllSendStreams( + const WebRtcVideoChannel2::VideoCodecSettings& codec) { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + assert(it->second != NULL); + it->second->SetCodec(options_, codec); + } +} + +WebRtcVideoChannel2::WebRtcVideoSendStream::VideoSendStreamParameters:: + VideoSendStreamParameters( + const webrtc::VideoSendStream::Config& config, + const VideoOptions& options, + const VideoCodec& codec, + const std::vector& video_streams) + : config(config), + options(options), + codec(codec), + video_streams(video_streams) { +} + +WebRtcVideoChannel2::WebRtcVideoSendStream::WebRtcVideoSendStream( + webrtc::Call* call, + const webrtc::VideoSendStream::Config& config, + const VideoOptions& options, + const VideoCodec& codec, + const std::vector& video_streams, + WebRtcVideoEncoderFactory2* encoder_factory) + : call_(call), + parameters_(config, options, codec, video_streams), + encoder_factory_(encoder_factory), + capturer_(NULL), + stream_(NULL), + sending_(false), + muted_(false), + format_(static_cast(video_streams.back().height), + static_cast(video_streams.back().width), + VideoFormat::FpsToInterval(video_streams.back().max_framerate), + FOURCC_I420) { + RecreateWebRtcStream(); +} + +WebRtcVideoChannel2::WebRtcVideoSendStream::~WebRtcVideoSendStream() { + DisconnectCapturer(); + call_->DestroyVideoSendStream(stream_); + delete parameters_.config.encoder_settings.encoder; +} + +static void SetWebRtcFrameToBlack(webrtc::I420VideoFrame* video_frame) { + assert(video_frame != NULL); + memset(video_frame->buffer(webrtc::kYPlane), + 16, + video_frame->allocated_size(webrtc::kYPlane)); + memset(video_frame->buffer(webrtc::kUPlane), + 128, + video_frame->allocated_size(webrtc::kUPlane)); + memset(video_frame->buffer(webrtc::kVPlane), + 128, + video_frame->allocated_size(webrtc::kVPlane)); +} + +static void CreateBlackFrame(webrtc::I420VideoFrame* video_frame, + int width, + int height) { + video_frame->CreateEmptyFrame( + width, height, width, (width + 1) / 2, (width + 1) / 2); + SetWebRtcFrameToBlack(video_frame); +} + +static void ConvertToI420VideoFrame(const VideoFrame& frame, + webrtc::I420VideoFrame* i420_frame) { + i420_frame->CreateFrame( + static_cast(frame.GetYPitch() * frame.GetHeight()), + frame.GetYPlane(), + static_cast(frame.GetUPitch() * ((frame.GetHeight() + 1) / 2)), + frame.GetUPlane(), + static_cast(frame.GetVPitch() * ((frame.GetHeight() + 1) / 2)), + frame.GetVPlane(), + static_cast(frame.GetWidth()), + static_cast(frame.GetHeight()), + static_cast(frame.GetYPitch()), + static_cast(frame.GetUPitch()), + static_cast(frame.GetVPitch())); +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::InputFrame( + VideoCapturer* capturer, + const VideoFrame* frame) { + LOG(LS_VERBOSE) << "InputFrame: " << frame->GetWidth() << "x" + << frame->GetHeight(); + bool is_screencast = capturer->IsScreencast(); + // Lock before copying, can be called concurrently when swapping input source. + talk_base::CritScope frame_cs(&frame_lock_); + if (!muted_) { + ConvertToI420VideoFrame(*frame, &video_frame_); + } else { + // Create a tiny black frame to transmit instead. + CreateBlackFrame(&video_frame_, 1, 1); + is_screencast = false; + } + talk_base::CritScope cs(&lock_); + if (format_.width == 0) { // Dropping frames. + assert(format_.height == 0); + LOG(LS_VERBOSE) << "VideoFormat 0x0 set, Dropping frame."; + return; + } + // Reconfigure codec if necessary. + if (is_screencast) { + SetDimensions(video_frame_.width(), video_frame_.height()); + } + LOG(LS_VERBOSE) << "SwapFrame: " << video_frame_.width() << "x" + << video_frame_.height() << " -> (codec) " + << parameters_.video_streams.back().width << "x" + << parameters_.video_streams.back().height; + stream_->Input()->SwapFrame(&video_frame_); +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetCapturer( + VideoCapturer* capturer) { + if (!DisconnectCapturer() && capturer == NULL) { + return false; + } + + { + talk_base::CritScope cs(&lock_); + + if (capturer == NULL) { + LOG(LS_VERBOSE) << "Disabling capturer, sending black frame."; + webrtc::I420VideoFrame black_frame; + + int width = format_.width; + int height = format_.height; + int half_width = (width + 1) / 2; + black_frame.CreateEmptyFrame( + width, height, width, half_width, half_width); + SetWebRtcFrameToBlack(&black_frame); + SetDimensions(width, height); + stream_->Input()->SwapFrame(&black_frame); + + capturer_ = NULL; + return true; + } + + capturer_ = capturer; + } + // Lock cannot be held while connecting the capturer to prevent lock-order + // violations. + capturer->SignalVideoFrame.connect(this, &WebRtcVideoSendStream::InputFrame); + return true; +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetVideoFormat( + const VideoFormat& format) { + if ((format.width == 0 || format.height == 0) && + format.width != format.height) { + LOG(LS_ERROR) << "Can't set VideoFormat, width or height is zero (but not " + "both, 0x0 drops frames)."; + return false; + } + + talk_base::CritScope cs(&lock_); + if (format.width == 0 && format.height == 0) { + LOG(LS_INFO) + << "0x0 resolution selected. Captured frames will be dropped for ssrc: " + << parameters_.config.rtp.ssrcs[0] << "."; + } else { + // TODO(pbos): Fix me, this only affects the last stream! + parameters_.video_streams.back().max_framerate = + VideoFormat::IntervalToFps(format.interval); + SetDimensions(format.width, format.height); + } + + format_ = format; + return true; +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::MuteStream(bool mute) { + talk_base::CritScope cs(&lock_); + bool was_muted = muted_; + muted_ = mute; + return was_muted != mute; +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::DisconnectCapturer() { + talk_base::CritScope cs(&lock_); + if (capturer_ == NULL) { + return false; + } + capturer_->SignalVideoFrame.disconnect(this); + capturer_ = NULL; + return true; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::SetCodec( + const VideoOptions& options, + const VideoCodecSettings& codec) { + talk_base::CritScope cs(&lock_); + + std::vector video_streams = + encoder_factory_->CreateVideoStreams( + codec.codec, options, parameters_.video_streams.size()); + if (video_streams.empty()) { + return; + } + parameters_.video_streams = video_streams; + format_ = VideoFormat(codec.codec.width, + codec.codec.height, + VideoFormat::FpsToInterval(30), + FOURCC_I420); + + webrtc::VideoEncoder* old_encoder = + parameters_.config.encoder_settings.encoder; + parameters_.config.encoder_settings.encoder = + encoder_factory_->CreateVideoEncoder(codec.codec, options); + parameters_.config.rtp.fec = codec.fec; + // TODO(pbos): Should changing RTX payload type be allowed? + parameters_.codec = codec.codec; + parameters_.options = options; + RecreateWebRtcStream(); + delete old_encoder; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::SetDimensions(int width, + int height) { + assert(!parameters_.video_streams.empty()); + LOG(LS_VERBOSE) << "SetDimensions: " << width << "x" << height; + if (parameters_.video_streams.back().width == width && + parameters_.video_streams.back().height == height) { + return; + } + + // TODO(pbos): Fix me, this only affects the last stream! + parameters_.video_streams.back().width = width; + parameters_.video_streams.back().height = height; + + // TODO(pbos): Wire up encoder_parameters, webrtc:3424. + if (!stream_->ReconfigureVideoEncoder(parameters_.video_streams, NULL)) { + LOG(LS_WARNING) << "Failed to reconfigure video encoder for dimensions: " + << width << "x" << height; + return; + } +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::Start() { + talk_base::CritScope cs(&lock_); + stream_->Start(); + sending_ = true; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::Stop() { + talk_base::CritScope cs(&lock_); + stream_->Stop(); + sending_ = false; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::RecreateWebRtcStream() { + if (stream_ != NULL) { + call_->DestroyVideoSendStream(stream_); + } + + // TODO(pbos): Wire up encoder_parameters, webrtc:3424. + stream_ = call_->CreateVideoSendStream( + parameters_.config, parameters_.video_streams, NULL); + if (sending_) { + stream_->Start(); + } +} + +WebRtcVideoChannel2::VideoCodecSettings::VideoCodecSettings() + : rtx_payload_type(-1) {} + +std::vector +WebRtcVideoChannel2::MapCodecs(const std::vector& codecs) { + assert(!codecs.empty()); + + std::vector video_codecs; + std::map payload_used; + std::map payload_codec_type; + std::map rtx_mapping; // video payload type -> rtx payload type. + + webrtc::FecConfig fec_settings; + + for (size_t i = 0; i < codecs.size(); ++i) { + const VideoCodec& in_codec = codecs[i]; + int payload_type = in_codec.id; + + if (payload_used[payload_type]) { + LOG(LS_ERROR) << "Payload type already registered: " + << in_codec.ToString(); + return std::vector(); + } + payload_used[payload_type] = true; + payload_codec_type[payload_type] = in_codec.GetCodecType(); + + switch (in_codec.GetCodecType()) { + case VideoCodec::CODEC_RED: { + // RED payload type, should not have duplicates. + assert(fec_settings.red_payload_type == -1); + fec_settings.red_payload_type = in_codec.id; + continue; + } + + case VideoCodec::CODEC_ULPFEC: { + // ULPFEC payload type, should not have duplicates. + assert(fec_settings.ulpfec_payload_type == -1); + fec_settings.ulpfec_payload_type = in_codec.id; + continue; + } + + case VideoCodec::CODEC_RTX: { + int associated_payload_type; + if (!in_codec.GetParam(kCodecParamAssociatedPayloadType, + &associated_payload_type)) { + LOG(LS_ERROR) << "RTX codec without associated payload type: " + << in_codec.ToString(); + return std::vector(); + } + rtx_mapping[associated_payload_type] = in_codec.id; + continue; + } + + case VideoCodec::CODEC_VIDEO: + break; + } + + video_codecs.push_back(VideoCodecSettings()); + video_codecs.back().codec = in_codec; + } + + // One of these codecs should have been a video codec. Only having FEC + // parameters into this code is a logic error. + assert(!video_codecs.empty()); + + for (std::map::const_iterator it = rtx_mapping.begin(); + it != rtx_mapping.end(); + ++it) { + if (!payload_used[it->first]) { + LOG(LS_ERROR) << "RTX mapped to payload not in codec list."; + return std::vector(); + } + if (payload_codec_type[it->first] != VideoCodec::CODEC_VIDEO) { + LOG(LS_ERROR) << "RTX not mapped to regular video codec."; + return std::vector(); + } + } + + // TODO(pbos): Write tests that figure out that I have not verified that RTX + // codecs aren't mapped to bogus payloads. + for (size_t i = 0; i < video_codecs.size(); ++i) { + video_codecs[i].fec = fec_settings; + if (rtx_mapping[video_codecs[i].codec.id] != 0) { + video_codecs[i].rtx_payload_type = rtx_mapping[video_codecs[i].codec.id]; + } + } + + return video_codecs; +} + +std::vector +WebRtcVideoChannel2::FilterSupportedCodecs( + const std::vector& mapped_codecs) { + std::vector supported_codecs; + for (size_t i = 0; i < mapped_codecs.size(); ++i) { + if (encoder_factory_->SupportsCodec(mapped_codecs[i].codec)) { + supported_codecs.push_back(mapped_codecs[i]); + } + } + return supported_codecs; +} + +} // namespace cricket + +#endif // HAVE_WEBRTC_VIDEO diff --git a/talk/media/webrtc/webrtcvideoengine2.h b/talk/media/webrtc/webrtcvideoengine2.h new file mode 100644 index 000000000..81466eb65 --- /dev/null +++ b/talk/media/webrtc/webrtcvideoengine2.h @@ -0,0 +1,367 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_H_ +#define TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_H_ + +#include +#include +#include + +#include "talk/base/cpumonitor.h" +#include "talk/base/scoped_ptr.h" +#include "talk/media/base/mediaengine.h" +#include "talk/media/webrtc/webrtcvideochannelfactory.h" +#include "webrtc/common_video/interface/i420_video_frame.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" +#include "webrtc/transport.h" +#include "webrtc/video_renderer.h" +#include "webrtc/video_send_stream.h" + +namespace webrtc { +class Call; +class VideoCaptureModule; +class VideoDecoder; +class VideoEncoder; +class VideoRender; +class VideoSendStreamInput; +class VideoReceiveStream; +} + +namespace talk_base { +class CpuMonitor; +class Thread; +} // namespace talk_base + +namespace cricket { + +class VideoCapturer; +class VideoFrame; +class VideoProcessor; +class VideoRenderer; +class VoiceMediaChannel; +class WebRtcVideoChannel2; +class WebRtcDecoderObserver; +class WebRtcEncoderObserver; +class WebRtcLocalStreamInfo; +class WebRtcRenderAdapter; +class WebRtcVideoChannelRecvInfo; +class WebRtcVideoChannelSendInfo; +class WebRtcVideoDecoderFactory; +class WebRtcVoiceEngine; + +struct CapturedFrame; +struct Device; + +class WebRtcVideoEngine2; +class WebRtcVideoChannel2; + +class WebRtcVideoEncoderFactory2 { + public: + virtual ~WebRtcVideoEncoderFactory2(); + virtual std::vector CreateVideoStreams( + const VideoCodec& codec, + const VideoOptions& options, + size_t num_streams); + + virtual webrtc::VideoEncoder* CreateVideoEncoder( + const VideoCodec& codec, + const VideoOptions& options); + + virtual bool SupportsCodec(const cricket::VideoCodec& codec); +}; + +// WebRtcVideoEngine2 is used for the new native WebRTC Video API (webrtc:1667). +class WebRtcVideoEngine2 : public sigslot::has_slots<> { + public: + // Creates the WebRtcVideoEngine2 with internal VideoCaptureModule. + WebRtcVideoEngine2(); + // Custom WebRtcVideoChannelFactory for testing purposes. + explicit WebRtcVideoEngine2(WebRtcVideoChannelFactory* channel_factory); + ~WebRtcVideoEngine2(); + + // Basic video engine implementation. + bool Init(talk_base::Thread* worker_thread); + void Terminate(); + + int GetCapabilities(); + bool SetOptions(const VideoOptions& options); + bool SetDefaultEncoderConfig(const VideoEncoderConfig& config); + VideoEncoderConfig GetDefaultEncoderConfig() const; + + WebRtcVideoChannel2* CreateChannel(VoiceMediaChannel* voice_channel); + + const std::vector& codecs() const; + const std::vector& rtp_header_extensions() const; + void SetLogging(int min_sev, const char* filter); + + bool EnableTimedRender(); + // No-op, never used. + bool SetLocalRenderer(VideoRenderer* renderer); + // This is currently ignored. + sigslot::repeater2 SignalCaptureStateChange; + + // Set the VoiceEngine for A/V sync. This can only be called before Init. + bool SetVoiceEngine(WebRtcVoiceEngine* voice_engine); + + // Functions called by WebRtcVideoChannel2. + const VideoFormat& default_codec_format() const { + return default_codec_format_; + } + + bool FindCodec(const VideoCodec& in); + bool CanSendCodec(const VideoCodec& in, + const VideoCodec& current, + VideoCodec* out); + // Check whether the supplied trace should be ignored. + bool ShouldIgnoreTrace(const std::string& trace); + + VideoFormat GetStartCaptureFormat() const { return default_codec_format_; } + + talk_base::CpuMonitor* cpu_monitor() { return cpu_monitor_.get(); } + + virtual WebRtcVideoEncoderFactory2* GetVideoEncoderFactory(); + + private: + void Construct(WebRtcVideoChannelFactory* channel_factory, + WebRtcVoiceEngine* voice_engine, + talk_base::CpuMonitor* cpu_monitor); + + talk_base::Thread* worker_thread_; + WebRtcVoiceEngine* voice_engine_; + std::vector video_codecs_; + std::vector rtp_header_extensions_; + VideoFormat default_codec_format_; + + bool initialized_; + + bool capture_started_; + + // Critical section to protect the media processor register/unregister + // while processing a frame + talk_base::CriticalSection signal_media_critical_; + + talk_base::scoped_ptr cpu_monitor_; + WebRtcVideoChannelFactory* channel_factory_; + WebRtcVideoEncoderFactory2 default_video_encoder_factory_; +}; + +// Adapter between webrtc::VideoRenderer and cricket::VideoRenderer. +// The webrtc::VideoRenderer is set once, whereas the cricket::VideoRenderer can +// be set after initialization. This adapter will also convert the incoming +// webrtc::I420VideoFrame to a frame type that cricket::VideoRenderer can +// render. +class WebRtcVideoRenderer : public webrtc::VideoRenderer { + public: + WebRtcVideoRenderer(); + + virtual void RenderFrame(const webrtc::I420VideoFrame& frame, + int time_to_render_ms) OVERRIDE; + + void SetRenderer(cricket::VideoRenderer* renderer); + cricket::VideoRenderer* GetRenderer(); + + private: + void SetSize(int width, int height); + int last_width_; + int last_height_; + talk_base::CriticalSection lock_; + cricket::VideoRenderer* renderer_ GUARDED_BY(lock_); +}; + +class WebRtcVideoChannel2 : public talk_base::MessageHandler, + public VideoMediaChannel, + public webrtc::newapi::Transport { + public: + WebRtcVideoChannel2(WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel, + WebRtcVideoEncoderFactory2* encoder_factory); + // For testing purposes insert a pre-constructed call to verify that + // WebRtcVideoChannel2 calls the correct corresponding methods. + WebRtcVideoChannel2(webrtc::Call* call, + WebRtcVideoEngine2* engine, + WebRtcVideoEncoderFactory2* encoder_factory); + ~WebRtcVideoChannel2(); + bool Init(); + + // VideoMediaChannel implementation + virtual bool SetRecvCodecs(const std::vector& codecs) OVERRIDE; + virtual bool SetSendCodecs(const std::vector& codecs) OVERRIDE; + virtual bool GetSendCodec(VideoCodec* send_codec) OVERRIDE; + virtual bool SetSendStreamFormat(uint32 ssrc, + const VideoFormat& format) OVERRIDE; + virtual bool SetRender(bool render) OVERRIDE; + virtual bool SetSend(bool send) OVERRIDE; + + virtual bool AddSendStream(const StreamParams& sp) OVERRIDE; + virtual bool RemoveSendStream(uint32 ssrc) OVERRIDE; + virtual bool AddRecvStream(const StreamParams& sp) OVERRIDE; + virtual bool RemoveRecvStream(uint32 ssrc) OVERRIDE; + virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) OVERRIDE; + virtual bool GetStats(const StatsOptions& options, + VideoMediaInfo* info) OVERRIDE; + virtual bool SetCapturer(uint32 ssrc, VideoCapturer* capturer) OVERRIDE; + virtual bool SendIntraFrame() OVERRIDE; + virtual bool RequestIntraFrame() OVERRIDE; + + virtual void OnPacketReceived(talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) + OVERRIDE; + virtual void OnRtcpReceived(talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) + OVERRIDE; + virtual void OnReadyToSend(bool ready) OVERRIDE; + virtual bool MuteStream(uint32 ssrc, bool mute) OVERRIDE; + + // Set send/receive RTP header extensions. This must be done before creating + // streams as it only has effect on future streams. + virtual bool SetRecvRtpHeaderExtensions( + const std::vector& extensions) OVERRIDE; + virtual bool SetSendRtpHeaderExtensions( + const std::vector& extensions) OVERRIDE; + virtual bool SetStartSendBandwidth(int bps) OVERRIDE; + virtual bool SetMaxSendBandwidth(int bps) OVERRIDE; + virtual bool SetOptions(const VideoOptions& options) OVERRIDE; + virtual bool GetOptions(VideoOptions* options) const OVERRIDE { + *options = options_; + return true; + } + virtual void SetInterface(NetworkInterface* iface) OVERRIDE; + virtual void UpdateAspectRatio(int ratio_w, int ratio_h) OVERRIDE; + + virtual void OnMessage(talk_base::Message* msg) OVERRIDE; + + // Implemented for VideoMediaChannelTest. + bool sending() const { return sending_; } + uint32 GetDefaultChannelSsrc() { return default_send_ssrc_; } + bool GetRenderer(uint32 ssrc, VideoRenderer** renderer); + + private: + struct VideoCodecSettings { + VideoCodecSettings(); + + VideoCodec codec; + webrtc::FecConfig fec; + int rtx_payload_type; + }; + + class WebRtcVideoSendStream : public sigslot::has_slots<> { + public: + WebRtcVideoSendStream(webrtc::Call* call, + const webrtc::VideoSendStream::Config& config, + const VideoOptions& options, + const VideoCodec& codec, + const std::vector& video_streams, + WebRtcVideoEncoderFactory2* encoder_factory); + ~WebRtcVideoSendStream(); + void SetCodec(const VideoOptions& options, const VideoCodecSettings& codec); + + void InputFrame(VideoCapturer* capturer, const VideoFrame* frame); + bool SetCapturer(VideoCapturer* capturer); + bool SetVideoFormat(const VideoFormat& format); + bool MuteStream(bool mute); + bool DisconnectCapturer(); + + void Start(); + void Stop(); + + private: + // Parameters needed to reconstruct the underlying stream. + // webrtc::VideoSendStream doesn't support setting a lot of options on the + // fly, so when those need to be changed we tear down and reconstruct with + // similar parameters depending on which options changed etc. + struct VideoSendStreamParameters { + VideoSendStreamParameters( + const webrtc::VideoSendStream::Config& config, + const VideoOptions& options, + const VideoCodec& codec, + const std::vector& video_streams); + webrtc::VideoSendStream::Config config; + VideoOptions options; + VideoCodec codec; + // Sent resolutions + bitrates etc. by the underlying VideoSendStream, + // typically changes when setting a new resolution or reconfiguring + // bitrates. + std::vector video_streams; + }; + + void RecreateWebRtcStream(); + void SetDimensions(int width, int height); + + webrtc::Call* const call_; + WebRtcVideoEncoderFactory2* const encoder_factory_; + + talk_base::CriticalSection lock_; + webrtc::VideoSendStream* stream_ GUARDED_BY(lock_); + VideoSendStreamParameters parameters_ GUARDED_BY(lock_); + + VideoCapturer* capturer_ GUARDED_BY(lock_); + bool sending_ GUARDED_BY(lock_); + bool muted_ GUARDED_BY(lock_); + VideoFormat format_ GUARDED_BY(lock_); + + talk_base::CriticalSection frame_lock_; + webrtc::I420VideoFrame video_frame_ GUARDED_BY(frame_lock_); + }; + + void Construct(webrtc::Call* call, WebRtcVideoEngine2* engine); + + virtual bool SendRtp(const uint8_t* data, size_t len) OVERRIDE; + virtual bool SendRtcp(const uint8_t* data, size_t len) OVERRIDE; + + void StartAllSendStreams(); + void StopAllSendStreams(); + void SetCodecForAllSendStreams(const VideoCodecSettings& codec); + static std::vector MapCodecs( + const std::vector& codecs); + std::vector FilterSupportedCodecs( + const std::vector& mapped_codecs); + + uint32_t rtcp_receiver_report_ssrc_; + bool sending_; + talk_base::scoped_ptr call_; + std::map renderers_; + VideoRenderer* default_renderer_; + uint32_t default_send_ssrc_; + uint32_t default_recv_ssrc_; + + // Using primary-ssrc (first ssrc) as key. + std::map send_streams_; + std::map receive_streams_; + + Settable send_codec_; + std::vector send_rtp_extensions_; + + WebRtcVideoEncoderFactory2* const encoder_factory_; + std::vector recv_codecs_; + std::vector recv_rtp_extensions_; + VideoOptions options_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_H_ diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.cc b/talk/media/webrtc/webrtcvideoengine2_unittest.cc new file mode 100644 index 000000000..688630023 --- /dev/null +++ b/talk/media/webrtc/webrtcvideoengine2_unittest.cc @@ -0,0 +1,1367 @@ +/* + * libjingle + * Copyright 2004 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "talk/base/gunit.h" +#include "talk/media/base/testutils.h" +#include "talk/media/base/videoengine_unittest.h" +#include "talk/media/webrtc/webrtcvideoengine2.h" +#include "talk/media/webrtc/webrtcvideoengine2_unittest.h" +#include "talk/media/webrtc/webrtcvideochannelfactory.h" + +namespace { +static const cricket::VideoCodec kVp8Codec720p(100, "VP8", 1280, 720, 30, 0); +static const cricket::VideoCodec kVp8Codec360p(100, "VP8", 640, 360, 30, 0); +static const cricket::VideoCodec kVp8Codec270p(100, "VP8", 480, 270, 30, 0); +static const cricket::VideoCodec kVp8Codec180p(100, "VP8", 320, 180, 30, 0); + +static const cricket::VideoCodec kVp8Codec(100, "VP8", 640, 400, 30, 0); +static const cricket::VideoCodec kVp9Codec(101, "VP9", 640, 400, 30, 0); +static const cricket::VideoCodec kRedCodec(116, "red", 0, 0, 0, 0); +static const cricket::VideoCodec kUlpfecCodec(117, "ulpfec", 0, 0, 0, 0); + +static const uint32 kSsrcs1[] = {1}; +static const uint32 kRtxSsrcs1[] = {4}; + +void VerifyCodecHasDefaultFeedbackParams(const cricket::VideoCodec& codec) { + EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam( + cricket::kRtcpFbParamNack, cricket::kParamValueEmpty))); + EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam( + cricket::kRtcpFbParamNack, cricket::kRtcpFbNackParamPli))); + EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam( + cricket::kRtcpFbParamRemb, cricket::kParamValueEmpty))); + EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam( + cricket::kRtcpFbParamCcm, cricket::kRtcpFbCcmParamFir))); +} + +} // namespace + +namespace cricket { +FakeVideoSendStream::FakeVideoSendStream( + const webrtc::VideoSendStream::Config& config, + const std::vector& video_streams) + : sending_(false), config_(config), video_streams_(video_streams) { +} + +webrtc::VideoSendStream::Config FakeVideoSendStream::GetConfig() { + return config_; +} + +std::vector FakeVideoSendStream::GetVideoStreams() { + return video_streams_; +} + +bool FakeVideoSendStream::IsSending() { + return sending_; +} + +webrtc::VideoSendStream::Stats FakeVideoSendStream::GetStats() const { + return webrtc::VideoSendStream::Stats(); +} + +bool FakeVideoSendStream::ReconfigureVideoEncoder( + const std::vector& streams, + const void* encoder_specific) { + video_streams_ = streams; + return true; +} + +webrtc::VideoSendStreamInput* FakeVideoSendStream::Input() { + // TODO(pbos): Fix. + return NULL; +} + +void FakeVideoSendStream::Start() { + sending_ = true; +} + +void FakeVideoSendStream::Stop() { + sending_ = false; +} + +FakeVideoReceiveStream::FakeVideoReceiveStream( + const webrtc::VideoReceiveStream::Config& config) + : config_(config), receiving_(false) { +} + +webrtc::VideoReceiveStream::Config FakeVideoReceiveStream::GetConfig() { + return config_; +} + +webrtc::VideoReceiveStream::Stats FakeVideoReceiveStream::GetStats() const { + return webrtc::VideoReceiveStream::Stats(); +} + +void FakeVideoReceiveStream::Start() { + receiving_ = true; +} +void FakeVideoReceiveStream::Stop() { + receiving_ = false; +} +void FakeVideoReceiveStream::GetCurrentReceiveCodec(webrtc::VideoCodec* codec) { +} + +FakeCall::FakeCall() { SetVideoCodecs(GetDefaultVideoCodecs()); } + +FakeCall::~FakeCall() { + EXPECT_EQ(0u, video_send_streams_.size()); + EXPECT_EQ(0u, video_receive_streams_.size()); +} + +void FakeCall::SetVideoCodecs(const std::vector codecs) { + codecs_ = codecs; +} + +std::vector FakeCall::GetVideoSendStreams() { + return video_send_streams_; +} + +std::vector FakeCall::GetVideoReceiveStreams() { + return video_receive_streams_; +} + +webrtc::VideoCodec FakeCall::GetEmptyVideoCodec() { + webrtc::VideoCodec codec; + codec.minBitrate = 300; + codec.startBitrate = 800; + codec.maxBitrate = 1500; + codec.maxFramerate = 10; + codec.width = 640; + codec.height = 480; + codec.qpMax = 56; + + return codec; +} + +webrtc::VideoCodec FakeCall::GetVideoCodecVp8() { + webrtc::VideoCodec vp8_codec = GetEmptyVideoCodec(); + vp8_codec.codecType = webrtc::kVideoCodecVP8; + strcpy(vp8_codec.plName, kVp8Codec.name.c_str()); + vp8_codec.plType = kVp8Codec.id; + + return vp8_codec; +} + +webrtc::VideoCodec FakeCall::GetVideoCodecVp9() { + webrtc::VideoCodec vp9_codec = GetEmptyVideoCodec(); + // TODO(pbos): Add a correct codecType when webrtc has one. + vp9_codec.codecType = webrtc::kVideoCodecVP8; + strcpy(vp9_codec.plName, kVp9Codec.name.c_str()); + vp9_codec.plType = kVp9Codec.id; + + return vp9_codec; +} + +std::vector FakeCall::GetDefaultVideoCodecs() { + std::vector codecs; + codecs.push_back(GetVideoCodecVp8()); + // codecs.push_back(GetVideoCodecVp9()); + + return codecs; +} + +webrtc::VideoSendStream::Config FakeCall::GetDefaultSendConfig() { + webrtc::VideoSendStream::Config config; + // TODO(pbos): Encoder settings. + // config.codec = GetVideoCodecVp8(); + return config; +} + +webrtc::VideoSendStream* FakeCall::CreateVideoSendStream( + const webrtc::VideoSendStream::Config& config, + const std::vector& video_streams, + const void* encoder_settings) { + FakeVideoSendStream* fake_stream = + new FakeVideoSendStream(config, video_streams); + video_send_streams_.push_back(fake_stream); + return fake_stream; +} + +void FakeCall::DestroyVideoSendStream(webrtc::VideoSendStream* send_stream) { + FakeVideoSendStream* fake_stream = + static_cast(send_stream); + for (size_t i = 0; i < video_send_streams_.size(); ++i) { + if (video_send_streams_[i] == fake_stream) { + delete video_send_streams_[i]; + video_send_streams_.erase(video_send_streams_.begin() + i); + return; + } + } + ADD_FAILURE() << "DestroyVideoSendStream called with unknown paramter."; +} + +webrtc::VideoReceiveStream::Config FakeCall::GetDefaultReceiveConfig() { + return webrtc::VideoReceiveStream::Config(); +} + +webrtc::VideoReceiveStream* FakeCall::CreateVideoReceiveStream( + const webrtc::VideoReceiveStream::Config& config) { + video_receive_streams_.push_back(new FakeVideoReceiveStream(config)); + return video_receive_streams_[video_receive_streams_.size() - 1]; +} + +void FakeCall::DestroyVideoReceiveStream( + webrtc::VideoReceiveStream* receive_stream) { + FakeVideoReceiveStream* fake_stream = + static_cast(receive_stream); + for (size_t i = 0; i < video_receive_streams_.size(); ++i) { + if (video_receive_streams_[i] == fake_stream) { + delete video_receive_streams_[i]; + video_receive_streams_.erase(video_receive_streams_.begin() + i); + return; + } + } + ADD_FAILURE() << "DestroyVideoReceiveStream called with unknown paramter."; +} + +webrtc::PacketReceiver* FakeCall::Receiver() { + // TODO(pbos): Fix this. + return NULL; +} + +uint32_t FakeCall::SendBitrateEstimate() { + return 0; +} + +uint32_t FakeCall::ReceiveBitrateEstimate() { + return 0; +} + +FakeWebRtcVideoChannel2::FakeWebRtcVideoChannel2( + FakeCall* call, + WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel) + : WebRtcVideoChannel2(call, engine, engine->GetVideoEncoderFactory()), + fake_call_(call), + voice_channel_(voice_channel) { +} + +FakeWebRtcVideoChannel2::~FakeWebRtcVideoChannel2() { +} + +VoiceMediaChannel* FakeWebRtcVideoChannel2::GetVoiceChannel() { + return voice_channel_; +} +FakeCall* FakeWebRtcVideoChannel2::GetFakeCall() { + return fake_call_; +} + +FakeWebRtcVideoChannel2* FakeWebRtcVideoMediaChannelFactory::GetFakeChannel( + VideoMediaChannel* channel) { + return channel_map_[channel]; +} + +WebRtcVideoChannel2* FakeWebRtcVideoMediaChannelFactory::Create( + WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel) { + FakeWebRtcVideoChannel2* channel = + new FakeWebRtcVideoChannel2(new FakeCall(), engine, voice_channel); + channel_map_[channel] = channel; + return channel; +} + +class WebRtcVideoEngine2Test : public testing::Test { + public: + WebRtcVideoEngine2Test() : engine_(&factory_) { + std::vector engine_codecs = engine_.codecs(); + assert(!engine_codecs.empty()); + bool codec_set = false; + for (size_t i = 0; i < engine_codecs.size(); ++i) { + if (engine_codecs[i].name == "red") { + default_red_codec_ = engine_codecs[i]; + } else if (engine_codecs[i].name == "ulpfec") { + default_ulpfec_codec_ = engine_codecs[i]; + } else if (engine_codecs[i].name == "rtx") { + default_rtx_codec_ = engine_codecs[i]; + } else if (!codec_set) { + default_codec_ = engine_codecs[i]; + codec_set = true; + } + } + + assert(codec_set); + } + + protected: + FakeWebRtcVideoMediaChannelFactory factory_; + WebRtcVideoEngine2 engine_; + VideoCodec default_codec_; + VideoCodec default_red_codec_; + VideoCodec default_ulpfec_codec_; + VideoCodec default_rtx_codec_; +}; + +TEST_F(WebRtcVideoEngine2Test, CreateChannel) { + talk_base::scoped_ptr channel(engine_.CreateChannel(NULL)); + ASSERT_TRUE(channel.get() != NULL) << "Could not create channel."; + EXPECT_TRUE(factory_.GetFakeChannel(channel.get()) != NULL) + << "Channel not created through factory."; +} + +TEST_F(WebRtcVideoEngine2Test, CreateChannelWithVoiceEngine) { + VoiceMediaChannel* voice_channel = reinterpret_cast(0x42); + talk_base::scoped_ptr channel( + engine_.CreateChannel(voice_channel)); + ASSERT_TRUE(channel.get() != NULL) << "Could not create channel."; + + FakeWebRtcVideoChannel2* fake_channel = + factory_.GetFakeChannel(channel.get()); + ASSERT_TRUE(fake_channel != NULL) << "Channel not created through factory."; + + EXPECT_TRUE(fake_channel->GetVoiceChannel() != NULL) + << "VoiceChannel not set."; + EXPECT_EQ(voice_channel, fake_channel->GetVoiceChannel()) + << "Different VoiceChannel set than the provided one."; +} + +TEST_F(WebRtcVideoEngine2Test, FindCodec) { + const std::vector& c = engine_.codecs(); + EXPECT_EQ(4U, c.size()); + + cricket::VideoCodec vp8(104, "VP8", 320, 200, 30, 0); + EXPECT_TRUE(engine_.FindCodec(vp8)); + + cricket::VideoCodec vp8_ci(104, "vp8", 320, 200, 30, 0); + EXPECT_TRUE(engine_.FindCodec(vp8)); + + cricket::VideoCodec vp8_diff_fr_diff_pref(104, "VP8", 320, 200, 50, 50); + EXPECT_TRUE(engine_.FindCodec(vp8_diff_fr_diff_pref)); + + cricket::VideoCodec vp8_diff_id(95, "VP8", 320, 200, 30, 0); + EXPECT_FALSE(engine_.FindCodec(vp8_diff_id)); + vp8_diff_id.id = 97; + EXPECT_TRUE(engine_.FindCodec(vp8_diff_id)); + + cricket::VideoCodec vp8_diff_res(104, "VP8", 320, 111, 30, 0); + EXPECT_FALSE(engine_.FindCodec(vp8_diff_res)); + + // PeerConnection doesn't negotiate the resolution at this point. + // Test that FindCodec can handle the case when width/height is 0. + cricket::VideoCodec vp8_zero_res(104, "VP8", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(vp8_zero_res)); + + cricket::VideoCodec red(101, "RED", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(red)); + + cricket::VideoCodec red_ci(101, "red", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(red)); + + cricket::VideoCodec fec(102, "ULPFEC", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(fec)); + + cricket::VideoCodec fec_ci(102, "ulpfec", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(fec)); + + cricket::VideoCodec rtx(96, "rtx", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(rtx)); +} + +TEST_F(WebRtcVideoEngine2Test, DefaultRtxCodecHasAssociatedPayloadTypeSet) { + std::vector engine_codecs = engine_.codecs(); + for (size_t i = 0; i < engine_codecs.size(); ++i) { + if (engine_codecs[i].name != kRtxCodecName) + continue; + int associated_payload_type; + EXPECT_TRUE(engine_codecs[i].GetParam(kCodecParamAssociatedPayloadType, + &associated_payload_type)); + EXPECT_EQ(default_codec_.id, associated_payload_type); + return; + } + FAIL() << "No RTX codec found among default codecs."; +} + +TEST_F(WebRtcVideoEngine2Test, SupportsTimestampOffsetHeaderExtension) { + std::vector extensions = engine_.rtp_header_extensions(); + ASSERT_FALSE(extensions.empty()); + for (size_t i = 0; i < extensions.size(); ++i) { + if (extensions[i].uri == kRtpTimestampOffsetHeaderExtension) { + EXPECT_EQ(kRtpTimestampOffsetHeaderExtensionDefaultId, extensions[i].id); + return; + } + } + FAIL() << "Timestamp offset extension not in header-extension list."; +} + +TEST_F(WebRtcVideoEngine2Test, SupportsAbsoluteSenderTimeHeaderExtension) { + std::vector extensions = engine_.rtp_header_extensions(); + ASSERT_FALSE(extensions.empty()); + for (size_t i = 0; i < extensions.size(); ++i) { + if (extensions[i].uri == kRtpAbsoluteSenderTimeHeaderExtension) { + EXPECT_EQ(kRtpAbsoluteSenderTimeHeaderExtensionDefaultId, + extensions[i].id); + return; + } + } + FAIL() << "Absolute Sender Time extension not in header-extension list."; +} + +class WebRtcVideoChannel2BaseTest + : public VideoMediaChannelTest { + protected: + virtual cricket::VideoCodec DefaultCodec() OVERRIDE { return kVp8Codec; } + typedef VideoMediaChannelTest Base; +}; + +// TODO(pbos): Fix WebRtcVideoEngine2BaseTest, where we want CheckCoInitialize. +#if 0 +// TODO(juberti): Figure out why ViE is munging the COM refcount. +#ifdef WIN32 +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_CheckCoInitialize) { + Base::CheckCoInitialize(); +} +#endif +#endif + +TEST_F(WebRtcVideoChannel2BaseTest, SetSend) { Base::SetSend(); } + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendWithoutCodecs) { + Base::SetSendWithoutCodecs(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendSetsTransportBufferSizes) { + Base::SetSendSetsTransportBufferSizes(); +} + +// TODO(juberti): Fix this test to tolerate missing stats. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_GetStats) { Base::GetStats(); } + +// TODO(juberti): Fix this test to tolerate missing stats. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_GetStatsMultipleRecvStreams) { + Base::GetStatsMultipleRecvStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_GetStatsMultipleSendStreams) { + Base::GetStatsMultipleSendStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendBandwidth) { + Base::SetSendBandwidth(); +} +TEST_F(WebRtcVideoChannel2BaseTest, SetSendSsrc) { Base::SetSendSsrc(); } +TEST_F(WebRtcVideoChannel2BaseTest, SetSendSsrcAfterSetCodecs) { + Base::SetSendSsrcAfterSetCodecs(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetRenderer) { Base::SetRenderer(); } + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveRecvStreams) { + Base::AddRemoveRecvStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_AddRemoveRecvStreamAndRender) { + Base::AddRemoveRecvStreamAndRender(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveRecvStreamsNoConference) { + Base::AddRemoveRecvStreamsNoConference(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveSendStreams) { + Base::AddRemoveSendStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SimulateConference) { + Base::SimulateConference(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveCapturer) { + Base::AddRemoveCapturer(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, RemoveCapturerWithoutAdd) { + Base::RemoveCapturerWithoutAdd(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveCapturerMultipleSources) { + Base::AddRemoveCapturerMultipleSources(); +} + +// TODO(pbos): Figure out why this fails so often. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_HighAspectHighHeightCapturer) { + Base::HighAspectHighHeightCapturer(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, RejectEmptyStreamParams) { + Base::RejectEmptyStreamParams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AdaptResolution16x10) { + Base::AdaptResolution16x10(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AdaptResolution4x3) { + Base::AdaptResolution4x3(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, MuteStream) { Base::MuteStream(); } + +TEST_F(WebRtcVideoChannel2BaseTest, MultipleSendStreams) { + Base::MultipleSendStreams(); +} + +// TODO(juberti): Restore this test once we support sending 0 fps. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_AdaptDropAllFrames) { + Base::AdaptDropAllFrames(); +} +// TODO(juberti): Understand why we get decode errors on this test. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_AdaptFramerate) { + Base::AdaptFramerate(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendStreamFormat0x0) { + Base::SetSendStreamFormat0x0(); +} + +// TODO(zhurunz): Fix the flakey test. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_SetSendStreamFormat) { + Base::SetSendStreamFormat(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, TwoStreamsSendAndReceive) { + Base::TwoStreamsSendAndReceive(kVp8Codec); +} + +TEST_F(WebRtcVideoChannel2BaseTest, TwoStreamsReUseFirstStream) { + Base::TwoStreamsReUseFirstStream(kVp8Codec); +} + +class WebRtcVideoChannel2Test : public WebRtcVideoEngine2Test { + public: + virtual void SetUp() OVERRIDE { + channel_.reset(engine_.CreateChannel(NULL)); + fake_channel_ = factory_.GetFakeChannel(channel_.get()); + last_ssrc_ = 123; + ASSERT_TRUE(fake_channel_ != NULL) + << "Channel not created through factory."; + } + + protected: + FakeVideoSendStream* AddSendStream() { + return AddSendStream(StreamParams::CreateLegacy(last_ssrc_++)); + } + + FakeVideoSendStream* AddSendStream(const StreamParams& sp) { + size_t num_streams = + fake_channel_->GetFakeCall()->GetVideoSendStreams().size(); + EXPECT_TRUE(channel_->AddSendStream(sp)); + std::vector streams = + fake_channel_->GetFakeCall()->GetVideoSendStreams(); + EXPECT_EQ(num_streams + 1, streams.size()); + return streams[streams.size() - 1]; + } + + std::vector GetFakeSendStreams() { + return fake_channel_->GetFakeCall()->GetVideoSendStreams(); + } + + FakeVideoReceiveStream* AddRecvStream() { + return AddRecvStream(StreamParams::CreateLegacy(last_ssrc_++)); + } + + FakeVideoReceiveStream* AddRecvStream(const StreamParams& sp) { + size_t num_streams = + fake_channel_->GetFakeCall()->GetVideoReceiveStreams().size(); + EXPECT_TRUE(channel_->AddRecvStream(sp)); + std::vector streams = + fake_channel_->GetFakeCall()->GetVideoReceiveStreams(); + EXPECT_EQ(num_streams + 1, streams.size()); + return streams[streams.size() - 1]; + } + + void SetSendCodecsShouldWorkForBitrates(const char* min_bitrate, + const char* max_bitrate) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs[0].params[kCodecParamMinBitrate] = min_bitrate; + codecs[0].params[kCodecParamMaxBitrate] = max_bitrate; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + + FakeVideoSendStream* stream = AddSendStream(); + + std::vector video_streams = stream->GetVideoStreams(); + ASSERT_EQ(1u, video_streams.size()); + EXPECT_EQ(atoi(min_bitrate), video_streams.back().min_bitrate_bps / 1000); + EXPECT_EQ(atoi(max_bitrate), video_streams.back().max_bitrate_bps / 1000); + + VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_EQ(min_bitrate, codec.params[kCodecParamMinBitrate]); + EXPECT_EQ(max_bitrate, codec.params[kCodecParamMaxBitrate]); + } + + void ExpectEqualCodecs(const VideoCodec video_codec, + const webrtc::VideoCodec& webrtc_codec) { + EXPECT_STREQ(video_codec.name.c_str(), webrtc_codec.plName); + EXPECT_EQ(video_codec.id, webrtc_codec.plType); + EXPECT_EQ(video_codec.width, webrtc_codec.width); + EXPECT_EQ(video_codec.height, webrtc_codec.height); + EXPECT_EQ(video_codec.framerate, webrtc_codec.maxFramerate); + } + + void TestSetSendRtpHeaderExtensions(const std::string& cricket_ext, + const std::string& webrtc_ext) { + // Enable extension. + const int id = 1; + std::vector extensions; + extensions.push_back(cricket::RtpHeaderExtension(cricket_ext, id)); + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); + + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(123)); + + // Verify the send extension id. + ASSERT_EQ(1u, send_stream->GetConfig().rtp.extensions.size()); + EXPECT_EQ(id, send_stream->GetConfig().rtp.extensions[0].id); + EXPECT_EQ(webrtc_ext, send_stream->GetConfig().rtp.extensions[0].name); + // Verify call with same set of extensions returns true. + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); + // Verify that SetSendRtpHeaderExtensions doesn't implicitly add them for + // receivers. + EXPECT_TRUE(AddRecvStream(cricket::StreamParams::CreateLegacy(123)) + ->GetConfig() + .rtp.extensions.empty()); + + // Remove the extension id, verify that this doesn't reset extensions as + // they should be set before creating channels. + std::vector empty_extensions; + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions)); + EXPECT_FALSE(send_stream->GetConfig().rtp.extensions.empty()); + } + + void TestSetRecvRtpHeaderExtensions(const std::string& cricket_ext, + const std::string& webrtc_ext) { + // Enable extension. + const int id = 1; + std::vector extensions; + extensions.push_back(cricket::RtpHeaderExtension(cricket_ext, id)); + EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); + + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(123)); + + // Verify the recv extension id. + ASSERT_EQ(1u, recv_stream->GetConfig().rtp.extensions.size()); + EXPECT_EQ(id, recv_stream->GetConfig().rtp.extensions[0].id); + EXPECT_EQ(webrtc_ext, recv_stream->GetConfig().rtp.extensions[0].name); + // Verify call with same set of extensions returns true. + EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); + // Verify that SetRecvRtpHeaderExtensions doesn't implicitly add them for + // senders. + EXPECT_TRUE(AddSendStream(cricket::StreamParams::CreateLegacy(123)) + ->GetConfig() + .rtp.extensions.empty()); + + // Remove the extension id, verify that this doesn't reset extensions as + // they should be set before creating channels. + std::vector empty_extensions; + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions)); + EXPECT_FALSE(recv_stream->GetConfig().rtp.extensions.empty()); + } + + talk_base::scoped_ptr channel_; + FakeWebRtcVideoChannel2* fake_channel_; + uint32 last_ssrc_; +}; + +TEST_F(WebRtcVideoChannel2Test, DISABLED_MaxBitrateResetsWithConferenceMode) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_StartSendBitrate) { + // TODO(pbos): Is this test testing vie_ ? this is confusing. No API to set + // start send bitrate from outside? Add defaults here that should be kept? + std::vector codec_list; + codec_list.push_back(kVp8Codec); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + const unsigned int kVideoMinSendBitrateKbps = 50; + const unsigned int kVideoTargetSendBitrateKbps = 300; + const unsigned int kVideoMaxSendBitrateKbps = 2000; + FakeVideoSendStream* stream = AddSendStream(); + std::vector video_streams = stream->GetVideoStreams(); + ASSERT_EQ(1u, video_streams.size()); + EXPECT_EQ(kVideoMinSendBitrateKbps, + video_streams.back().min_bitrate_bps / 1000); + EXPECT_EQ(kVideoTargetSendBitrateKbps, + video_streams.back().target_bitrate_bps / 1000); + EXPECT_EQ(kVideoMaxSendBitrateKbps, + video_streams.back().max_bitrate_bps / 1000); +#if 0 + // TODO(pbos): un-#if + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoDefaultStartSendBitrateKbps); + EXPECT_EQ(0, vie_.StartSend(send_channel)); + + // Increase the send bitrate and verify it is used as start bitrate. + const unsigned int kVideoSendBitrateBps = 768000; + vie_.SetSendBitrates(send_channel, kVideoSendBitrateBps, 0, 0); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoSendBitrateBps / 1000); + + // Never set a start bitrate higher than the max bitrate. + vie_.SetSendBitrates(send_channel, kVideoMaxSendBitrateKbps + 500, 0, 0); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoDefaultStartSendBitrateKbps); + + // Use the default start bitrate if the send bitrate is lower. + vie_.SetSendBitrates(send_channel, kVideoDefaultStartSendBitrateKbps - 50, 0, + 0); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoDefaultStartSendBitrateKbps); +#endif +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RtcpEnabled) { + // Note(pbos): This is a receiver-side setting, dumbo. + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_KeyFrameRequestEnabled) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, RembIsEnabledByDefault) { + FakeVideoReceiveStream* stream = AddRecvStream(); + EXPECT_TRUE(stream->GetConfig().rtp.remb); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RembEnabledOnReceiveChannels) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, RecvStreamWithSimAndRtx) { + EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs())); + EXPECT_TRUE(channel_->SetSend(true)); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Send side. + const std::vector ssrcs = MAKE_VECTOR(kSsrcs1); + const std::vector rtx_ssrcs = MAKE_VECTOR(kRtxSsrcs1); + FakeVideoSendStream* send_stream = AddSendStream( + cricket::CreateSimWithRtxStreamParams("cname", ssrcs, rtx_ssrcs)); + + ASSERT_EQ(rtx_ssrcs.size(), send_stream->GetConfig().rtp.rtx.ssrcs.size()); + for (size_t i = 0; i < rtx_ssrcs.size(); ++i) + EXPECT_EQ(rtx_ssrcs[i], send_stream->GetConfig().rtp.rtx.ssrcs[i]); + + // Receiver side. + FakeVideoReceiveStream* recv_stream = AddRecvStream( + cricket::CreateSimWithRtxStreamParams("cname", ssrcs, rtx_ssrcs)); + ASSERT_GT(recv_stream->GetConfig().rtp.rtx.size(), 0u) + << "No SSRCs for RTX configured by AddRecvStream."; + ASSERT_EQ(1u, recv_stream->GetConfig().rtp.rtx.size()) + << "This test only works with one receive codec. Please update the test."; + EXPECT_EQ(rtx_ssrcs[0], + recv_stream->GetConfig().rtp.rtx.begin()->second.ssrc); + // TODO(pbos): Make sure we set the RTX for correct payloads etc. +} + +TEST_F(WebRtcVideoChannel2Test, RecvStreamWithRtx) { + // Setup one channel with an associated RTX stream. + cricket::StreamParams params = + cricket::StreamParams::CreateLegacy(kSsrcs1[0]); + params.AddFidSsrc(kSsrcs1[0], kRtxSsrcs1[0]); + FakeVideoReceiveStream* recv_stream = AddRecvStream(params); + ASSERT_EQ(1u, recv_stream->GetConfig().rtp.rtx.size()); + EXPECT_EQ(kRtxSsrcs1[0], + recv_stream->GetConfig().rtp.rtx.begin()->second.ssrc); +} + +TEST_F(WebRtcVideoChannel2Test, RecvStreamNoRtx) { + // Setup one channel without an associated RTX stream. + cricket::StreamParams params = + cricket::StreamParams::CreateLegacy(kSsrcs1[0]); + FakeVideoReceiveStream* recv_stream = AddRecvStream(params); + ASSERT_TRUE(recv_stream->GetConfig().rtp.rtx.empty()); +} + +TEST_F(WebRtcVideoChannel2Test, NoHeaderExtesionsByDefault) { + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(kSsrcs1[0])); + ASSERT_TRUE(send_stream->GetConfig().rtp.extensions.empty()); + + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrcs1[0])); + ASSERT_TRUE(recv_stream->GetConfig().rtp.extensions.empty()); +} + +// Test support for RTP timestamp offset header extension. +TEST_F(WebRtcVideoChannel2Test, SendRtpTimestampOffsetHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpTimestampOffsetHeaderExtension, + webrtc::RtpExtension::kTOffset); +} +TEST_F(WebRtcVideoChannel2Test, RecvRtpTimestampOffsetHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpTimestampOffsetHeaderExtension, + webrtc::RtpExtension::kTOffset); +} + +// Test support for absolute send time header extension. +TEST_F(WebRtcVideoChannel2Test, SendAbsoluteSendTimeHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension, + webrtc::RtpExtension::kAbsSendTime); +} +TEST_F(WebRtcVideoChannel2Test, RecvAbsoluteSendTimeHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension, + webrtc::RtpExtension::kAbsSendTime); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_LeakyBucketTest) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_BufferedModeLatency) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_AdditiveVideoOptions) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, AddRecvStreamOnlyUsesOneReceiveStream) { + EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1))); + EXPECT_EQ(1u, fake_channel_->GetFakeCall()->GetVideoReceiveStreams().size()); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_NoRembChangeAfterAddRecvStream) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RembOnOff) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, NackIsEnabledByDefault) { + VerifyCodecHasDefaultFeedbackParams(default_codec_); + + EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs())); + EXPECT_TRUE(channel_->SetSend(true)); + + // Send side. + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(1)); + EXPECT_GT(send_stream->GetConfig().rtp.nack.rtp_history_ms, 0); + + // Receiver side. + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(1)); + EXPECT_GT(recv_stream->GetConfig().rtp.nack.rtp_history_ms, 0); + + // Nack history size should match between sender and receiver. + EXPECT_EQ(send_stream->GetConfig().rtp.nack.rtp_history_ms, + recv_stream->GetConfig().rtp.nack.rtp_history_ms); +} + +TEST_F(WebRtcVideoChannel2Test, NackCanBeDisabled) { + std::vector codecs; + codecs.push_back(kVp8Codec); + + // Send side. + ASSERT_TRUE(channel_->SetSendCodecs(codecs)); + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(1)); + EXPECT_EQ(0, send_stream->GetConfig().rtp.nack.rtp_history_ms); + + // Receiver side. + ASSERT_TRUE(channel_->SetRecvCodecs(codecs)); + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(1)); + EXPECT_EQ(0, recv_stream->GetConfig().rtp.nack.rtp_history_ms); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_VideoProtectionInterop) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_VideoProtectionInteropReversed) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_HybridNackFecConference) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_AddRemoveRecvStreamConference) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetRender) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthAuto) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthAutoCapped) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthFixed) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthInConference) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthScreencast) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetSendSsrcAndCname) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetSendSsrcAfterCreatingReceiveChannel) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsWithDenoising) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_MultipleSendStreamsWithOneCapturer) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DISABLED_SendReceiveBitratesStats) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetAdaptInputToCpuUsage) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetCpuThreshold) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetInvalidCpuThreshold) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_WebRtcShouldLog) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_WebRtcShouldNotLog) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetDefaultSendCodecs) { + ASSERT_TRUE(channel_->SetSendCodecs(engine_.codecs())); + + VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_TRUE(codec.Matches(engine_.codecs()[0])); + + // Using a RTX setup to verify that the default RTX payload type is good. + const std::vector ssrcs = MAKE_VECTOR(kSsrcs1); + const std::vector rtx_ssrcs = MAKE_VECTOR(kRtxSsrcs1); + FakeVideoSendStream* stream = AddSendStream( + cricket::CreateSimWithRtxStreamParams("cname", ssrcs, rtx_ssrcs)); + webrtc::VideoSendStream::Config config = stream->GetConfig(); + // TODO(pbos): Replace ExpectEqualCodecs. + // ExpectEqualCodecs(engine_.codecs()[0], config.codec); + + // Make sure NACK and FEC are enabled on the correct payload types. + EXPECT_EQ(1000, config.rtp.nack.rtp_history_ms); + EXPECT_EQ(default_ulpfec_codec_.id, config.rtp.fec.ulpfec_payload_type); + EXPECT_EQ(default_red_codec_.id, config.rtp.fec.red_payload_type); + // TODO(pbos): Verify that the rtx ssrc is set, correct, not taken by anything + // else. + // ASSERT_EQ(1u, config.rtp.rtx.ssrcs.size()); + EXPECT_EQ(static_cast(default_rtx_codec_.id), + config.rtp.rtx.payload_type); + // TODO(juberti): Check RTCP, PLI, TMMBR. +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsWithoutFec) { + std::vector codecs; + codecs.push_back(kVp8Codec); + ASSERT_TRUE(channel_->SetSendCodecs(codecs)); + + FakeVideoSendStream* stream = AddSendStream(); + webrtc::VideoSendStream::Config config = stream->GetConfig(); + + EXPECT_EQ(-1, config.rtp.fec.ulpfec_payload_type); + EXPECT_EQ(-1, config.rtp.fec.red_payload_type); +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetSendCodecRejectsRtxWithoutAssociatedPayloadType) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetSendCodecRejectsRtxWithoutMatchingVideoCodec) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetCodecsWithoutFecDisablesCurrentFec) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetSendCodecsChangesExistingStreams) { + FAIL(); // TODO(pbos): Implement, make sure that it's changing running + // streams. Should it? +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_ConstrainsSetCodecsAccordingToEncoderConfig) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsWithMinMaxBitrate) { + SetSendCodecsShouldWorkForBitrates("10", "20"); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectsMaxLessThanMinBitrate) { + std::vector video_codecs = engine_.codecs(); + video_codecs[0].params[kCodecParamMinBitrate] = "30"; + video_codecs[0].params[kCodecParamMaxBitrate] = "20"; + EXPECT_FALSE(channel_->SetSendCodecs(video_codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsAcceptLargeMinMaxBitrate) { + SetSendCodecsShouldWorkForBitrates("1000", "2000"); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsWithMaxQuantization) { + static const char* kMaxQuantization = "21"; + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs[0].params[kCodecParamMaxQuantization] = kMaxQuantization; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(static_cast(atoi(kMaxQuantization)), + AddSendStream()->GetVideoStreams().back().max_qp); + + VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_EQ(kMaxQuantization, codec.params[kCodecParamMaxQuantization]); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectBadDimensions) { + std::vector codecs; + codecs.push_back(kVp8Codec); + + codecs[0].width = 0; + EXPECT_FALSE(channel_->SetSendCodecs(codecs)) + << "Codec set though codec width is zero."; + + codecs[0].width = kVp8Codec.width; + codecs[0].height = 0; + EXPECT_FALSE(channel_->SetSendCodecs(codecs)) + << "Codec set though codec height is zero."; +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectBadPayloadTypes) { + // TODO(pbos): Should we only allow the dynamic range? + static const size_t kNumIncorrectPayloads = 4; + static const int kIncorrectPayloads[kNumIncorrectPayloads] = {-2, -1, 128, + 129}; + std::vector codecs; + codecs.push_back(kVp8Codec); + for (size_t i = 0; i < kNumIncorrectPayloads; ++i) { + int payload_type = kIncorrectPayloads[i]; + codecs[0].id = payload_type; + EXPECT_FALSE(channel_->SetSendCodecs(codecs)) + << "Bad payload type '" << payload_type << "' accepted."; + } +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsAcceptAllValidPayloadTypes) { + std::vector codecs; + codecs.push_back(kVp8Codec); + for (int payload_type = 0; payload_type <= 127; ++payload_type) { + codecs[0].id = payload_type; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)) + << "Payload type '" << payload_type << "' rejected."; + } +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ResetVieSendCodecOnNewFrameSize) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsWithOnlyVp8) { + std::vector codecs; + codecs.push_back(kVp8Codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +// Test that we set our inbound RTX codecs properly. +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsWithRtx) { + std::vector codecs; + codecs.push_back(kVp8Codec); + cricket::VideoCodec rtx_codec(96, "rtx", 0, 0, 0, 0); + codecs.push_back(rtx_codec); + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)) + << "RTX codec without associated payload should be rejected."; + + codecs[1].SetParam("apt", kVp8Codec.id + 1); + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)) + << "RTX codec with invalid associated payload type should be rejected."; + + codecs[1].SetParam("apt", kVp8Codec.id); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); + + cricket::VideoCodec rtx_codec2(97, "rtx", 0, 0, 0, 0); + rtx_codec2.SetParam("apt", rtx_codec.id); + codecs.push_back(rtx_codec2); + + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)) << "RTX codec with another RTX " + "as associated payload type " + "should be rejected."; +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsDifferentPayloadType) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs[0].id = 99; + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetRecvCodecsAcceptDefaultCodecs) { + EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs())); + // (I've added this one.) Make sure they propagate down to VideoReceiveStream! + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsRejectUnsupportedCodec) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(VideoCodec(101, "WTF3", 640, 400, 30, 0)); + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); +} + +// TODO(pbos): Enable VP9 through external codec support +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetRecvCodecsAcceptsMultipleVideoCodecs) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp9Codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetRecvCodecsSetsFecForAllVideoCodecs) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp9Codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); + FAIL(); // TODO(pbos): Verify that the FEC parameters are set for all codecs. +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectDuplicateFecPayloads) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kRedCodec); + codecs[1].id = codecs[0].id; + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsRejectDuplicateCodecPayloads) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp9Codec); + codecs[1].id = codecs[0].id; + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, + SetRecvCodecsAcceptSameCodecOnMultiplePayloadTypes) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp8Codec); + codecs[1].id += 1; + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, SendStreamNotSendingByDefault) { + EXPECT_FALSE(AddSendStream()->IsSending()); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ReceiveStreamReceivingByDefault) { + // Is this test correct though? Auto-receive? Enable receive on first packet? + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetSend) { + AddSendStream(); + EXPECT_FALSE(channel_->SetSend(true)) + << "Channel should not start without codecs."; + EXPECT_TRUE(channel_->SetSend(false)) + << "Channel should be stoppable even without set codecs."; + + std::vector codecs; + codecs.push_back(kVp8Codec); + channel_->SetSendCodecs(codecs); + std::vector streams = GetFakeSendStreams(); + ASSERT_EQ(1u, streams.size()); + FakeVideoSendStream* stream = streams.back(); + + EXPECT_FALSE(stream->IsSending()); + + // false->true + EXPECT_TRUE(channel_->SetSend(true)); + EXPECT_TRUE(stream->IsSending()); + // true->true + EXPECT_TRUE(channel_->SetSend(true)); + EXPECT_TRUE(stream->IsSending()); + // true->false + EXPECT_TRUE(channel_->SetSend(false)); + EXPECT_FALSE(stream->IsSending()); + // false->false + EXPECT_TRUE(channel_->SetSend(false)); + EXPECT_FALSE(stream->IsSending()); + + EXPECT_TRUE(channel_->SetSend(true)); + FakeVideoSendStream* new_stream = AddSendStream(); + EXPECT_TRUE(new_stream->IsSending()) + << "Send stream created after SetSend(true) not sending initially."; +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendAndReceiveVp8Vga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendAndReceiveVp8Qvga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendAndReceiveH264SvcQqvga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendManyResizeOnce) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendVp8HdAndReceiveAdaptedVp8Vga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetDscpOptions) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsWithMaxBitrate) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsWithLoweredBitrate) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsSucceedsWhenSending) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ResetCodecOnScreencast) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontResetCodecOnSendFrame) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_DontRegisterDecoderIfFactoryIsNotGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RegisterDecoderIfFactoryIsGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterDecoderMultipleTimes) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterDecoderForNonVP8) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_DontRegisterEncoderIfFactoryIsNotGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RegisterEncoderIfFactoryIsGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterEncoderMultipleTimes) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_RegisterEncoderWithMultipleSendStreams) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterEncoderForNonVP8) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_FeedbackParamsForNonVP8) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ExternalCodecAddedToTheEnd) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ExternalCodecIgnored) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_UpdateEncoderCodecsAfterSetFactory) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_OnReadyToSend) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_CaptureFrameTimestampToNtpTimestamp) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} +} // namespace cricket diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.h b/talk/media/webrtc/webrtcvideoengine2_unittest.h new file mode 100644 index 000000000..879b4f404 --- /dev/null +++ b/talk/media/webrtc/webrtcvideoengine2_unittest.h @@ -0,0 +1,157 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_UNITTEST_H_ +#define TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_UNITTEST_H_ + +#include +#include + +#include "webrtc/call.h" +#include "webrtc/video_receive_stream.h" +#include "webrtc/video_send_stream.h" + +namespace cricket { +class FakeVideoSendStream : public webrtc::VideoSendStream { + public: + FakeVideoSendStream(const webrtc::VideoSendStream::Config& config, + const std::vector& video_streams); + webrtc::VideoSendStream::Config GetConfig(); + std::vector GetVideoStreams(); + + bool IsSending(); + + private: + virtual webrtc::VideoSendStream::Stats GetStats() const OVERRIDE; + + virtual bool ReconfigureVideoEncoder( + const std::vector& streams, + const void* encoder_specific); + + virtual webrtc::VideoSendStreamInput* Input() OVERRIDE; + + virtual void Start() OVERRIDE; + virtual void Stop() OVERRIDE; + + bool sending_; + webrtc::VideoSendStream::Config config_; + std::vector video_streams_; +}; + +class FakeVideoReceiveStream : public webrtc::VideoReceiveStream { + public: + explicit FakeVideoReceiveStream( + const webrtc::VideoReceiveStream::Config& config); + + webrtc::VideoReceiveStream::Config GetConfig(); + + private: + virtual webrtc::VideoReceiveStream::Stats GetStats() const OVERRIDE; + + virtual void Start() OVERRIDE; + virtual void Stop() OVERRIDE; + virtual void GetCurrentReceiveCodec(webrtc::VideoCodec* codec); + + webrtc::VideoReceiveStream::Config config_; + bool receiving_; +}; + +class FakeCall : public webrtc::Call { + public: + FakeCall(); + ~FakeCall(); + + void SetVideoCodecs(const std::vector codecs); + + std::vector GetVideoSendStreams(); + std::vector GetVideoReceiveStreams(); + + webrtc::VideoCodec GetEmptyVideoCodec(); + + webrtc::VideoCodec GetVideoCodecVp8(); + webrtc::VideoCodec GetVideoCodecVp9(); + + std::vector GetDefaultVideoCodecs(); + + private: + virtual webrtc::VideoSendStream::Config GetDefaultSendConfig() OVERRIDE; + + virtual webrtc::VideoSendStream* CreateVideoSendStream( + const webrtc::VideoSendStream::Config& config, + const std::vector& video_streams, + const void* encoder_settings) OVERRIDE; + + virtual void DestroyVideoSendStream( + webrtc::VideoSendStream* send_stream) OVERRIDE; + + virtual webrtc::VideoReceiveStream::Config GetDefaultReceiveConfig() OVERRIDE; + + virtual webrtc::VideoReceiveStream* CreateVideoReceiveStream( + const webrtc::VideoReceiveStream::Config& config) OVERRIDE; + + virtual void DestroyVideoReceiveStream( + webrtc::VideoReceiveStream* receive_stream) OVERRIDE; + virtual webrtc::PacketReceiver* Receiver() OVERRIDE; + + virtual uint32_t SendBitrateEstimate() OVERRIDE; + virtual uint32_t ReceiveBitrateEstimate() OVERRIDE; + + std::vector codecs_; + std::vector video_send_streams_; + std::vector video_receive_streams_; +}; + +class FakeWebRtcVideoChannel2 : public WebRtcVideoChannel2 { + public: + FakeWebRtcVideoChannel2(FakeCall* call, + WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel); + virtual ~FakeWebRtcVideoChannel2(); + + VoiceMediaChannel* GetVoiceChannel(); + FakeCall* GetFakeCall(); + + private: + FakeCall* fake_call_; + VoiceMediaChannel* voice_channel_; +}; + +class FakeWebRtcVideoMediaChannelFactory : public WebRtcVideoChannelFactory { + public: + FakeWebRtcVideoChannel2* GetFakeChannel(VideoMediaChannel* channel); + + private: + virtual WebRtcVideoChannel2* Create( + WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel) OVERRIDE; + + std::map channel_map_; +}; + + +} // namespace cricket +#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_UNITTEST_H_ diff --git a/talk/media/webrtc/webrtcvideoengine_unittest.cc b/talk/media/webrtc/webrtcvideoengine_unittest.cc index 32e742c2f..307e594b9 100644 --- a/talk/media/webrtc/webrtcvideoengine_unittest.cc +++ b/talk/media/webrtc/webrtcvideoengine_unittest.cc @@ -50,6 +50,9 @@ // Tests for the WebRtcVideoEngine/VideoChannel code. +using cricket::kRtpTimestampOffsetHeaderExtension; +using cricket::kRtpAbsoluteSenderTimeHeaderExtension; + static const cricket::VideoCodec kVP8Codec720p(100, "VP8", 1280, 720, 30, 0); static const cricket::VideoCodec kVP8Codec360p(100, "VP8", 640, 360, 30, 0); static const cricket::VideoCodec kVP8Codec270p(100, "VP8", 480, 270, 30, 0); @@ -68,12 +71,10 @@ static const unsigned int kStartBandwidthKbps = 300; static const unsigned int kMinBandwidthKbps = 50; static const unsigned int kMaxBandwidthKbps = 2000; -static const unsigned int kNumberOfTemporalLayers = 1; - static const uint32 kSsrcs1[] = {1}; static const uint32 kSsrcs2[] = {1, 2}; static const uint32 kSsrcs3[] = {1, 2, 3}; -static const uint32 kRtxSsrc1[] = {4}; +static const uint32 kRtxSsrcs1[] = {4}; static const uint32 kRtxSsrcs3[] = {4, 5, 6}; @@ -148,6 +149,76 @@ class WebRtcVideoEngineTestFake : public testing::Test, channel_->SendFrame(&capturer, &frame); return true; } + void TestSetSendRtpHeaderExtensions(const std::string& ext) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + + // Verify extensions are off by default. + EXPECT_EQ(-1, vie_.GetSendRtpExtensionId(channel_num, ext)); + + // Enable extension. + const int id = 1; + std::vector extensions; + extensions.push_back(cricket::RtpHeaderExtension(ext, id)); + + // Verify the send extension id. + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); + EXPECT_EQ(id, vie_.GetSendRtpExtensionId(channel_num, ext)); + // Verify call with same set of extensions returns true. + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); + EXPECT_EQ(id, vie_.GetSendRtpExtensionId(channel_num, ext)); + + // Add a new send stream and verify the extension is set. + // The first send stream to occupy the default channel. + EXPECT_TRUE( + channel_->AddSendStream(cricket::StreamParams::CreateLegacy(123))); + EXPECT_TRUE( + channel_->AddSendStream(cricket::StreamParams::CreateLegacy(234))); + int new_send_channel_num = vie_.GetLastChannel(); + EXPECT_NE(channel_num, new_send_channel_num); + EXPECT_EQ(id, vie_.GetSendRtpExtensionId(new_send_channel_num, ext)); + + // Remove the extension id. + std::vector empty_extensions; + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions)); + EXPECT_EQ(-1, vie_.GetSendRtpExtensionId(channel_num, ext)); + EXPECT_EQ(-1, vie_.GetSendRtpExtensionId(new_send_channel_num, ext)); + } + void TestSetRecvRtpHeaderExtensions(const std::string& ext) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + + // Verify extensions are off by default. + EXPECT_EQ(-1, vie_.GetReceiveRtpExtensionId(channel_num, ext)); + + // Enable extension. + const int id = 2; + std::vector extensions; + extensions.push_back(cricket::RtpHeaderExtension(ext, id)); + + // Verify receive extension id. + EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); + EXPECT_EQ(id, vie_.GetReceiveRtpExtensionId(channel_num, ext)); + // Verify call with same set of extensions returns true. + EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); + EXPECT_EQ(id, vie_.GetReceiveRtpExtensionId(channel_num, ext)); + + // Add a new receive stream and verify the extension is set. + // The first send stream to occupy the default channel. + EXPECT_TRUE( + channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(345))); + EXPECT_TRUE( + channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(456))); + int new_recv_channel_num = vie_.GetLastChannel(); + EXPECT_NE(channel_num, new_recv_channel_num); + EXPECT_EQ(id, vie_.GetReceiveRtpExtensionId(new_recv_channel_num, ext)); + + // Remove the extension id. + std::vector empty_extensions; + EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(empty_extensions)); + EXPECT_EQ(-1, vie_.GetReceiveRtpExtensionId(channel_num, ext)); + EXPECT_EQ(-1, vie_.GetReceiveRtpExtensionId(new_recv_channel_num, ext)); + } void VerifyCodecFeedbackParams(const cricket::VideoCodec& codec) { EXPECT_TRUE(codec.HasFeedbackParam( cricket::FeedbackParam(cricket::kRtcpFbParamNack, @@ -352,6 +423,40 @@ TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithMinMaxBitrate) { EXPECT_EQ("20", codec.params[cricket::kCodecParamMaxBitrate]); } +TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithStartBitrate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + std::vector codecs(engine_.codecs()); + codecs[0].params[cricket::kCodecParamStartBitrate] = "450"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + + VerifyVP8SendCodec( + channel_num, kVP8Codec.width, kVP8Codec.height, 0, 2000, 50, 450); + + cricket::VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_EQ("450", codec.params[cricket::kCodecParamStartBitrate]); +} + +TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithMinMaxStartBitrate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + std::vector codecs(engine_.codecs()); + codecs[0].params[cricket::kCodecParamMinBitrate] = "10"; + codecs[0].params[cricket::kCodecParamMaxBitrate] = "20"; + codecs[0].params[cricket::kCodecParamStartBitrate] = "14"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + + VerifyVP8SendCodec( + channel_num, kVP8Codec.width, kVP8Codec.height, 0, 20, 10, 14); + + cricket::VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_EQ("10", codec.params[cricket::kCodecParamMinBitrate]); + EXPECT_EQ("20", codec.params[cricket::kCodecParamMaxBitrate]); + EXPECT_EQ("14", codec.params[cricket::kCodecParamStartBitrate]); +} + TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithMinMaxBitrateInvalid) { EXPECT_TRUE(SetupEngine()); std::vector codecs(engine_.codecs()); @@ -451,7 +556,7 @@ TEST_F(WebRtcVideoEngineTestFake, MaxBitrateResetWithConferenceMode) { EXPECT_TRUE(channel_->SetOptions(options)); VerifyVP8SendCodec( channel_num, kVP8Codec.width, kVP8Codec.height, 0, - kMaxBandwidthKbps, 10, 20); + kMaxBandwidthKbps, 10, kStartBandwidthKbps); } // Verify the current send bitrate is used as start bitrate when reconfiguring @@ -612,6 +717,112 @@ TEST_F(WebRtcVideoEngineTestFake, SetRecvCodecs) { EXPECT_TRUE(vie_.ReceiveCodecRegistered(channel_num, wcodec)); } +// Test that we set our inbound RTX codecs properly. +TEST_F(WebRtcVideoEngineTestFake, SetRecvCodecsWithRtx) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + + std::vector codecs; + cricket::VideoCodec rtx_codec(96, "rtx", 0, 0, 0, 0); + codecs.push_back(rtx_codec); + // Should fail since there's no associated payload type set. + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); + + codecs[0].SetParam("apt", 97); + // Should still fail since the we don't support RTX on this APT. + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); + + codecs[0].SetParam("apt", kVP8Codec.id); + // Should still fail since the associated payload type is unknown. + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); + + codecs.push_back(kVP8Codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); + + webrtc::VideoCodec wcodec; + // Should not have been registered as a WebRTC codec. + EXPECT_TRUE(engine_.ConvertFromCricketVideoCodec(rtx_codec, &wcodec)); + EXPECT_STREQ("rtx", wcodec.plName); + EXPECT_FALSE(vie_.ReceiveCodecRegistered(channel_num, wcodec)); + + // The RTX payload type should have been set. + EXPECT_EQ(rtx_codec.id, vie_.GetRtxRecvPayloadType(channel_num)); +} + +// Test that RTX packets are routed to the default video channel if +// there's only one recv stream. +TEST_F(WebRtcVideoEngineTestFake, TestReceiveRtxOneStream) { + EXPECT_TRUE(SetupEngine()); + + // Setup one channel with an associated RTX stream. + cricket::StreamParams params = + cricket::StreamParams::CreateLegacy(kSsrcs1[0]); + params.AddFidSsrc(kSsrcs1[0], kRtxSsrcs1[0]); + EXPECT_TRUE(channel_->AddRecvStream(params)); + int channel_num = vie_.GetLastChannel(); + EXPECT_EQ(static_cast(kRtxSsrcs1[0]), + vie_.GetRemoteRtxSsrc(channel_num)); + + // Register codecs. + std::vector codec_list; + codec_list.push_back(kVP8Codec720p); + cricket::VideoCodec rtx_codec(96, "rtx", 0, 0, 0, 0); + rtx_codec.SetParam("apt", kVP8Codec.id); + codec_list.push_back(rtx_codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codec_list)); + + // Construct a fake RTX packet and verify that it is passed to the + // right WebRTC channel. + const size_t kDataLength = 12; + uint8_t data[kDataLength]; + memset(data, 0, sizeof(data)); + data[0] = 0x80; + data[1] = rtx_codec.id; + talk_base::SetBE32(&data[8], kRtxSsrcs1[0]); + talk_base::Buffer packet(data, kDataLength); + talk_base::PacketTime packet_time; + channel_->OnPacketReceived(&packet, packet_time); + EXPECT_EQ(rtx_codec.id, vie_.GetLastRecvdPayloadType(channel_num)); +} + +// Test that RTX packets are routed to the correct video channel. +TEST_F(WebRtcVideoEngineTestFake, TestReceiveRtxThreeStreams) { + EXPECT_TRUE(SetupEngine()); + + // Setup three channels with associated RTX streams. + int channel_num[ARRAY_SIZE(kSsrcs3)]; + for (size_t i = 0; i < ARRAY_SIZE(kSsrcs3); ++i) { + cricket::StreamParams params = + cricket::StreamParams::CreateLegacy(kSsrcs3[i]); + params.AddFidSsrc(kSsrcs3[i], kRtxSsrcs3[i]); + EXPECT_TRUE(channel_->AddRecvStream(params)); + channel_num[i] = vie_.GetLastChannel(); + } + + // Register codecs. + std::vector codec_list; + codec_list.push_back(kVP8Codec720p); + cricket::VideoCodec rtx_codec(96, "rtx", 0, 0, 0, 0); + rtx_codec.SetParam("apt", kVP8Codec.id); + codec_list.push_back(rtx_codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codec_list)); + + // Construct a fake RTX packet and verify that it is passed to the + // right WebRTC channel. + const size_t kDataLength = 12; + uint8_t data[kDataLength]; + memset(data, 0, sizeof(data)); + data[0] = 0x80; + data[1] = rtx_codec.id; + talk_base::SetBE32(&data[8], kRtxSsrcs3[1]); + talk_base::Buffer packet(data, kDataLength); + talk_base::PacketTime packet_time; + channel_->OnPacketReceived(&packet, packet_time); + EXPECT_NE(rtx_codec.id, vie_.GetLastRecvdPayloadType(channel_num[0])); + EXPECT_EQ(rtx_codec.id, vie_.GetLastRecvdPayloadType(channel_num[1])); + EXPECT_NE(rtx_codec.id, vie_.GetLastRecvdPayloadType(channel_num[2])); +} + // Test that channel connects and disconnects external capturer correctly. TEST_F(WebRtcVideoEngineTestFake, HasExternalCapturer) { EXPECT_TRUE(SetupEngine()); @@ -705,7 +916,7 @@ TEST_F(WebRtcVideoEngineTestFake, RecvStreamWithRtx) { EXPECT_TRUE(channel_->AddRecvStream( cricket::CreateSimWithRtxStreamParams("cname", MAKE_VECTOR(kSsrcs1), - MAKE_VECTOR(kRtxSsrc1)))); + MAKE_VECTOR(kRtxSsrcs1)))); int new_channel_num = vie_.GetLastChannel(); EXPECT_NE(default_channel, new_channel_num); EXPECT_EQ(4, vie_.GetRemoteRtxSsrc(new_channel_num)); @@ -730,105 +941,35 @@ TEST_F(WebRtcVideoEngineTestFake, RecvStreamNoRtx) { } // Test support for RTP timestamp offset header extension. -TEST_F(WebRtcVideoEngineTestFake, RtpTimestampOffsetHeaderExtensions) { - EXPECT_TRUE(SetupEngine()); - int channel_num = vie_.GetLastChannel(); - cricket::VideoOptions options; - options.conference_mode.Set(true); - EXPECT_TRUE(channel_->SetOptions(options)); - - // Verify extensions are off by default. - EXPECT_EQ(0, vie_.GetSendRtpTimestampOffsetExtensionId(channel_num)); - EXPECT_EQ(0, vie_.GetReceiveRtpTimestampOffsetExtensionId(channel_num)); - - // Enable RTP timestamp extension. - const int id = 14; - std::vector extensions; - extensions.push_back(cricket::RtpHeaderExtension( - "urn:ietf:params:rtp-hdrext:toffset", id)); - - // Verify the send extension id. - EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); - EXPECT_EQ(id, vie_.GetSendRtpTimestampOffsetExtensionId(channel_num)); - - // Remove the extension id. - std::vector empty_extensions; - EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions)); - EXPECT_EQ(0, vie_.GetSendRtpTimestampOffsetExtensionId(channel_num)); - - // Verify receive extension id. - EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); - EXPECT_EQ(id, vie_.GetReceiveRtpTimestampOffsetExtensionId(channel_num)); - - // Add a new receive stream and verify the extension is set. - EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2))); - int new_channel_num = vie_.GetLastChannel(); - EXPECT_NE(channel_num, new_channel_num); - EXPECT_EQ(id, vie_.GetReceiveRtpTimestampOffsetExtensionId(new_channel_num)); - - // Remove the extension id. - EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(empty_extensions)); - EXPECT_EQ(0, vie_.GetReceiveRtpTimestampOffsetExtensionId(channel_num)); - EXPECT_EQ(0, vie_.GetReceiveRtpTimestampOffsetExtensionId(new_channel_num)); +TEST_F(WebRtcVideoEngineTestFake, SendRtpTimestampOffsetHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpTimestampOffsetHeaderExtension); +} +TEST_F(WebRtcVideoEngineTestFake, RecvRtpTimestampOffsetHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpTimestampOffsetHeaderExtension); } // Test support for absolute send time header extension. -TEST_F(WebRtcVideoEngineTestFake, AbsoluteSendTimeHeaderExtensions) { - EXPECT_TRUE(SetupEngine()); - int channel_num = vie_.GetLastChannel(); - cricket::VideoOptions options; - options.conference_mode.Set(true); - EXPECT_TRUE(channel_->SetOptions(options)); - - // Verify extensions are off by default. - EXPECT_EQ(0, vie_.GetSendAbsoluteSendTimeExtensionId(channel_num)); - EXPECT_EQ(0, vie_.GetReceiveAbsoluteSendTimeExtensionId(channel_num)); - - // Enable RTP timestamp extension. - const int id = 12; - std::vector extensions; - extensions.push_back(cricket::RtpHeaderExtension( - "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", id)); - - // Verify the send extension id. - EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); - EXPECT_EQ(id, vie_.GetSendAbsoluteSendTimeExtensionId(channel_num)); - - // Remove the extension id. - std::vector empty_extensions; - EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions)); - EXPECT_EQ(0, vie_.GetSendAbsoluteSendTimeExtensionId(channel_num)); - - // Verify receive extension id. - EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); - EXPECT_EQ(id, vie_.GetReceiveAbsoluteSendTimeExtensionId(channel_num)); - - // Add a new receive stream and verify the extension is set. - EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2))); - int new_channel_num = vie_.GetLastChannel(); - EXPECT_NE(channel_num, new_channel_num); - EXPECT_EQ(id, vie_.GetReceiveAbsoluteSendTimeExtensionId(new_channel_num)); - - // Remove the extension id. - EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(empty_extensions)); - EXPECT_EQ(0, vie_.GetReceiveAbsoluteSendTimeExtensionId(channel_num)); - EXPECT_EQ(0, vie_.GetReceiveAbsoluteSendTimeExtensionId(new_channel_num)); +TEST_F(WebRtcVideoEngineTestFake, SendAbsoluteSendTimeHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension); +} +TEST_F(WebRtcVideoEngineTestFake, RecvAbsoluteSendTimeHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension); } TEST_F(WebRtcVideoEngineTestFake, LeakyBucketTest) { EXPECT_TRUE(SetupEngine()); - // Verify this is off by default. + // Verify this is on by default. EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(1))); int first_send_channel = vie_.GetLastChannel(); - EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); + EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); - // Enable the experiment and verify. + // Disable the experiment and verify. cricket::VideoOptions options; options.conference_mode.Set(true); - options.video_leaky_bucket.Set(true); + options.video_leaky_bucket.Set(false); EXPECT_TRUE(channel_->SetOptions(options)); - EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); + EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); // Add a receive channel and verify leaky bucket isn't enabled. EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2))); @@ -836,10 +977,16 @@ TEST_F(WebRtcVideoEngineTestFake, LeakyBucketTest) { EXPECT_NE(first_send_channel, recv_channel_num); EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(recv_channel_num)); - // Add a new send stream and verify leaky bucket is enabled from start. + // Add a new send stream and verify leaky bucket is disabled from start. EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(3))); int second_send_channel = vie_.GetLastChannel(); EXPECT_NE(first_send_channel, second_send_channel); + EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(second_send_channel)); + + // Reenable leaky bucket. + options.video_leaky_bucket.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(second_send_channel)); } @@ -921,12 +1068,12 @@ TEST_F(WebRtcVideoEngineTestFake, AdditiveVideoOptions) { EXPECT_TRUE(channel_->SetOptions(options1)); EXPECT_EQ(100, vie_.GetSenderTargetDelay(first_send_channel)); EXPECT_EQ(100, vie_.GetReceiverTargetDelay(first_send_channel)); - EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); + EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); cricket::VideoOptions options2; - options2.video_leaky_bucket.Set(true); + options2.video_leaky_bucket.Set(false); EXPECT_TRUE(channel_->SetOptions(options2)); - EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); + EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); // The buffered_mode_latency still takes effect. EXPECT_EQ(100, vie_.GetSenderTargetDelay(first_send_channel)); EXPECT_EQ(100, vie_.GetReceiverTargetDelay(first_send_channel)); @@ -936,10 +1083,9 @@ TEST_F(WebRtcVideoEngineTestFake, AdditiveVideoOptions) { EXPECT_EQ(50, vie_.GetSenderTargetDelay(first_send_channel)); EXPECT_EQ(50, vie_.GetReceiverTargetDelay(first_send_channel)); // The video_leaky_bucket still takes effect. - EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); + EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(first_send_channel)); } -#ifdef USE_WEBRTC_DEV_BRANCH TEST_F(WebRtcVideoEngineTestFake, SetCpuOveruseOptionsWithCaptureJitterMethod) { EXPECT_TRUE(SetupEngine()); @@ -1040,6 +1186,11 @@ TEST_F(WebRtcVideoEngineTestFake, SetCpuOveruseOptionsWithEncodeUsageMethod) { EXPECT_EQ(20, cpu_option.high_encode_usage_threshold_percent); EXPECT_FALSE(cpu_option.enable_capture_jitter_method); EXPECT_TRUE(cpu_option.enable_encode_usage_method); +#ifdef USE_WEBRTC_DEV_BRANCH + // Verify that optional encode rsd thresholds are not set. + EXPECT_EQ(-1, cpu_option.low_encode_time_rsd_threshold); + EXPECT_EQ(-1, cpu_option.high_encode_time_rsd_threshold); +#endif // Add a new send stream and verify that cpu options are set from start. EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(3))); @@ -1050,8 +1201,52 @@ TEST_F(WebRtcVideoEngineTestFake, SetCpuOveruseOptionsWithEncodeUsageMethod) { EXPECT_EQ(20, cpu_option.high_encode_usage_threshold_percent); EXPECT_FALSE(cpu_option.enable_capture_jitter_method); EXPECT_TRUE(cpu_option.enable_encode_usage_method); +#ifdef USE_WEBRTC_DEV_BRANCH + // Verify that optional encode rsd thresholds are not set. + EXPECT_EQ(-1, cpu_option.low_encode_time_rsd_threshold); + EXPECT_EQ(-1, cpu_option.high_encode_time_rsd_threshold); +#endif } + +TEST_F(WebRtcVideoEngineTestFake, SetCpuOveruseOptionsWithEncodeRsdThresholds) { + EXPECT_TRUE(SetupEngine()); + EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(1))); + int first_send_channel = vie_.GetLastChannel(); + + // Set optional encode rsd thresholds and verify cpu options. + cricket::VideoOptions options; + options.conference_mode.Set(true); + options.cpu_underuse_threshold.Set(10); + options.cpu_overuse_threshold.Set(20); + options.cpu_underuse_encode_rsd_threshold.Set(30); + options.cpu_overuse_encode_rsd_threshold.Set(40); + options.cpu_overuse_encode_usage.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + webrtc::CpuOveruseOptions cpu_option = + vie_.GetCpuOveruseOptions(first_send_channel); + EXPECT_EQ(10, cpu_option.low_encode_usage_threshold_percent); + EXPECT_EQ(20, cpu_option.high_encode_usage_threshold_percent); + EXPECT_FALSE(cpu_option.enable_capture_jitter_method); + EXPECT_TRUE(cpu_option.enable_encode_usage_method); +#ifdef USE_WEBRTC_DEV_BRANCH + EXPECT_EQ(30, cpu_option.low_encode_time_rsd_threshold); + EXPECT_EQ(40, cpu_option.high_encode_time_rsd_threshold); +#endif + + // Add a new send stream and verify that cpu options are set from start. + EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(3))); + int second_send_channel = vie_.GetLastChannel(); + EXPECT_NE(first_send_channel, second_send_channel); + cpu_option = vie_.GetCpuOveruseOptions(second_send_channel); + EXPECT_EQ(10, cpu_option.low_encode_usage_threshold_percent); + EXPECT_EQ(20, cpu_option.high_encode_usage_threshold_percent); + EXPECT_FALSE(cpu_option.enable_capture_jitter_method); + EXPECT_TRUE(cpu_option.enable_encode_usage_method); +#ifdef USE_WEBRTC_DEV_BRANCH + EXPECT_EQ(30, cpu_option.low_encode_time_rsd_threshold); + EXPECT_EQ(40, cpu_option.high_encode_time_rsd_threshold); #endif +} // Test that AddRecvStream doesn't create new channel for 1:1 call. TEST_F(WebRtcVideoEngineTestFake, AddRecvStream1On1) { @@ -1353,7 +1548,7 @@ TEST_F(WebRtcVideoEngineTestFake, SetStartBandwidthOption) { kMaxBandwidthKbps, kMinBandwidthKbps, start_bandwidth_kbps); } -// Test that SetMaxSendBandwidth is ignored in conference mode. +// Test that SetMaxSendBandwidth works as expected in conference mode. TEST_F(WebRtcVideoEngineTestFake, SetBandwidthInConference) { EXPECT_TRUE(SetupEngine()); int channel_num = vie_.GetLastChannel(); @@ -1366,17 +1561,12 @@ TEST_F(WebRtcVideoEngineTestFake, SetBandwidthInConference) { // Set send bandwidth. EXPECT_TRUE(channel_->SetMaxSendBandwidth(768000)); - // Verify bitrate not changed. - webrtc::VideoCodec gcodec; - EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec)); - EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate); - EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate); - EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate); - EXPECT_NE(768U, gcodec.minBitrate); - EXPECT_NE(768U, gcodec.startBitrate); - EXPECT_NE(768U, gcodec.maxBitrate); + // Verify that the max bitrate has changed. + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 0, + 768, kMinBandwidthKbps, kStartBandwidthKbps); } + // Test that sending screencast frames doesn't change bitrate. TEST_F(WebRtcVideoEngineTestFake, SetBandwidthScreencast) { EXPECT_TRUE(SetupEngine()); @@ -1528,26 +1718,28 @@ TEST_F(WebRtcVideoEngineTestFake, MultipleSendStreamsWithOneCapturer) { } -// Disabled since its flaky: b/11288120 -TEST_F(WebRtcVideoEngineTestFake, DISABLED_SendReceiveBitratesStats) { +TEST_F(WebRtcVideoEngineTestFake, SendReceiveBitratesStats) { EXPECT_TRUE(SetupEngine()); cricket::VideoOptions options; options.conference_mode.Set(true); EXPECT_TRUE(channel_->SetOptions(options)); EXPECT_TRUE(channel_->AddSendStream( cricket::StreamParams::CreateLegacy(1))); - int send_channel = vie_.GetLastChannel(); + int first_send_channel = vie_.GetLastChannel(); + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(2))); + int second_send_channel = vie_.GetLastChannel(); cricket::VideoCodec codec(kVP8Codec720p); std::vector codec_list; codec_list.push_back(codec); EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); EXPECT_TRUE(channel_->AddRecvStream( - cricket::StreamParams::CreateLegacy(2))); + cricket::StreamParams::CreateLegacy(3))); int first_receive_channel = vie_.GetLastChannel(); - EXPECT_NE(send_channel, first_receive_channel); + EXPECT_NE(first_send_channel, first_receive_channel); EXPECT_TRUE(channel_->AddRecvStream( - cricket::StreamParams::CreateLegacy(3))); + cricket::StreamParams::CreateLegacy(4))); int second_receive_channel = vie_.GetLastChannel(); EXPECT_NE(first_receive_channel, second_receive_channel); @@ -1562,21 +1754,20 @@ TEST_F(WebRtcVideoEngineTestFake, DISABLED_SendReceiveBitratesStats) { ASSERT_EQ(0, info.bw_estimations[0].target_enc_bitrate); // Start sending and receiving on one of the channels and verify bitrates. - EXPECT_EQ(0, vie_.StartSend(send_channel)); + EXPECT_EQ(0, vie_.StartSend(first_send_channel)); int send_video_bitrate = 800; int send_fec_bitrate = 100; int send_nack_bitrate = 20; int send_total_bitrate = send_video_bitrate + send_fec_bitrate + send_nack_bitrate; - int send_bandwidth = 950; - vie_.SetSendBitrates(send_channel, send_video_bitrate, send_fec_bitrate, + int send_bandwidth = 1900; + vie_.SetSendBitrates(first_send_channel, send_video_bitrate, send_fec_bitrate, send_nack_bitrate); - vie_.SetSendBandwidthEstimate(send_channel, send_bandwidth); + vie_.SetSendBandwidthEstimate(first_send_channel, send_bandwidth); EXPECT_EQ(0, vie_.StartReceive(first_receive_channel)); - int first_channel_receive_bandwidth = 600; - vie_.SetReceiveBandwidthEstimate(first_receive_channel, - first_channel_receive_bandwidth); + int receive_bandwidth = 600; + vie_.SetReceiveBandwidthEstimate(first_receive_channel, receive_bandwidth); info.Clear(); EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); @@ -1585,26 +1776,26 @@ TEST_F(WebRtcVideoEngineTestFake, DISABLED_SendReceiveBitratesStats) { ASSERT_EQ(send_total_bitrate, info.bw_estimations[0].transmit_bitrate); ASSERT_EQ(send_nack_bitrate, info.bw_estimations[0].retransmit_bitrate); ASSERT_EQ(send_bandwidth, info.bw_estimations[0].available_send_bandwidth); - ASSERT_EQ(first_channel_receive_bandwidth, - info.bw_estimations[0].available_recv_bandwidth); + ASSERT_EQ(receive_bandwidth, info.bw_estimations[0].available_recv_bandwidth); ASSERT_EQ(send_video_bitrate, info.bw_estimations[0].target_enc_bitrate); // Start receiving on the second channel and verify received rate. + EXPECT_EQ(0, vie_.StartSend(second_send_channel)); + vie_.SetSendBitrates(second_send_channel, + send_video_bitrate, + send_fec_bitrate, + send_nack_bitrate); EXPECT_EQ(0, vie_.StartReceive(second_receive_channel)); - int second_channel_receive_bandwidth = 100; - vie_.SetReceiveBandwidthEstimate(second_receive_channel, - second_channel_receive_bandwidth); info.Clear(); EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); ASSERT_EQ(1U, info.bw_estimations.size()); - ASSERT_EQ(send_video_bitrate, info.bw_estimations[0].actual_enc_bitrate); - ASSERT_EQ(send_total_bitrate, info.bw_estimations[0].transmit_bitrate); - ASSERT_EQ(send_nack_bitrate, info.bw_estimations[0].retransmit_bitrate); + ASSERT_EQ(2 * send_video_bitrate, info.bw_estimations[0].actual_enc_bitrate); + ASSERT_EQ(2 * send_total_bitrate, info.bw_estimations[0].transmit_bitrate); + ASSERT_EQ(2 * send_nack_bitrate, info.bw_estimations[0].retransmit_bitrate); ASSERT_EQ(send_bandwidth, info.bw_estimations[0].available_send_bandwidth); - ASSERT_EQ(first_channel_receive_bandwidth + second_channel_receive_bandwidth, - info.bw_estimations[0].available_recv_bandwidth); - ASSERT_EQ(send_video_bitrate, info.bw_estimations[0].target_enc_bitrate); + ASSERT_EQ(receive_bandwidth, info.bw_estimations[0].available_recv_bandwidth); + ASSERT_EQ(2 * send_video_bitrate, info.bw_estimations[0].target_enc_bitrate); } TEST_F(WebRtcVideoEngineTestFake, TestSetAdaptInputToCpuUsage) { @@ -2017,6 +2208,7 @@ TEST_F(WebRtcVideoEngineTest, FindCodec) { EXPECT_TRUE(engine_.FindCodec(fec)); cricket::VideoCodec rtx(96, "rtx", 0, 0, 30, 0); + rtx.SetParam("apt", kVP8Codec.id); EXPECT_TRUE(engine_.FindCodec(rtx)); } @@ -2113,7 +2305,7 @@ TEST_F(WebRtcVideoMediaChannelTest, SendManyResizeOnce) { SendManyResizeOnce(); } -TEST_F(WebRtcVideoMediaChannelTest, SendVp8HdAndReceiveAdaptedVp8Vga) { +TEST_F(WebRtcVideoMediaChannelTest, DISABLED_SendVp8HdAndReceiveAdaptedVp8Vga) { EXPECT_TRUE(channel_->SetCapturer(kSsrc, NULL)); channel_->UpdateAspectRatio(1280, 720); video_capturer_.reset(new cricket::FakeVideoCapturer); @@ -2135,13 +2327,19 @@ TEST_F(WebRtcVideoMediaChannelTest, SendVp8HdAndReceiveAdaptedVp8Vga) { EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout); } -// TODO(juberti): Fix this test to tolerate missing stats. +#ifdef USE_WEBRTC_DEV_BRANCH +TEST_F(WebRtcVideoMediaChannelTest, GetStats) { +#else TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStats) { +#endif Base::GetStats(); } -// TODO(juberti): Fix this test to tolerate missing stats. +#ifdef USE_WEBRTC_DEV_BRANCH +TEST_F(WebRtcVideoMediaChannelTest, GetStatsMultipleRecvStreams) { +#else TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStatsMultipleRecvStreams) { +#endif Base::GetStatsMultipleRecvStreams(); } @@ -2287,7 +2485,7 @@ TEST_F(WebRtcVideoMediaChannelTest, TwoStreamsReUseFirstStream) { 0)); } -TEST_F(WebRtcVideoMediaChannelTest, TwoStreamsSendAndUnsignalledRecv) { +TEST_F(WebRtcVideoMediaChannelTest, DISABLED_TwoStreamsSendAndUnsignalledRecv) { Base::TwoStreamsSendAndUnsignalledRecv(cricket::VideoCodec(100, "VP8", 640, 400, 30, 0)); } diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc index bf4cef6e0..7df478844 100644 --- a/talk/media/webrtc/webrtcvoiceengine.cc +++ b/talk/media/webrtc/webrtcvoiceengine.cc @@ -220,22 +220,6 @@ static bool IsNackEnabled(const AudioCodec& codec) { kParamValueEmpty)); } -// TODO(mallinath) - Remove this after trunk of webrtc is pushed to GTP. -#if !defined(USE_WEBRTC_DEV_BRANCH) -bool operator==(const webrtc::CodecInst& lhs, const webrtc::CodecInst& rhs) { - return lhs.pltype == rhs.pltype && - (_stricmp(lhs.plname, rhs.plname) == 0) && - lhs.plfreq == rhs.plfreq && - lhs.pacsize == rhs.pacsize && - lhs.channels == rhs.channels && - lhs.rate == rhs.rate; -} - -bool operator!=(const webrtc::CodecInst& lhs, const webrtc::CodecInst& rhs) { - return !(lhs == rhs); -} -#endif - // Gets the default set of options applied to the engine. Historically, these // were supplied as a combination of flags from the channel manager (ec, agc, // ns, and highpass) and the rest hardcoded in InitInternal. @@ -253,7 +237,7 @@ static AudioOptions GetDefaultEngineOptions() { options.experimental_aec.Set(false); options.experimental_ns.Set(false); options.aec_dump.Set(false); - options.experimental_acm.Set(false); + options.opus_fec.Set(false); return options; } @@ -357,7 +341,6 @@ WebRtcVoiceEngine::WebRtcVoiceEngine() log_filter_(SeverityToFilter(kDefaultLogSeverity)), is_dumping_aec_(false), desired_local_monitor_enable_(false), - use_experimental_acm_(false), tx_processor_ssrc_(0), rx_processor_ssrc_(0) { Construct(); @@ -375,7 +358,6 @@ WebRtcVoiceEngine::WebRtcVoiceEngine(VoEWrapper* voe_wrapper, log_filter_(SeverityToFilter(kDefaultLogSeverity)), is_dumping_aec_(false), desired_local_monitor_enable_(false), - use_experimental_acm_(false), tx_processor_ssrc_(0), rx_processor_ssrc_(0) { Construct(); @@ -402,16 +384,10 @@ void WebRtcVoiceEngine::Construct() { rtp_header_extensions_.push_back( RtpHeaderExtension(kRtpAudioLevelHeaderExtension, kRtpAudioLevelHeaderExtensionDefaultId)); -#ifdef USE_WEBRTC_DEV_BRANCH rtp_header_extensions_.push_back( RtpHeaderExtension(kRtpAbsoluteSenderTimeHeaderExtension, kRtpAbsoluteSenderTimeHeaderExtensionDefaultId)); -#endif options_ = GetDefaultEngineOptions(); - - // Initialize the VoE Configuration to the default ACM. - voe_config_.Set( - new webrtc::AudioCodingModuleFactory); } static bool IsOpus(const AudioCodec& codec) { @@ -424,12 +400,8 @@ static bool IsIsac(const AudioCodec& codec) { // True if params["stereo"] == "1" static bool IsOpusStereoEnabled(const AudioCodec& codec) { - CodecParameterMap::const_iterator param = - codec.params.find(kCodecParamStereo); - if (param == codec.params.end()) { - return false; - } - return param->second == kParamValueTrue; + int value; + return codec.GetParam(kCodecParamStereo, &value) && value == 1; } static bool IsValidOpusBitrate(int bitrate) { @@ -451,6 +423,22 @@ static int GetOpusBitrateFromParams(const AudioCodec& codec) { return bitrate; } +// Return true if params[kCodecParamUseInbandFec] == "1", false +// otherwise. +static bool IsOpusFecEnabled(const AudioCodec& codec) { + int value; + return codec.GetParam(kCodecParamUseInbandFec, &value) && value == 1; +} + +// Set params[kCodecParamUseInbandFec]. Caller should make sure codec is Opus. +static void SetOpusFec(AudioCodec* codec, bool opus_fec) { + if (opus_fec) { + codec->SetParam(kCodecParamUseInbandFec, 1); + } else { + codec->RemoveParam(kCodecParamUseInbandFec); + } +} + void WebRtcVoiceEngine::ConstructCodecs() { LOG(LS_INFO) << "WebRtc VoiceEngine codecs:"; int ncodecs = voe_wrapper_->codec()->NumOfCodecs(); @@ -495,6 +483,7 @@ void WebRtcVoiceEngine::ConstructCodecs() { } // TODO(hellner): Add ptime, sprop-stereo, stereo and useinbandfec // when they can be set to values other than the default. + SetOpusFec(&codec, false); } codecs_.push_back(codec); } else { @@ -750,12 +739,6 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { LOG(LS_INFO) << "Applying audio options: " << options.ToString(); - // Configure whether ACM1 or ACM2 is used. - bool enable_acm2 = false; - if (options.experimental_acm.Get(&enable_acm2)) { - EnableExperimentalAcm(enable_acm2); - } - webrtc::VoEAudioProcessing* voep = voe_wrapper_->processing(); bool echo_cancellation; @@ -833,7 +816,6 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { } } -#ifdef USE_WEBRTC_DEV_BRANCH bool experimental_ns; if (options.experimental_ns.Get(&experimental_ns)) { webrtc::AudioProcessing* audioproc = @@ -850,10 +832,10 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { << experimental_ns; } } -#endif // USE_WEBRTC_DEV_BRANCH bool highpass_filter; if (options.highpass_filter.Get(&highpass_filter)) { + LOG(LS_INFO) << "High pass filter enabled? " << highpass_filter; if (voep->EnableHighPassFilter(highpass_filter) == -1) { LOG_RTCERR1(SetHighpassFilterStatus, highpass_filter); return false; @@ -862,6 +844,7 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { bool stereo_swapping; if (options.stereo_swapping.Get(&stereo_swapping)) { + LOG(LS_INFO) << "Stereo swapping enabled? " << stereo_swapping; voep->EnableStereoChannelSwapping(stereo_swapping); if (voep->IsStereoChannelSwappingEnabled() != stereo_swapping) { LOG_RTCERR1(EnableStereoChannelSwapping, stereo_swapping); @@ -871,6 +854,7 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { bool typing_detection; if (options.typing_detection.Get(&typing_detection)) { + LOG(LS_INFO) << "Typing detection is enabled? " << typing_detection; if (voep->SetTypingDetectionStatus(typing_detection) == -1) { // In case of error, log the info and continue LOG_RTCERR1(SetTypingDetectionStatus, typing_detection); @@ -879,6 +863,7 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { int adjust_agc_delta; if (options.adjust_agc_delta.Get(&adjust_agc_delta)) { + LOG(LS_INFO) << "Adjust agc delta is " << adjust_agc_delta; if (!AdjustAgcLevel(adjust_agc_delta)) { return false; } @@ -886,6 +871,7 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { bool aec_dump; if (options.aec_dump.Get(&aec_dump)) { + LOG(LS_INFO) << "Aec dump is enabled? " << aec_dump; if (aec_dump) StartAecDump(kAecDumpByAudioOptionFilename); else @@ -894,6 +880,7 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { bool experimental_aec; if (options.experimental_aec.Get(&experimental_aec)) { + LOG(LS_INFO) << "Experimental aec is " << experimental_aec; webrtc::AudioProcessing* audioproc = voe_wrapper_->base()->audio_processing(); // We check audioproc for the benefit of tests, since FakeWebRtcVoiceEngine @@ -908,6 +895,7 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { uint32 recording_sample_rate; if (options.recording_sample_rate.Get(&recording_sample_rate)) { + LOG(LS_INFO) << "Recording sample rate is " << recording_sample_rate; if (voe_wrapper_->hw()->SetRecordingSampleRate(recording_sample_rate)) { LOG_RTCERR1(SetRecordingSampleRate, recording_sample_rate); } @@ -915,11 +903,21 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { uint32 playout_sample_rate; if (options.playout_sample_rate.Get(&playout_sample_rate)) { + LOG(LS_INFO) << "Playout sample rate is " << playout_sample_rate; if (voe_wrapper_->hw()->SetPlayoutSampleRate(playout_sample_rate)) { LOG_RTCERR1(SetPlayoutSampleRate, playout_sample_rate); } } + bool opus_fec; + if (options.opus_fec.Get(&opus_fec)) { + LOG(LS_INFO) << "Opus FEC is enabled? " << opus_fec; + for (std::vector::iterator it = codecs_.begin(); + it != codecs_.end(); ++it) { + if (IsOpus(*it)) + SetOpusFec(&(*it), opus_fec); + } + } return true; } @@ -1309,21 +1307,6 @@ bool WebRtcVoiceEngine::ShouldIgnoreTrace(const std::string& trace) { return false; } -void WebRtcVoiceEngine::EnableExperimentalAcm(bool enable) { - if (enable == use_experimental_acm_) - return; - if (enable) { - LOG(LS_INFO) << "VoiceEngine is set to use new ACM (ACM2 + NetEq4)."; - voe_config_.Set( - new webrtc::NewAudioCodingModuleFactory()); - } else { - LOG(LS_INFO) << "VoiceEngine is set to use legacy ACM (ACM1 + Neteq3)."; - voe_config_.Set( - new webrtc::AudioCodingModuleFactory()); - } - use_experimental_acm_ = enable; -} - void WebRtcVoiceEngine::Print(webrtc::TraceLevel level, const char* trace, int length) { talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE; @@ -1739,14 +1722,12 @@ class WebRtcVoiceMediaChannel::WebRtcVoiceChannelRenderer int sample_rate, int number_of_channels, int number_of_frames) OVERRIDE { -#ifdef USE_WEBRTC_DEV_BRANCH voe_audio_transport_->OnData(channel_, audio_data, bits_per_sample, sample_rate, number_of_channels, number_of_frames); -#endif } // Callback from the |renderer_| when it is going away. In case Start() has @@ -1986,10 +1967,16 @@ bool WebRtcVoiceMediaChannel::SetRecvCodecs( bool WebRtcVoiceMediaChannel::SetSendCodecs( int channel, const std::vector& codecs) { - // Disable VAD, and FEC unless we know the other side wants them. + // Disable VAD, FEC, and RED unless we know the other side wants them. engine()->voe()->codec()->SetVADStatus(channel, false); engine()->voe()->rtp()->SetNACKStatus(channel, false, 0); +#ifdef USE_WEBRTC_DEV_BRANCH + engine()->voe()->rtp()->SetREDStatus(channel, false); + engine()->voe()->codec()->SetFECStatus(channel, false); +#else + // TODO(minyue): Remove code under #else case after new WebRTC roll. engine()->voe()->rtp()->SetFECStatus(channel, false); +#endif // USE_WEBRTC_DEV_BRANCH // Scan through the list to figure out the codec to use for sending, along // with the proper configuration for VAD and DTMF. @@ -2048,11 +2035,24 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( if (bitrate_from_params != 0) { voe_codec.rate = bitrate_from_params; } + + // For Opus, we also enable inband FEC if it is requested. + if (IsOpusFecEnabled(*it)) { + LOG(LS_INFO) << "Enabling Opus FEC on channel " << channel; +#ifdef USE_WEBRTC_DEV_BRANCH + if (engine()->voe()->codec()->SetFECStatus(channel, true) == -1) { + // Enable in-band FEC of the Opus codec. Treat any failure as a fatal + // internal error. + LOG_RTCERR2(SetFECStatus, channel, true); + return false; + } +#endif // USE_WEBRTC_DEV_BRANCH + } } // We'll use the first codec in the list to actually send audio data. // Be sure to use the payload type requested by the remote side. - // "red", for FEC audio, is a special case where the actual codec to be + // "red", for RED audio, is a special case where the actual codec to be // used is specified in params. if (IsRedCodec(it->name)) { // Parse out the RED parameters. If we fail, just ignore RED; @@ -2063,9 +2063,16 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( // Enable redundant encoding of the specified codec. Treat any // failure as a fatal internal error. +#ifdef USE_WEBRTC_DEV_BRANCH + LOG(LS_INFO) << "Enabling RED on channel " << channel; + if (engine()->voe()->rtp()->SetREDStatus(channel, true, it->id) == -1) { + LOG_RTCERR3(SetREDStatus, channel, true, it->id); +#else + // TODO(minyue): Remove code under #else case after new WebRTC roll. LOG(LS_INFO) << "Enabling FEC"; if (engine()->voe()->rtp()->SetFECStatus(channel, true, it->id) == -1) { LOG_RTCERR3(SetFECStatus, channel, true, it->id); +#endif // USE_WEBRTC_DEV_BRANCH return false; } } else { @@ -2245,74 +2252,96 @@ bool WebRtcVoiceMediaChannel::SetSendCodec( bool WebRtcVoiceMediaChannel::SetRecvRtpHeaderExtensions( const std::vector& extensions) { -#ifdef USE_WEBRTC_DEV_BRANCH - const RtpHeaderExtension* send_time_extension = - FindHeaderExtension(extensions, kRtpAbsoluteSenderTimeHeaderExtension); + if (receive_extensions_ == extensions) { + return true; + } + + // The default channel may or may not be in |receive_channels_|. Set the rtp + // header extensions for default channel regardless. + if (!SetChannelRecvRtpHeaderExtensions(voe_channel(), extensions)) { + return false; + } // Loop through all receive channels and enable/disable the extensions. for (ChannelMap::const_iterator channel_it = receive_channels_.begin(); channel_it != receive_channels_.end(); ++channel_it) { - int channel_id = channel_it->second->channel(); - if (!SetHeaderExtension( - &webrtc::VoERTP_RTCP::SetReceiveAbsoluteSenderTimeStatus, channel_id, - send_time_extension)) { + if (!SetChannelRecvRtpHeaderExtensions(channel_it->second->channel(), + extensions)) { return false; } } -#endif + + receive_extensions_ = extensions; return true; } -bool WebRtcVoiceMediaChannel::SetSendRtpHeaderExtensions( - const std::vector& extensions) { +bool WebRtcVoiceMediaChannel::SetChannelRecvRtpHeaderExtensions( + int channel_id, const std::vector& extensions) { +#ifdef USE_WEBRTC_DEV_BRANCH const RtpHeaderExtension* audio_level_extension = FindHeaderExtension(extensions, kRtpAudioLevelHeaderExtension); -#ifdef USE_WEBRTC_DEV_BRANCH - const RtpHeaderExtension* send_time_extension = - FindHeaderExtension(extensions, kRtpAbsoluteSenderTimeHeaderExtension); -#endif - -#ifndef USE_WEBRTC_DEV_BRANCH if (!SetHeaderExtension( - &webrtc::VoERTP_RTCP::SetRTPAudioLevelIndicationStatus, voe_channel(), + &webrtc::VoERTP_RTCP::SetReceiveAudioLevelIndicationStatus, channel_id, audio_level_extension)) { return false; } -#else +#endif // USE_WEBRTC_DEV_BRANCH + + const RtpHeaderExtension* send_time_extension = + FindHeaderExtension(extensions, kRtpAbsoluteSenderTimeHeaderExtension); if (!SetHeaderExtension( - &webrtc::VoERTP_RTCP::SetSendAudioLevelIndicationStatus, voe_channel(), - audio_level_extension)) { + &webrtc::VoERTP_RTCP::SetReceiveAbsoluteSenderTimeStatus, channel_id, + send_time_extension)) { return false; } - if (!SetHeaderExtension( - &webrtc::VoERTP_RTCP::SetSendAbsoluteSenderTimeStatus, voe_channel(), - send_time_extension)) { + return true; +} + +bool WebRtcVoiceMediaChannel::SetSendRtpHeaderExtensions( + const std::vector& extensions) { + if (send_extensions_ == extensions) { + return true; + } + + // The default channel may or may not be in |send_channels_|. Set the rtp + // header extensions for default channel regardless. + + if (!SetChannelSendRtpHeaderExtensions(voe_channel(), extensions)) { return false; } -#endif + // Loop through all send channels and enable/disable the extensions. for (ChannelMap::const_iterator channel_it = send_channels_.begin(); channel_it != send_channels_.end(); ++channel_it) { - int channel_id = channel_it->second->channel(); -#ifndef USE_WEBRTC_DEV_BRANCH - if (!SetHeaderExtension( - &webrtc::VoERTP_RTCP::SetRTPAudioLevelIndicationStatus, channel_id, - audio_level_extension)) { - return false; - } -#else - if (!SetHeaderExtension( - &webrtc::VoERTP_RTCP::SetSendAudioLevelIndicationStatus, channel_id, - audio_level_extension)) { - return false; - } - if (!SetHeaderExtension( - &webrtc::VoERTP_RTCP::SetSendAbsoluteSenderTimeStatus, channel_id, - send_time_extension)) { + if (!SetChannelSendRtpHeaderExtensions(channel_it->second->channel(), + extensions)) { return false; } -#endif } + + send_extensions_ = extensions; + return true; +} + +bool WebRtcVoiceMediaChannel::SetChannelSendRtpHeaderExtensions( + int channel_id, const std::vector& extensions) { + const RtpHeaderExtension* audio_level_extension = + FindHeaderExtension(extensions, kRtpAudioLevelHeaderExtension); + + if (!SetHeaderExtension( + &webrtc::VoERTP_RTCP::SetSendAudioLevelIndicationStatus, channel_id, + audio_level_extension)) { + return false; + } + + const RtpHeaderExtension* send_time_extension = + FindHeaderExtension(extensions, kRtpAbsoluteSenderTimeHeaderExtension); + if (!SetHeaderExtension( + &webrtc::VoERTP_RTCP::SetSendAbsoluteSenderTimeStatus, channel_id, + send_time_extension)) { + return false; + } + return true; } @@ -2416,6 +2445,7 @@ bool WebRtcVoiceMediaChannel::ChangeSend(int channel, SendFlags send) { return true; } +// TODO(ronghuawu): Change this method to return bool. void WebRtcVoiceMediaChannel::ConfigureSendChannel(int channel) { if (engine()->voe()->network()->RegisterExternalTransport( channel, *this) == -1) { @@ -2427,6 +2457,9 @@ void WebRtcVoiceMediaChannel::ConfigureSendChannel(int channel) { // Reset all recv codecs; they will be enabled via SetRecvCodecs. ResetRecvCodecs(channel); + + // Set RTP header extension for the new channel. + SetChannelSendRtpHeaderExtensions(channel, send_extensions_); } bool WebRtcVoiceMediaChannel::DeleteChannel(int channel) { @@ -2474,12 +2507,8 @@ bool WebRtcVoiceMediaChannel::AddSendStream(const StreamParams& sp) { // Save the channel to send_channels_, so that RemoveSendStream() can still // delete the channel in case failure happens below. -#ifdef USE_WEBRTC_DEV_BRANCH webrtc::AudioTransport* audio_transport = engine()->voe()->base()->audio_transport(); -#else - webrtc::AudioTransport* audio_transport = NULL; -#endif send_channels_.insert(std::make_pair( sp.first_ssrc(), new WebRtcVoiceChannelRenderer(channel, audio_transport))); @@ -2576,12 +2605,8 @@ bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { // Reuse default channel for recv stream in non-conference mode call // when the default channel is not being used. -#ifdef USE_WEBRTC_DEV_BRANCH webrtc::AudioTransport* audio_transport = engine()->voe()->base()->audio_transport(); -#else - webrtc::AudioTransport* audio_transport = NULL; -#endif if (!InConferenceMode() && default_receive_ssrc_ == 0) { LOG(LS_INFO) << "Recv stream " << sp.first_ssrc() << " reuse default channel"; @@ -2671,6 +2696,11 @@ bool WebRtcVoiceMediaChannel::ConfigureRecvChannel(int channel) { } SetNack(channel, nack_enabled_); + // Set RTP header extension for the new channel. + if (!SetChannelRecvRtpHeaderExtensions(channel, receive_extensions_)) { + return false; + } + return SetPlayout(channel, playout_); } @@ -3287,6 +3317,12 @@ bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) { rinfo.fraction_lost = static_cast(cs.fractionLost) / (1 << 8); rinfo.packets_lost = cs.cumulativeLost; rinfo.ext_seqnum = cs.extendedMax; +#ifdef USE_WEBRTC_DEV_BRANCH + rinfo.capture_start_ntp_time_ms = cs.capture_start_ntp_time_ms_; +#endif + if (codec.pltype != -1) { + rinfo.codec_name = codec.plname; + } // Convert samples to milliseconds. if (codec.plfreq / 1000 > 0) { rinfo.jitter_ms = cs.jitterSamples / (codec.plfreq / 1000); @@ -3546,13 +3582,15 @@ VoiceMediaChannel::Error bool WebRtcVoiceMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter, int channel_id, const RtpHeaderExtension* extension) { bool enable = false; - unsigned char id = 0; + int id = 0; + std::string uri; if (extension) { enable = true; id = extension->id; + uri = extension->uri; } if ((engine()->voe()->rtp()->*setter)(channel_id, enable, id) != 0) { - LOG_RTCERR4(*setter, extension->uri, channel_id, enable, id); + LOG_RTCERR4(*setter, uri, channel_id, enable, id); return false; } return true; diff --git a/talk/media/webrtc/webrtcvoiceengine.h b/talk/media/webrtc/webrtcvoiceengine.h index 1721000db..efc388fc3 100644 --- a/talk/media/webrtc/webrtcvoiceengine.h +++ b/talk/media/webrtc/webrtcvoiceengine.h @@ -44,14 +44,12 @@ #include "talk/media/webrtc/webrtcvoe.h" #include "talk/session/media/channel.h" #include "webrtc/common.h" -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #if !defined(LIBPEERCONNECTION_LIB) && \ !defined(LIBPEERCONNECTION_IMPLEMENTATION) #error "Bogus include." #endif - namespace cricket { // WebRtcSoundclipStream is an adapter object that allows a memory stream to be @@ -201,9 +199,6 @@ class WebRtcVoiceEngine // allows us to selectively turn on and off different options easily // at any time. bool ApplyOptions(const AudioOptions& options); - // Configure for using ACM2, if |enable| is true, otherwise configure for - // ACM1. - void EnableExperimentalAcm(bool enable); virtual void Print(webrtc::TraceLevel level, const char* trace, int length); virtual void CallbackOnError(int channel, int errCode); // Given the device type, name, and id, find device id. Return true and @@ -261,7 +256,6 @@ class WebRtcVoiceEngine webrtc::AgcConfig default_agc_config_; webrtc::Config voe_config_; - bool use_experimental_acm_; bool initialized_; // See SetOptions and SetOptionOverrides for a description of the @@ -422,6 +416,13 @@ class WebRtcVoiceMediaChannel bool SetHeaderExtension(ExtensionSetterFunction setter, int channel_id, const RtpHeaderExtension* extension); + bool SetChannelRecvRtpHeaderExtensions( + int channel_id, + const std::vector& extensions); + bool SetChannelSendRtpHeaderExtensions( + int channel_id, + const std::vector& extensions); + talk_base::scoped_ptr ringback_tone_; std::set ringback_channels_; // channels playing ringback std::vector recv_codecs_; @@ -442,6 +443,7 @@ class WebRtcVoiceMediaChannel // When the default channel (voe_channel) is used for sending, it is // contained in send_channels_, otherwise not. ChannelMap send_channels_; + std::vector send_extensions_; uint32 default_receive_ssrc_; // Note the default channel (voe_channel()) can reside in both // receive_channels_ and send_channels_ in non-conference mode and in that @@ -451,6 +453,7 @@ class WebRtcVoiceMediaChannel // the WebRtc thread must be synchronized with edits on the worker thread. // Reads on the worker thread are ok. // + std::vector receive_extensions_; // Do not lock this on the VoE media processor thread; potential for deadlock // exists. mutable talk_base::CriticalSection receive_channels_cs_; diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc index 4dbeebaaa..58893b98a 100644 --- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc +++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc @@ -44,6 +44,9 @@ // Tests for the WebRtcVoiceEngine/VoiceChannel code. +using cricket::kRtpAudioLevelHeaderExtension; +using cricket::kRtpAbsoluteSenderTimeHeaderExtension; + static const cricket::AudioCodec kPcmuCodec(0, "PCMU", 8000, 64000, 1, 0); static const cricket::AudioCodec kIsacCodec(103, "ISAC", 16000, 32000, 1, 0); static const cricket::AudioCodec kCeltCodec(110, "CELT", 32000, 64000, 2, 0); @@ -136,17 +139,19 @@ class WebRtcVoiceEngineTestFake : public testing::Test { options_conference_.conference_mode.Set(true); options_adjust_agc_.adjust_agc_delta.Set(-10); } - bool SetupEngine() { - bool result = engine_.Init(talk_base::Thread::Current()); - if (result) { - channel_ = engine_.CreateChannel(); - result = (channel_ != NULL); + bool SetupEngineWithoutStream() { + if (!engine_.Init(talk_base::Thread::Current())) { + return false; } - if (result) { - result = channel_->AddSendStream( - cricket::StreamParams::CreateLegacy(kSsrc1)); + channel_ = engine_.CreateChannel(); + return (channel_ != NULL); + } + bool SetupEngine() { + if (!SetupEngineWithoutStream()) { + return false; } - return result; + return channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(kSsrc1)); } void SetupForMultiSendStream() { EXPECT_TRUE(SetupEngine()); @@ -246,98 +251,88 @@ class WebRtcVoiceEngineTestFake : public testing::Test { EXPECT_EQ(expected_bitrate, temp_codec.rate); } - - void TestSetSendRtpHeaderExtensions(int channel_id) { - std::vector extensions; + void TestSetSendRtpHeaderExtensions(const std::string& ext) { + EXPECT_TRUE(SetupEngineWithoutStream()); + int channel_num = voe_.GetLastChannel(); // Ensure extensions are off by default. - EXPECT_EQ(-1, voe_.GetSendAudioLevelId(channel_id)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetSendAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetSendRtpExtensionId(channel_num, ext)); + std::vector extensions; // Ensure unknown extensions won't cause an error. extensions.push_back(cricket::RtpHeaderExtension( "urn:ietf:params:unknownextention", 1)); EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); - EXPECT_EQ(-1, voe_.GetSendAudioLevelId(channel_id)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetSendAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetSendRtpExtensionId(channel_num, ext)); // Ensure extensions stay off with an empty list of headers. extensions.clear(); EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); - EXPECT_EQ(-1, voe_.GetSendAudioLevelId(channel_id)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetSendAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetSendRtpExtensionId(channel_num, ext)); - // Ensure audio levels are enabled if the audio-level header is specified - // (but AST is still off). - extensions.push_back(cricket::RtpHeaderExtension( - "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8)); + // Ensure extension is set properly. + const int id = 1; + extensions.push_back(cricket::RtpHeaderExtension(ext, id)); EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); - EXPECT_EQ(8, voe_.GetSendAudioLevelId(channel_id)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetSendAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(id, voe_.GetSendRtpExtensionId(channel_num, ext)); -#ifdef USE_WEBRTC_DEV_BRANCH - // Ensure audio level and AST are enabled if the extensions are specified. - extensions.push_back(cricket::RtpHeaderExtension( - "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", 12)); - EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); - EXPECT_EQ(8, voe_.GetSendAudioLevelId(channel_id)); - EXPECT_EQ(12, voe_.GetSendAbsoluteSenderTimeId(channel_id)); -#endif + // Ensure extension is set properly on new channel. + // The first stream to occupy the default channel. + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(123))); + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(234))); + int new_channel_num = voe_.GetLastChannel(); + EXPECT_NE(channel_num, new_channel_num); + EXPECT_EQ(id, voe_.GetSendRtpExtensionId(new_channel_num, ext)); // Ensure all extensions go back off with an empty list. extensions.clear(); EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); - EXPECT_EQ(-1, voe_.GetSendAudioLevelId(channel_id)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetSendAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetSendRtpExtensionId(channel_num, ext)); + EXPECT_EQ(-1, voe_.GetSendRtpExtensionId(new_channel_num, ext)); } - void TestSetRecvRtpHeaderExtensions(int channel_id) { - std::vector extensions; + void TestSetRecvRtpHeaderExtensions(const std::string& ext) { + EXPECT_TRUE(SetupEngineWithoutStream()); + int channel_num = voe_.GetLastChannel(); -#ifdef USE_WEBRTC_DEV_BRANCH // Ensure extensions are off by default. - EXPECT_EQ(-1, voe_.GetReceiveAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetReceiveRtpExtensionId(channel_num, ext)); + std::vector extensions; // Ensure unknown extensions won't cause an error. extensions.push_back(cricket::RtpHeaderExtension( "urn:ietf:params:unknownextention", 1)); EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetReceiveAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetReceiveRtpExtensionId(channel_num, ext)); - // An empty list shouldn't cause any headers to be enabled. + // Ensure extensions stay off with an empty list of headers. extensions.clear(); EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetReceiveAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetReceiveRtpExtensionId(channel_num, ext)); -#ifdef USE_WEBRTC_DEV_BRANCH - // Nor should indicating we can receive the absolute sender time header. - extensions.push_back(cricket::RtpHeaderExtension( - "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", 11)); + // Ensure extension is set properly. + const int id = 2; + extensions.push_back(cricket::RtpHeaderExtension(ext, id)); EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); - EXPECT_EQ(11, voe_.GetReceiveAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(id, voe_.GetReceiveRtpExtensionId(channel_num, ext)); - // Resetting to an empty list shouldn't cause any headers to be enabled. + // Ensure extension is set properly on new channel. + // The first stream to occupy the default channel. + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(345))); + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(456))); + int new_channel_num = voe_.GetLastChannel(); + EXPECT_NE(channel_num, new_channel_num); + EXPECT_EQ(id, voe_.GetReceiveRtpExtensionId(new_channel_num, ext)); + + // Ensure all extensions go back off with an empty list. extensions.clear(); EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); -#ifdef USE_WEBRTC_DEV_BRANCH - EXPECT_EQ(-1, voe_.GetReceiveAbsoluteSenderTimeId(channel_id)); -#endif + EXPECT_EQ(-1, voe_.GetReceiveRtpExtensionId(channel_num, ext)); + EXPECT_EQ(-1, voe_.GetReceiveRtpExtensionId(new_channel_num, ext)); } protected: @@ -750,7 +745,7 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecs) { EXPECT_EQ(48000, gcodec.rate); EXPECT_STREQ("ISAC", gcodec.plname); EXPECT_FALSE(voe_.GetVAD(channel_num)); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false)); EXPECT_EQ(105, voe_.GetSendCNPayloadType(channel_num, true)); EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num)); @@ -1149,6 +1144,106 @@ TEST_F(WebRtcVoiceEngineTestFake, AddRecvStreamEnableNack) { EXPECT_TRUE(voe_.GetNACK(channel_num)); } +#ifdef USE_WEBRTC_DEV_BRANCH +// Test that without useinbandfec, Opus FEC is off. +TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecNoOpusFec) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); +} + +// Test that with useinbandfec=0, Opus FEC is off. +TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusDisableFec) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].params["useinbandfec"] = "0"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + EXPECT_EQ(1, gcodec.channels); + EXPECT_EQ(32000, gcodec.rate); +} + +// Test that with useinbandfec=1, Opus FEC is on. +TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusEnableFec) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].params["useinbandfec"] = "1"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(voe_.GetCodecFEC(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + EXPECT_EQ(1, gcodec.channels); + EXPECT_EQ(32000, gcodec.rate); +} + +// Test that with useinbandfec=1, stereo=1, Opus FEC is on. +TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusEnableFecStereo) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].params["stereo"] = "1"; + codecs[0].params["useinbandfec"] = "1"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(voe_.GetCodecFEC(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + EXPECT_EQ(2, gcodec.channels); + EXPECT_EQ(64000, gcodec.rate); +} + +// Test that with non-Opus, codec FEC is off. +TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecIsacNoFec) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector codecs; + codecs.push_back(kIsacCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); +} +#endif // USE_WEBRTC_DEV_BRANCH + +// Test AudioOptions controls whether opus FEC is supported in codec list. +TEST_F(WebRtcVoiceEngineTestFake, OpusFecViaOptions) { + EXPECT_TRUE(SetupEngine()); + std::vector codecs = engine_.codecs(); + int value; + for (std::vector::const_iterator it = codecs.begin(); + it != codecs.end(); ++it) { + if (_stricmp(it->name.c_str(), cricket::kOpusCodecName) == 0) { + EXPECT_FALSE(it->GetParam(cricket::kCodecParamUseInbandFec, &value)); + } + } + + cricket::AudioOptions options; + options.opus_fec.Set(true); + EXPECT_TRUE(engine_.SetOptions(options)); + codecs = engine_.codecs(); + for (std::vector::const_iterator it = codecs.begin(); + it != codecs.end(); ++it) { + if (_stricmp(it->name.c_str(), cricket::kOpusCodecName) == 0) { + EXPECT_TRUE(it->GetParam(cricket::kCodecParamUseInbandFec, &value)); + EXPECT_EQ(1, value); + } + } +} + // Test that we can apply CELT with stereo mode but fail with mono mode. TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCelt) { EXPECT_TRUE(SetupEngine()); @@ -1320,7 +1415,7 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCNandDTMFAsCaller) { EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); EXPECT_TRUE(voe_.GetVAD(channel_num)); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false)); EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true)); EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num)); @@ -1353,7 +1448,7 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCNandDTMFAsCallee) { EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); EXPECT_TRUE(voe_.GetVAD(channel_num)); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false)); EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true)); EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num)); @@ -1417,13 +1512,13 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCaseInsensitive) { EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); EXPECT_TRUE(voe_.GetVAD(channel_num)); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false)); EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true)); EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num)); } -// Test that we set up FEC correctly as caller. +// Test that we set up RED correctly as caller. TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDAsCaller) { EXPECT_TRUE(SetupEngine()); int channel_num = voe_.GetLastChannel(); @@ -1439,11 +1534,11 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDAsCaller) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_TRUE(voe_.GetFEC(channel_num)); - EXPECT_EQ(127, voe_.GetSendFECPayloadType(channel_num)); + EXPECT_TRUE(voe_.GetRED(channel_num)); + EXPECT_EQ(127, voe_.GetSendREDPayloadType(channel_num)); } -// Test that we set up FEC correctly as callee. +// Test that we set up RED correctly as callee. TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDAsCallee) { EXPECT_TRUE(engine_.Init(talk_base::Thread::Current())); channel_ = engine_.CreateChannel(); @@ -1464,11 +1559,11 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDAsCallee) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_TRUE(voe_.GetFEC(channel_num)); - EXPECT_EQ(127, voe_.GetSendFECPayloadType(channel_num)); + EXPECT_TRUE(voe_.GetRED(channel_num)); + EXPECT_EQ(127, voe_.GetSendREDPayloadType(channel_num)); } -// Test that we set up FEC correctly if params are omitted. +// Test that we set up RED correctly if params are omitted. TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDNoParams) { EXPECT_TRUE(SetupEngine()); int channel_num = voe_.GetLastChannel(); @@ -1483,8 +1578,8 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDNoParams) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_TRUE(voe_.GetFEC(channel_num)); - EXPECT_EQ(127, voe_.GetSendFECPayloadType(channel_num)); + EXPECT_TRUE(voe_.GetRED(channel_num)); + EXPECT_EQ(127, voe_.GetSendREDPayloadType(channel_num)); } // Test that we ignore RED if the parameters aren't named the way we expect. @@ -1503,7 +1598,7 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED1) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); } // Test that we ignore RED if it uses different primary/secondary encoding. @@ -1522,7 +1617,7 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED2) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); } // Test that we ignore RED if it uses more than 2 encodings. @@ -1541,7 +1636,7 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED3) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); } // Test that we ignore RED if it has bogus codec ids. @@ -1560,7 +1655,7 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED4) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); } // Test that we ignore RED if it refers to a codec that is not present. @@ -1579,20 +1674,25 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED5) { EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); EXPECT_EQ(96, gcodec.pltype); EXPECT_STREQ("ISAC", gcodec.plname); - EXPECT_FALSE(voe_.GetFEC(channel_num)); + EXPECT_FALSE(voe_.GetRED(channel_num)); } -// Test that we support setting certain send header extensions. -TEST_F(WebRtcVoiceEngineTestFake, SetSendRtpHeaderExtensions) { - EXPECT_TRUE(SetupEngine()); - TestSetSendRtpHeaderExtensions(voe_.GetLastChannel()); +// Test support for audio level header extension. +TEST_F(WebRtcVoiceEngineTestFake, SendAudioLevelHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpAudioLevelHeaderExtension); } +#ifdef USE_WEBRTC_DEV_BRANCH +TEST_F(WebRtcVoiceEngineTestFake, RecvAudioLevelHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpAudioLevelHeaderExtension); +} +#endif // USE_WEBRTC_DEV_BRANCH -// Test that we support setting recv header extensions. -TEST_F(WebRtcVoiceEngineTestFake, SetRecvRtpHeaderExtensions) { - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2))); - TestSetRecvRtpHeaderExtensions(voe_.GetLastChannel()); +// Test support for absolute send time header extension. +TEST_F(WebRtcVoiceEngineTestFake, SendAbsoluteSendTimeHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension); +} +TEST_F(WebRtcVoiceEngineTestFake, RecvAbsoluteSendTimeHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension); } // Test that we can create a channel and start sending/playing out on it. @@ -1726,11 +1826,15 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStatsWithMultipleSendStreams) { EXPECT_TRUE(channel_->AddSendStream( cricket::StreamParams::CreateLegacy(kSsrcs4[i]))); } - + // Create a receive stream to check that none of the send streams end up in + // the receive stream stats. + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrc2))); // We need send codec to be set to get all stats. std::vector codecs; codecs.push_back(kPcmuCodec); EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); cricket::VoiceMediaInfo info; EXPECT_EQ(true, channel_->GetStats(&info)); @@ -1747,44 +1851,19 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStatsWithMultipleSendStreams) { EXPECT_EQ(cricket::kIntStatValue, info.senders[i].ext_seqnum); EXPECT_EQ(cricket::kIntStatValue, info.senders[i].rtt_ms); EXPECT_EQ(cricket::kIntStatValue, info.senders[i].jitter_ms); + EXPECT_EQ(kPcmuCodec.name, info.senders[i].codec_name); } - EXPECT_EQ(1u, info.receivers.size()); -} - -// Test that we support setting header extensions on multiple send streams. -TEST_F(WebRtcVoiceEngineTestFake, - SetSendRtpHeaderExtensionsWithMultipleSendStreams) { - SetupForMultiSendStream(); - - static const uint32 kSsrcs[] = {1, 2, 3, 4}; - for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs); ++i) { - EXPECT_TRUE(channel_->AddSendStream( - cricket::StreamParams::CreateLegacy(kSsrcs[i]))); - } - - for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs); ++i) { - int channel_num = voe_.GetChannelFromLocalSsrc(kSsrcs[i]); - TestSetSendRtpHeaderExtensions(channel_num); - } -} - -// Test that we support setting header extensions on multiple receive streams. -TEST_F(WebRtcVoiceEngineTestFake, - SetRecvRtpHeaderExtensionsWithMultipleRecvStreams) { - EXPECT_TRUE(SetupEngine()); - - static const uint32 kSsrcs[] = {1, 2, 3, 4}; - int channel_ids[ARRAY_SIZE(kSsrcs)] = {0}; - for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs); ++i) { - EXPECT_TRUE(channel_->AddRecvStream( - cricket::StreamParams::CreateLegacy(kSsrcs[i]))); - channel_ids[i] = voe_.GetLastChannel(); - } + EXPECT_EQ(0u, info.receivers.size()); + DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame)); + EXPECT_EQ(true, channel_->GetStats(&info)); - for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs); ++i) { - TestSetRecvRtpHeaderExtensions(channel_ids[i]); - } + EXPECT_EQ(1u, info.receivers.size()); + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].bytes_rcvd); + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].packets_rcvd); + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].packets_lost); + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].ext_seqnum); + EXPECT_EQ(kPcmuCodec.name, info.receivers[0].codec_name); } // Test that we can add and remove receive streams, and do proper send/playout. @@ -2071,9 +2150,14 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrc) { TEST_F(WebRtcVoiceEngineTestFake, GetStats) { // Setup. We need send codec to be set to get all stats. EXPECT_TRUE(SetupEngine()); + // SetupEngine adds a send stream with kSsrc1, so the receive stream has to + // use a different SSRC. + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrc2))); std::vector codecs; codecs.push_back(kPcmuCodec); EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); cricket::VoiceMediaInfo info; EXPECT_EQ(true, channel_->GetStats(&info)); @@ -2087,6 +2171,7 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStats) { EXPECT_EQ(cricket::kIntStatValue, info.senders[0].ext_seqnum); EXPECT_EQ(cricket::kIntStatValue, info.senders[0].rtt_ms); EXPECT_EQ(cricket::kIntStatValue, info.senders[0].jitter_ms); + EXPECT_EQ(kPcmuCodec.name, info.senders[0].codec_name); // TODO(sriniv): Add testing for more fields. These are not populated // in FakeWebrtcVoiceEngine yet. // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].audio_level); @@ -2096,8 +2181,17 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStats) { // EXPECT_EQ(cricket::kIntStatValue, // info.senders[0].echo_return_loss_enhancement); + EXPECT_EQ(0u, info.receivers.size()); + DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame)); + EXPECT_EQ(true, channel_->GetStats(&info)); EXPECT_EQ(1u, info.receivers.size()); - // TODO(sriniv): Add testing for receiver fields. + + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].bytes_rcvd); + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].packets_rcvd); + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].packets_lost); + EXPECT_EQ(cricket::kIntStatValue, info.receivers[0].ext_seqnum); + EXPECT_EQ(kPcmuCodec.name, info.receivers[0].codec_name); + // TODO(sriniv): Add testing for more receiver fields. } // Test that we can set the outgoing SSRC properly with multiple streams. @@ -2686,7 +2780,6 @@ TEST_F(WebRtcVoiceEngineTestFake, InitDoesNotOverwriteDefaultAgcConfig) { EXPECT_EQ(set_config.limiterEnable, config.limiterEnable); } - TEST_F(WebRtcVoiceEngineTestFake, SetOptionOverridesViaChannels) { EXPECT_TRUE(SetupEngine()); talk_base::scoped_ptr channel1( @@ -2886,7 +2979,6 @@ TEST_F(WebRtcVoiceEngineTestFake, SetOutputScaling) { EXPECT_DOUBLE_EQ(1, right); } - // Tests for the actual WebRtc VoE library. // Tests that the library initializes and shuts down properly. @@ -3085,39 +3177,3 @@ TEST(WebRtcVoiceEngineTest, CoInitialize) { } #endif - -TEST_F(WebRtcVoiceEngineTestFake, SetExperimentalAcm) { - EXPECT_TRUE(SetupEngine()); - - // By default experimental ACM should not be used. - int media_channel = engine_.CreateMediaVoiceChannel(); - ASSERT_GE(media_channel, 0); - EXPECT_FALSE(voe_.IsUsingExperimentalAcm(media_channel)); - - int soundclip_channel = engine_.CreateSoundclipVoiceChannel(); - ASSERT_GE(soundclip_channel, 0); - EXPECT_FALSE(voe_sc_.IsUsingExperimentalAcm(soundclip_channel)); - - // Set options to use experimental ACM. - cricket::AudioOptions options; - options.experimental_acm.Set(true); - ASSERT_TRUE(engine_.SetOptions(options)); - media_channel = engine_.CreateMediaVoiceChannel(); - ASSERT_GE(media_channel, 0); - EXPECT_TRUE(voe_.IsUsingExperimentalAcm(media_channel)); - - soundclip_channel = engine_.CreateSoundclipVoiceChannel(); - ASSERT_GE(soundclip_channel, 0); - EXPECT_TRUE(voe_sc_.IsUsingExperimentalAcm(soundclip_channel)); - - // Set option to use legacy ACM. - options.experimental_acm.Set(false); - ASSERT_TRUE(engine_.SetOptions(options)); - media_channel = engine_.CreateMediaVoiceChannel(); - ASSERT_GE(media_channel, 0); - EXPECT_FALSE(voe_.IsUsingExperimentalAcm(media_channel)); - - soundclip_channel = engine_.CreateSoundclipVoiceChannel(); - ASSERT_GE(soundclip_channel, 0); - EXPECT_FALSE(voe_sc_.IsUsingExperimentalAcm(soundclip_channel)); -} diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc index 14cac8802..2e57d9d18 100644 --- a/talk/p2p/base/constants.cc +++ b/talk/p2p/base/constants.cc @@ -176,6 +176,10 @@ const int ICE_UFRAG_LENGTH = 16; // Minimum password length of 22 characters as per RFC5245. We chose 24 because // some internal systems expect password to be multiple of 4. const int ICE_PWD_LENGTH = 24; +const size_t ICE_UFRAG_MIN_LENGTH = 4; +const size_t ICE_PWD_MIN_LENGTH = 22; +const size_t ICE_UFRAG_MAX_LENGTH = 255; +const size_t ICE_PWD_MAX_LENGTH = 256; // TODO: This is media-specific, so might belong // somewhere like media/base/constants.h const int ICE_CANDIDATE_COMPONENT_RTP = 1; diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h index 99e006a01..61dd815b3 100644 --- a/talk/p2p/base/constants.h +++ b/talk/p2p/base/constants.h @@ -178,6 +178,10 @@ extern const char ICE_CANDIDATE_TYPE_PEER_STUN[]; extern const char ICE_CANDIDATE_TYPE_SERVER_STUN[]; extern const int ICE_UFRAG_LENGTH; extern const int ICE_PWD_LENGTH; +extern const size_t ICE_UFRAG_MIN_LENGTH; +extern const size_t ICE_PWD_MIN_LENGTH; +extern const size_t ICE_UFRAG_MAX_LENGTH; +extern const size_t ICE_PWD_MAX_LENGTH; extern const int ICE_CANDIDATE_COMPONENT_RTP; extern const int ICE_CANDIDATE_COMPONENT_RTCP; extern const int ICE_CANDIDATE_COMPONENT_DEFAULT; diff --git a/talk/p2p/base/dtlstransportchannel.cc b/talk/p2p/base/dtlstransportchannel.cc index 30cd80e70..416e6e932 100644 --- a/talk/p2p/base/dtlstransportchannel.cc +++ b/talk/p2p/base/dtlstransportchannel.cc @@ -156,14 +156,15 @@ void DtlsTransportChannelWrapper::Reset() { bool DtlsTransportChannelWrapper::SetLocalIdentity( talk_base::SSLIdentity* identity) { - if (dtls_state_ == STATE_OPEN && identity == local_identity_) { - return true; - } - - // TODO(ekr@rtfm.com): Forbid this if Connect() has been called. if (dtls_state_ != STATE_NONE) { - LOG_J(LS_ERROR, this) << "Can't set DTLS local identity in this state"; - return false; + if (identity == local_identity_) { + // This may happen during renegotiation. + LOG_J(LS_INFO, this) << "Ignoring identical DTLS identity"; + return true; + } else { + LOG_J(LS_ERROR, this) << "Can't change DTLS local identity in this state"; + return false; + } } if (identity) { @@ -210,8 +211,11 @@ bool DtlsTransportChannelWrapper::SetRemoteFingerprint( talk_base::Buffer remote_fingerprint_value(digest, digest_len); - if ((dtls_state_ == STATE_OPEN) && - (remote_fingerprint_value_ == remote_fingerprint_value)) { + if (dtls_state_ != STATE_NONE && + remote_fingerprint_value_ == remote_fingerprint_value && + !digest_alg.empty()) { + // This may happen during renegotiation. + LOG_J(LS_INFO, this) << "Ignoring identical remote DTLS fingerprint"; return true; } @@ -363,7 +367,7 @@ int DtlsTransportChannelWrapper::SendPacket( if (flags & PF_SRTP_BYPASS) { ASSERT(!srtp_ciphers_.empty()); if (!IsRtpPacket(data, size)) { - result = false; + result = -1; break; } diff --git a/talk/p2p/base/dtlstransportchannel_unittest.cc b/talk/p2p/base/dtlstransportchannel_unittest.cc index cdab3321a..5727ac4cf 100644 --- a/talk/p2p/base/dtlstransportchannel_unittest.cc +++ b/talk/p2p/base/dtlstransportchannel_unittest.cc @@ -254,6 +254,17 @@ class DtlsTestClient : public sigslot::has_slots<> { } while (sent < count); } + int SendInvalidSrtpPacket(size_t channel, size_t size) { + ASSERT(channel < channels_.size()); + talk_base::scoped_ptr packet(new char[size]); + // Fill the packet with 0 to form an invalid SRTP packet. + memset(packet.get(), 0, size); + + talk_base::PacketOptions packet_options; + return channels_[channel]->SendPacket( + packet.get(), size, packet_options, cricket::PF_SRTP_BYPASS); + } + void ExpectPackets(size_t channel, size_t size) { packet_size_ = size; received_.clear(); @@ -624,6 +635,16 @@ TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtp) { TestTransfer(0, 1000, 100, true); } +// Connect with DTLS-SRTP, transfer an invalid SRTP packet, and expects -1 +// returned. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsInvalidSrtpPacket) { + MAYBE_SKIP_TEST(HaveDtls); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect()); + int result = client1_.SendInvalidSrtpPacket(0, 100); + ASSERT_EQ(-1, result); +} // Connect with DTLS. A does DTLS-SRTP but B does not. TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpRejected) { @@ -755,6 +776,25 @@ TEST_F(DtlsTransportChannelTest, TestDtlsReOfferWithDifferentSetupAttr) { TestTransfer(1, 1000, 100, true); } +// Test that re-negotiation can be started before the clients become connected +// in the first negotiation. +TEST_F(DtlsTransportChannelTest, TestRenegotiateBeforeConnect) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + Negotiate(); + + Renegotiate(&client1_, cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER); + bool rv = client1_.Connect(&client2_); + EXPECT_TRUE(rv); + EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000); + + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + // Test Certificates state after negotiation but before connection. TEST_F(DtlsTransportChannelTest, TestCertificatesBeforeConnect) { MAYBE_SKIP_TEST(HaveDtls); diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc index e7f9d45f0..8a3ec7ff7 100644 --- a/talk/p2p/base/p2ptransportchannel.cc +++ b/talk/p2p/base/p2ptransportchannel.cc @@ -259,7 +259,8 @@ void P2PTransportChannel::SetIceCredentials(const std::string& ice_ufrag, if (!ice_ufrag_.empty() && !ice_pwd_.empty()) { // Restart candidate allocation if there is any change in either // ice ufrag or password. - ice_restart = (ice_ufrag_ != ice_ufrag) || (ice_pwd_!= ice_pwd); + ice_restart = + IceCredentialsChanged(ice_ufrag_, ice_pwd_, ice_ufrag, ice_pwd); } ice_ufrag_ = ice_ufrag; @@ -470,7 +471,6 @@ void P2PTransportChannel::OnUnknownAddress( } } else { // Create a new candidate with this address. - std::string type; if (port->IceProtocol() == ICEPROTO_RFC5245) { type = PRFLX_PORT_TYPE; diff --git a/talk/p2p/base/p2ptransportchannel_unittest.cc b/talk/p2p/base/p2ptransportchannel_unittest.cc index 566fd14a2..498fde9dc 100644 --- a/talk/p2p/base/p2ptransportchannel_unittest.cc +++ b/talk/p2p/base/p2ptransportchannel_unittest.cc @@ -42,6 +42,7 @@ #include "talk/p2p/base/p2ptransportchannel.h" #include "talk/p2p/base/testrelayserver.h" #include "talk/p2p/base/teststunserver.h" +#include "talk/p2p/base/testturnserver.h" #include "talk/p2p/client/basicportallocator.h" using cricket::kDefaultPortAllocatorFlags; @@ -93,6 +94,11 @@ static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002); static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003); static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004); static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005); +// The addresses for the public turn server. +static const SocketAddress kTurnUdpIntAddr("99.99.99.4", + cricket::STUN_SERVER_PORT); +static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0); +static const cricket::RelayCredentials kRelayCredentials("test", "test"); // Based on ICE_UFRAG_LENGTH static const char* kIceUfrag[4] = {"TESTICEUFRAG0000", "TESTICEUFRAG0001", @@ -133,6 +139,7 @@ class P2PTransportChannelTestBase : public testing::Test, ss_(new talk_base::FirewallSocketServer(nss_.get())), ss_scope_(ss_.get()), stun_server_(main_, kStunAddr), + turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr), relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr, kRelayTcpIntAddr, kRelayTcpExtAddr, kRelaySslTcpIntAddr, kRelaySslTcpExtAddr), @@ -140,9 +147,11 @@ class P2PTransportChannelTestBase : public testing::Test, ss_.get(), kSocksProxyAddrs[0]), socks_server2_(ss_.get(), kSocksProxyAddrs[1], ss_.get(), kSocksProxyAddrs[1]), - clear_remote_candidates_ufrag_pwd_(false) { + clear_remote_candidates_ufrag_pwd_(false), + force_relay_(false) { ep1_.role_ = cricket::ICEROLE_CONTROLLING; ep2_.role_ = cricket::ICEROLE_CONTROLLED; + ep1_.allocator_.reset(new cricket::BasicPortAllocator( &ep1_.network_manager_, kStunAddr, kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr)); @@ -238,7 +247,7 @@ class P2PTransportChannelTestBase : public testing::Test, } talk_base::FakeNetworkManager network_manager_; - talk_base::scoped_ptr allocator_; + talk_base::scoped_ptr allocator_; ChannelData cd1_; ChannelData cd2_; int signaling_delay_; @@ -482,13 +491,14 @@ class P2PTransportChannelTestBase : public testing::Test, // i.e. when don't match its remote type is either local or stun. // TODO(ronghuawu): Refine the test criteria. // https://code.google.com/p/webrtc/issues/detail?id=1953 - if (expected.remote_type2 != RemoteCandidate(ep2_ch1())->type()) + if (expected.remote_type2 != RemoteCandidate(ep2_ch1())->type()) { EXPECT_TRUE(expected.remote_type2 == cricket::LOCAL_PORT_TYPE || expected.remote_type2 == cricket::STUN_PORT_TYPE); EXPECT_TRUE( RemoteCandidate(ep2_ch1())->type() == cricket::LOCAL_PORT_TYPE || RemoteCandidate(ep2_ch1())->type() == cricket::STUN_PORT_TYPE || RemoteCandidate(ep2_ch1())->type() == cricket::PRFLX_PORT_TYPE); + } } converge_time = talk_base::TimeSince(converge_start); @@ -638,6 +648,9 @@ class P2PTransportChannelTestBase : public testing::Test, // We pass the candidates directly to the other side. void OnCandidate(cricket::TransportChannelImpl* ch, const cricket::Candidate& c) { + if (force_relay_ && c.type() != cricket::RELAY_PORT_TYPE) + return; + main_->PostDelayed(GetEndpoint(ch)->signaling_delay_, this, 0, new CandidateData(ch, c)); } @@ -718,6 +731,10 @@ class P2PTransportChannelTestBase : public testing::Test, clear_remote_candidates_ufrag_pwd_ = clear; } + void set_force_relay(bool relay) { + force_relay_ = relay; + } + private: talk_base::Thread* main_; talk_base::scoped_ptr pss_; @@ -726,12 +743,14 @@ class P2PTransportChannelTestBase : public testing::Test, talk_base::scoped_ptr ss_; talk_base::SocketServerScope ss_scope_; cricket::TestStunServer stun_server_; + cricket::TestTurnServer turn_server_; cricket::TestRelayServer relay_server_; talk_base::SocksProxyServer socks_server1_; talk_base::SocksProxyServer socks_server2_; Endpoint ep1_; Endpoint ep2_; bool clear_remote_candidates_ufrag_pwd_; + bool force_relay_; }; // The tests have only a few outcomes, which we predefine. @@ -784,6 +803,35 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase { int allocator_flags1, int allocator_flags2, int delay1, int delay2, cricket::IceProtocolType type) { + // Ideally we want to use TURN server for both GICE and ICE, but in case + // of GICE, TURN server usage is not producing results reliabally. + // TODO(mallinath): Remove Relay and use TURN server for all tests. + GetEndpoint(0)->allocator_.reset( + new cricket::BasicPortAllocator(&(GetEndpoint(0)->network_manager_), + kStunAddr, talk_base::SocketAddress(), talk_base::SocketAddress(), + talk_base::SocketAddress())); + GetEndpoint(1)->allocator_.reset( + new cricket::BasicPortAllocator(&(GetEndpoint(1)->network_manager_), + kStunAddr, talk_base::SocketAddress(), talk_base::SocketAddress(), + talk_base::SocketAddress())); + + cricket::RelayServerConfig relay_server(cricket::RELAY_GTURN); + if (type == cricket::ICEPROTO_RFC5245) { + relay_server.type = cricket::RELAY_TURN; + relay_server.credentials = kRelayCredentials; + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + } else { + relay_server.ports.push_back(cricket::ProtocolAddress( + kRelayUdpIntAddr, cricket::PROTO_UDP, false)); + relay_server.ports.push_back(cricket::ProtocolAddress( + kRelayTcpIntAddr, cricket::PROTO_TCP, false)); + relay_server.ports.push_back(cricket::ProtocolAddress( + kRelaySslTcpIntAddr, cricket::PROTO_SSLTCP, false)); + } + GetEndpoint(0)->allocator_->AddRelay(relay_server); + GetEndpoint(1)->allocator_->AddRelay(relay_server); + ConfigureEndpoint(0, config1); SetIceProtocol(0, type); SetAllocatorFlags(0, allocator_flags1); @@ -1482,7 +1530,8 @@ TEST_F(P2PTransportChannelTest, TestDefaultDscpValue) { } // Verify IPv6 connection is preferred over IPv4. -TEST_F(P2PTransportChannelTest, TestIPv6Connections) { +// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3317 +TEST_F(P2PTransportChannelTest, DISABLED_TestIPv6Connections) { AddAddress(0, kIPv6PublicAddrs[0]); AddAddress(0, kPublicAddrs[0]); AddAddress(1, kIPv6PublicAddrs[1]); @@ -1509,6 +1558,42 @@ TEST_F(P2PTransportChannelTest, TestIPv6Connections) { DestroyChannels(); } +// Testing forceful TURN connections. +TEST_F(P2PTransportChannelTest, TestForceTurn) { + ConfigureEndpoints(NAT_PORT_RESTRICTED, NAT_SYMMETRIC, + kDefaultPortAllocatorFlags | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kDefaultPortAllocatorFlags | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_RFC5245); + set_force_relay(true); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + CreateChannels(1); + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && + ep1_ch1()->writable() && + ep2_ch1()->readable() && + ep2_ch1()->writable(), + 1000); + + EXPECT_TRUE(ep1_ch1()->best_connection() && + ep2_ch1()->best_connection()); + + EXPECT_EQ("relay", RemoteCandidate(ep1_ch1())->type()); + EXPECT_EQ("relay", LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ("relay", RemoteCandidate(ep2_ch1())->type()); + EXPECT_EQ("relay", LocalCandidate(ep2_ch1())->type()); + + TestSendRecv(1); + DestroyChannels(); +} + // Test what happens when we have 2 users behind the same NAT. This can lead // to interesting behavior because the STUN server will only give out the // address of the outermost NAT. diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc index 38157f4c4..ad692cec1 100644 --- a/talk/p2p/base/port.cc +++ b/talk/p2p/base/port.cc @@ -248,6 +248,7 @@ Connection* Port::GetConnection(const talk_base::SocketAddress& remote_addr) { void Port::AddAddress(const talk_base::SocketAddress& address, const talk_base::SocketAddress& base_address, + const talk_base::SocketAddress& related_address, const std::string& protocol, const std::string& type, uint32 type_preference, @@ -263,7 +264,7 @@ void Port::AddAddress(const talk_base::SocketAddress& address, c.set_password(password_); c.set_network_name(network_->name()); c.set_generation(generation_); - c.set_related_address(related_address_); + c.set_related_address(related_address); c.set_foundation(ComputeFoundation(type, protocol, base_address)); candidates_.push_back(c); SignalCandidateReady(this, c); diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h index ff68dd031..6e5c383b7 100644 --- a/talk/p2p/base/port.h +++ b/talk/p2p/base/port.h @@ -172,13 +172,6 @@ class Port : public PortInterface, public talk_base::MessageHandler, send_retransmit_count_attribute_ = enable; } - const talk_base::SocketAddress& related_address() const { - return related_address_; - } - void set_related_address(const talk_base::SocketAddress& address) { - related_address_ = address; - } - // Identifies the generation that this port was created in. uint32 generation() { return generation_; } void set_generation(uint32 generation) { generation_ = generation; } @@ -315,6 +308,7 @@ class Port : public PortInterface, public talk_base::MessageHandler, // Fills in the local address of the port. void AddAddress(const talk_base::SocketAddress& address, const talk_base::SocketAddress& base_address, + const talk_base::SocketAddress& related_address, const std::string& protocol, const std::string& type, uint32 type_preference, bool final); @@ -365,7 +359,6 @@ class Port : public PortInterface, public talk_base::MessageHandler, std::string content_name_; int component_; uint32 generation_; - talk_base::SocketAddress related_address_; // In order to establish a connection to this Port (so that real data can be // sent through), the other side must send us a STUN binding request that is // authenticated with this username_fragment and password. diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc index a6365d54e..fc6d48c9a 100644 --- a/talk/p2p/base/port_unittest.cc +++ b/talk/p2p/base/port_unittest.cc @@ -146,19 +146,21 @@ class TestPort : public Port { virtual void PrepareAddress() { talk_base::SocketAddress addr(ip(), min_port()); - AddAddress(addr, addr, "udp", Type(), ICE_TYPE_PREFERENCE_HOST, true); + AddAddress(addr, addr, talk_base::SocketAddress(), "udp", Type(), + ICE_TYPE_PREFERENCE_HOST, true); } // Exposed for testing candidate building. void AddCandidateAddress(const talk_base::SocketAddress& addr) { - AddAddress(addr, addr, "udp", Type(), type_preference_, false); + AddAddress(addr, addr, talk_base::SocketAddress(), "udp", Type(), + type_preference_, false); } void AddCandidateAddress(const talk_base::SocketAddress& addr, const talk_base::SocketAddress& base_address, const std::string& type, int type_preference, bool final) { - AddAddress(addr, base_address, "udp", type, + AddAddress(addr, base_address, talk_base::SocketAddress(), "udp", type, type_preference, final); } @@ -468,10 +470,17 @@ class PortTest : public testing::Test, public sigslot::has_slots<> { TurnPort* CreateTurnPort(const SocketAddress& addr, PacketSocketFactory* socket_factory, ProtocolType int_proto, ProtocolType ext_proto) { + return CreateTurnPort(addr, socket_factory, + int_proto, ext_proto, kTurnUdpIntAddr); + } + TurnPort* CreateTurnPort(const SocketAddress& addr, + PacketSocketFactory* socket_factory, + ProtocolType int_proto, ProtocolType ext_proto, + const talk_base::SocketAddress& server_addr) { TurnPort* port = TurnPort::Create(main_, socket_factory, &network_, addr.ipaddr(), 0, 0, username_, password_, ProtocolAddress( - kTurnUdpIntAddr, PROTO_UDP), + server_addr, PROTO_UDP), kRelayCredentials); port->SetIceProtocolType(ice_protocol_); return port; @@ -886,7 +895,8 @@ TEST_F(PortTest, TestLocalToSymNat) { TestLocalToStun(NAT_SYMMETRIC); } -TEST_F(PortTest, TestLocalToTurn) { +// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3316. +TEST_F(PortTest, DISABLED_TestLocalToTurn) { TestLocalToRelay(RELAY_TURN, PROTO_UDP); } @@ -2166,16 +2176,35 @@ TEST_F(PortTest, TestCandidateFoundation) { EXPECT_NE(udpport2->Candidates()[0].foundation(), relayport->Candidates()[0].foundation()); // Verifying TURN candidate foundation. - talk_base::scoped_ptr turnport(CreateTurnPort( + talk_base::scoped_ptr turnport1(CreateTurnPort( kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); - turnport->PrepareAddress(); - ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kTimeout); + turnport1->PrepareAddress(); + ASSERT_EQ_WAIT(1U, turnport1->Candidates().size(), kTimeout); EXPECT_NE(udpport1->Candidates()[0].foundation(), - turnport->Candidates()[0].foundation()); + turnport1->Candidates()[0].foundation()); EXPECT_NE(udpport2->Candidates()[0].foundation(), - turnport->Candidates()[0].foundation()); + turnport1->Candidates()[0].foundation()); EXPECT_NE(stunport->Candidates()[0].foundation(), - turnport->Candidates()[0].foundation()); + turnport1->Candidates()[0].foundation()); + talk_base::scoped_ptr turnport2(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); + turnport2->PrepareAddress(); + ASSERT_EQ_WAIT(1U, turnport2->Candidates().size(), kTimeout); + EXPECT_EQ(turnport1->Candidates()[0].foundation(), + turnport2->Candidates()[0].foundation()); + + // Running a second turn server, to get different base IP address. + SocketAddress kTurnUdpIntAddr2("99.99.98.4", STUN_SERVER_PORT); + SocketAddress kTurnUdpExtAddr2("99.99.98.5", 0); + TestTurnServer turn_server2( + talk_base::Thread::Current(), kTurnUdpIntAddr2, kTurnUdpExtAddr2); + talk_base::scoped_ptr turnport3(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP, + kTurnUdpIntAddr2)); + turnport3->PrepareAddress(); + ASSERT_EQ_WAIT(1U, turnport3->Candidates().size(), kTimeout); + EXPECT_NE(turnport3->Candidates()[0].foundation(), + turnport2->Candidates()[0].foundation()); } // This test verifies the related addresses of different types of diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc index af768e4f6..23571ea5f 100644 --- a/talk/p2p/base/relayport.cc +++ b/talk/p2p/base/relayport.cc @@ -240,8 +240,11 @@ void RelayPort::SetReady() { for (iter = external_addr_.begin(); iter != external_addr_.end(); ++iter) { std::string proto_name = ProtoToString(iter->proto); - AddAddress(iter->address, iter->address, proto_name, - RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY, false); + // In case of Gturn, related address is set to null socket address. + // This is due to as mapped address stun attribute is used for allocated + // address. + AddAddress(iter->address, iter->address, talk_base::SocketAddress(), + proto_name, RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY, false); } ready_ = true; SignalPortComplete(this); @@ -548,10 +551,6 @@ void RelayEntry::OnConnect(const talk_base::SocketAddress& mapped_addr, << " @ " << mapped_addr.ToSensitiveString(); connected_ = true; - // In case of Gturn related address is set to null socket address. - // This is due to mapped address stun attribute is used for allocated - // address. - port_->set_related_address(talk_base::SocketAddress()); port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto)); port_->SetReady(); } diff --git a/talk/p2p/base/relayserver.cc b/talk/p2p/base/relayserver.cc index 990c818ca..3dd850657 100644 --- a/talk/p2p/base/relayserver.cc +++ b/talk/p2p/base/relayserver.cc @@ -46,8 +46,6 @@ const int MAX_LIFETIME = 15 * 60 * 1000; // The number of bytes in each of the usernames we use. const uint32 USERNAME_LENGTH = 16; -static const uint32 kMessageAcceptConnection = 1; - // Calls SendTo on the given socket and logs any bad results. void Send(talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size, const talk_base::SocketAddress& addr) { @@ -548,7 +546,10 @@ void RelayServer::RemoveBinding(RelayServerBinding* binding) { } void RelayServer::OnMessage(talk_base::Message *pmsg) { +#if ENABLE_DEBUG + static const uint32 kMessageAcceptConnection = 1; ASSERT(pmsg->message_id == kMessageAcceptConnection); +#endif talk_base::MessageData* data = pmsg->pdata; talk_base::AsyncSocket* socket = static_cast *> diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc index 352098433..a48f3cb0f 100644 --- a/talk/p2p/base/session.cc +++ b/talk/p2p/base/session.cc @@ -284,9 +284,12 @@ bool TransportProxy::SetLocalTransportDescription( if (action == CA_ANSWER) { CompleteNegotiation(); } - return transport_->get()->SetLocalTransportDescription(description, - action, - error_desc); + bool result = transport_->get()->SetLocalTransportDescription(description, + action, + error_desc); + if (result) + local_description_set_ = true; + return result; } bool TransportProxy::SetRemoteTransportDescription( @@ -297,9 +300,12 @@ bool TransportProxy::SetRemoteTransportDescription( if (action == CA_ANSWER) { CompleteNegotiation(); } - return transport_->get()->SetRemoteTransportDescription(description, - action, - error_desc); + bool result = transport_->get()->SetRemoteTransportDescription(description, + action, + error_desc); + if (result) + remote_description_set_ = true; + return result; } void TransportProxy::OnSignalingReady() { diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h index 826baaa5f..504187f62 100644 --- a/talk/p2p/base/session.h +++ b/talk/p2p/base/session.h @@ -102,7 +102,9 @@ class TransportProxy : public sigslot::has_slots<>, connecting_(false), negotiated_(false), sent_candidates_(false), - candidates_allocated_(false) { + candidates_allocated_(false), + local_description_set_(false), + remote_description_set_(false) { transport_->get()->SignalCandidatesReady.connect( this, &TransportProxy::OnTransportCandidatesReady); } @@ -165,6 +167,13 @@ class TransportProxy : public sigslot::has_slots<>, SignalCandidatesReady(this, candidates); } + bool local_description_set() const { + return local_description_set_; + } + bool remote_description_set() const { + return remote_description_set_; + } + // Handles sending of ready candidates and receiving of remote candidates. sigslot::signal2&> SignalCandidatesReady; @@ -196,6 +205,8 @@ class TransportProxy : public sigslot::has_slots<>, Candidates sent_candidates_; Candidates unsent_candidates_; bool candidates_allocated_; + bool local_description_set_; + bool remote_description_set_; }; typedef std::map TransportMap; diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc index 95b26ac11..6e18fc505 100644 --- a/talk/p2p/base/stunport.cc +++ b/talk/p2p/base/stunport.cc @@ -173,7 +173,7 @@ bool UDPPort::Init() { UDPPort::~UDPPort() { if (resolver_) { - resolver_->Destroy(false); + resolver_->Destroy(true); } if (!SharedSocket()) delete socket_; @@ -192,7 +192,7 @@ void UDPPort::MaybePrepareStunCandidate() { if (!server_addr_.IsNil()) { SendStunBindingRequest(); } else { - // Processing host candidate address. + // Port is done allocating candidates. SetResult(true); } } @@ -243,7 +243,8 @@ int UDPPort::GetError() { void UDPPort::OnLocalAddressReady(talk_base::AsyncPacketSocket* socket, const talk_base::SocketAddress& address) { - AddAddress(address, address, UDP_PROTOCOL_NAME, LOCAL_PORT_TYPE, + AddAddress(address, address, talk_base::SocketAddress(), + UDP_PROTOCOL_NAME, LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST, false); MaybePrepareStunCandidate(); } @@ -324,10 +325,9 @@ void UDPPort::OnStunBindingRequestSucceeded( if (!SharedSocket() || stun_addr != socket_->GetLocalAddress()) { // If socket is shared and |stun_addr| is equal to local socket // address then discarding the stun address. - // Setting related address before STUN candidate is added. For STUN - // related address is local socket address. - set_related_address(socket_->GetLocalAddress()); - AddAddress(stun_addr, socket_->GetLocalAddress(), UDP_PROTOCOL_NAME, + // For STUN related address is local socket address. + AddAddress(stun_addr, socket_->GetLocalAddress(), + socket_->GetLocalAddress(), UDP_PROTOCOL_NAME, STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, false); } SetResult(true); diff --git a/talk/p2p/base/stunport.h b/talk/p2p/base/stunport.h index 1612c97bc..c45d6af33 100644 --- a/talk/p2p/base/stunport.h +++ b/talk/p2p/base/stunport.h @@ -142,7 +142,6 @@ class UDPPort : public Port { void SendStunBindingRequest(); - private: // DNS resolution of the STUN server. void ResolveStunAddress(); diff --git a/talk/p2p/base/stunport_unittest.cc b/talk/p2p/base/stunport_unittest.cc index 2a98a9fdb..5850027ec 100644 --- a/talk/p2p/base/stunport_unittest.cc +++ b/talk/p2p/base/stunport_unittest.cc @@ -43,6 +43,8 @@ static const SocketAddress kBadAddr("0.0.0.1", 5000); static const SocketAddress kStunHostnameAddr("localhost", 5000); static const SocketAddress kBadHostnameAddr("not-a-real-hostname", 5000); static const int kTimeoutMs = 10000; +// stun prio = 100 << 24 | 30 (IPV4) << 8 | 256 - 0 +static const uint32 kStunCandidatePriority = 1677729535; // Tests connecting a StunPort to a fake STUN server (cricket::StunServer) // TODO: Use a VirtualSocketServer here. We have to use a @@ -178,6 +180,7 @@ TEST_F(StunPortTest, TestPrepareAddressHostname) { EXPECT_TRUE_WAIT(done(), kTimeoutMs); ASSERT_EQ(1U, port()->Candidates().size()); EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address())); + EXPECT_EQ(kStunCandidatePriority, port()->Candidates()[0].priority()); } // Test that we handle hostname lookup failures properly. diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc index d83623f7f..069323a3c 100644 --- a/talk/p2p/base/tcpport.cc +++ b/talk/p2p/base/tcpport.cc @@ -121,6 +121,7 @@ void TCPPort::PrepareAddress() { if (socket_->GetState() == talk_base::AsyncPacketSocket::STATE_BOUND || socket_->GetState() == talk_base::AsyncPacketSocket::STATE_CLOSED) AddAddress(socket_->GetLocalAddress(), socket_->GetLocalAddress(), + talk_base::SocketAddress(), TCP_PROTOCOL_NAME, LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP, true); } else { @@ -128,8 +129,9 @@ void TCPPort::PrepareAddress() { // Note: We still add the address, since otherwise the remote side won't // recognize our incoming TCP connections. AddAddress(talk_base::SocketAddress(ip(), 0), - talk_base::SocketAddress(ip(), 0), TCP_PROTOCOL_NAME, - LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP, true); + talk_base::SocketAddress(ip(), 0), talk_base::SocketAddress(), + TCP_PROTOCOL_NAME, LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP, + true); } } @@ -221,7 +223,7 @@ void TCPPort::OnReadyToSend(talk_base::AsyncPacketSocket* socket) { void TCPPort::OnAddressReady(talk_base::AsyncPacketSocket* socket, const talk_base::SocketAddress& address) { - AddAddress(address, address, "tcp", + AddAddress(address, address, talk_base::SocketAddress(), "tcp", LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP, true); } @@ -235,7 +237,7 @@ TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate, int opts = (candidate.protocol() == SSLTCP_PROTOCOL_NAME) ? talk_base::PacketSocketFactory::OPT_SSLTCP : 0; socket_ = port->socket_factory()->CreateClientTcpSocket( - talk_base::SocketAddress(port_->Network()->ip(), 0), + talk_base::SocketAddress(port->ip(), 0), candidate.address(), port->proxy(), port->user_agent(), opts); if (socket_) { LOG_J(LS_VERBOSE, this) << "Connecting from " @@ -291,9 +293,19 @@ int TCPConnection::GetError() { void TCPConnection::OnConnect(talk_base::AsyncPacketSocket* socket) { ASSERT(socket == socket_); - LOG_J(LS_VERBOSE, this) << "Connection established to " - << socket->GetRemoteAddress().ToSensitiveString(); - set_connected(true); + // Do not use this connection if the socket bound to a different address than + // the one we asked for. This is seen in Chrome, where TCP sockets cannot be + // given a binding address, and the platform is expected to pick the + // correct local address. + if (socket->GetLocalAddress().ipaddr() == port()->ip()) { + LOG_J(LS_VERBOSE, this) << "Connection established to " + << socket->GetRemoteAddress().ToSensitiveString(); + set_connected(true); + } else { + LOG_J(LS_WARNING, this) << "Dropping connection as TCP socket bound to a " + << "different address from the local candidate."; + socket_->Close(); + } } void TCPConnection::OnClose(talk_base::AsyncPacketSocket* socket, int error) { diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc index 8261f723e..2996487c4 100644 --- a/talk/p2p/base/transport.cc +++ b/talk/p2p/base/transport.cc @@ -94,6 +94,22 @@ static std::string IceProtoToString(TransportProtocol proto) { return proto_str; } +static bool VerifyIceParams(const TransportDescription& desc) { + // For legacy protocols. + if (desc.ice_ufrag.empty() && desc.ice_pwd.empty()) + return true; + + if (desc.ice_ufrag.length() < ICE_UFRAG_MIN_LENGTH || + desc.ice_ufrag.length() > ICE_UFRAG_MAX_LENGTH) { + return false; + } + if (desc.ice_pwd.length() < ICE_PWD_MIN_LENGTH || + desc.ice_pwd.length() > ICE_PWD_MAX_LENGTH) { + return false; + } + return true; +} + bool BadTransportDescription(const std::string& desc, std::string* err_desc) { if (err_desc) { *err_desc = desc; @@ -102,6 +118,23 @@ bool BadTransportDescription(const std::string& desc, std::string* err_desc) { return false; } +bool IceCredentialsChanged(const std::string& old_ufrag, + const std::string& old_pwd, + const std::string& new_ufrag, + const std::string& new_pwd) { + // TODO(jiayl): The standard (RFC 5245 Section 9.1.1.1) says that ICE should + // restart when both the ufrag and password are changed, but we do restart + // when either ufrag or passwrod is changed to keep compatible with GICE. We + // should clean this up when GICE is no longer used. + return (old_ufrag != new_ufrag) || (old_pwd != new_pwd); +} + +static bool IceCredentialsChanged(const TransportDescription& old_desc, + const TransportDescription& new_desc) { + return IceCredentialsChanged(old_desc.ice_ufrag, old_desc.ice_pwd, + new_desc.ice_ufrag, new_desc.ice_pwd); +} + Transport::Transport(talk_base::Thread* signaling_thread, talk_base::Thread* worker_thread, const std::string& content_name, @@ -704,6 +737,21 @@ bool Transport::SetLocalTransportDescription_w( std::string* error_desc) { bool ret = true; talk_base::CritScope cs(&crit_); + + if (!VerifyIceParams(desc)) { + return BadTransportDescription("Invalid ice-ufrag or ice-pwd length", + error_desc); + } + + if (local_description_ && IceCredentialsChanged(*local_description_, desc)) { + IceRole new_ice_role = (action == CA_OFFER) ? ICEROLE_CONTROLLING + : ICEROLE_CONTROLLED; + + // It must be called before ApplyLocalTransportDescription_w, which may + // trigger an ICE restart and depends on the new ICE role. + SetIceRole_w(new_ice_role); + } + local_description_.reset(new TransportDescription(desc)); for (ChannelMap::iterator iter = channels_.begin(); @@ -726,8 +774,13 @@ bool Transport::SetRemoteTransportDescription_w( std::string* error_desc) { bool ret = true; talk_base::CritScope cs(&crit_); - remote_description_.reset(new TransportDescription(desc)); + if (!VerifyIceParams(desc)) { + return BadTransportDescription("Invalid ice-ufrag or ice-pwd length", + error_desc); + } + + remote_description_.reset(new TransportDescription(desc)); for (ChannelMap::iterator iter = channels_.begin(); iter != channels_.end(); ++iter) { ret &= ApplyRemoteTransportDescription_w(iter->second.get(), error_desc); diff --git a/talk/p2p/base/transport.h b/talk/p2p/base/transport.h index 7f460d112..5a4b75feb 100644 --- a/talk/p2p/base/transport.h +++ b/talk/p2p/base/transport.h @@ -189,6 +189,11 @@ struct TransportStats { bool BadTransportDescription(const std::string& desc, std::string* err_desc); +bool IceCredentialsChanged(const std::string& old_ufrag, + const std::string& old_pwd, + const std::string& new_ufrag, + const std::string& new_pwd); + class Transport : public talk_base::MessageHandler, public sigslot::has_slots<> { public: diff --git a/talk/p2p/base/transport_unittest.cc b/talk/p2p/base/transport_unittest.cc index b91b1a0d0..a83d2566d 100644 --- a/talk/p2p/base/transport_unittest.cc +++ b/talk/p2p/base/transport_unittest.cc @@ -52,6 +52,9 @@ using talk_base::SocketAddress; static const char kIceUfrag1[] = "TESTICEUFRAG0001"; static const char kIcePwd1[] = "TESTICEPWD00000000000001"; +static const char kIceUfrag2[] = "TESTICEUFRAG0002"; +static const char kIcePwd2[] = "TESTICEPWD00000000000002"; + class TransportTest : public testing::Test, public sigslot::has_slots<> { public: @@ -184,6 +187,96 @@ TEST_F(TransportTest, TestChannelIceParameters) { EXPECT_EQ(kIcePwd1, channel_->remote_ice_pwd()); } +// Verifies that IceCredentialsChanged returns true when either ufrag or pwd +// changed, and false in other cases. +TEST_F(TransportTest, TestIceCredentialsChanged) { + EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p2")); + EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p1")); + EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p2")); + EXPECT_FALSE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p1")); +} + +// This test verifies that the callee's ICE role changes from controlled to +// controlling when the callee triggers an ICE restart. +TEST_F(TransportTest, TestIceControlledToControllingOnIceRestart) { + EXPECT_TRUE(SetupChannel()); + transport_->SetIceRole(cricket::ICEROLE_CONTROLLED); + + cricket::TransportDescription desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(desc, + cricket::CA_OFFER, + NULL)); + ASSERT_TRUE(transport_->SetLocalTransportDescription(desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, transport_->ice_role()); + + cricket::TransportDescription new_local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2); + ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc, + cricket::CA_OFFER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); +} + +// This test verifies that the caller's ICE role changes from controlling to +// controlled when the callee triggers an ICE restart. +TEST_F(TransportTest, TestIceControllingToControlledOnIceRestart) { + EXPECT_TRUE(SetupChannel()); + transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); + + cricket::TransportDescription desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(desc, + cricket::CA_OFFER, + NULL)); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + + cricket::TransportDescription new_local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2); + ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, transport_->ice_role()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel_->GetIceRole()); +} + +// This test verifies that the caller's ICE role is still controlling after the +// callee triggers ICE restart if the callee's ICE mode is LITE. +TEST_F(TransportTest, TestIceControllingOnIceRestartIfRemoteIsIceLite) { + EXPECT_TRUE(SetupChannel()); + transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); + + cricket::TransportDescription desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(desc, + cricket::CA_OFFER, + NULL)); + + cricket::TransportDescription remote_desc( + cricket::NS_JINGLE_ICE_UDP, std::vector(), + kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE, + cricket::CONNECTIONROLE_NONE, NULL, cricket::Candidates()); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc, + cricket::CA_ANSWER, + NULL)); + + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + + cricket::TransportDescription new_local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2); + ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); +} + // This test verifies that the Completed and Failed states can be reached. TEST_F(TransportTest, TestChannelCompletedAndFailed) { transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); diff --git a/talk/p2p/base/transportchannelproxy.cc b/talk/p2p/base/transportchannelproxy.cc index 3c4aeb128..fdcc509d3 100644 --- a/talk/p2p/base/transportchannelproxy.cc +++ b/talk/p2p/base/transportchannelproxy.cc @@ -58,7 +58,6 @@ void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) { ASSERT(talk_base::Thread::Current() == worker_thread_); if (impl == impl_) { - ASSERT(false); // Ignore if the |impl| has already been set. LOG(LS_WARNING) << "Ignored TransportChannelProxy::SetImplementation call " << "with a same impl as the existing one."; diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc index eeaa3af60..195e9707b 100644 --- a/talk/p2p/base/turnport.cc +++ b/talk/p2p/base/turnport.cc @@ -168,6 +168,27 @@ class TurnEntry : public sigslot::has_slots<> { BindState state_; }; +TurnPort::TurnPort(talk_base::Thread* thread, + talk_base::PacketSocketFactory* factory, + talk_base::Network* network, + talk_base::AsyncPacketSocket* socket, + const std::string& username, + const std::string& password, + const ProtocolAddress& server_address, + const RelayCredentials& credentials) + : Port(thread, factory, network, socket->GetLocalAddress().ipaddr(), + username, password), + server_address_(server_address), + credentials_(credentials), + socket_(socket), + resolver_(NULL), + error_(0), + request_manager_(thread), + next_channel_number_(TURN_CHANNEL_NUMBER_START), + connected_(false) { + request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket); +} + TurnPort::TurnPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory, talk_base::Network* network, @@ -181,6 +202,7 @@ TurnPort::TurnPort(talk_base::Thread* thread, username, password), server_address_(server_address), credentials_(credentials), + socket_(NULL), resolver_(NULL), error_(0), request_manager_(thread), @@ -197,6 +219,9 @@ TurnPort::~TurnPort() { if (resolver_) { resolver_->Destroy(false); } + if (!SharedSocket()) { + delete socket_; + } } void TurnPort::PrepareAddress() { @@ -227,19 +252,19 @@ void TurnPort::PrepareAddress() { LOG_J(LS_INFO, this) << "Trying to connect to TURN server via " << ProtoToString(server_address_.proto) << " @ " << server_address_.address.ToSensitiveString(); - if (server_address_.proto == PROTO_UDP) { - socket_.reset(socket_factory()->CreateUdpSocket( - talk_base::SocketAddress(ip(), 0), min_port(), max_port())); + if (server_address_.proto == PROTO_UDP && !SharedSocket()) { + socket_ = socket_factory()->CreateUdpSocket( + talk_base::SocketAddress(ip(), 0), min_port(), max_port()); } else if (server_address_.proto == PROTO_TCP) { + ASSERT(!SharedSocket()); int opts = talk_base::PacketSocketFactory::OPT_STUN; // If secure bit is enabled in server address, use TLS over TCP. if (server_address_.secure) { opts |= talk_base::PacketSocketFactory::OPT_TLS; } - - socket_.reset(socket_factory()->CreateClientTcpSocket( + socket_ = socket_factory()->CreateClientTcpSocket( talk_base::SocketAddress(ip(), 0), server_address_.address, - proxy(), user_agent(), opts)); + proxy(), user_agent(), opts); } if (!socket_) { @@ -253,7 +278,11 @@ void TurnPort::PrepareAddress() { socket_->SetOption(iter->first, iter->second); } - socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket); + if (!SharedSocket()) { + // If socket is shared, AllocationSequence will receive the packet. + socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket); + } + socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend); if (server_address_.proto == PROTO_TCP) { @@ -268,6 +297,18 @@ void TurnPort::PrepareAddress() { } void TurnPort::OnSocketConnect(talk_base::AsyncPacketSocket* socket) { + ASSERT(server_address_.proto == PROTO_TCP); + // Do not use this port if the socket bound to a different address than + // the one we asked for. This is seen in Chrome, where TCP sockets cannot be + // given a binding address, and the platform is expected to pick the + // correct local address. + if (socket->GetLocalAddress().ipaddr() != ip()) { + LOG(LS_WARNING) << "Socket is bound to a different address then the " + << "local port. Discarding TURN port."; + OnAllocateError(); + return; + } + LOG(LS_INFO) << "TurnPort connected to " << socket->GetRemoteAddress() << " using tcp."; SendRequest(new TurnAllocateRequest(this), 0); @@ -294,12 +335,18 @@ Connection* TurnPort::CreateConnection(const Candidate& address, // Create an entry, if needed, so we can get our permissions set up correctly. CreateEntry(address.address()); - // TODO(juberti): The '0' index will need to change if we start gathering STUN - // candidates on this port. - ProxyConnection* conn = new ProxyConnection(this, 0, address); - conn->SignalDestroyed.connect(this, &TurnPort::OnConnectionDestroyed); - AddConnection(conn); - return conn; + // A TURN port will have two candiates, STUN and TURN. STUN may not + // present in all cases. If present stun candidate will be added first + // and TURN candidate later. + for (size_t index = 0; index < Candidates().size(); ++index) { + if (Candidates()[index].type() == RELAY_PORT_TYPE) { + ProxyConnection* conn = new ProxyConnection(this, index, address); + conn->SignalDestroyed.connect(this, &TurnPort::OnConnectionDestroyed); + AddConnection(conn); + return conn; + } + } + return NULL; } int TurnPort::SetOption(talk_base::Socket::Option opt, int value) { @@ -360,7 +407,7 @@ void TurnPort::OnReadPacket( talk_base::AsyncPacketSocket* socket, const char* data, size_t size, const talk_base::SocketAddress& remote_addr, const talk_base::PacketTime& packet_time) { - ASSERT(socket == socket_.get()); + ASSERT(socket == socket_); ASSERT(remote_addr == server_address_.address); // The message must be at least the size of a channel header. @@ -407,14 +454,21 @@ void TurnPort::ResolveTurnAddress(const talk_base::SocketAddress& address) { void TurnPort::OnResolveResult(talk_base::AsyncResolverInterface* resolver) { ASSERT(resolver == resolver_); + // Copy the original server address in |resolved_address|. For TLS based + // sockets we need hostname along with resolved address. + talk_base::SocketAddress resolved_address = server_address_.address; if (resolver_->GetError() != 0 || - !resolver_->GetResolvedAddress(ip().family(), &server_address_.address)) { + !resolver_->GetResolvedAddress(ip().family(), &resolved_address)) { LOG_J(LS_WARNING, this) << "TURN host lookup received error " << resolver_->GetError(); OnAllocateError(); return; } - + // Signal needs both resolved and unresolved address. After signal is sent + // we can copy resolved address back into |server_address_|. + SignalResolvedServerAddress(this, server_address_.address, + resolved_address); + server_address_.address = resolved_address; PrepareAddress(); } @@ -428,15 +482,23 @@ void TurnPort::OnSendStunPacket(const void* data, size_t size, } void TurnPort::OnStunAddress(const talk_base::SocketAddress& address) { - // For relay, mapped address is rel-addr. - set_related_address(address); + // STUN Port will discover STUN candidate, as it's supplied with first TURN + // server address. + // Why not using this address? - P2PTransportChannel will start creating + // connections after first candidate, which means it could start creating the + // connections before TURN candidate added. For that to handle, we need to + // supply STUN candidate from this port to UDPPort, and TurnPort should have + // handle to UDPPort to pass back the address. } -void TurnPort::OnAllocateSuccess(const talk_base::SocketAddress& address) { +void TurnPort::OnAllocateSuccess(const talk_base::SocketAddress& address, + const talk_base::SocketAddress& stun_address) { connected_ = true; - AddAddress(address, - socket_->GetLocalAddress(), - "udp", + // For relayed candidate, Base is the candidate itself. + AddAddress(address, // Candidate address. + address, // Base address. + stun_address, // Related address. + UDP_PROTOCOL_NAME, RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto, server_address_.secure), true); @@ -683,8 +745,7 @@ void TurnAllocateRequest::OnResponse(StunMessage* response) { << "attribute in allocate success response"; return; } - - // TODO(mallinath) - Use mapped address for STUN candidate. + // Using XOR-Mapped-Address for stun. port_->OnStunAddress(mapped_attr->GetAddress()); const StunAddressAttribute* relayed_attr = @@ -703,7 +764,8 @@ void TurnAllocateRequest::OnResponse(StunMessage* response) { return; } // Notify the port the allocate succeeded, and schedule a refresh request. - port_->OnAllocateSuccess(relayed_attr->GetAddress()); + port_->OnAllocateSuccess(relayed_attr->GetAddress(), + mapped_attr->GetAddress()); port_->ScheduleRefresh(lifetime_attr->value()); } diff --git a/talk/p2p/base/turnport.h b/talk/p2p/base/turnport.h index efec18bef..2f5e8c4ab 100644 --- a/talk/p2p/base/turnport.h +++ b/talk/p2p/base/turnport.h @@ -49,6 +49,18 @@ class TurnEntry; class TurnPort : public Port { public: + static TurnPort* Create(talk_base::Thread* thread, + talk_base::PacketSocketFactory* factory, + talk_base::Network* network, + talk_base::AsyncPacketSocket* socket, + const std::string& username, // ice username. + const std::string& password, // ice password. + const ProtocolAddress& server_address, + const RelayCredentials& credentials) { + return new TurnPort(thread, factory, network, socket, + username, password, server_address, credentials); + } + static TurnPort* Create(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory, talk_base::Network* network, @@ -79,10 +91,19 @@ class TurnPort : public Port { virtual int SetOption(talk_base::Socket::Option opt, int value); virtual int GetOption(talk_base::Socket::Option opt, int* value); virtual int GetError(); - virtual void OnReadPacket( + + virtual bool HandleIncomingPacket( talk_base::AsyncPacketSocket* socket, const char* data, size_t size, const talk_base::SocketAddress& remote_addr, - const talk_base::PacketTime& packet_time); + const talk_base::PacketTime& packet_time) { + OnReadPacket(socket, data, size, remote_addr, packet_time); + return true; + } + virtual void OnReadPacket(talk_base::AsyncPacketSocket* socket, + const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + const talk_base::PacketTime& packet_time); + virtual void OnReadyToSend(talk_base::AsyncPacketSocket* socket); void OnSocketConnect(talk_base::AsyncPacketSocket* socket); @@ -92,11 +113,27 @@ class TurnPort : public Port { const std::string& hash() const { return hash_; } const std::string& nonce() const { return nonce_; } + // Signal with resolved server address. + // Parameters are port, server address and resolved server address. + // This signal will be sent only if server address is resolved successfully. + sigslot::signal3 SignalResolvedServerAddress; + // This signal is only for testing purpose. sigslot::signal3 SignalCreatePermissionResult; protected: + TurnPort(talk_base::Thread* thread, + talk_base::PacketSocketFactory* factory, + talk_base::Network* network, + talk_base::AsyncPacketSocket* socket, + const std::string& username, + const std::string& password, + const ProtocolAddress& server_address, + const RelayCredentials& credentials); + TurnPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory, talk_base::Network* network, @@ -131,7 +168,8 @@ class TurnPort : public Port { // Stun address from allocate success response. // Currently used only for testing. void OnStunAddress(const talk_base::SocketAddress& address); - void OnAllocateSuccess(const talk_base::SocketAddress& address); + void OnAllocateSuccess(const talk_base::SocketAddress& address, + const talk_base::SocketAddress& stun_address); void OnAllocateError(); void OnAllocateRequestTimeout(); @@ -160,7 +198,7 @@ class TurnPort : public Port { ProtocolAddress server_address_; RelayCredentials credentials_; - talk_base::scoped_ptr socket_; + talk_base::AsyncPacketSocket* socket_; SocketOptionsMap socket_options_; talk_base::AsyncResolverInterface* resolver_; int error_; diff --git a/talk/p2p/base/turnport_unittest.cc b/talk/p2p/base/turnport_unittest.cc index 75ac6b5b9..12a19aa4d 100644 --- a/talk/p2p/base/turnport_unittest.cc +++ b/talk/p2p/base/turnport_unittest.cc @@ -157,7 +157,13 @@ class TurnPortTest : public testing::Test, const talk_base::PacketTime& packet_time) { udp_packets_.push_back(talk_base::Buffer(data, size)); } - + void OnSocketReadPacket(talk_base::AsyncPacketSocket* socket, + const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + const talk_base::PacketTime& packet_time) { + turn_port_->HandleIncomingPacket(socket, data, size, remote_addr, + packet_time); + } talk_base::AsyncSocket* CreateServerSocket(const SocketAddress addr) { talk_base::AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_STREAM); EXPECT_GE(socket->Bind(addr), 0); @@ -185,6 +191,33 @@ class TurnPortTest : public testing::Test, // This TURN port will be the controlling. turn_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245); turn_port_->SetIceRole(cricket::ICEROLE_CONTROLLING); + ConnectSignals(); + } + + void CreateSharedTurnPort(const std::string& username, + const std::string& password, + const cricket::ProtocolAddress& server_address) { + ASSERT(server_address.proto == cricket::PROTO_UDP); + + socket_.reset(socket_factory_.CreateUdpSocket( + talk_base::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0)); + ASSERT_TRUE(socket_ != NULL); + socket_->SignalReadPacket.connect(this, &TurnPortTest::OnSocketReadPacket); + + cricket::RelayCredentials credentials(username, password); + turn_port_.reset(cricket::TurnPort::Create( + main_, &socket_factory_, &network_, socket_.get(), + kIceUfrag1, kIcePwd1, server_address, credentials)); + // Set ICE protocol type to ICEPROTO_RFC5245, as port by default will be + // in Hybrid mode. Protocol type is necessary to send correct type STUN ping + // messages. + // This TURN port will be the controlling. + turn_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245); + turn_port_->SetIceRole(cricket::ICEROLE_CONTROLLING); + ConnectSignals(); + } + + void ConnectSignals() { turn_port_->SignalPortComplete.connect(this, &TurnPortTest::OnTurnPortComplete); turn_port_->SignalPortError.connect(this, @@ -294,6 +327,7 @@ class TurnPortTest : public testing::Test, talk_base::SocketServerScope ss_scope_; talk_base::Network network_; talk_base::BasicPacketSocketFactory socket_factory_; + talk_base::scoped_ptr socket_; cricket::TestTurnServer turn_server_; talk_base::scoped_ptr turn_port_; talk_base::scoped_ptr udp_port_; @@ -349,6 +383,12 @@ TEST_F(TurnPortTest, TestTurnConnection) { TestTurnConnection(); } +// Similar to above, except that this test will use the shared socket. +TEST_F(TurnPortTest, TestTurnConnectionUsingSharedSocket) { + CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestTurnConnection(); +} + // Test that we can establish a TCP connection with TURN server. TEST_F(TurnPortTest, TestTurnTcpConnection) { turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); diff --git a/talk/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc index 7c285d1c3..5c3e387ab 100644 --- a/talk/p2p/client/basicportallocator.cc +++ b/talk/p2p/client/basicportallocator.cc @@ -55,8 +55,6 @@ const uint32 MSG_SHAKE = 5; const uint32 MSG_SEQUENCEOBJECTS_CREATED = 6; const uint32 MSG_CONFIG_STOP = 7; -const uint32 ALLOCATE_DELAY = 250; - const int PHASE_UDP = 0; const int PHASE_RELAY = 1; const int PHASE_TCP = 2; @@ -102,6 +100,7 @@ class AllocationSequence : public talk_base::MessageHandler, uint32 flags); ~AllocationSequence(); bool Init(); + void Clear(); State state() const { return state_; } @@ -148,6 +147,9 @@ class AllocationSequence : public talk_base::MessageHandler, const talk_base::PacketTime& packet_time); void OnPortDestroyed(PortInterface* port); + void OnResolvedTurnServerAddress( + TurnPort* port, const talk_base::SocketAddress& server_address, + const talk_base::SocketAddress& resolved_server_address); BasicPortAllocatorSession* session_; talk_base::Network* network_; @@ -157,8 +159,10 @@ class AllocationSequence : public talk_base::MessageHandler, uint32 flags_; ProtocolList protocols_; talk_base::scoped_ptr udp_socket_; - // Keeping a list of all UDP based ports. - std::deque ports; + // There will be only one udp port per AllocationSequence. + UDPPort* udp_port_; + // Keeping a map for turn ports keyed with server addresses. + std::map turn_ports_; int phase_; }; @@ -201,13 +205,15 @@ BasicPortAllocator::BasicPortAllocator( stun_address_(stun_address) { RelayServerConfig config(RELAY_GTURN); - if (!relay_address_udp.IsAny()) + if (!relay_address_udp.IsNil()) config.ports.push_back(ProtocolAddress(relay_address_udp, PROTO_UDP)); - if (!relay_address_tcp.IsAny()) + if (!relay_address_tcp.IsNil()) config.ports.push_back(ProtocolAddress(relay_address_tcp, PROTO_TCP)); - if (!relay_address_ssl.IsAny()) + if (!relay_address_ssl.IsNil()) config.ports.push_back(ProtocolAddress(relay_address_ssl, PROTO_SSLTCP)); - AddRelay(config); + + if (!config.ports.empty()) + AddRelay(config); Construct(); } @@ -251,6 +257,12 @@ BasicPortAllocatorSession::~BasicPortAllocatorSession() { if (network_thread_ != NULL) network_thread_->Clear(this); + for (uint32 i = 0; i < sequences_.size(); ++i) { + // AllocationSequence should clear it's map entry for turn ports before + // ports are destroyed. + sequences_[i]->Clear(); + } + std::vector::iterator it; for (it = ports_.begin(); it != ports_.end(); it++) delete it->port(); @@ -384,8 +396,6 @@ void BasicPortAllocatorSession::OnAllocate() { DoAllocate(); allocation_started_ = true; - if (running_) - network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE); } // For each network, see if we have a sequence that covers it already. If not, @@ -693,6 +703,7 @@ AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session, state_(kInit), flags_(flags), udp_socket_(), + udp_port_(NULL), phase_(0) { } @@ -719,6 +730,11 @@ bool AllocationSequence::Init() { return true; } +void AllocationSequence::Clear() { + udp_port_ = NULL; + turn_ports_.clear(); +} + AllocationSequence::~AllocationSequence() { session_->network_thread()->Clear(this); } @@ -855,18 +871,27 @@ void AllocationSequence::CreateUDPPorts() { } if (port) { - ports.push_back(port); // If shared socket is enabled, STUN candidate will be allocated by the // UDPPort. - if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && - !IsFlagSet(PORTALLOCATOR_DISABLE_STUN)) { - ASSERT(config_ && !config_->stun_address.IsNil()); - if (!(config_ && !config_->stun_address.IsNil())) { - LOG(LS_WARNING) - << "AllocationSequence: No STUN server configured, skipping."; - return; + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) { + udp_port_ = port; + + // If STUN is not disabled, setting stun server address to port. + if (!IsFlagSet(PORTALLOCATOR_DISABLE_STUN)) { + // If config has stun_address, use it to get server reflexive candidate + // otherwise use first TURN server which supports UDP. + if (config_ && !config_->stun_address.IsNil()) { + LOG(LS_INFO) << "AllocationSequence: UDPPort will be handling the " + << "STUN candidate generation."; + port->set_server_addr(config_->stun_address); + } else if (config_ && + config_->SupportsProtocol(RELAY_TURN, PROTO_UDP)) { + port->set_server_addr(config_->GetFirstRelayServerAddress( + RELAY_TURN, PROTO_UDP)); + LOG(LS_INFO) << "AllocationSequence: TURN Server address will be " + << " used for generating STUN candidate."; + } } - port->set_server_addr(config_->stun_address); } session_->AddAllocatedPort(port, this, true); @@ -901,8 +926,6 @@ void AllocationSequence::CreateStunPorts() { } if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) { - LOG(LS_INFO) << "AllocationSequence: " - << "UDPPort will be handling the STUN candidate generation."; return; } @@ -992,17 +1015,44 @@ void AllocationSequence::CreateTurnPort(const RelayServerConfig& config) { PortList::const_iterator relay_port; for (relay_port = config.ports.begin(); relay_port != config.ports.end(); ++relay_port) { - TurnPort* port = TurnPort::Create(session_->network_thread(), - session_->socket_factory(), - network_, ip_, - session_->allocator()->min_port(), - session_->allocator()->max_port(), - session_->username(), - session_->password(), - *relay_port, config.credentials); - if (port) { - session_->AddAllocatedPort(port, this, true); + TurnPort* port = NULL; + // Shared socket mode must be enabled only for UDP based ports. Hence + // don't pass shared socket for ports which will create TCP sockets. + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && + relay_port->proto == PROTO_UDP) { + port = TurnPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, udp_socket_.get(), + session_->username(), session_->password(), + *relay_port, config.credentials); + // If we are using shared socket for TURN and udp ports, we need to + // find a way to demux the packets to the correct port when received. + // Mapping against server_address is one way of doing this. When packet + // is received the remote_address will be checked against the map. + // If server address is not resolved, a signal will be sent from the port + // after the address is resolved. The map entry will updated with the + // resolved address when the signal is received from the port. + if ((*relay_port).address.IsUnresolved()) { + // If server address is not resolved then listen for signal from port. + port->SignalResolvedServerAddress.connect( + this, &AllocationSequence::OnResolvedTurnServerAddress); + } + turn_ports_[(*relay_port).address] = port; + // Listen to the port destroyed signal, to allow AllocationSequence to + // remove entrt from it's map. + port->SignalDestroyed.connect(this, &AllocationSequence::OnPortDestroyed); + } else { + port = TurnPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, ip_, + session_->allocator()->min_port(), + session_->allocator()->max_port(), + session_->username(), + session_->password(), + *relay_port, config.credentials); } + ASSERT(port != NULL); + session_->AddAllocatedPort(port, this, true); } } @@ -1011,22 +1061,51 @@ void AllocationSequence::OnReadPacket( const talk_base::SocketAddress& remote_addr, const talk_base::PacketTime& packet_time) { ASSERT(socket == udp_socket_.get()); - for (std::deque::iterator iter = ports.begin(); - iter != ports.end(); ++iter) { - // We have only one port in the queue. - // TODO(mallinath) - Add shared socket support to Relay and Turn ports. - if ((*iter)->HandleIncomingPacket( - socket, data, size, remote_addr, packet_time)) { - break; - } + // If the packet is received from one of the TURN server in the config, then + // pass down the packet to that port, otherwise it will be handed down to + // the local udp port. + Port* port = NULL; + std::map::iterator iter = + turn_ports_.find(remote_addr); + if (iter != turn_ports_.end()) { + port = iter->second; + } else if (udp_port_) { + port = udp_port_; + } + ASSERT(port != NULL); + if (port) { + port->HandleIncomingPacket(socket, data, size, remote_addr, packet_time); } } void AllocationSequence::OnPortDestroyed(PortInterface* port) { - std::deque::iterator iter = - std::find(ports.begin(), ports.end(), port); - ASSERT(iter != ports.end()); - ports.erase(iter); + if (udp_port_ == port) { + udp_port_ = NULL; + } else { + std::map::iterator iter; + for (iter = turn_ports_.begin(); iter != turn_ports_.end(); ++iter) { + if (iter->second == port) { + turn_ports_.erase(iter); + break; + } + } + } +} + +void AllocationSequence::OnResolvedTurnServerAddress( + TurnPort* port, const talk_base::SocketAddress& server_address, + const talk_base::SocketAddress& resolved_server_address) { + std::map::iterator iter; + iter = turn_ports_.find(server_address); + if (iter == turn_ports_.end()) { + LOG(LS_INFO) << "TurnPort entry is not found in the map."; + return; + } + + ASSERT(iter->second == port); + // Remove old entry and then insert using the resolved address as key. + turn_ports_.erase(iter); + turn_ports_[resolved_server_address] = port; } // PortConfiguration @@ -1044,7 +1123,7 @@ void PortConfiguration::AddRelay(const RelayServerConfig& config) { } bool PortConfiguration::SupportsProtocol( - const RelayServerConfig& relay, ProtocolType type) { + const RelayServerConfig& relay, ProtocolType type) const { PortList::const_iterator relay_port; for (relay_port = relay.ports.begin(); relay_port != relay.ports.end(); @@ -1055,4 +1134,24 @@ bool PortConfiguration::SupportsProtocol( return false; } +bool PortConfiguration::SupportsProtocol(RelayType turn_type, + ProtocolType type) const { + for (size_t i = 0; i < relays.size(); ++i) { + if (relays[i].type == turn_type && + SupportsProtocol(relays[i], type)) + return true; + } + return false; +} + +talk_base::SocketAddress PortConfiguration::GetFirstRelayServerAddress( + RelayType turn_type, ProtocolType type) const { + for (size_t i = 0; i < relays.size(); ++i) { + if (relays[i].type == turn_type && SupportsProtocol(relays[i], type)) { + return relays[i].ports.front().address; + } + } + return talk_base::SocketAddress(); +} + } // namespace cricket diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h index c46c29aaf..8a60c425c 100644 --- a/talk/p2p/client/basicportallocator.h +++ b/talk/p2p/client/basicportallocator.h @@ -232,8 +232,13 @@ struct PortConfiguration : public talk_base::MessageData { void AddRelay(const RelayServerConfig& config); // Determines whether the given relay server supports the given protocol. - static bool SupportsProtocol(const RelayServerConfig& relay, - ProtocolType type); + bool SupportsProtocol(const RelayServerConfig& relay, + ProtocolType type) const; + bool SupportsProtocol(RelayType turn_type, ProtocolType type) const; + // Helper method returns the first server address for the matching + // RelayType and Protocol type. + talk_base::SocketAddress GetFirstRelayServerAddress( + RelayType turn_type, ProtocolType type) const; }; } // namespace cricket diff --git a/talk/p2p/client/connectivitychecker_unittest.cc b/talk/p2p/client/connectivitychecker_unittest.cc index 59fdfc2f7..c62120bee 100644 --- a/talk/p2p/client/connectivitychecker_unittest.cc +++ b/talk/p2p/client/connectivitychecker_unittest.cc @@ -73,7 +73,7 @@ class FakeStunPort : public StunPort { // Just set external address and signal that we are done. virtual void PrepareAddress() { - AddAddress(kExternalAddr, kExternalAddr, "udp", + AddAddress(kExternalAddr, kExternalAddr, talk_base::SocketAddress(), "udp", STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, true); SignalPortComplete(this); } diff --git a/talk/p2p/client/portallocator_unittest.cc b/talk/p2p/client/portallocator_unittest.cc index 0ea8fb54a..b9def3041 100644 --- a/talk/p2p/client/portallocator_unittest.cc +++ b/talk/p2p/client/portallocator_unittest.cc @@ -44,6 +44,7 @@ #include "talk/p2p/base/portallocatorsessionproxy.h" #include "talk/p2p/base/testrelayserver.h" #include "talk/p2p/base/teststunserver.h" +#include "talk/p2p/base/testturnserver.h" #include "talk/p2p/client/basicportallocator.h" #include "talk/p2p/client/httpportallocator.h" @@ -55,6 +56,7 @@ static const SocketAddress kClientIPv6Addr( "2401:fa00:4:1000:be30:5bff:fee5:c3", 0); static const SocketAddress kClientAddr2("22.22.22.22", 0); static const SocketAddress kNatAddr("77.77.77.77", talk_base::NAT_SERVER_PORT); +static const SocketAddress kRemoteClientAddr("22.22.22.22", 0); static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT); static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000); static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001); @@ -62,6 +64,9 @@ static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002); static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003); static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004); static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005); +static const SocketAddress kTurnUdpIntAddr("99.99.99.4", 3478); +static const SocketAddress kTurnTcpIntAddr("99.99.99.5", 3478); +static const SocketAddress kTurnUdpExtAddr("99.99.99.6", 0); // Minimum and maximum port for port range tests. static const int kMinPort = 10000; @@ -75,6 +80,8 @@ static const char kIcePwd0[] = "TESTICEPWD00000000000000"; static const char kContentName[] = "test content"; static const int kDefaultAllocationTimeout = 1000; +static const char kTurnUsername[] = "test"; +static const char kTurnPassword[] = "test"; namespace cricket { @@ -107,6 +114,7 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { relay_server_(Thread::Current(), kRelayUdpIntAddr, kRelayUdpExtAddr, kRelayTcpIntAddr, kRelayTcpExtAddr, kRelaySslTcpIntAddr, kRelaySslTcpExtAddr), + turn_server_(Thread::Current(), kTurnUdpIntAddr, kTurnUdpExtAddr), allocator_(new cricket::BasicPortAllocator( &network_manager_, kStunAddr, kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr)), @@ -245,6 +253,7 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { talk_base::BasicPacketSocketFactory nat_socket_factory_; cricket::TestStunServer stun_server_; cricket::TestRelayServer relay_server_; + cricket::TestTurnServer turn_server_; talk_base::FakeNetworkManager network_manager_; talk_base::scoped_ptr allocator_; talk_base::scoped_ptr session_; @@ -271,6 +280,19 @@ TEST_F(PortAllocatorTest, TestBasic) { EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); } +// Tests that we allocator session not trying to allocate ports for every 250ms. +TEST_F(PortAllocatorTest, TestNoNetworkInterface) { + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + // Waiting for one second to make sure BasicPortAllocatorSession has not + // called OnAllocate multiple times. In old behavior it's called every 250ms. + // When there are no network interfaces, each execution of OnAllocate will + // result in SignalCandidatesAllocationDone signal. + talk_base::Thread::Current()->ProcessMessages(1000); + EXPECT_TRUE(candidate_allocation_done_); + EXPECT_EQ(0U, candidates_.size()); +} + // Tests that we can get all the desired addresses successfully. TEST_F(PortAllocatorTest, TestGetAllPortsWithMinimumStepDelay) { AddInterface(kClientAddr); @@ -653,7 +675,7 @@ TEST_F(PortAllocatorTest, TestDisableSharedUfrag) { // is allocated for udp and stun. Also verify there is only one candidate // (local) if stun candidate is same as local candidate, which will be the case // in a public network like the below test. -TEST_F(PortAllocatorTest, TestEnableSharedSocketWithoutNat) { +TEST_F(PortAllocatorTest, TestSharedSocketWithoutNat) { AddInterface(kClientAddr); allocator_->set_flags(allocator().flags() | cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | @@ -670,7 +692,7 @@ TEST_F(PortAllocatorTest, TestEnableSharedSocketWithoutNat) { // Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled only one port // is allocated for udp and stun. In this test we should expect both stun and // local candidates as client behind a nat. -TEST_F(PortAllocatorTest, TestEnableSharedSocketWithNat) { +TEST_F(PortAllocatorTest, TestSharedSocketWithNat) { AddInterface(kClientAddr); talk_base::scoped_ptr nat_server( CreateNatServer(kNatAddr, talk_base::NAT_OPEN_CONE)); @@ -693,10 +715,116 @@ TEST_F(PortAllocatorTest, TestEnableSharedSocketWithNat) { EXPECT_EQ(3U, candidates_.size()); } +// Test TURN port in shared socket mode with UDP and TCP TURN server adderesses. +TEST_F(PortAllocatorTest, TestSharedSocketWithoutNatUsingTurn) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + AddInterface(kClientAddr); + allocator_.reset(new cricket::BasicPortAllocator(&network_manager_)); + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); + relay_server.credentials = credentials; + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnTcpIntAddr, cricket::PROTO_TCP, false)); + allocator_->AddRelay(relay_server); + + allocator_->set_step_delay(cricket::kMinimumStepDelay); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout); + ASSERT_EQ(3U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + talk_base::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + talk_base::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(3U, candidates_.size()); +} + +// Testing DNS resolve for the TURN server, this will test AllocationSequence +// handling the unresolved address signal from TurnPort. +TEST_F(PortAllocatorTest, TestSharedSocketWithServerAddressResolve) { + turn_server_.AddInternalSocket(talk_base::SocketAddress("127.0.0.1", 3478), + cricket::PROTO_UDP); + AddInterface(kClientAddr); + allocator_.reset(new cricket::BasicPortAllocator(&network_manager_)); + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); + relay_server.credentials = credentials; + relay_server.ports.push_back(cricket::ProtocolAddress( + talk_base::SocketAddress("localhost", 3478), + cricket::PROTO_UDP, false)); + allocator_->AddRelay(relay_server); + + allocator_->set_step_delay(cricket::kMinimumStepDelay); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + EXPECT_EQ_WAIT(2U, ports_.size(), kDefaultAllocationTimeout); +} + +// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled only one port +// is allocated for udp/stun/turn. In this test we should expect all local, +// stun and turn candidates. +TEST_F(PortAllocatorTest, TestSharedSocketWithNatUsingTurn) { + AddInterface(kClientAddr); + talk_base::scoped_ptr nat_server( + CreateNatServer(kNatAddr, talk_base::NAT_OPEN_CONE)); + allocator_.reset(new cricket::BasicPortAllocator( + &network_manager_, &nat_socket_factory_, kStunAddr)); + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); + relay_server.credentials = credentials; + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + allocator_->AddRelay(relay_server); + + allocator_->set_step_delay(cricket::kMinimumStepDelay); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout); + ASSERT_EQ(2U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", + talk_base::SocketAddress(kNatAddr.ipaddr(), 0)); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + talk_base::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(3U, candidates_.size()); + // Local port will be created first and then TURN port. + EXPECT_EQ(2U, ports_[0]->Candidates().size()); + EXPECT_EQ(1U, ports_[1]->Candidates().size()); +} + // This test verifies when PORTALLOCATOR_ENABLE_SHARED_SOCKET flag is enabled // and fail to generate STUN candidate, local UDP candidate is generated // properly. -TEST_F(PortAllocatorTest, TestEnableSharedSocketNoUdpAllowed) { +TEST_F(PortAllocatorTest, TestSharedSocketNoUdpAllowed) { allocator().set_flags(allocator().flags() | cricket::PORTALLOCATOR_DISABLE_RELAY | cricket::PORTALLOCATOR_DISABLE_TCP | diff --git a/talk/session/media/ssrcmuxfilter.cc b/talk/session/media/bundlefilter.cc old mode 100644 new mode 100755 similarity index 51% rename from talk/session/media/ssrcmuxfilter.cc rename to talk/session/media/bundlefilter.cc index 638167d18..d3b51c4c4 --- a/talk/session/media/ssrcmuxfilter.cc +++ b/talk/session/media/bundlefilter.cc @@ -25,9 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/session/media/ssrcmuxfilter.h" - -#include +#include "talk/session/media/bundlefilter.h" #include "talk/base/logging.h" #include "talk/media/base/rtputils.h" @@ -36,41 +34,56 @@ namespace cricket { static const uint32 kSsrc01 = 0x01; -SsrcMuxFilter::SsrcMuxFilter() { +BundleFilter::BundleFilter() { } -SsrcMuxFilter::~SsrcMuxFilter() { +BundleFilter::~BundleFilter() { } -bool SsrcMuxFilter::IsActive() const { - return !streams_.empty(); -} +bool BundleFilter::DemuxPacket(const char* data, size_t len, bool rtcp) { + // For rtp packets, we check whether the payload type can be found. + // For rtcp packets, we check whether the ssrc can be found or is the special + // value 1 except for SDES packets which always pass through. Plus, if + // |streams_| is empty, we will allow all rtcp packets pass through provided + // that they are valid rtcp packets in case that they are for early media. + if (!rtcp) { + // It may not be a RTP packet (e.g. SCTP). + if (!IsRtpPacket(data, len)) + return false; -bool SsrcMuxFilter::DemuxPacket(const char* data, size_t len, bool rtcp) { + int payload_type = 0; + if (!GetRtpPayloadType(data, len, &payload_type)) { + return false; + } + return FindPayloadType(payload_type); + } + + // Rtcp packets using ssrc filter. + int pl_type = 0; uint32 ssrc = 0; - if (!rtcp) { - GetRtpSsrc(data, len, &ssrc); + if (!GetRtcpType(data, len, &pl_type)) return false; + if (pl_type == kRtcpTypeSDES) { + // SDES packet parsing not supported. + LOG(LS_INFO) << "SDES packet received for demux."; + return true; } else { - int pl_type = 0; - if (!GetRtcpType(data, len, &pl_type)) return false; - if (pl_type == kRtcpTypeSDES) { - // SDES packet parsing not supported. - LOG(LS_INFO) << "SDES packet received for demux."; + if (!GetRtcpSsrc(data, len, &ssrc)) return false; + if (ssrc == kSsrc01) { + // SSRC 1 has a special meaning and indicates generic feedback on + // some systems and should never be dropped. If it is forwarded + // incorrectly it will be ignored by lower layers anyway. return true; - } else { - if (!GetRtcpSsrc(data, len, &ssrc)) return false; - if (ssrc == kSsrc01) { - // SSRC 1 has a special meaning and indicates generic feedback on - // some systems and should never be dropped. If it is forwarded - // incorrectly it will be ignored by lower layers anyway. - return true; - } } } - return FindStream(ssrc); + // Pass through if |streams_| is empty to allow early rtcp packets in. + return !HasStreams() || FindStream(ssrc); } -bool SsrcMuxFilter::AddStream(const StreamParams& stream) { +void BundleFilter::AddPayloadType(int payload_type) { + payload_types_.insert(payload_type); +} + +bool BundleFilter::AddStream(const StreamParams& stream) { if (GetStreamBySsrc(streams_, stream.first_ssrc(), NULL)) { LOG(LS_WARNING) << "Stream already added to filter"; return false; @@ -79,15 +92,27 @@ bool SsrcMuxFilter::AddStream(const StreamParams& stream) { return true; } -bool SsrcMuxFilter::RemoveStream(uint32 ssrc) { +bool BundleFilter::RemoveStream(uint32 ssrc) { return RemoveStreamBySsrc(&streams_, ssrc); } -bool SsrcMuxFilter::FindStream(uint32 ssrc) const { +bool BundleFilter::HasStreams() const { + return !streams_.empty(); +} + +bool BundleFilter::FindStream(uint32 ssrc) const { if (ssrc == 0) { return false; } return (GetStreamBySsrc(streams_, ssrc, NULL)); } +bool BundleFilter::FindPayloadType(int pl_type) const { + return payload_types_.find(pl_type) != payload_types_.end(); +} + +void BundleFilter::ClearAllPayloadTypes() { + payload_types_.clear(); +} + } // namespace cricket diff --git a/talk/session/media/ssrcmuxfilter.h b/talk/session/media/bundlefilter.h old mode 100644 new mode 100755 similarity index 76% rename from talk/session/media/ssrcmuxfilter.h rename to talk/session/media/bundlefilter.h index 9420f54cc..34bc33073 --- a/talk/session/media/ssrcmuxfilter.h +++ b/talk/session/media/bundlefilter.h @@ -25,9 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_SESSION_MEDIA_SSRCMUXFILTER_H_ -#define TALK_SESSION_MEDIA_SSRCMUXFILTER_H_ +#ifndef TALK_SESSION_MEDIA_BUNDLEFILTER_H_ +#define TALK_SESSION_MEDIA_BUNDLEFILTER_H_ +#include #include #include "talk/base/basictypes.h" @@ -35,33 +36,45 @@ namespace cricket { -// This class maintains list of recv SSRC's destined for cricket::BaseChannel. // In case of single RTP session and single transport channel, all session // ( or media) channels share a common transport channel. Hence they all get // SignalReadPacket when packet received on transport channel. This requires // cricket::BaseChannel to know all the valid sources, else media channel // will decode invalid packets. -class SsrcMuxFilter { +// +// This class determines whether a packet is destined for cricket::BaseChannel. +// For rtp packets, this is decided based on the payload type. For rtcp packets, +// this is decided based on the sender ssrc values. +class BundleFilter { public: - SsrcMuxFilter(); - ~SsrcMuxFilter(); + BundleFilter(); + ~BundleFilter(); - // Whether the rtp mux is active for a sdp session. - // Returns true if the filter contains a stream. - bool IsActive() const; // Determines packet belongs to valid cricket::BaseChannel. bool DemuxPacket(const char* data, size_t len, bool rtcp); + + // Adds the supported payload type. + void AddPayloadType(int payload_type); + // Adding a valid source to the filter. bool AddStream(const StreamParams& stream); + // Removes source from the filter. bool RemoveStream(uint32 ssrc); - // Utility method added for unitest. + + // Utility methods added for unitest. + // True if |streams_| is not empty. + bool HasStreams() const; bool FindStream(uint32 ssrc) const; + bool FindPayloadType(int pl_type) const; + void ClearAllPayloadTypes(); + private: + std::set payload_types_; std::vector streams_; }; } // namespace cricket -#endif // TALK_SESSION_MEDIA_SSRCMUXFILTER_H_ +#endif // TALK_SESSION_MEDIA_BUNDLEFILTER_H_ diff --git a/talk/session/media/ssrcmuxfilter_unittest.cc b/talk/session/media/bundlefilter_unittest.cc old mode 100644 new mode 100755 similarity index 54% rename from talk/session/media/ssrcmuxfilter_unittest.cc rename to talk/session/media/bundlefilter_unittest.cc index 85a4dbe50..a3e58c1ec --- a/talk/session/media/ssrcmuxfilter_unittest.cc +++ b/talk/session/media/bundlefilter_unittest.cc @@ -25,34 +25,34 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #include "talk/base/gunit.h" -#include "talk/session/media/ssrcmuxfilter.h" +#include "talk/session/media/bundlefilter.h" + +using cricket::StreamParams; static const int kSsrc1 = 0x1111; static const int kSsrc2 = 0x2222; static const int kSsrc3 = 0x3333; - -using cricket::StreamParams; - -// SSRC = 0x1111 -static const unsigned char kRtpPacketSsrc1[] = { - 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, -}; - -// SSRC = 0x2222 -static const unsigned char kRtpPacketSsrc2[] = { - 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, +static const int kPayloadType1 = 0x11; +static const int kPayloadType2 = 0x22; +static const int kPayloadType3 = 0x33; + +// SSRC = 0x1111, Payload type = 0x11 +static const unsigned char kRtpPacketPt1Ssrc1[] = { + 0x80, kPayloadType1, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, + 0x11, }; -// SSRC = 0 -static const unsigned char kRtpPacketInvalidSsrc[] = { - 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// SSRC = 0x2222, Payload type = 0x22 +static const unsigned char kRtpPacketPt2Ssrc2[] = { + 0x80, 0x80 + kPayloadType2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, }; -// invalid size -static const unsigned char kRtpPacketTooSmall[] = { - 0x80, 0x80, 0x00, 0x00, +// SSRC = 0x2222, Payload type = 0x33 +static const unsigned char kRtpPacketPt3Ssrc2[] = { + 0x80, kPayloadType3, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, + 0x22, }; // PT = 200 = SR, len = 28, SSRC of sender = 0x0001 @@ -105,80 +105,109 @@ static const unsigned char kRtcpPacketNonCompoundRtcpPliFeedback[] = { 0x81, 0xCE, 0x00, 0x0C, 0x00, 0x00, 0x11, 0x11, 0x00, 0x00, 0x11, 0x11, }; -TEST(SsrcMuxFilterTest, AddRemoveStreamTest) { - cricket::SsrcMuxFilter ssrc_filter; - EXPECT_FALSE(ssrc_filter.IsActive()); - EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1))); +// An SCTP packet. +static const unsigned char kSctpPacket[] = { + 0x00, 0x01, 0x00, 0x01, + 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, +}; + +TEST(BundleFilterTest, AddRemoveStreamTest) { + cricket::BundleFilter bundle_filter; + EXPECT_FALSE(bundle_filter.HasStreams()); + EXPECT_TRUE(bundle_filter.AddStream(StreamParams::CreateLegacy(kSsrc1))); StreamParams stream2; stream2.ssrcs.push_back(kSsrc2); stream2.ssrcs.push_back(kSsrc3); - EXPECT_TRUE(ssrc_filter.AddStream(stream2)); - - EXPECT_TRUE(ssrc_filter.IsActive()); - EXPECT_TRUE(ssrc_filter.FindStream(kSsrc1)); - EXPECT_TRUE(ssrc_filter.FindStream(kSsrc2)); - EXPECT_TRUE(ssrc_filter.FindStream(kSsrc3)); - EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc1)); - EXPECT_FALSE(ssrc_filter.FindStream(kSsrc1)); - EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc3)); - EXPECT_FALSE(ssrc_filter.RemoveStream(kSsrc2)); // Already removed. - EXPECT_FALSE(ssrc_filter.IsActive()); + EXPECT_TRUE(bundle_filter.AddStream(stream2)); + + EXPECT_TRUE(bundle_filter.HasStreams()); + EXPECT_TRUE(bundle_filter.FindStream(kSsrc1)); + EXPECT_TRUE(bundle_filter.FindStream(kSsrc2)); + EXPECT_TRUE(bundle_filter.FindStream(kSsrc3)); + EXPECT_TRUE(bundle_filter.RemoveStream(kSsrc1)); + EXPECT_FALSE(bundle_filter.FindStream(kSsrc1)); + EXPECT_TRUE(bundle_filter.RemoveStream(kSsrc3)); + EXPECT_FALSE(bundle_filter.RemoveStream(kSsrc2)); // Already removed. + EXPECT_FALSE(bundle_filter.HasStreams()); } -TEST(SsrcMuxFilterTest, RtpPacketTest) { - cricket::SsrcMuxFilter ssrc_filter; - EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1))); - EXPECT_TRUE(ssrc_filter.DemuxPacket( - reinterpret_cast(kRtpPacketSsrc1), - sizeof(kRtpPacketSsrc1), false)); - EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc2))); - EXPECT_TRUE(ssrc_filter.DemuxPacket( - reinterpret_cast(kRtpPacketSsrc2), - sizeof(kRtpPacketSsrc2), false)); - EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc2)); - EXPECT_FALSE(ssrc_filter.DemuxPacket( - reinterpret_cast(kRtpPacketSsrc2), - sizeof(kRtpPacketSsrc2), false)); - EXPECT_FALSE(ssrc_filter.DemuxPacket( - reinterpret_cast(kRtpPacketInvalidSsrc), - sizeof(kRtpPacketInvalidSsrc), false)); - EXPECT_FALSE(ssrc_filter.DemuxPacket( - reinterpret_cast(kRtpPacketTooSmall), - sizeof(kRtpPacketTooSmall), false)); +TEST(BundleFilterTest, RtpPacketTest) { + cricket::BundleFilter bundle_filter; + bundle_filter.AddPayloadType(kPayloadType1); + EXPECT_TRUE(bundle_filter.DemuxPacket( + reinterpret_cast(kRtpPacketPt1Ssrc1), + sizeof(kRtpPacketPt1Ssrc1), false)); + bundle_filter.AddPayloadType(kPayloadType2); + EXPECT_TRUE(bundle_filter.DemuxPacket( + reinterpret_cast(kRtpPacketPt2Ssrc2), + sizeof(kRtpPacketPt2Ssrc2), false)); + + // Payload type 0x33 is not added. + EXPECT_FALSE(bundle_filter.DemuxPacket( + reinterpret_cast(kRtpPacketPt3Ssrc2), + sizeof(kRtpPacketPt3Ssrc2), false)); + // Size is too small. + EXPECT_FALSE(bundle_filter.DemuxPacket( + reinterpret_cast(kRtpPacketPt1Ssrc1), 11, false)); + + bundle_filter.ClearAllPayloadTypes(); + EXPECT_FALSE(bundle_filter.DemuxPacket( + reinterpret_cast(kRtpPacketPt1Ssrc1), + sizeof(kRtpPacketPt1Ssrc1), false)); + EXPECT_FALSE(bundle_filter.DemuxPacket( + reinterpret_cast(kRtpPacketPt2Ssrc2), + sizeof(kRtpPacketPt2Ssrc2), false)); } -TEST(SsrcMuxFilterTest, RtcpPacketTest) { - cricket::SsrcMuxFilter ssrc_filter; - EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1))); - EXPECT_TRUE(ssrc_filter.DemuxPacket( +TEST(BundleFilterTest, RtcpPacketTest) { + cricket::BundleFilter bundle_filter; + EXPECT_TRUE(bundle_filter.AddStream(StreamParams::CreateLegacy(kSsrc1))); + EXPECT_TRUE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketCompoundSrSdesSsrc1), sizeof(kRtcpPacketCompoundSrSdesSsrc1), true)); - EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc2))); - EXPECT_TRUE(ssrc_filter.DemuxPacket( + EXPECT_TRUE(bundle_filter.AddStream(StreamParams::CreateLegacy(kSsrc2))); + EXPECT_TRUE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketSrSsrc2), sizeof(kRtcpPacketSrSsrc2), true)); - EXPECT_TRUE(ssrc_filter.DemuxPacket( + EXPECT_TRUE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketSdesSsrc2), sizeof(kRtcpPacketSdesSsrc2), true)); - EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc2)); + EXPECT_TRUE(bundle_filter.RemoveStream(kSsrc2)); // RTCP Packets other than SR and RR are demuxed regardless of SSRC. - EXPECT_TRUE(ssrc_filter.DemuxPacket( + EXPECT_TRUE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketSdesSsrc2), sizeof(kRtcpPacketSdesSsrc2), true)); // RTCP Packets with 'special' SSRC 0x01 are demuxed also - EXPECT_TRUE(ssrc_filter.DemuxPacket( + EXPECT_TRUE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketSrSsrc01), sizeof(kRtcpPacketSrSsrc01), true)); - EXPECT_FALSE(ssrc_filter.DemuxPacket( + EXPECT_FALSE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketSrSsrc2), sizeof(kRtcpPacketSrSsrc2), true)); - EXPECT_FALSE(ssrc_filter.DemuxPacket( + EXPECT_FALSE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketFixedHeaderOnly), sizeof(kRtcpPacketFixedHeaderOnly), true)); - EXPECT_FALSE(ssrc_filter.DemuxPacket( + EXPECT_FALSE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketTooSmall), sizeof(kRtcpPacketTooSmall), true)); - EXPECT_TRUE(ssrc_filter.DemuxPacket( + EXPECT_TRUE(bundle_filter.DemuxPacket( reinterpret_cast(kRtcpPacketNonCompoundRtcpPliFeedback), sizeof(kRtcpPacketNonCompoundRtcpPliFeedback), true)); + // If the streams_ is empty, rtcp packet passes through + EXPECT_TRUE(bundle_filter.RemoveStream(kSsrc1)); + EXPECT_FALSE(bundle_filter.HasStreams()); + EXPECT_TRUE(bundle_filter.DemuxPacket( + reinterpret_cast(kRtcpPacketSrSsrc2), + sizeof(kRtcpPacketSrSsrc2), true)); +} + +TEST(BundleFilterTest, InvalidRtpPacket) { + cricket::BundleFilter bundle_filter; + EXPECT_TRUE(bundle_filter.AddStream(StreamParams::CreateLegacy(kSsrc1))); + EXPECT_FALSE(bundle_filter.DemuxPacket( + reinterpret_cast(kSctpPacket), + sizeof(kSctpPacket), false)); } diff --git a/talk/session/media/call.cc b/talk/session/media/call.cc index 5939bff56..91fe146e9 100644 --- a/talk/session/media/call.cc +++ b/talk/session/media/call.cc @@ -34,6 +34,7 @@ #include "talk/media/base/screencastid.h" #include "talk/p2p/base/parsing.h" #include "talk/session/media/call.h" +#include "talk/session/media/currentspeakermonitor.h" #include "talk/session/media/mediasessionclient.h" namespace cricket { @@ -74,6 +75,22 @@ bool ContentContainsCrypto(const cricket::ContentInfo* content) { } +AudioSourceProxy::AudioSourceProxy(Call* call) + : call_(call) { + call_->SignalAudioMonitor.connect(this, &AudioSourceProxy::OnAudioMonitor); + call_->SignalMediaStreamsUpdate.connect( + this, &AudioSourceProxy::OnMediaStreamsUpdate); +} + +void AudioSourceProxy::OnAudioMonitor(Call* call, const AudioInfo& info) { + SignalAudioMonitor(this, info); +} + +void AudioSourceProxy::OnMediaStreamsUpdate(Call* call, Session* session, + const MediaStreams& added, const MediaStreams& removed) { + SignalMediaStreamsUpdate(this, session, added, removed); +} + Call::Call(MediaSessionClient* session_client) : id_(talk_base::CreateRandomId()), session_client_(session_client), @@ -84,6 +101,7 @@ Call::Call(MediaSessionClient* session_client) video_muted_(false), send_to_voicemail_(true), playing_dtmf_(false) { + audio_source_proxy_.reset(new AudioSourceProxy(this)); } Call::~Call() { @@ -718,7 +736,8 @@ void Call::StartSpeakerMonitor(Session* session) { StartAudioMonitor(session, kAudioMonitorPollPeriodMillis); } CurrentSpeakerMonitor* speaker_monitor = - new cricket::CurrentSpeakerMonitor(this, session); + new cricket::CurrentSpeakerMonitor( + audio_source_proxy_.get(), session); speaker_monitor->SignalUpdate.connect(this, &Call::OnSpeakerMonitor); speaker_monitor->Start(); speaker_monitor_map_[session->id()] = speaker_monitor; @@ -1104,4 +1123,8 @@ Session* Call::InternalInitiateSession(const std::string& id, return session; } +AudioSourceProxy* Call::GetAudioSourceProxy() { + return audio_source_proxy_.get(); +} + } // namespace cricket diff --git a/talk/session/media/call.h b/talk/session/media/call.h index efb4ea396..063447a7f 100644 --- a/talk/session/media/call.h +++ b/talk/session/media/call.h @@ -48,6 +48,8 @@ namespace cricket { +struct AudioInfo; +class Call; class MediaSessionClient; class BaseChannel; class VoiceChannel; @@ -58,6 +60,26 @@ class DataChannel; struct CallOptions : public MediaSessionOptions { }; +// CurrentSpeakerMonitor used to have a dependency on Call. To remove this +// dependency, we create AudioSourceContext. CurrentSpeakerMonitor depends on +// AudioSourceContext. +// AudioSourceProxy acts as a proxy so that when SignalAudioMonitor +// in Call is triggered, SignalAudioMonitor in AudioSourceContext is triggered. +// Likewise, when OnMediaStreamsUpdate in Call is triggered, +// OnMediaStreamsUpdate in AudioSourceContext is triggered. +class AudioSourceProxy: public AudioSourceContext, public sigslot::has_slots<> { + public: + explicit AudioSourceProxy(Call* call); + + private: + void OnAudioMonitor(Call* call, const AudioInfo& info); + void OnMediaStreamsUpdate(Call* call, cricket::Session*, + const cricket::MediaStreams&, const cricket::MediaStreams&); + + AudioSourceContext* audio_source_context_; + Call* call_; +}; + class Call : public talk_base::MessageHandler, public sigslot::has_slots<> { public: explicit Call(MediaSessionClient* session_client); @@ -167,6 +189,8 @@ class Call : public talk_base::MessageHandler, public sigslot::has_slots<> { const ReceiveDataParams&, const talk_base::Buffer&> SignalDataReceived; + AudioSourceProxy* GetAudioSourceProxy(); + private: void OnMessage(talk_base::Message* message); void OnSessionState(BaseSession* base_session, BaseSession::State state); @@ -276,6 +300,8 @@ class Call : public talk_base::MessageHandler, public sigslot::has_slots<> { VoiceMediaInfo last_voice_media_info_; + talk_base::scoped_ptr audio_source_proxy_; + friend class MediaSessionClient; }; diff --git a/talk/session/media/channel.cc b/talk/session/media/channel.cc index 9440e173f..d705d4d54 100644 --- a/talk/session/media/channel.cc +++ b/talk/session/media/channel.cc @@ -38,7 +38,6 @@ #include "talk/p2p/base/transportchannel.h" #include "talk/session/media/channelmanager.h" #include "talk/session/media/mediamessages.h" -#include "talk/session/media/rtcpmuxfilter.h" #include "talk/session/media/typingmonitor.h" @@ -55,6 +54,7 @@ enum { MSG_READYTOSENDDATA, MSG_DATARECEIVED, MSG_FIRSTPACKETRECEIVED, + MSG_STREAMCLOSEDREMOTELY, }; // Value specified in RFC 5764. @@ -559,15 +559,9 @@ bool BaseChannel::WantsPacket(bool rtcp, talk_base::Buffer* packet) { << packet->length(); return false; } - // If this channel is suppose to handle RTP data, that is determined by - // checking against ssrc filter. This is necessary to do it here to avoid - // double decryption. - if (ssrc_filter_.IsActive() && - !ssrc_filter_.DemuxPacket(packet->data(), packet->length(), rtcp)) { - return false; - } - return true; + // Bundle filter handles both rtp and rtcp packets. + return bundle_filter_.DemuxPacket(packet->data(), packet->length(), rtcp); } void BaseChannel::HandlePacket(bool rtcp, talk_base::Buffer* packet, @@ -904,6 +898,42 @@ bool BaseChannel::CheckSrtpConfig(const std::vector& cryptos, return true; } +bool BaseChannel::SetRecvRtpHeaderExtensions_w( + const MediaContentDescription* content, + MediaChannel* media_channel, + std::string* error_desc) { + if (content->rtp_header_extensions_set()) { + if (!media_channel->SetRecvRtpHeaderExtensions( + content->rtp_header_extensions())) { + std::ostringstream desc; + desc << "Failed to set receive rtp header extensions for " + << MediaTypeToString(content->type()) << " content."; + SafeSetError(desc.str(), error_desc); + return false; + } + } + return true; +} + +bool BaseChannel::SetSendRtpHeaderExtensions_w( + const MediaContentDescription* content, + MediaChannel* media_channel, + std::string* error_desc) { + if (content->rtp_header_extensions_set()) { + if (!media_channel->SetSendRtpHeaderExtensions( + content->rtp_header_extensions())) { + std::ostringstream desc; + desc << "Failed to set send rtp header extensions for " + << MediaTypeToString(content->type()) << " content."; + SafeSetError(desc.str(), error_desc); + return false; + } else { + MaybeCacheRtpAbsSendTimeHeaderExtension(content->rtp_header_extensions()); + } + } + return true; +} + bool BaseChannel::SetSrtp_w(const std::vector& cryptos, ContentAction action, ContentSource src, @@ -996,12 +1026,12 @@ bool BaseChannel::AddRecvStream_w(const StreamParams& sp) { if (!media_channel()->AddRecvStream(sp)) return false; - return ssrc_filter_.AddStream(sp); + return bundle_filter_.AddStream(sp); } bool BaseChannel::RemoveRecvStream_w(uint32 ssrc) { ASSERT(worker_thread() == talk_base::Thread::Current()); - ssrc_filter_.RemoveStream(ssrc); + bundle_filter_.RemoveStream(ssrc); return media_channel()->RemoveRecvStream(ssrc); } @@ -1160,22 +1190,16 @@ bool BaseChannel::SetBaseLocalContent_w(const MediaContentDescription* content, std::string* error_desc) { // Cache secure_required_ for belt and suspenders check on SendPacket secure_required_ = content->crypto_required() != CT_NONE; - bool ret = UpdateLocalStreams_w(content->streams(), action, error_desc); + // Set local RTP header extensions. + bool ret = SetRecvRtpHeaderExtensions_w(content, media_channel(), error_desc); // Set local SRTP parameters (what we will encrypt with). ret &= SetSrtp_w(content->cryptos(), action, CS_LOCAL, error_desc); // Set local RTCP mux parameters. ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_LOCAL, error_desc); - // Set local RTP header extensions. - if (content->rtp_header_extensions_set()) { - if (!media_channel()->SetRecvRtpHeaderExtensions( - content->rtp_header_extensions())) { - std::ostringstream desc; - desc << "Failed to set receive rtp header extensions for " - << MediaTypeToString(content->type()) << " content."; - SafeSetError(desc.str(), error_desc); - ret = false; - } - } + + // Call UpdateLocalStreams_w last to make sure as many settings as possible + // are already set when creating streams. + ret &= UpdateLocalStreams_w(content->streams(), action, error_desc); set_local_content_direction(content->direction()); return ret; } @@ -1183,25 +1207,12 @@ bool BaseChannel::SetBaseLocalContent_w(const MediaContentDescription* content, bool BaseChannel::SetBaseRemoteContent_w(const MediaContentDescription* content, ContentAction action, std::string* error_desc) { - bool ret = UpdateRemoteStreams_w(content->streams(), action, error_desc); + // Set remote RTP header extensions. + bool ret = SetSendRtpHeaderExtensions_w(content, media_channel(), error_desc); // Set remote SRTP parameters (what the other side will encrypt with). ret &= SetSrtp_w(content->cryptos(), action, CS_REMOTE, error_desc); // Set remote RTCP mux parameters. ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_REMOTE, error_desc); - // Set remote RTP header extensions. - if (content->rtp_header_extensions_set()) { - if (!media_channel()->SetSendRtpHeaderExtensions( - content->rtp_header_extensions())) { - std::ostringstream desc; - desc << "Failed to set send rtp header extensions for " - << MediaTypeToString(content->type()) << " content."; - SafeSetError(desc.str(), error_desc); - ret = false; - } else { - MaybeCacheRtpAbsSendTimeHeaderExtension(content->rtp_header_extensions()); - } - } - if (!media_channel()->SetMaxSendBandwidth(content->bandwidth())) { std::ostringstream desc; desc << "Failed to set max send bandwidth for " @@ -1209,6 +1220,10 @@ bool BaseChannel::SetBaseRemoteContent_w(const MediaContentDescription* content, SafeSetError(desc.str(), error_desc); ret = false; } + + // Call UpdateRemoteStreams_w last to make sure as many settings as possible + // are already set when creating streams. + ret &= UpdateRemoteStreams_w(content->streams(), action, error_desc); set_remote_content_direction(content->direction()); return ret; } @@ -1479,6 +1494,10 @@ bool VoiceChannel::SetLocalContent_w(const MediaContentDescription* content, // If everything worked, see if we can start receiving. if (ret) { + std::vector::const_iterator it = audio->codecs().begin(); + for (; it != audio->codecs().end(); ++it) { + bundle_filter()->AddPayloadType(it->id); + } ChangeState(); } else { LOG(LS_WARNING) << "Failed to set local voice description"; @@ -1844,6 +1863,10 @@ bool VideoChannel::SetLocalContent_w(const MediaContentDescription* content, // If everything worked, see if we can start receiving. if (ret) { + std::vector::const_iterator it = video->codecs().begin(); + for (; it != video->codecs().end(); ++it) { + bundle_filter()->AddPayloadType(it->id); + } ChangeState(); } else { LOG(LS_WARNING) << "Failed to set local video description"; @@ -2147,6 +2170,8 @@ bool DataChannel::Init() { this, &DataChannel::OnDataChannelError); media_channel()->SignalReadyToSend.connect( this, &DataChannel::OnDataChannelReadyToSend); + media_channel()->SignalStreamClosedRemotely.connect( + this, &DataChannel::OnStreamClosedRemotely); srtp_filter()->SignalSrtpError.connect( this, &DataChannel::OnSrtpError); return true; @@ -2164,21 +2189,11 @@ const ContentInfo* DataChannel::GetFirstContent( return GetFirstDataContent(sdesc); } - -static bool IsRtpPacket(const talk_base::Buffer* packet) { - int version; - if (!GetRtpVersion(packet->data(), packet->length(), &version)) { - return false; - } - - return version == 2; -} - bool DataChannel::WantsPacket(bool rtcp, talk_base::Buffer* packet) { if (data_channel_type_ == DCT_SCTP) { // TODO(pthatcher): Do this in a more robust way by checking for // SCTP or DTLS. - return !IsRtpPacket(packet); + return !IsRtpPacket(packet->data(), packet->length()); } else if (data_channel_type_ == DCT_RTP) { return BaseChannel::WantsPacket(rtcp, packet); } @@ -2249,7 +2264,6 @@ bool DataChannel::SetLocalContent_w(const MediaContentDescription* content, } } else { ret = SetBaseLocalContent_w(content, action, error_desc); - if (action != CA_UPDATE || data->has_codecs()) { if (!media_channel()->SetRecvCodecs(data->codecs())) { SafeSetError("Failed to set data receive codecs.", error_desc); @@ -2260,6 +2274,10 @@ bool DataChannel::SetLocalContent_w(const MediaContentDescription* content, // If everything worked, see if we can start receiving. if (ret) { + std::vector::const_iterator it = data->codecs().begin(); + for (; it != data->codecs().end(); ++it) { + bundle_filter()->AddPayloadType(it->id); + } ChangeState(); } else { LOG(LS_WARNING) << "Failed to set local data description"; @@ -2383,6 +2401,13 @@ void DataChannel::OnMessage(talk_base::Message *pmsg) { delete data; break; } + case MSG_STREAMCLOSEDREMOTELY: { + talk_base::TypedMessageData* data = + static_cast*>(pmsg->pdata); + SignalStreamClosedRemotely(data->data()); + delete data; + break; + } default: BaseChannel::OnMessage(pmsg); break; @@ -2469,4 +2494,10 @@ bool DataChannel::ShouldSetupDtlsSrtp() const { return (data_channel_type_ == DCT_RTP); } +void DataChannel::OnStreamClosedRemotely(uint32 sid) { + talk_base::TypedMessageData* message = + new talk_base::TypedMessageData(sid); + signaling_thread()->Post(this, MSG_STREAMCLOSEDREMOTELY, message); +} + } // namespace cricket diff --git a/talk/session/media/channel.h b/talk/session/media/channel.h index 2896bcede..340caa7a6 100644 --- a/talk/session/media/channel.h +++ b/talk/session/media/channel.h @@ -44,11 +44,11 @@ #include "talk/p2p/base/session.h" #include "talk/p2p/client/socketmonitor.h" #include "talk/session/media/audiomonitor.h" +#include "talk/session/media/bundlefilter.h" #include "talk/session/media/mediamonitor.h" #include "talk/session/media/mediasession.h" #include "talk/session/media/rtcpmuxfilter.h" #include "talk/session/media/srtpfilter.h" -#include "talk/session/media/ssrcmuxfilter.h" namespace cricket { @@ -213,7 +213,7 @@ class BaseChannel } } - SsrcMuxFilter* ssrc_filter() { return &ssrc_filter_; } + BundleFilter* bundle_filter() { return &bundle_filter_; } const std::vector& local_streams() const { return local_streams_; @@ -324,6 +324,13 @@ class BaseChannel void MaybeCacheRtpAbsSendTimeHeaderExtension( const std::vector& extensions); + bool SetRecvRtpHeaderExtensions_w(const MediaContentDescription* content, + MediaChannel* media_channel, + std::string* error_desc); + bool SetSendRtpHeaderExtensions_w(const MediaContentDescription* content, + MediaChannel* media_channel, + std::string* error_desc); + bool CheckSrtpConfig(const std::vector& cryptos, bool* dtls, std::string* error_desc); @@ -372,7 +379,7 @@ class BaseChannel TransportChannel* rtcp_transport_channel_; SrtpFilter srtp_filter_; RtcpMuxFilter rtcp_mux_filter_; - SsrcMuxFilter ssrc_filter_; + BundleFilter bundle_filter_; talk_base::scoped_ptr socket_monitor_; bool enabled_; bool writable_; @@ -640,6 +647,8 @@ class DataChannel : public BaseChannel { // That occurs when the channel is enabled, the transport is writable, // both local and remote descriptions are set, and the channel is unblocked. sigslot::signal1 SignalReadyToSendData; + // Signal for notifying that the remote side has closed the DataChannel. + sigslot::signal1 SignalStreamClosedRemotely; protected: // downcasts a MediaChannel. @@ -711,6 +720,7 @@ class DataChannel : public BaseChannel { void OnDataChannelError(uint32 ssrc, DataMediaChannel::Error error); void OnDataChannelReadyToSend(bool writable); void OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode, SrtpFilter::Error error); + void OnStreamClosedRemotely(uint32 sid); talk_base::scoped_ptr media_monitor_; // TODO(pthatcher): Make a separate SctpDataChannel and diff --git a/talk/session/media/channel_unittest.cc b/talk/session/media/channel_unittest.cc index cb2bd6997..c22361d65 100644 --- a/talk/session/media/channel_unittest.cc +++ b/talk/session/media/channel_unittest.cc @@ -71,6 +71,8 @@ static const cricket::DataCodec kGoogleDataCodec(101, "google-data", 0); static const uint32 kSsrc1 = 0x1111; static const uint32 kSsrc2 = 0x2222; static const uint32 kSsrc3 = 0x3333; +static const int kAudioPts[] = {0, 8}; +static const int kVideoPts[] = {97, 99}; template { static_cast(rtcp_packet_.size())); } // Methods to send custom data. - bool SendCustomRtp1(uint32 ssrc, int sequence_number) { - std::string data(CreateRtpData(ssrc, sequence_number)); + bool SendCustomRtp1(uint32 ssrc, int sequence_number, int pl_type = -1) { + std::string data(CreateRtpData(ssrc, sequence_number, pl_type)); return media_channel1_->SendRtp(data.c_str(), static_cast(data.size())); } - bool SendCustomRtp2(uint32 ssrc, int sequence_number) { - std::string data(CreateRtpData(ssrc, sequence_number)); + bool SendCustomRtp2(uint32 ssrc, int sequence_number, int pl_type = -1) { + std::string data(CreateRtpData(ssrc, sequence_number, pl_type)); return media_channel2_->SendRtp(data.c_str(), static_cast(data.size())); } @@ -445,13 +447,13 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { static_cast(rtcp_packet_.size())); } // Methods to check custom data. - bool CheckCustomRtp1(uint32 ssrc, int sequence_number) { - std::string data(CreateRtpData(ssrc, sequence_number)); + bool CheckCustomRtp1(uint32 ssrc, int sequence_number, int pl_type = -1 ) { + std::string data(CreateRtpData(ssrc, sequence_number, pl_type)); return media_channel1_->CheckRtp(data.c_str(), static_cast(data.size())); } - bool CheckCustomRtp2(uint32 ssrc, int sequence_number) { - std::string data(CreateRtpData(ssrc, sequence_number)); + bool CheckCustomRtp2(uint32 ssrc, int sequence_number, int pl_type = -1) { + std::string data(CreateRtpData(ssrc, sequence_number, pl_type)); return media_channel2_->CheckRtp(data.c_str(), static_cast(data.size())); } @@ -465,11 +467,14 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { return media_channel2_->CheckRtcp(data.c_str(), static_cast(data.size())); } - std::string CreateRtpData(uint32 ssrc, int sequence_number) { + std::string CreateRtpData(uint32 ssrc, int sequence_number, int pl_type) { std::string data(rtp_packet_); // Set SSRC in the rtp packet copy. talk_base::SetBE32(const_cast(data.c_str()) + 8, ssrc); talk_base::SetBE16(const_cast(data.c_str()) + 2, sequence_number); + if (pl_type >= 0) { + talk_base::Set8(const_cast(data.c_str()), 1, pl_type); + } return data; } std::string CreateRtcpData(uint32 ssrc) { @@ -1438,60 +1443,63 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { EXPECT_TRUE(CheckNoRtp2()); } - void SendSsrcMuxToSsrcMuxWithRtcpMux() { + void SendBundleToBundle( + const int* pl_types, int len, bool rtcp_mux, bool secure) { + ASSERT_EQ(2, len); int sequence_number1_1 = 0, sequence_number2_2 = 0; - CreateChannels(SSRC_MUX | RTCP | RTCP_MUX, SSRC_MUX | RTCP | RTCP_MUX); + // Only pl_type1 was added to the bundle filter for both |channel1_| + // and |channel2_|. + int pl_type1 = pl_types[0]; + int pl_type2 = pl_types[1]; + int flags = SSRC_MUX | RTCP; + if (secure) flags |= SECURE; + uint32 expected_channels = 2U; + if (rtcp_mux) { + flags |= RTCP_MUX; + expected_channels = 1U; + } + CreateChannels(flags, flags); EXPECT_TRUE(SendInitiate()); EXPECT_EQ(2U, GetTransport1()->channels().size()); - EXPECT_EQ(1U, GetTransport2()->channels().size()); + EXPECT_EQ(expected_channels, GetTransport2()->channels().size()); EXPECT_TRUE(SendAccept()); - EXPECT_EQ(1U, GetTransport1()->channels().size()); - EXPECT_EQ(1U, GetTransport2()->channels().size()); - EXPECT_TRUE(channel1_->ssrc_filter()->IsActive()); - // channel1 - should have media_content2 as remote. i.e. kSsrc2 - EXPECT_TRUE(channel1_->ssrc_filter()->FindStream(kSsrc2)); - EXPECT_TRUE(channel2_->ssrc_filter()->IsActive()); - // channel2 - should have media_content1 as remote. i.e. kSsrc1 - EXPECT_TRUE(channel2_->ssrc_filter()->FindStream(kSsrc1)); - EXPECT_TRUE(SendCustomRtp1(kSsrc1, ++sequence_number1_1)); - EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2)); - EXPECT_TRUE(SendCustomRtcp1(kSsrc1)); - EXPECT_TRUE(SendCustomRtcp2(kSsrc2)); - EXPECT_TRUE(CheckCustomRtp1(kSsrc2, sequence_number2_2)); + EXPECT_EQ(expected_channels, GetTransport1()->channels().size()); + EXPECT_EQ(expected_channels, GetTransport2()->channels().size()); + EXPECT_TRUE(channel1_->bundle_filter()->FindPayloadType(pl_type1)); + EXPECT_TRUE(channel2_->bundle_filter()->FindPayloadType(pl_type1)); + EXPECT_FALSE(channel1_->bundle_filter()->FindPayloadType(pl_type2)); + EXPECT_FALSE(channel2_->bundle_filter()->FindPayloadType(pl_type2)); + // channel1 - should only have media_content2 as remote. i.e. kSsrc2 + EXPECT_TRUE(channel1_->bundle_filter()->FindStream(kSsrc2)); + EXPECT_FALSE(channel1_->bundle_filter()->FindStream(kSsrc1)); + // channel2 - should only have media_content1 as remote. i.e. kSsrc1 + EXPECT_TRUE(channel2_->bundle_filter()->FindStream(kSsrc1)); + EXPECT_FALSE(channel2_->bundle_filter()->FindStream(kSsrc2)); + + // Both channels can receive pl_type1 only. + EXPECT_TRUE(SendCustomRtp1(kSsrc1, ++sequence_number1_1, pl_type1)); + EXPECT_TRUE(CheckCustomRtp2(kSsrc1, sequence_number1_1, pl_type1)); + EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2, pl_type1)); + EXPECT_TRUE(CheckCustomRtp1(kSsrc2, sequence_number2_2, pl_type1)); EXPECT_TRUE(CheckNoRtp1()); - EXPECT_TRUE(CheckCustomRtp2(kSsrc1, sequence_number1_1)); EXPECT_TRUE(CheckNoRtp2()); + + // RTCP test + EXPECT_TRUE(SendCustomRtp1(kSsrc1, ++sequence_number1_1, pl_type2)); + EXPECT_FALSE(CheckCustomRtp2(kSsrc1, sequence_number1_1, pl_type2)); + EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2, pl_type2)); + EXPECT_FALSE(CheckCustomRtp1(kSsrc2, sequence_number2_2, pl_type2)); + + EXPECT_TRUE(SendCustomRtcp1(kSsrc1)); + EXPECT_TRUE(SendCustomRtcp2(kSsrc2)); EXPECT_TRUE(CheckCustomRtcp1(kSsrc2)); EXPECT_TRUE(CheckNoRtcp1()); EXPECT_TRUE(CheckCustomRtcp2(kSsrc1)); EXPECT_TRUE(CheckNoRtcp2()); - } - void SendSsrcMuxToSsrcMux() { - int sequence_number1_1 = 0, sequence_number2_2 = 0; - CreateChannels(SSRC_MUX | RTCP, SSRC_MUX | RTCP); - EXPECT_TRUE(SendInitiate()); - EXPECT_EQ(2U, GetTransport1()->channels().size()); - EXPECT_EQ(2U, GetTransport2()->channels().size()); - EXPECT_TRUE(SendAccept()); - EXPECT_EQ(2U, GetTransport1()->channels().size()); - EXPECT_EQ(2U, GetTransport2()->channels().size()); - EXPECT_TRUE(channel1_->ssrc_filter()->IsActive()); - // channel1 - should have media_content2 as remote. i.e. kSsrc2 - EXPECT_TRUE(channel1_->ssrc_filter()->FindStream(kSsrc2)); - EXPECT_TRUE(channel2_->ssrc_filter()->IsActive()); - // channel2 - should have media_content1 as remote. i.e. kSsrc1 - EXPECT_TRUE(SendCustomRtp1(kSsrc1, ++sequence_number1_1)); - EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2)); - EXPECT_TRUE(SendCustomRtcp1(kSsrc1)); - EXPECT_TRUE(SendCustomRtcp2(kSsrc2)); - EXPECT_TRUE(CheckCustomRtp1(kSsrc2, sequence_number2_2)); - EXPECT_FALSE(CheckCustomRtp1(kSsrc1, sequence_number2_2)); - EXPECT_TRUE(CheckCustomRtp2(kSsrc1, sequence_number1_1)); - EXPECT_FALSE(CheckCustomRtp2(kSsrc2, sequence_number1_1)); - EXPECT_TRUE(CheckCustomRtcp1(kSsrc2)); + EXPECT_TRUE(SendCustomRtcp1(kSsrc2)); + EXPECT_TRUE(SendCustomRtcp2(kSsrc1)); EXPECT_FALSE(CheckCustomRtcp1(kSsrc1)); - EXPECT_TRUE(CheckCustomRtcp2(kSsrc1)); EXPECT_FALSE(CheckCustomRtcp2(kSsrc2)); } @@ -1753,10 +1761,24 @@ class ChannelTest : public testing::Test, public sigslot::has_slots<> { error_); } - void TestSrtpError() { - static const unsigned char kBadPacket[] = { - 0x84, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 - }; + void TestSrtpError(int pl_type) { + // For Audio, only pl_type 0 is added to the bundle filter. + // For Video, only pl_type 97 is added to the bundle filter. + // So we need to pass in pl_type so that the packet can pass through + // the bundle filter before it can be processed by the srtp filter. + // The packet is not a valid srtp packet because it is too short. + unsigned const char kBadPacket[] = {0x84, + static_cast(pl_type), + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01}; CreateChannels(RTCP | SECURE, RTCP | SECURE); EXPECT_FALSE(channel1_->secure()); EXPECT_FALSE(channel2_->secure()); @@ -2277,7 +2299,7 @@ TEST_F(VoiceChannelTest, TestChangeStateError) { } TEST_F(VoiceChannelTest, TestSrtpError) { - Base::TestSrtpError(); + Base::TestSrtpError(kAudioPts[0]); } TEST_F(VoiceChannelTest, TestOnReadyToSend) { @@ -2389,12 +2411,22 @@ TEST_F(VoiceChannelTest, TestScaleVolumeMultiwayCall) { EXPECT_DOUBLE_EQ(0.0, right); } -TEST_F(VoiceChannelTest, SendSsrcMuxToSsrcMux) { - Base::SendSsrcMuxToSsrcMux(); +TEST_F(VoiceChannelTest, SendBundleToBundle) { + Base::SendBundleToBundle(kAudioPts, ARRAY_SIZE(kAudioPts), false, false); } -TEST_F(VoiceChannelTest, SendSsrcMuxToSsrcMuxWithRtcpMux) { - Base::SendSsrcMuxToSsrcMuxWithRtcpMux(); +TEST_F(VoiceChannelTest, SendBundleToBundleSecure) { + Base::SendBundleToBundle(kAudioPts, ARRAY_SIZE(kAudioPts), false, true); +} + +TEST_F(VoiceChannelTest, SendBundleToBundleWithRtcpMux) { + Base::SendBundleToBundle( + kAudioPts, ARRAY_SIZE(kAudioPts), true, false); +} + +TEST_F(VoiceChannelTest, SendBundleToBundleWithRtcpMuxSecure) { + Base::SendBundleToBundle( + kAudioPts, ARRAY_SIZE(kAudioPts), true, true); } TEST_F(VoiceChannelTest, TestSetChannelOptions) { @@ -2604,18 +2636,28 @@ TEST_F(VideoChannelTest, TestFlushRtcp) { Base::TestFlushRtcp(); } -TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMux) { - Base::SendSsrcMuxToSsrcMux(); +TEST_F(VideoChannelTest, SendBundleToBundle) { + Base::SendBundleToBundle(kVideoPts, ARRAY_SIZE(kVideoPts), false, false); +} + +TEST_F(VideoChannelTest, SendBundleToBundleSecure) { + Base::SendBundleToBundle(kVideoPts, ARRAY_SIZE(kVideoPts), false, true); +} + +TEST_F(VideoChannelTest, SendBundleToBundleWithRtcpMux) { + Base::SendBundleToBundle( + kVideoPts, ARRAY_SIZE(kVideoPts), true, false); } -TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMuxWithRtcpMux) { - Base::SendSsrcMuxToSsrcMuxWithRtcpMux(); +TEST_F(VideoChannelTest, SendBundleToBundleWithRtcpMuxSecure) { + Base::SendBundleToBundle( + kVideoPts, ARRAY_SIZE(kVideoPts), true, true); } // TODO(gangji): Add VideoChannelTest.TestChangeStateError. TEST_F(VideoChannelTest, TestSrtpError) { - Base::TestSrtpError(); + Base::TestSrtpError(kVideoPts[0]); } TEST_F(VideoChannelTest, TestOnReadyToSend) { diff --git a/talk/session/media/channelmanager.cc b/talk/session/media/channelmanager.cc index 056e09c3b..45d1a9521 100644 --- a/talk/session/media/channelmanager.cc +++ b/talk/session/media/channelmanager.cc @@ -217,7 +217,11 @@ bool ChannelManager::Init() { } ASSERT(worker_thread_ != NULL); - if (worker_thread_ && worker_thread_->started()) { + ASSERT(worker_thread_->RunningForChannelManager()); + // TODO(fischman): remove the if below (and + // Thread::RunningForChannelManager()) once the ASSERT above has stuck for a + // month (2014/06/22). + if (worker_thread_ && worker_thread_->RunningForChannelManager()) { if (media_engine_->Init(worker_thread_)) { initialized_ = true; @@ -577,6 +581,29 @@ bool ChannelManager::SetAudioOptions_w( return ret; } +// Sets Engine-specific audio options according to enabled experiments. +bool ChannelManager::SetEngineAudioOptions(const AudioOptions& options) { + // If we're initialized, pass the settings to the media engine. + bool ret = false; + if (initialized_) { + ret = worker_thread_->Invoke( + Bind(&ChannelManager::SetEngineAudioOptions_w, this, options)); + } + + // If all worked well, save the audio options. + if (ret) { + audio_options_ = options; + } + return ret; +} + +bool ChannelManager::SetEngineAudioOptions_w(const AudioOptions& options) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + + return media_engine_->SetAudioOptions(options); +} + bool ChannelManager::GetOutputVolume(int* level) { if (!initialized_) { return false; @@ -942,14 +969,6 @@ VideoFormat ChannelManager::GetStartCaptureFormat() { Bind(&MediaEngineInterface::GetStartCaptureFormat, media_engine_.get())); } -bool ChannelManager::SetAudioOptions(const AudioOptions& options) { - if (!media_engine_->SetAudioOptions(options)) { - return false; - } - audio_options_ = options; - return true; -} - bool ChannelManager::StartAecDump(talk_base::PlatformFile file) { return worker_thread_->Invoke( Bind(&MediaEngineInterface::StartAecDump, media_engine_.get(), file)); diff --git a/talk/session/media/channelmanager.h b/talk/session/media/channelmanager.h index deb7b9ebd..e8d6c0e5e 100644 --- a/talk/session/media/channelmanager.h +++ b/talk/session/media/channelmanager.h @@ -143,6 +143,8 @@ class ChannelManager : public talk_base::MessageHandler, bool SetAudioOptions(const std::string& wave_in_device, const std::string& wave_out_device, const AudioOptions& options); + // Sets Engine-specific audio options according to enabled experiments. + bool SetEngineAudioOptions(const AudioOptions& options); bool GetOutputVolume(int* level); bool SetOutputVolume(int level); bool IsSameCapturer(const std::string& capturer_name, @@ -230,10 +232,6 @@ class ChannelManager : public talk_base::MessageHandler, // removed. VideoFormat GetStartCaptureFormat(); - // TODO(turajs): Remove this function when ACM2 is in use. Used mainly to - // choose between ACM1 and ACM2. - bool SetAudioOptions(const AudioOptions& options); - protected: // Adds non-transient parameters which can only be changed through the // options store. @@ -270,6 +268,7 @@ class ChannelManager : public talk_base::MessageHandler, void DestroySoundclip_w(Soundclip* soundclip); bool SetAudioOptions_w(const AudioOptions& options, int delay_offset, const Device* in_dev, const Device* out_dev); + bool SetEngineAudioOptions_w(const AudioOptions& options); bool SetCaptureDevice_w(const Device* cam_device); void OnVideoCaptureStateChange(VideoCapturer* capturer, CaptureState result); diff --git a/talk/session/media/channelmanager_unittest.cc b/talk/session/media/channelmanager_unittest.cc index d0d380d3e..cbf19f888 100644 --- a/talk/session/media/channelmanager_unittest.cc +++ b/talk/session/media/channelmanager_unittest.cc @@ -122,7 +122,9 @@ TEST_F(ChannelManagerTest, StartupShutdownOnThread) { } // Test that we fail to startup if we're given an unstarted thread. -TEST_F(ChannelManagerTest, StartupShutdownOnUnstartedThread) { +// TODO(fischman): delete once Thread::RunningForChannelManager() is gone +// (webrtc:3388). +TEST_F(ChannelManagerTest, DISABLED_StartupShutdownOnUnstartedThread) { EXPECT_TRUE(cm_->set_worker_thread(&worker_)); EXPECT_FALSE(cm_->Init()); EXPECT_FALSE(cm_->initialized()); @@ -319,6 +321,25 @@ TEST_F(ChannelManagerTest, SetAudioOptions) { EXPECT_FALSE(cm_->SetAudioOptions("audio-in9", "audio-out2", options)); } +TEST_F(ChannelManagerTest, SetEngineAudioOptions) { + EXPECT_TRUE(cm_->Init()); + // Test setting specific values. + AudioOptions options; + options.experimental_ns.Set(true); + EXPECT_TRUE(cm_->SetEngineAudioOptions(options)); + bool experimental_ns = false; + EXPECT_TRUE(fme_->audio_options().experimental_ns.Get(&experimental_ns)); + EXPECT_TRUE(experimental_ns); +} + +TEST_F(ChannelManagerTest, SetEngineAudioOptionsBeforeInitFails) { + // Test that values that we set before Init are not applied. + AudioOptions options; + options.experimental_ns.Set(true); + EXPECT_FALSE(cm_->SetEngineAudioOptions(options)); + EXPECT_FALSE(fme_->audio_options().experimental_ns.IsSet()); +} + TEST_F(ChannelManagerTest, SetCaptureDeviceBeforeInit) { // Test that values that we set before Init are applied. EXPECT_TRUE(cm_->SetCaptureDevice("video-in2")); diff --git a/talk/session/media/currentspeakermonitor.cc b/talk/session/media/currentspeakermonitor.cc index 1f3e0938f..8965cde95 100644 --- a/talk/session/media/currentspeakermonitor.cc +++ b/talk/session/media/currentspeakermonitor.cc @@ -28,7 +28,9 @@ #include "talk/session/media/currentspeakermonitor.h" #include "talk/base/logging.h" -#include "talk/session/media/call.h" +#include "talk/media/base/streamparams.h" +#include "talk/session/media/audiomonitor.h" +#include "talk/session/media/mediamessages.h" namespace cricket { @@ -39,9 +41,10 @@ const int kMaxAudioLevel = 9; const int kDefaultMinTimeBetweenSwitches = 1000; } -CurrentSpeakerMonitor::CurrentSpeakerMonitor(Call* call, BaseSession* session) +CurrentSpeakerMonitor::CurrentSpeakerMonitor( + AudioSourceContext* audio_source_context, BaseSession* session) : started_(false), - call_(call), + audio_source_context_(audio_source_context), session_(session), current_speaker_ssrc_(0), earliest_permitted_switch_time_(0), @@ -54,10 +57,12 @@ CurrentSpeakerMonitor::~CurrentSpeakerMonitor() { void CurrentSpeakerMonitor::Start() { if (!started_) { - call_->SignalAudioMonitor.connect( + audio_source_context_->SignalAudioMonitor.connect( this, &CurrentSpeakerMonitor::OnAudioMonitor); - call_->SignalMediaStreamsUpdate.connect( + audio_source_context_->SignalMediaStreamsUpdate.connect( this, &CurrentSpeakerMonitor::OnMediaStreamsUpdate); + audio_source_context_->SignalMediaStreamsReset.connect( + this, &CurrentSpeakerMonitor::OnMediaStreamsReset); started_ = true; } @@ -65,8 +70,8 @@ void CurrentSpeakerMonitor::Start() { void CurrentSpeakerMonitor::Stop() { if (started_) { - call_->SignalAudioMonitor.disconnect(this); - call_->SignalMediaStreamsUpdate.disconnect(this); + audio_source_context_->SignalAudioMonitor.disconnect(this); + audio_source_context_->SignalMediaStreamsUpdate.disconnect(this); started_ = false; ssrc_to_speaking_state_map_.clear(); @@ -80,7 +85,8 @@ void CurrentSpeakerMonitor::set_min_time_between_switches( min_time_between_switches_ = min_time_between_switches; } -void CurrentSpeakerMonitor::OnAudioMonitor(Call* call, const AudioInfo& info) { +void CurrentSpeakerMonitor::OnAudioMonitor( + AudioSourceContext* audio_source_context, const AudioInfo& info) { std::map active_ssrc_to_level_map; cricket::AudioInfo::StreamList::const_iterator stream_list_it; for (stream_list_it = info.active_streams.begin(); @@ -187,22 +193,29 @@ void CurrentSpeakerMonitor::OnAudioMonitor(Call* call, const AudioInfo& info) { } } -void CurrentSpeakerMonitor::OnMediaStreamsUpdate(Call* call, - Session* session, - const MediaStreams& added, - const MediaStreams& removed) { - if (call == call_ && session == session_) { +void CurrentSpeakerMonitor::OnMediaStreamsUpdate( + AudioSourceContext* audio_source_context, BaseSession* session, + const MediaStreams& added, const MediaStreams& removed) { + + if (audio_source_context == audio_source_context_ && session == session_) { // Update the speaking state map based on added and removed streams. for (std::vector::const_iterator - it = removed.video().begin(); it != removed.video().end(); ++it) { + it = removed.audio().begin(); it != removed.audio().end(); ++it) { ssrc_to_speaking_state_map_.erase(it->first_ssrc()); } for (std::vector::const_iterator - it = added.video().begin(); it != added.video().end(); ++it) { + it = added.audio().begin(); it != added.audio().end(); ++it) { ssrc_to_speaking_state_map_[it->first_ssrc()] = SS_NOT_SPEAKING; } } } +void CurrentSpeakerMonitor::OnMediaStreamsReset( + AudioSourceContext* audio_source_context, BaseSession* session) { + if (audio_source_context == audio_source_context_ && session == session_) { + ssrc_to_speaking_state_map_.clear(); + } +} + } // namespace cricket diff --git a/talk/session/media/currentspeakermonitor.h b/talk/session/media/currentspeakermonitor.h index 1781db58c..8e05c8e67 100644 --- a/talk/session/media/currentspeakermonitor.h +++ b/talk/session/media/currentspeakermonitor.h @@ -39,16 +39,34 @@ namespace cricket { class BaseSession; -class Call; class Session; struct AudioInfo; struct MediaStreams; -// Note that the call's audio monitor must be started before this is started. +class AudioSourceContext { + public: + sigslot::signal2 + SignalAudioMonitor; + sigslot::signal2 + SignalMediaStreamsReset; + sigslot::signal4 + SignalMediaStreamsUpdate; +}; + +// CurrentSpeakerMonitor can be used to monitor the audio-levels from +// many audio-sources and report on changes in the loudest audio-source. +// Its a generic type and relies on an AudioSourceContext which is aware of +// the audio-sources. AudioSourceContext needs to provide two signals namely +// SignalAudioInfoMonitor - provides audio info of the all current speakers. +// SignalMediaSourcesUpdated - provides updates when a speaker leaves or joins. +// Note that the AudioSourceContext's audio monitor must be started +// before this is started. // It's recommended that the audio monitor be started with a 100 ms period. class CurrentSpeakerMonitor : public sigslot::has_slots<> { public: - CurrentSpeakerMonitor(Call* call, BaseSession* session); + CurrentSpeakerMonitor(AudioSourceContext* audio_source_context, + BaseSession* session); ~CurrentSpeakerMonitor(); BaseSession* session() const { return session_; } @@ -62,16 +80,19 @@ class CurrentSpeakerMonitor : public sigslot::has_slots<> { void set_min_time_between_switches(uint32 min_time_between_switches); // This is fired when the current speaker changes, and provides his audio - // SSRC. This only fires after the audio monitor on the underlying Call has - // been started. + // SSRC. This only fires after the audio monitor on the underlying + // AudioSourceContext has been started. sigslot::signal2 SignalUpdate; private: - void OnAudioMonitor(Call* call, const AudioInfo& info); - void OnMediaStreamsUpdate(Call* call, - Session* session, + void OnAudioMonitor(AudioSourceContext* audio_source_context, + const AudioInfo& info); + void OnMediaStreamsUpdate(AudioSourceContext* audio_source_context, + BaseSession* session, const MediaStreams& added, const MediaStreams& removed); + void OnMediaStreamsReset(AudioSourceContext* audio_source_context, + BaseSession* session); // These are states that a participant will pass through so that we gradually // recognize that they have started and stopped speaking. This avoids @@ -85,7 +106,7 @@ class CurrentSpeakerMonitor : public sigslot::has_slots<> { }; bool started_; - Call* call_; + AudioSourceContext* audio_source_context_; BaseSession* session_; std::map ssrc_to_speaking_state_map_; uint32 current_speaker_ssrc_; diff --git a/talk/session/media/currentspeakermonitor_unittest.cc b/talk/session/media/currentspeakermonitor_unittest.cc index 84c761855..b65611f6d 100644 --- a/talk/session/media/currentspeakermonitor_unittest.cc +++ b/talk/session/media/currentspeakermonitor_unittest.cc @@ -47,7 +47,7 @@ class MockCall : public Call { MockCall() : Call(NULL) {} void EmitAudioMonitor(const AudioInfo& info) { - SignalAudioMonitor(this, info); + GetAudioSourceProxy()->SignalAudioMonitor(GetAudioSourceProxy(), info); } }; @@ -56,7 +56,7 @@ class CurrentSpeakerMonitorTest : public testing::Test, public: CurrentSpeakerMonitorTest() { call_ = new MockCall(); - monitor_ = new CurrentSpeakerMonitor(call_, NULL); + monitor_ = new CurrentSpeakerMonitor(call_->GetAudioSourceProxy(), NULL); // Shrink the minimum time betweeen switches to 10 ms so we don't have to // slow down our tests. monitor_->set_min_time_between_switches(kMinTimeBetweenSwitches); diff --git a/talk/session/media/mediasession.cc b/talk/session/media/mediasession.cc index 17f7a1a7b..a5b1eb0c6 100644 --- a/talk/session/media/mediasession.cc +++ b/talk/session/media/mediasession.cc @@ -62,6 +62,10 @@ using talk_base::scoped_ptr; // RFC4585 const char kMediaProtocolAvpf[] = "RTP/AVPF"; // RFC5124 +const char kMediaProtocolDtlsSavpf[] = "UDP/TLS/RTP/SAVPF"; + +// This should be replaced by "UDP/TLS/RTP/SAVPF", but we need to support it for +// now to be compatible with previous Chrome versions. const char kMediaProtocolSavpf[] = "RTP/SAVPF"; const char kMediaProtocolRtpPrefix[] = "RTP/"; @@ -308,6 +312,20 @@ static void GetCurrentStreamParams(const SessionDescription* sdesc, } } +// Filters the data codecs for the data channel type. +void FilterDataCodecs(std::vector* codecs, bool sctp) { + // Filter RTP codec for SCTP and vice versa. + int codec_id = sctp ? kGoogleRtpDataCodecId : kGoogleSctpDataCodecId; + for (std::vector::iterator iter = codecs->begin(); + iter != codecs->end();) { + if (iter->id == codec_id) { + iter = codecs->erase(iter); + } else { + ++iter; + } + } +} + template class UsedIds { public: @@ -757,6 +775,11 @@ static void NegotiateCodecs(const std::vector& local_codecs, negotiated.SetParam(kCodecParamAssociatedPayloadType, apt_value); } negotiated.id = theirs->id; + // RFC3264: Although the answerer MAY list the formats in their desired + // order of preference, it is RECOMMENDED that unless there is a + // specific reason, the answerer list formats in the same relative order + // they were present in the offer. + negotiated.preference = theirs->preference; negotiated_codecs->push_back(negotiated); } } @@ -973,17 +996,20 @@ static bool CreateMediaContentAnswer( } static bool IsMediaProtocolSupported(MediaType type, - const std::string& protocol) { + const std::string& protocol, + bool secure_transport) { // Data channels can have a protocol of SCTP or SCTP/DTLS. if (type == MEDIA_TYPE_DATA && - (protocol == kMediaProtocolSctp || - protocol == kMediaProtocolDtlsSctp)) { + ((protocol == kMediaProtocolSctp && !secure_transport)|| + (protocol == kMediaProtocolDtlsSctp && secure_transport))) { return true; } + // Since not all applications serialize and deserialize the media protocol, // we will have to accept |protocol| to be empty. - return protocol == kMediaProtocolAvpf || protocol == kMediaProtocolSavpf || - protocol.empty(); + return protocol == kMediaProtocolAvpf || protocol.empty() || + protocol == kMediaProtocolSavpf || + (protocol == kMediaProtocolDtlsSavpf && secure_transport); } static void SetMediaProtocol(bool secure_transport, @@ -1204,6 +1230,8 @@ SessionDescription* MediaSessionDescriptionFactory::CreateOffer( scoped_ptr data(new DataContentDescription()); bool is_sctp = (options.data_channel_type == DCT_SCTP); + FilterDataCodecs(&data_codecs, is_sctp); + cricket::SecurePolicy sdes_policy = IsDtlsActive(CN_DATA, current_description) ? cricket::SEC_DISABLED : secure(); @@ -1321,8 +1349,9 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( } bool rejected = !options.has_audio || audio_content->rejected || - !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, - audio_answer->protocol()); + !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, + audio_answer->protocol(), + audio_transport->secure()); if (!rejected) { AddTransportAnswer(audio_content->name, *(audio_transport.get()), answer.get()); @@ -1369,7 +1398,9 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( return NULL; } bool rejected = !options.has_video || video_content->rejected || - !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, video_answer->protocol()); + !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, + video_answer->protocol(), + video_transport->secure()); if (!rejected) { if (!AddTransportAnswer(video_content->name, *(video_transport.get()), answer.get())) { @@ -1397,6 +1428,10 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( if (!data_transport) { return NULL; } + bool is_sctp = (options.data_channel_type == DCT_SCTP); + std::vector data_codecs(data_codecs_); + FilterDataCodecs(&data_codecs, is_sctp); + scoped_ptr data_answer( new DataContentDescription()); // Do not require or create SDES cryptos if DTLS is used. @@ -1418,7 +1453,9 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( } bool rejected = !options.has_data() || data_content->rejected || - !IsMediaProtocolSupported(MEDIA_TYPE_DATA, data_answer->protocol()); + !IsMediaProtocolSupported(MEDIA_TYPE_DATA, + data_answer->protocol(), + data_transport->secure()); if (!rejected) { data_answer->set_bandwidth(options.data_bandwidth); if (!AddTransportAnswer(data_content->name, *(data_transport.get()), diff --git a/talk/session/media/mediasession.h b/talk/session/media/mediasession.h index dff254f1a..5041de0a9 100644 --- a/talk/session/media/mediasession.h +++ b/talk/session/media/mediasession.h @@ -80,6 +80,8 @@ extern const char kMediaProtocolAvpf[]; // RFC5124 RTP/SAVPF extern const char kMediaProtocolSavpf[]; +extern const char kMediaProtocolDtlsSavpf[]; + extern const char kMediaProtocolRtpPrefix[]; extern const char kMediaProtocolSctp[]; @@ -318,6 +320,16 @@ class MediaContentDescriptionImpl : public MediaContentDescription { void AddCodec(const C& codec) { codecs_.push_back(codec); } + void AddOrReplaceCodec(const C& codec) { + for (typename std::vector::iterator iter = codecs_.begin(); + iter != codecs_.end(); ++iter) { + if (iter->id == codec.id) { + *iter = codec; + return; + } + } + AddCodec(codec); + } void AddCodecs(const std::vector& codecs) { typename std::vector::const_iterator codec; for (codec = codecs.begin(); codec != codecs.end(); ++codec) { diff --git a/talk/session/media/mediasession_unittest.cc b/talk/session/media/mediasession_unittest.cc index ad7cc132e..b76cce48c 100644 --- a/talk/session/media/mediasession_unittest.cc +++ b/talk/session/media/mediasession_unittest.cc @@ -31,6 +31,7 @@ #include "talk/base/gunit.h" #include "talk/base/fakesslidentity.h" #include "talk/base/messagedigest.h" +#include "talk/base/ssladapter.h" #include "talk/media/base/codec.h" #include "talk/media/base/testutils.h" #include "talk/p2p/base/constants.h" @@ -97,13 +98,13 @@ static const AudioCodec kAudioCodecs1[] = { static const AudioCodec kAudioCodecs2[] = { AudioCodec(126, "speex", 16000, 22000, 1, 3), - AudioCodec(127, "iLBC", 8000, 13300, 1, 2), - AudioCodec(0, "PCMU", 8000, 64000, 1, 1), + AudioCodec(0, "PCMU", 8000, 64000, 1, 2), + AudioCodec(127, "iLBC", 8000, 13300, 1, 1), }; static const AudioCodec kAudioCodecsAnswer[] = { - AudioCodec(102, "iLBC", 8000, 13300, 1, 2), - AudioCodec(0, "PCMU", 8000, 64000, 1, 1), + AudioCodec(102, "iLBC", 8000, 13300, 1, 5), + AudioCodec(0, "PCMU", 8000, 64000, 1, 4), }; static const VideoCodec kVideoCodecs1[] = { @@ -117,7 +118,7 @@ static const VideoCodec kVideoCodecs2[] = { }; static const VideoCodec kVideoCodecsAnswer[] = { - VideoCodec(97, "H264", 320, 200, 30, 2) + VideoCodec(97, "H264", 320, 200, 30, 1) }; static const DataCodec kDataCodecs1[] = { @@ -196,6 +197,14 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { tdf2_.set_identity(&id2_); } + static void SetUpTestCase() { + talk_base::InitializeSSL(); + } + + static void TearDownTestCase() { + talk_base::CleanupSSL(); + } + // Create a video StreamParamsVec object with: // - one video stream with 3 simulcast streams and FEC, StreamParamsVec CreateComplexVideoStreamParamsVec() { @@ -1379,10 +1388,12 @@ TEST_F(MediaSessionDescriptionFactoryTest, // The expected audio codecs are the common audio codecs from the first // offer/answer exchange plus the audio codecs only |f2_| offer, sorted in // preference order. + // TODO(wu): |updated_offer| should not include the codec + // (i.e. |kAudioCodecs2[0]|) the other side doesn't support. const AudioCodec kUpdatedAudioCodecOffer[] = { - kAudioCodecs2[0], kAudioCodecsAnswer[0], kAudioCodecsAnswer[1], + kAudioCodecs2[0], }; // The expected video codecs are the common video codecs from the first @@ -1788,6 +1799,64 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoWithAnswerBundle) { TestCryptoWithBundle(false); } +// Verifies that creating answer fails if the offer has UDP/TLS/RTP/SAVPF but +// DTLS is not enabled locally. +TEST_F(MediaSessionDescriptionFactoryTest, + TestOfferDtlsSavpfWithoutDtlsFailed) { + f1_.set_secure(SEC_ENABLED); + f2_.set_secure(SEC_ENABLED); + tdf1_.set_secure(SEC_DISABLED); + tdf2_.set_secure(SEC_DISABLED); + + talk_base::scoped_ptr offer( + f1_.CreateOffer(MediaSessionOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + ContentInfo* offer_content = offer->GetContentByName("audio"); + ASSERT_TRUE(offer_content != NULL); + AudioContentDescription* offer_audio_desc = + static_cast(offer_content->description); + offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf); + + talk_base::scoped_ptr answer( + f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL)); + ASSERT_TRUE(answer != NULL); + ContentInfo* answer_content = answer->GetContentByName("audio"); + ASSERT_TRUE(answer_content != NULL); + + ASSERT_TRUE(answer_content->rejected); +} + +// Offers UDP/TLS/RTP/SAVPF and verifies the answer can be created and contains +// UDP/TLS/RTP/SAVPF. +TEST_F(MediaSessionDescriptionFactoryTest, TestOfferDtlsSavpfCreateAnswer) { + f1_.set_secure(SEC_ENABLED); + f2_.set_secure(SEC_ENABLED); + tdf1_.set_secure(SEC_ENABLED); + tdf2_.set_secure(SEC_ENABLED); + + talk_base::scoped_ptr offer( + f1_.CreateOffer(MediaSessionOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + ContentInfo* offer_content = offer->GetContentByName("audio"); + ASSERT_TRUE(offer_content != NULL); + AudioContentDescription* offer_audio_desc = + static_cast(offer_content->description); + offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf); + + talk_base::scoped_ptr answer( + f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL)); + ASSERT_TRUE(answer != NULL); + + const ContentInfo* answer_content = answer->GetContentByName("audio"); + ASSERT_TRUE(answer_content != NULL); + ASSERT_FALSE(answer_content->rejected); + + const AudioContentDescription* answer_audio_desc = + static_cast(answer_content->description); + EXPECT_EQ(std::string(cricket::kMediaProtocolDtlsSavpf), + answer_audio_desc->protocol()); +} + // Test that we include both SDES and DTLS in the offer, but only include SDES // in the answer if DTLS isn't negotiated. TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoDtls) { diff --git a/talk/session/media/mediasessionclient.cc b/talk/session/media/mediasessionclient.cc index a5a652cc7..2ada987c4 100644 --- a/talk/session/media/mediasessionclient.cc +++ b/talk/session/media/mediasessionclient.cc @@ -373,6 +373,7 @@ bool ParseGingleAudioContent(const buzz::XmlElement* content_elem, ParseError* error) { AudioContentDescription* audio = new AudioContentDescription(); + int preference = kMaxPayloadId; if (content_elem->FirstElement()) { for (const buzz::XmlElement* codec_elem = content_elem->FirstNamed(QN_GINGLE_AUDIO_PAYLOADTYPE); @@ -380,6 +381,7 @@ bool ParseGingleAudioContent(const buzz::XmlElement* content_elem, codec_elem = codec_elem->NextNamed(QN_GINGLE_AUDIO_PAYLOADTYPE)) { AudioCodec codec; if (ParseGingleAudioCodec(codec_elem, &codec)) { + codec.preference = preference--; audio->AddCodec(codec); } } @@ -406,12 +408,14 @@ bool ParseGingleVideoContent(const buzz::XmlElement* content_elem, ParseError* error) { VideoContentDescription* video = new VideoContentDescription(); + int preference = kMaxPayloadId; for (const buzz::XmlElement* codec_elem = content_elem->FirstNamed(QN_GINGLE_VIDEO_PAYLOADTYPE); codec_elem != NULL; codec_elem = codec_elem->NextNamed(QN_GINGLE_VIDEO_PAYLOADTYPE)) { VideoCodec codec; if (ParseGingleVideoCodec(codec_elem, &codec)) { + codec.preference = preference--; video->AddCodec(codec); } } @@ -571,6 +575,7 @@ bool ParseJingleAudioContent(const buzz::XmlElement* content_elem, FeedbackParams content_feedback_params; ParseFeedbackParams(content_elem, &content_feedback_params); + int preference = kMaxPayloadId; for (const buzz::XmlElement* payload_elem = content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE); payload_elem != NULL; @@ -578,6 +583,7 @@ bool ParseJingleAudioContent(const buzz::XmlElement* content_elem, AudioCodec codec; if (ParseJingleAudioCodec(payload_elem, &codec)) { AddFeedbackParams(content_feedback_params, &codec.feedback_params); + codec.preference = preference--; audio->AddCodec(codec); } } @@ -611,6 +617,7 @@ bool ParseJingleVideoContent(const buzz::XmlElement* content_elem, FeedbackParams content_feedback_params; ParseFeedbackParams(content_elem, &content_feedback_params); + int preference = kMaxPayloadId; for (const buzz::XmlElement* payload_elem = content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE); payload_elem != NULL; @@ -618,6 +625,7 @@ bool ParseJingleVideoContent(const buzz::XmlElement* content_elem, VideoCodec codec; if (ParseJingleVideoCodec(payload_elem, &codec)) { AddFeedbackParams(content_feedback_params, &codec.feedback_params); + codec.preference = preference--; video->AddCodec(codec); } } @@ -681,6 +689,7 @@ bool ParseJingleRtpDataContent(const buzz::XmlElement* content_elem, FeedbackParams content_feedback_params; ParseFeedbackParams(content_elem, &content_feedback_params); + int preference = kMaxPayloadId; for (const buzz::XmlElement* payload_elem = content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE); payload_elem != NULL; @@ -688,6 +697,7 @@ bool ParseJingleRtpDataContent(const buzz::XmlElement* content_elem, DataCodec codec; if (ParseJingleDataCodec(payload_elem, &codec)) { AddFeedbackParams(content_feedback_params, &codec.feedback_params); + codec.preference = preference--; data->AddCodec(codec); } } diff --git a/talk/session/media/mediasessionclient_unittest.cc b/talk/session/media/mediasessionclient_unittest.cc index 7cb1ec97e..3f3c4fa49 100644 --- a/talk/session/media/mediasessionclient_unittest.cc +++ b/talk/session/media/mediasessionclient_unittest.cc @@ -32,6 +32,7 @@ #include "talk/base/logging.h" #include "talk/base/scoped_ptr.h" #include "talk/media/base/fakemediaengine.h" +#include "talk/media/base/testutils.h" #include "talk/media/devices/fakedevicemanager.h" #include "talk/p2p/base/constants.h" #include "talk/p2p/client/basicportallocator.h" @@ -74,6 +75,29 @@ static const cricket::AudioCodec kAudioCodecs[] = { cricket::AudioCodec(106, "telephone-event", 8000, 0, 1, 1) }; +// The codecs that our FakeMediaEngine will support with a different order of +// supported codecs. +static const cricket::AudioCodec kAudioCodecsDifferentPreference[] = { + cricket::AudioCodec(104, "ISAC", 32000, -1, 1, 17), + cricket::AudioCodec(97, "IPCMWB", 16000, 80000, 1, 14), + cricket::AudioCodec(9, "G722", 16000, 64000, 1, 13), + cricket::AudioCodec(119, "ISACLC", 16000, 40000, 1, 16), + cricket::AudioCodec(103, "ISAC", 16000, -1, 1, 18), + cricket::AudioCodec(99, "speex", 16000, 22000, 1, 15), + cricket::AudioCodec(100, "EG711U", 8000, 64000, 1, 9), + cricket::AudioCodec(101, "EG711A", 8000, 64000, 1, 8), + cricket::AudioCodec(0, "PCMU", 8000, 64000, 1, 7), + cricket::AudioCodec(8, "PCMA", 8000, 64000, 1, 6), + cricket::AudioCodec(102, "iLBC", 8000, 13300, 1, 12), + cricket::AudioCodec(3, "GSM", 8000, 13000, 1, 10), + cricket::AudioCodec(98, "speex", 8000, 11000, 1, 11), + cricket::AudioCodec(126, "CN", 32000, 0, 1, 5), + cricket::AudioCodec(105, "CN", 16000, 0, 1, 4), + cricket::AudioCodec(13, "CN", 8000, 0, 1, 3), + cricket::AudioCodec(117, "red", 8000, 0, 1, 2), + cricket::AudioCodec(106, "telephone-event", 8000, 0, 1, 1) +}; + static const cricket::VideoCodec kVideoCodecs[] = { cricket::VideoCodec(96, "H264-SVC", 320, 200, 30, 1) }; @@ -270,123 +294,6 @@ const std::string kJingleInitiate( " " \ " "); -// Initiate string with a different order of supported codecs. -// Should accept the supported ones, but with our desired order. -const std::string kGingleInitiateDifferentPreference( - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " "); - -const std::string kJingleInitiateDifferentPreference( - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " " \ - " "); - const std::string kJingleInitiateWithRtcpFb( " " \ @@ -2793,6 +2700,8 @@ class MediaSessionClientTest : public sigslot::has_slots<> { expected_data_fb_params_ = params_nack; } + cricket::FakeMediaEngine* fme() { return fme_; } + private: void OnSendStanza(cricket::SessionManager* manager, const buzz::XmlElement* stanza) { @@ -2949,11 +2858,15 @@ TEST(MediaSessionTest, JingleGoodInitiateAllSupportedAudioCodecs) { test->TestHasAllSupportedAudioCodecs(elem.get()); } +// Changes the codecs that our FakeMediaEngine will support with a different +// preference order than the incoming offer. +// Verifies the answer accepts the preference order of the remote peer. TEST(MediaSessionTest, JingleGoodInitiateDifferentPreferenceAudioCodecs) { talk_base::scoped_ptr test(JingleTest()); + test->fme()->SetAudioCodecs(MAKE_VECTOR(kAudioCodecsDifferentPreference)); talk_base::scoped_ptr elem; test->TestGoodIncomingInitiate( - kJingleInitiateDifferentPreference, AudioCallOptions(), elem.use()); + kJingleInitiate, AudioCallOptions(), elem.use()); test->TestHasAllSupportedAudioCodecs(elem.get()); } @@ -3213,11 +3126,15 @@ TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecsWithCrypto) { test->TestHasAllSupportedAudioCodecs(elem.get()); } +// Changes the codecs that our FakeMediaEngine will support with a different +// preference order than the incoming offer. +// Verifies the answer accepts the preference order of the remote peer. TEST(MediaSessionTest, GingleGoodInitiateDifferentPreferenceAudioCodecs) { - talk_base::scoped_ptr elem; talk_base::scoped_ptr test(GingleTest()); + test->fme()->SetAudioCodecs(MAKE_VECTOR(kAudioCodecsDifferentPreference)); + talk_base::scoped_ptr elem; test->TestGoodIncomingInitiate( - kGingleInitiateDifferentPreference, AudioCallOptions(), elem.use()); + kGingleInitiate, AudioCallOptions(), elem.use()); test->TestHasAllSupportedAudioCodecs(elem.get()); } diff --git a/talk/xmpp/chatroommoduleimpl.cc b/talk/xmpp/chatroommoduleimpl.cc index eb046d721..a12ff5e02 100644 --- a/talk/xmpp/chatroommoduleimpl.cc +++ b/talk/xmpp/chatroommoduleimpl.cc @@ -320,17 +320,13 @@ XmppChatroomModuleImpl::RequestExitChatroom() { if (!engine()) return XMPP_RETURN_BADSTATE; - // currently, can't leave a room unless you've entered - // no way to cancel a pending enter call - is that bad? - if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) - return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? - // exiting a chatroom is a presence request to the server XmlElement element(QN_PRESENCE); element.AddAttr(QN_TO, member_jid().Str()); element.AddAttr(QN_TYPE, "unavailable"); XmppReturnStatus status = engine()->SendStanza(&element); - if (status == XMPP_RETURN_OK) { + if (status == XMPP_RETURN_OK && + chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) { return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT); } return status; @@ -513,6 +509,7 @@ XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement& FireMemberChanged(member); } else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) { + member->SetPresence(presence.get()); chatroom_jid_members_.erase(pos); chatroom_jid_members_version_++; FireMemberExited(member); diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc index 8144fe0c2..f69f84e46 100644 --- a/talk/xmpp/constants.cc +++ b/talk/xmpp/constants.cc @@ -121,6 +121,7 @@ const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[] = "muc_muvc"; const char STR_MUC_ROOM_FEATURE_RECORDABLE[] = "recordable"; const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[] = "custom_recording"; const char STR_MUC_ROOM_OWNER_PROFILE_ID[] = "muc#roominfo_owner_profile_id"; +const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[] = "abuse_recordable"; const char STR_ID_TYPE_CONVERSATION[] = "conversation"; const char NS_GOOGLE_MUC_HANGOUT[] = "google:muc#hangout"; diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h index 62e4901d0..6d940957d 100644 --- a/talk/xmpp/constants.h +++ b/talk/xmpp/constants.h @@ -114,6 +114,7 @@ extern const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[]; extern const char STR_MUC_ROOM_FEATURE_RECORDABLE[]; extern const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[]; extern const char STR_MUC_ROOM_OWNER_PROFILE_ID[]; +extern const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[]; extern const char STR_ID_TYPE_CONVERSATION[]; extern const char NS_GOOGLE_MUC_HANGOUT[]; diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc index d9ba6af30..aede56318 100644 --- a/talk/xmpp/hangoutpubsubclient.cc +++ b/talk/xmpp/hangoutpubsubclient.cc @@ -277,6 +277,7 @@ void HangoutPubSubClient::OnAudioMuteStateChange( bool is_muted = change.new_state; bool remote_action = (!change.publisher_nick.empty() && (change.publisher_nick != change.published_nick)); + if (remote_action) { const std::string& mutee_nick = change.published_nick; const std::string& muter_nick = change.publisher_nick; @@ -287,9 +288,8 @@ void HangoutPubSubClient::OnAudioMuteStateChange( } bool should_mute_locally = (mutee_nick == nick_); SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally); - } else { - SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted); } + SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted); } const std::string GetAudioMuteNickFromItem(const XmlElement* item) { diff --git a/talk/xmpp/hangoutpubsubclient_unittest.cc b/talk/xmpp/hangoutpubsubclient_unittest.cc index 0ffb248f3..1d1c14b13 100644 --- a/talk/xmpp/hangoutpubsubclient_unittest.cc +++ b/talk/xmpp/hangoutpubsubclient_unittest.cc @@ -449,11 +449,14 @@ TEST_F(HangoutPubSubClientTest, TestRequest) { " " ""; + listener->last_is_audio_muted = false; xmpp_client->HandleStanza( buzz::XmlElement::ForStr(incoming_remote_mute_message)); EXPECT_EQ("mutee", listener->last_mutee_nick); EXPECT_EQ("muter", listener->last_muter_nick); EXPECT_FALSE(listener->last_should_mute); + EXPECT_EQ("mutee", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_is_audio_muted); std::string incoming_remote_mute_me_message = "" @@ -466,11 +469,14 @@ TEST_F(HangoutPubSubClientTest, TestRequest) { " " ""; + listener->last_is_audio_muted = false; xmpp_client->HandleStanza( buzz::XmlElement::ForStr(incoming_remote_mute_me_message)); EXPECT_EQ("me", listener->last_mutee_nick); EXPECT_EQ("muter", listener->last_muter_nick); EXPECT_TRUE(listener->last_should_mute); + EXPECT_EQ("me", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_is_audio_muted); std::string incoming_media_block_message = "" diff --git a/talk/xmpp/pubsubtasks.cc b/talk/xmpp/pubsubtasks.cc index bbefbe55c..015708eb5 100644 --- a/talk/xmpp/pubsubtasks.cc +++ b/talk/xmpp/pubsubtasks.cc @@ -173,9 +173,16 @@ void PubSubRequestTask::HandleResult(const XmlElement* stanza) { SignalResult(this, items); } +int PubSubReceiveTask::ProcessStart() { + if (SignalUpdate.is_empty()) { + return STATE_DONE; + } + return ReceiveTask::ProcessStart(); +} + bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) { return MatchStanzaFrom(stanza, pubsubjid_) && - IsPubSubEventItemsElem(stanza, node_); + IsPubSubEventItemsElem(stanza, node_) && !SignalUpdate.is_empty(); } void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) { diff --git a/talk/xmpp/pubsubtasks.h b/talk/xmpp/pubsubtasks.h index f0a158178..2ba618b34 100644 --- a/talk/xmpp/pubsubtasks.h +++ b/talk/xmpp/pubsubtasks.h @@ -71,6 +71,7 @@ class PubSubReceiveTask : public ReceiveTask { node_(node) { } + virtual int ProcessStart(); sigslot::signal2&> SignalUpdate; diff --git a/talk/xmpp/rostermoduleimpl.cc b/talk/xmpp/rostermoduleimpl.cc index 31b3abdf0..993cfa905 100644 --- a/talk/xmpp/rostermoduleimpl.cc +++ b/talk/xmpp/rostermoduleimpl.cc @@ -351,10 +351,6 @@ XmppPresenceImpl::set_raw_xml(const XmlElement * xml) { xml->Name() != QN_PRESENCE) return XMPP_RETURN_BADARGUMENT; - const std::string& type = xml->Attr(QN_TYPE); - if (type != STR_EMPTY && type != "unavailable") - return XMPP_RETURN_BADARGUMENT; - raw_xml_.reset(new XmlElement(*xml)); return XMPP_RETURN_OK; } diff --git a/third_party/gflags/OWNERS b/third_party/gflags/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/third_party/gflags/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/third_party/gtest-parallel/LICENSE b/third_party/gtest-parallel/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/third_party/gtest-parallel/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/gtest-parallel/README.webrtc b/third_party/gtest-parallel/README.webrtc new file mode 100644 index 000000000..c2360b588 --- /dev/null +++ b/third_party/gtest-parallel/README.webrtc @@ -0,0 +1,9 @@ +URL: https://github.com/google/gtest-parallel +Version: 166648f68f6a26eaf4073fc244f0031ea139ce66 +License: Apache 2.0 +License File: LICENSE + +Description: +Parallelization script for gtest binaries. + +Local Modifications: None diff --git a/third_party/gtest-parallel/gtest-parallel b/third_party/gtest-parallel/gtest-parallel new file mode 100755 index 000000000..a0b0181aa --- /dev/null +++ b/third_party/gtest-parallel/gtest-parallel @@ -0,0 +1,189 @@ +#!/usr/bin/env python2 +# Copyright 2013 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import Queue +import optparse +import subprocess +import sys +import threading +import time + +class FilterFormat: + total_tests = 0 + finished_tests = 0 + + tests = {} + outputs = {} + failures = [] + + def print_test_status(self, last_finished_test, time_ms): + print "[%d/%d] %s (%d ms)" % (self.finished_tests, + self.total_tests, + last_finished_test, + time_ms) + + def handle_meta(self, job_id, args): + (command, arg) = args.split(' ', 1) + if command == "TEST": + (binary, test) = arg.split(' ', 1) + self.tests[job_id] = (binary, test.strip()) + self.outputs[job_id] = [] + self.total_tests += 1 + elif command == "EXIT": + (exit_code, time_ms) = [int(x) for x in arg.split(' ', 1)] + self.finished_tests += 1 + (binary, test) = self.tests[job_id] + self.print_test_status(test, time_ms) + if exit_code != 0: + self.failures.append(self.tests[job_id]) + for line in self.outputs[job_id]: + print line + + def add_stdout(self, job_id, output): + self.outputs[job_id].append(output) + + def log(self): + print "[0/?] Running tests...\r", + while True: + line = log.get() + if line == "": + break + (prefix, output) = line.split(' ', 1) + + if prefix[-1] == ':': + self.handle_meta(int(prefix[:-1]), output) + else: + self.add_stdout(int(prefix[:-1]), output) + if self.failures: + print "FAILED TESTS (%d/%d):" % (len(self.failures), self.total_tests) + for (binary, test) in self.failures: + print " ", binary + ": " + test + +class RawFormat: + def log(self): + while True: + line = log.get() + if line == "": + return + sys.stdout.write(line + "\n") + sys.stdout.flush() + +parser = optparse.OptionParser( + usage = 'usage: %prog [options] executable [executable ...]') + +parser.add_option('-r', '--repeat', type='int', default=1, + help='repeat tests') +parser.add_option('-w', '--workers', type='int', default=16, + help='number of workers to spawn') +parser.add_option('--gtest_color', type='string', default='yes', + help='color output') +parser.add_option('--gtest_filter', type='string', default='', + help='test filter') +parser.add_option('--gtest_also_run_disabled_tests', action='store_true', + default=False, help='run disabled tests too') +parser.add_option('--format', type='string', default='filter', + help='output format (raw,filter)') + +(options, binaries) = parser.parse_args() + +if binaries == []: + parser.print_usage() + sys.exit(1) + +logger = RawFormat() +if options.format == 'raw': + pass +elif options.format == 'filter': + logger = FilterFormat() +else: + sys.exit("Unknown output format: " + options.format) + +# Find tests. +tests = [] +for test_binary in binaries: + command = [test_binary] + if options.gtest_filter != '': + command += ['--gtest_filter=' + options.gtest_filter] + if options.gtest_also_run_disabled_tests: + command += ['--gtest_also_run_disabled_tests'] + + test_list = subprocess.Popen(command + ['--gtest_list_tests'], + stdout=subprocess.PIPE).communicate()[0] + + test_group = '' + for line in test_list.split('\n'): + if not line.strip(): + continue + if line[0] != " ": + test_group = line.strip() + continue + line = line.strip() + if not options.gtest_also_run_disabled_tests and 'DISABLED' in line: + continue + + test = test_group + line + tests.append((test_binary, command, test)) + +# Repeat tests (-r flag). +tests *= options.repeat + +log = Queue.Queue() +test_queue = Queue.Queue() + +for job_id, (test_binary, command, test) in enumerate(tests): + log.put(str(job_id) + ': TEST ' + test_binary + ' ' + test) + test_queue.put((command, job_id, test)) + +exit_code = 0 +def run_job((command, job_id, test)): + begin = time.time() + sub = subprocess.Popen(command + ['--gtest_filter=' + test] + + ['--gtest_color=' + options.gtest_color], + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT) + + while True: + line = sub.stdout.readline() + if line == '': + break + log.put(str(job_id) + '> ' + line.rstrip()) + + code = sub.wait() + runtime_ms = int(1000 * (time.time() - begin)) + log.put(str(job_id) + ': EXIT ' + str(code) + ' ' + str(runtime_ms)) + if code != 0: + global exit_code + exit_code = code + +def worker(): + while True: + try: + run_job(test_queue.get_nowait()) + test_queue.task_done() + except Queue.Empty: + return + +def start_daemon(func): + t = threading.Thread(target=func) + t.daemon = True + t.start() + return t + +workers = [start_daemon(worker) for i in range(options.workers)] +printer = start_daemon(logger.log) + +[t.join() for t in workers] +log.put("") +printer.join() +sys.exit(exit_code) diff --git a/third_party/winsdk_samples/OWNERS b/third_party/winsdk_samples/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/third_party/winsdk_samples/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/tools/lsan/suppressions.txt b/tools/lsan/suppressions.txt index d1f320292..fa488d5ef 100644 --- a/tools/lsan/suppressions.txt +++ b/tools/lsan/suppressions.txt @@ -8,14 +8,47 @@ # Leak reported in libstdc++ leak:std::string::_Rep::_S_create +# False positives in libfontconfig. http://crbug.com/39050 +leak:libfontconfig + +# Leaks in Nvidia's libGL. +leak:libGL.so + +# XRandR has several one time leaks. +leak:libxrandr + +# The NSS suppressions above will not fire when the fast stack unwinder is used, +# because it can't unwind through NSS libraries. Apply blanket suppressions for +# now. +leak:libnssutil3 +leak:libnspr4 +leak:libnss3 +leak:libplds4 +leak:libnssckbi + +# xrandr leak. http://crbug.com/119677 +leak:XRRFindDisplay #### Actual bugs in WebRTC code #### +# libjingle_media_unittest +# https://code.google.com/p/webrtc/issues/detail?id= +leak:cricket::FakeNetworkInterface::SetOption +leak:CodecTest_TestCodecOperators_Test::TestBody +leak:VideoEngineTest*::ConstrainNewCodecBody +leak:VideoMediaChannelTest*::AddRemoveRecvStreams +leak:WebRtcVideoCapturerTest_TestCapture_Test::TestBody +leak:WebRtcVideoEngineTestFake_MultipleSendStreamsWithOneCapturer_Test::TestBody +leak:WebRtcVideoEngineTestFake_SetBandwidthInConference_Test::TestBody +leak:WebRtcVideoEngineTestFake_SetSendCodecsRejectBadFormat_Test::TestBody + # libjingle_peerconnection_unittest # https://code.google.com/p/webrtc/issues/detail?id=2528 leak:cricket::FakeVideoMediaChannel::~FakeVideoMediaChannel leak:cricket::MediaSessionDescriptionFactory::CreateAnswer leak:cricket::MediaSessionDescriptionFactory::CreateOffer +leak:DtmfSenderTest_InsertEmptyTonesToCancelPreviousTask_Test::TestBody +leak:sigslot::_signal_base2*::~_signal_base2 leak:testing::internal::CmpHelperEQ leak:webrtc::AudioDeviceLinuxALSA::InitMicrophone leak:webrtc::AudioDeviceLinuxALSA::InitSpeaker @@ -24,7 +57,13 @@ leak:webrtc::FakeConstraints::AddOptional leak:webrtc::WebRtcIdentityRequestObserver::OnSuccess leak:webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer leak:webrtc::WebRtcSessionDescriptionFactory::InternalCreateOffer +leak:PeerConnectionInterfaceTest_SsrcInOfferAnswer_Test::TestBody +leak:PeerConnectionInterfaceTest_CloseAndTestMethods_Test::TestBody leak:WebRtcSdpTest::TestDeserializeRtcpFb +leak:WebRtcSdpTest::TestSerialize +leak:WebRtcSdpTest_SerializeSessionDescriptionWithDataChannelAndBandwidth_Test::TestBody +leak:WebRtcSdpTest_SerializeSessionDescriptionWithBandwidth_Test::TestBody +leak:WebRtcSessionTest::SetLocalDescriptionExpectError leak:WebRtcSessionTest_TestAVOfferWithAudioOnlyAnswer_Test::TestBody # libjingle_unittest diff --git a/tools/perf_expectations/README b/tools/perf_expectations/README deleted file mode 100644 index 87c6087e8..000000000 --- a/tools/perf_expectations/README +++ /dev/null @@ -1 +0,0 @@ -See https://sites.google.com/a/google.com/rtc-platform/engineering/managing-performance-expectations. \ No newline at end of file diff --git a/tools/perf_expectations/perf_expectations.json b/tools/perf_expectations/perf_expectations.json deleted file mode 100644 index a3dec6767..000000000 --- a/tools/perf_expectations/perf_expectations.json +++ /dev/null @@ -1,41 +0,0 @@ -{"linux-large-tests/audio_e2e_test/audio_e2e_score/e2e_score": {"reva": 3380, "revb": 3451, "type": "absolute", "better": "higher", "improve": 4.6977, "regress": 3.77055, "sha1": "b76f22a2"}, - "linux-large-tests/audioproc_perf/audioproc/time_per_10ms_frame": {"reva": 3435, "revb": 3510, "type": "absolute", "improve": 64, "regress": 75, "sha1": "b9b3d6a3"}, - "linux-large-tests/isac_fixed_perf/isac/time_per_10ms_frame": {"reva": 3435, "revb": 3510, "type": "absolute", "improve": 102, "regress": 120, "sha1": "0922ca06"}, - "linux-large-tests/vie_auto_test/psnr/net_delay_0_0_plr_0": {"reva": 3361, "revb": 3389, "type": "absolute", "better": "higher", "improve": 36.75, "regress": 34.47265, "sha1": "75dc797e"}, - "linux-large-tests/vie_auto_test/psnr/net_delay_100_10_plr_0": {"reva": 3361, "revb": 3389, "type": "absolute", "better": "higher", "improve": 36.75, "regress": 34.4907, "sha1": "82050834"}, - "linux-large-tests/vie_auto_test/psnr/net_delay_50_5_plr_5": {"reva": 3361, "revb": 3389, "type": "absolute", "better": "higher", "improve": 36.75, "regress": 34.4128, "sha1": "93ca8ea5"}, - "linux-large-tests/vie_auto_test/ssim/net_delay_0_0_plr_0": {"reva": 3381, "revb": 3389, "type": "absolute", "better": "higher", "improve": 1.0185, "regress": 0.9234, "sha1": "57fdf26c"}, - "linux-large-tests/vie_auto_test/ssim/net_delay_100_10_plr_0": {"reva": 3381, "revb": 3389, "type": "absolute", "better": "higher", "improve": 1.0185, "regress": 0.9234, "sha1": "b0a62939"}, - "linux-large-tests/vie_auto_test/ssim/net_delay_50_5_plr_5": {"reva": 3381, "revb": 3389, "type": "absolute", "better": "higher", "improve": 1.01745, "regress": 0.9234, "sha1": "580aea7a"}, - "linux-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_0_0_plr_0": {"reva": 3337, "revb": 3389, "type": "absolute", "improve": 31.29034, "regress": 34.966995, "sha1": "250facd1"}, - "linux-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_100_10_plr_0": {"reva": 3337, "revb": 3389, "type": "absolute", "improve": 31.15791, "regress": 34.936545, "sha1": "9ff072ce"}, - "linux-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_50_5_plr_5": {"reva": 3337, "revb": 3389, "type": "absolute", "improve": 31.23353, "regress": 36.812685, "sha1": "8f5af7a5"}, - "linux-large-tests/vie_auto_test/total_delay_incl_network/net_delay_0_0_plr_0": {"reva": 3415, "revb": 4020, "type": "absolute", "improve": 24.329215, "regress": 36.22248, "sha1": "9698dfda"}, - "linux-large-tests/vie_auto_test/total_delay_incl_network/net_delay_100_10_plr_0": {"reva": 3982, "revb": 4020, "type": "absolute", "improve": 124.5393, "regress": 212.1357, "sha1": "b182b10c"}, - "linux-large-tests/vie_auto_test/total_delay_incl_network/net_delay_50_5_plr_5": {"reva": 3337, "revb": 3451, "type": "absolute", "improve": 79.99912, "regress": 186.35715, "sha1": "377cb407"}, - "mac-large-tests/vie_auto_test/psnr/net_delay_0_0_plr_0": {"reva": 3394, "revb": 3452, "type": "absolute", "better": "higher", "improve": 38.2137, "regress": 34.0841, "sha1": "7f1bd1b0"}, - "mac-large-tests/vie_auto_test/psnr/net_delay_100_10_plr_0": {"reva": 3394, "revb": 3452, "type": "absolute", "better": "higher", "improve": 38.1759, "regress": 34.048, "sha1": "fe4facc2"}, - "mac-large-tests/vie_auto_test/psnr/net_delay_50_5_plr_5": {"reva": 3394, "revb": 3452, "type": "absolute", "better": "higher", "improve": 38.1528, "regress": 34.06035, "sha1": "59b4f938"}, - "mac-large-tests/vie_auto_test/ssim/net_delay_0_0_plr_0": {"reva": 3394, "revb": 3452, "type": "absolute", "better": "higher", "improve": 1.0206, "regress": 0.92055, "sha1": "cf3861b8"}, - "mac-large-tests/vie_auto_test/ssim/net_delay_100_10_plr_0": {"reva": 3394, "revb": 3452, "type": "absolute", "better": "higher", "improve": 1.0206, "regress": 0.92055, "sha1": "5cfe0c92"}, - "mac-large-tests/vie_auto_test/ssim/net_delay_50_5_plr_5": {"reva": 3394, "revb": 3452, "type": "absolute", "better": "higher", "improve": 1.0206, "regress": 0.9215, "sha1": "21e75d0c"}, - "mac-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_0_0_plr_0": {"reva": 3394, "revb": 3452, "type": "absolute", "improve": 31.933775, "regress": 38.39535, "sha1": "64644df0"}, - "mac-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_100_10_plr_0": {"reva": 3394, "revb": 3452, "type": "absolute", "improve": 31.9124, "regress": 35.920605, "sha1": "db0345c3"}, - "mac-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_50_5_plr_5": {"reva": 3394, "revb": 3452, "type": "absolute", "improve": 31.972535, "regress": 36.81552, "sha1": "04b8f506"}, - "mac-large-tests/vie_auto_test/total_delay_incl_network/net_delay_0_0_plr_0": {"reva": 3394, "revb": 3452, "type": "absolute", "improve": 26.810235, "regress": 35.407785, "sha1": "61f0eb7d"}, - "mac-large-tests/vie_auto_test/total_delay_incl_network/net_delay_100_10_plr_0": {"reva": 3980, "revb": 4018, "type": "absolute", "improve": 131.59305, "regress": 205.2288, "sha1": "81299208"}, - "mac-large-tests/vie_auto_test/total_delay_incl_network/net_delay_50_5_plr_5": {"reva": 3394, "revb": 3452, "type": "absolute", "improve": 85.895485, "regress": 196.05705, "sha1": "8ac10284"}, - "win-large-tests/vie_auto_test/psnr/net_delay_0_0_plr_0": {"reva": 3361, "revb": 3451, "type": "absolute", "better": "higher", "improve": 38.2431, "regress": 32.3, "sha1": "2b853279"}, - "win-large-tests/vie_auto_test/psnr/net_delay_100_10_plr_0": {"reva": 3361, "revb": 3389, "type": "absolute", "better": "higher", "improve": 37.8, "regress": 34.44225, "sha1": "d81a31e6"}, - "win-large-tests/vie_auto_test/psnr/net_delay_50_5_plr_5": {"reva": 3361, "revb": 3389, "type": "absolute", "better": "higher", "improve": 36.75, "regress": 34.17625, "sha1": "b65327da"}, - "win-large-tests/vie_auto_test/ssim/net_delay_0_0_plr_0": {"reva": 3381, "revb": 3389, "type": "absolute", "better": "higher", "improve": 0.9933, "regress": 0.92435, "sha1": "e6dbdfb6"}, - "win-large-tests/vie_auto_test/ssim/net_delay_100_10_plr_0": {"reva": 3381, "revb": 3389, "type": "absolute", "better": "higher", "improve": 1.01955, "regress": 0.9234, "sha1": "e5b7a3d8"}, - "win-large-tests/vie_auto_test/ssim/net_delay_50_5_plr_5": {"reva": 3381, "revb": 3389, "type": "absolute", "better": "higher", "improve": 1.01745, "regress": 0.9215, "sha1": "318e79ed"}, - "win-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_0_0_plr_0": {"reva": 3455, "revb": 3463, "type": "absolute", "improve": 31.142425, "regress": 53.839275, "sha1": "b5dcfcf6"}, - "win-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_100_10_plr_0": {"reva": 3337, "revb": 3389, "type": "absolute", "improve": 31.1125, "regress": 35.68572, "sha1": "d21f281f"}, - "win-large-tests/vie_auto_test/time_between_rendered_frames/net_delay_50_5_plr_5": {"reva": 3337, "revb": 3389, "type": "absolute", "improve": 31.616, "regress": 36.275295, "sha1": "51c950b1"}, - "win-large-tests/vie_auto_test/total_delay_incl_network/net_delay_0_0_plr_0": {"reva": 3935, "revb": 4022, "type": "absolute", "improve": 28.04761, "regress": 119.67795, "sha1": "7f4d427c"}, - "win-large-tests/vie_auto_test/total_delay_incl_network/net_delay_100_10_plr_0": {"reva": 4343, "revb": 4377, "type": "absolute", "improve": 124.58205, "regress": 149.44755, "sha1": "620d25b7"}, - "win-large-tests/vie_auto_test/total_delay_incl_network/net_delay_50_5_plr_5": {"reva": 3974, "revb": 3983, "type": "absolute", "improve": 153.1894, "regress": 205.27185, "sha1": "9e52c01a"}, - "load": true -} diff --git a/tools/perf_expectations/webrtc_perf_expectations.cfg b/tools/perf_expectations/webrtc_perf_expectations.cfg deleted file mode 100644 index 21944afbd..000000000 --- a/tools/perf_expectations/webrtc_perf_expectations.cfg +++ /dev/null @@ -1,4 +0,0 @@ -{ - "base_url": "http://www.corp.google.com/~webrtc-cb/perf/", - "perf_file": "perf_expectations.json" -} diff --git a/tools/valgrind-webrtc/drmemory/suppressions.txt b/tools/valgrind-webrtc/drmemory/suppressions.txt index 2a8a2dd7b..0687572ab 100644 --- a/tools/valgrind-webrtc/drmemory/suppressions.txt +++ b/tools/valgrind-webrtc/drmemory/suppressions.txt @@ -29,15 +29,60 @@ GDI32.dll!DeleteDC *!testing::internal::HandleSehExceptionsInMethodIfSupported UNINITIALIZED READ -name=https://code.google.com/p/webrtc/issues/detail?id=2323 (2) +name= system call NtUserGetThreadDesktop parameter value #1 *!webrtc::Desktop::GetThreadDesktop *!webrtc::ScopedThreadDesktop::ScopedThreadDesktop -*!webrtc::`anonymous namespace'::ScreenCapturerWin::ScreenCapturerWin -*!webrtc::ScreenCapturer::CreateWithDisableAero +*!webrtc::ScreenCapturerWinGdi::ScreenCapturerWinGdi *!webrtc::ScreenCapturer::Create -... -*!testing::internal::HandleSehExceptionsInMethodIfSupported +*!webrtc::ScreenCapturerTest::SetUp +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name= +system call NtUserGetThreadDesktop parameter value #1 +*!webrtc::Desktop::GetThreadDesktop +*!webrtc::ScopedThreadDesktop::ScopedThreadDesktop +*!webrtc::ScreenCapturerWinGdi::ScreenCapturerWinGdi +*!webrtc::ScreenCapturer::Create +*!webrtc::ScreenCapturerTest_UseMagnifier_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name= +system call NtUserGetThreadDesktop parameter value #1 +*!webrtc::Desktop::GetThreadDesktop +*!webrtc::ScopedThreadDesktop::ScopedThreadDesktop +*!webrtc::ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier +*!webrtc::ScreenCapturer::Create +*!webrtc::ScreenCapturerTest_UseMagnifier_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=2323 (5) +system call NtUserGetIconInfo parameter value #0 +USER32.dll!GetIconInfo +*!webrtc::CreateMouseCursorFromHCursor +*!webrtc::`anonymous namespace'::ScreenCapturerWin::CaptureCursor +*!webrtc::`anonymous namespace'::ScreenCapturerWin::Capture +*!webrtc::ScreenCapturerTest_Capture_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=2323 (6) +system call NtUserGetWindowPlacement +*!webrtc::GetCroppedWindowRect +*!webrtc::`anonymous namespace'::WindowCapturerWin::Capture +*!webrtc::WindowCapturerTest_Capture_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=2323 (7) +system call NtUserGetWindowPlacement parameter #1 +*!webrtc::GetCroppedWindowRect +*!webrtc::`anonymous namespace'::WindowCapturerWin::Capture +*!webrtc::WindowCapturerTest_Capture_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> LEAK name=https://code.google.com/p/webrtc/issues/detail?id=2333 @@ -140,6 +185,174 @@ name=https://code.google.com/p/webrtc/issues/detail?id=3158 (4) *!_towlower_l *!towlower *!tolowercase +*!rtc::IsDefaultBrowserFirefox +*!rtc::GetProxySettingsForUrl +*!rtc::AutoDetectProxy::GetProxyForUrl +*!rtc::AutoDetectProxy::DoWork +*!rtc::SignalThread::Run +*!rtc::SignalThread::Worker::Run +*!rtc::Thread::PreRun +KERNEL32.dll!BaseThreadInitThunk + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (12) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Print +libjingle_peerconnection_unittes!webrtc::TraceImpl::WriteToFile +libjingle_peerconnection_unittes!webrtc::TraceImpl::Process +libjingle_peerconnection_unittes!webrtc::TraceImpl::Run +libjingle_peerconnection_unittes!webrtc::ThreadWindows::Run +libjingle_peerconnection_unittes!webrtc::ThreadWindows::StartThread +libjingle_peerconnection_unittes!_callthreadstartex +libjingle_peerconnection_unittes!_threadstartex +KERNEL32.dll!BaseThreadInitThunk + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (14) +libjingle_peerconnection_unittes!std::list<>::begin +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::WebRtcVideoEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (15) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!TestInvalidParameterHandler +libjingle_peerconnection_unittes!_invalid_parameter +... +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::WebRtcVideoEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (16) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::ConstructCodecs +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::WebRtcVoiceEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (32) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +... +libjingle_peerconnection_unittes!testing::internal::CountIf<> +libjingle_peerconnection_unittes!testing::TestResult::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (34) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +... +libjingle_peerconnection_unittes!TestPureCallHandler +libjingle_peerconnection_unittes!_purecall +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +HANDLE LEAK +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (35) +system call NtCreateEvent +KERNELBASE.dll!CreateEventExW +KERNELBASE.dll!CreateEventW +libjingle_peerconnection_unittes!webrtc::EventWindows::EventWindows +libjingle_peerconnection_unittes!webrtc::EventWrapper::Create +libjingle_peerconnection_unittes!webrtc::ProcessThreadImpl::ProcessThreadImpl +libjingle_peerconnection_unittes!webrtc::ProcessThread::CreateProcessThread +libjingle_peerconnection_unittes!webrtc::voe::SharedData::SharedData +libjingle_peerconnection_unittes!webrtc::VoiceEngineImpl::VoiceEngineImpl +libjingle_peerconnection_unittes!webrtc::GetVoiceEngine +libjingle_peerconnection_unittes!webrtc::VoiceEngine::Create +libjingle_peerconnection_unittes!cricket::VoEWrapper::VoEWrapper +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::WebRtcVoiceEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (36) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!webrtc::PeerConnection::CreateAnswer +libjingle_peerconnection_unittes!webrtc::ReturnType<>::Invoke<> +libjingle_peerconnection_unittes!webrtc::MethodCall2<>::OnMessage +libjingle_peerconnection_unittes!rtc::Thread::Send +libjingle_peerconnection_unittes!webrtc::MethodCall2<>::Marshal +libjingle_peerconnection_unittes!webrtc::PeerConnectionProxy::CreateAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::DoCreateOfferAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::DoCreateAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::CreateAnswerAsLocalDescription +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest_ReceiveOfferCreatePrAnswerAndAnswer_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (37) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!rtc::CriticalSection::Enter +libjingle_peerconnection_unittes!rtc::CritScope::CritScope +libjingle_peerconnection_unittes!rtc::LogMessage::GetLogToStream +libjingle_peerconnection_unittes!rtc::LogMessage::ConfigureLogging +libjingle_peerconnection_unittes!main + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (4_tmp) +*!_towlower_l +*!towlower +*!tolowercase *!talk_base::IsDefaultBrowserFirefox *!talk_base::GetProxySettingsForUrl *!talk_base::AutoDetectProxy::GetProxyForUrl @@ -164,3 +377,554 @@ name=https://code.google.com/p/webrtc/issues/detail?id=3158 (6) *!NatTest_TestPhysicalIPv6_Test::TestBody *!testing::internal::HandleSehExceptionsInMethodIfSupported<> +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (7) +*!cricket::Candidate::generation +*!P2PTransportChannelTestBase::TestHandleIceUfragPasswordChanged +*!P2PTransportChannelTest_HandleUfragPwdChangeAsIce_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (8) +drmemorylib.dll!replace_memcmp +*!PseudoTcpTest::TestTransfer +*!PseudoTcpTest_TestSendBothUseLargeWindowScale_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (9) +*!testing::internal::CmpHelperEQ<> +*!testing::internal::EqHelper<>::Compare<> +*!PseudoTcpTest::TestTransfer +*!PseudoTcpTest_TestSendBothUseLargeWindowScale_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (10) +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::DoRenderThread +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::WSAPIRenderThread +KERNEL32.dll!BaseThreadInitThunk + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (11) +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::_Lock +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::DoRenderThread +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::WSAPIRenderThread +KERNEL32.dll!BaseThreadInitThunk + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (12_tmp) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!talk_base::CriticalSection::Enter +libjingle_peerconnection_unittes!talk_base::CritScope::CritScope +libjingle_peerconnection_unittes!talk_base::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Print +libjingle_peerconnection_unittes!webrtc::TraceImpl::WriteToFile +libjingle_peerconnection_unittes!webrtc::TraceImpl::Process +libjingle_peerconnection_unittes!webrtc::TraceImpl::Run +libjingle_peerconnection_unittes!webrtc::ThreadWindows::Run +libjingle_peerconnection_unittes!webrtc::ThreadWindows::StartThread +libjingle_peerconnection_unittes!_callthreadstartex +libjingle_peerconnection_unittes!_threadstartex +KERNEL32.dll!BaseThreadInitThunk + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (13) +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::_Lock +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::DoRenderThread +libjingle_peerconnection_unittes!webrtc::AudioDeviceWindowsCore::WSAPIRenderThread +KERNEL32.dll!BaseThreadInitThunk + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (14_tmp) +libjingle_peerconnection_unittes!std::list<>::begin +libjingle_peerconnection_unittes!talk_base::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::WebRtcVideoEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!talk_base::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (15_tmp) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!talk_base::CriticalSection::Enter +libjingle_peerconnection_unittes!talk_base::CritScope::CritScope +libjingle_peerconnection_unittes!talk_base::LogMessage::~LogMessage +libjingle_peerconnection_unittes!TestInvalidParameterHandler +libjingle_peerconnection_unittes!_invalid_parameter +... +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVideoEngine::WebRtcVideoEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!talk_base::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (16_tmp) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!talk_base::CriticalSection::Enter +libjingle_peerconnection_unittes!talk_base::CritScope::CritScope +libjingle_peerconnection_unittes!talk_base::LogMessage::~LogMessage +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::ConstructCodecs +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::Construct +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::WebRtcVoiceEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!talk_base::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (17) +libjingle_peerconnection_unittes!testing::Test::HasSameFixtureClass +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (18) +libjingle_peerconnection_unittes!std::_String_val<>::_Myptr +libjingle_peerconnection_unittes!std::basic_string<>::c_str +libjingle_peerconnection_unittes!testing::TestInfo::name +libjingle_peerconnection_unittes!testing::Test::HasSameFixtureClass +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (19) +libjingle_peerconnection_unittes!std::vector<>::_Inside +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (20) +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (21) +libjingle_peerconnection_unittes!std::vector<>::_Unused_capacity +libjingle_peerconnection_unittes!std::vector<>::_Reserve +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (22) +libjingle_peerconnection_unittes!std::vector<>::size +libjingle_peerconnection_unittes!std::vector<>::_Reserve +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (23) +libjingle_peerconnection_unittes!std::vector<>::capacity +libjingle_peerconnection_unittes!std::vector<>::_Grow_to +libjingle_peerconnection_unittes!std::vector<>::_Reserve +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (24) +libjingle_peerconnection_unittes!std::vector<>::_Reallocate +libjingle_peerconnection_unittes!std::vector<>::_Reserve +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (25) +libjingle_peerconnection_unittes!std::vector<>::size +libjingle_peerconnection_unittes!std::vector<>::_Reallocate +libjingle_peerconnection_unittes!std::vector<>::_Reserve +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (26) +libjingle_peerconnection_unittes!std::_Container_base12::_Orphan_all +libjingle_peerconnection_unittes!std::vector<>::_Reallocate +libjingle_peerconnection_unittes!std::vector<>::_Reserve +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (27) +libjingle_peerconnection_unittes!std::_Container_base12::_Getpfirst +libjingle_peerconnection_unittes!std::vector<>::_Orphan_range +libjingle_peerconnection_unittes!std::vector<>::push_back +libjingle_peerconnection_unittes!testing::TestResult::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (28) +libjingle_peerconnection_unittes!std::vector<>::begin +libjingle_peerconnection_unittes!testing::internal::CountIf<> +libjingle_peerconnection_unittes!testing::TestResult::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (29) +libjingle_peerconnection_unittes!std::_Iterator_base12::_Adopt +libjingle_peerconnection_unittes!std::_Vector_const_iterator<>::_Vector_const_iterator<> +libjingle_peerconnection_unittes!std::vector<>::begin +libjingle_peerconnection_unittes!testing::internal::CountIf<> +libjingle_peerconnection_unittes!testing::TestResult::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (30) +libjingle_peerconnection_unittes!std::vector<>::end +libjingle_peerconnection_unittes!testing::internal::CountIf<> +libjingle_peerconnection_unittes!testing::TestResult::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (31) +libjingle_peerconnection_unittes!std::_Iterator_base12::_Adopt +libjingle_peerconnection_unittes!std::_Vector_const_iterator<>::_Vector_const_iterator<> +libjingle_peerconnection_unittes!std::vector<>::end +libjingle_peerconnection_unittes!testing::internal::CountIf<> +libjingle_peerconnection_unittes!testing::TestResult::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (32_tmp) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!talk_base::CriticalSection::Enter +libjingle_peerconnection_unittes!talk_base::CritScope::CritScope +... +libjingle_peerconnection_unittes!testing::internal::CountIf<> +libjingle_peerconnection_unittes!testing::TestResult::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::HasFatalFailure +libjingle_peerconnection_unittes!testing::Test::Run + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (33) +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (34_tmp) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!talk_base::CriticalSection::Enter +libjingle_peerconnection_unittes!talk_base::CritScope::CritScope +... +libjingle_peerconnection_unittes!TestPureCallHandler +libjingle_peerconnection_unittes!_purecall +libjingle_peerconnection_unittes!testing::internal::DefaultGlobalTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::internal::DefaultPerThreadTestPartResultReporter::ReportTestPartResult +libjingle_peerconnection_unittes!testing::UnitTest::AddTestPartResult +libjingle_peerconnection_unittes!testing::internal::ReportFailureInUnknownLocation +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +HANDLE LEAK +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (35_tmp) +system call NtCreateEvent +KERNELBASE.dll!CreateEventExW +KERNELBASE.dll!CreateEventW +libjingle_peerconnection_unittes!webrtc::EventWindows::EventWindows +libjingle_peerconnection_unittes!webrtc::EventWrapper::Create +libjingle_peerconnection_unittes!webrtc::ProcessThreadImpl::ProcessThreadImpl +libjingle_peerconnection_unittes!webrtc::ProcessThread::CreateProcessThread +libjingle_peerconnection_unittes!webrtc::voe::SharedData::SharedData +libjingle_peerconnection_unittes!webrtc::VoiceEngineImpl::VoiceEngineImpl +libjingle_peerconnection_unittes!webrtc::GetVoiceEngine +libjingle_peerconnection_unittes!webrtc::VoiceEngine::Create +libjingle_peerconnection_unittes!cricket::VoEWrapper::VoEWrapper +libjingle_peerconnection_unittes!cricket::WebRtcVoiceEngine::WebRtcVoiceEngine +libjingle_peerconnection_unittes!cricket::CompositeMediaEngine<>::CompositeMediaEngine<> +libjingle_peerconnection_unittes!cricket::WebRtcMediaEngine::WebRtcMediaEngine +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize_s +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::OnMessage +libjingle_peerconnection_unittes!talk_base::Thread::Send +libjingle_peerconnection_unittes!webrtc::PeerConnectionFactory::Initialize +libjingle_peerconnection_unittes!webrtc::CreatePeerConnectionFactory +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::SetUp +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (36_tmp) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!webrtc::PeerConnection::CreateAnswer +libjingle_peerconnection_unittes!webrtc::ReturnType<>::Invoke<> +libjingle_peerconnection_unittes!webrtc::MethodCall2<>::OnMessage +libjingle_peerconnection_unittes!talk_base::Thread::Send +libjingle_peerconnection_unittes!webrtc::MethodCall2<>::Marshal +libjingle_peerconnection_unittes!webrtc::PeerConnectionProxy::CreateAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::DoCreateOfferAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::DoCreateAnswer +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest::CreateAnswerAsLocalDescription +libjingle_peerconnection_unittes!PeerConnectionInterfaceTest_ReceiveOfferCreatePrAnswerAndAnswer_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNADDRESSABLE ACCESS +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (37_tmp) +ntdll.dll!RtlIntegerToUnicodeString +ntdll.dll!RtlIntegerToUnicodeString +libjingle_peerconnection_unittes!talk_base::CriticalSection::Enter +libjingle_peerconnection_unittes!talk_base::CritScope::CritScope +libjingle_peerconnection_unittes!talk_base::LogMessage::GetLogToStream +libjingle_peerconnection_unittes!talk_base::LogMessage::ConfigureLogging +libjingle_peerconnection_unittes!main + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (38) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestReceiveSdesOfferCreateSdesAnswer_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (39) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestSetLocalPrAnswer_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (40) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAndSetRemoteOfferAndLocalAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestLocalCandidatesAddedToSessionDescription_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (41) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestSetLocalAndRemoteDescriptionWithCandidates_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (42) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAndSetRemoteOfferAndLocalAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestAVOfferWithAudioOnlyAnswer_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (43) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAndSetRemoteOfferAndLocalAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestAVOfferWithVideoOnlyAnswer_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (44) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestCreateAnswerWithNewUfragAndPassword_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3158 (45) +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::InternalCreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSessionDescriptionFactory::CreateAnswer +libjingle_peerconnection_unittes!webrtc::WebRtcSession::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest::CreateAnswer +libjingle_peerconnection_unittes!WebRtcSessionTest_TestCreateAnswerWithOldUfragAndPassword_Test::TestBody +libjingle_peerconnection_unittes!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3184 (1) +*!webrtc::MouseCursorMonitorWin::Capture +*!webrtc::MouseCursorMonitorTest_FromScreen_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3184 (2) +*!webrtc::DesktopRect::Contains +*!webrtc::MouseCursorMonitorWin::Capture +*!webrtc::MouseCursorMonitorTest_FromScreen_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3184 (3) +*!webrtc::MouseCursorMonitorWin::Capture +*!webrtc::MouseCursorMonitorTest_FromWindow_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3184 (4) +system call NtUserGetWindowPlacement +*!webrtc::GetCroppedWindowRect +*!webrtc::MouseCursorMonitorWin::Capture +*!webrtc::MouseCursorMonitorTest_FromWindow_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3184 (5) +*!webrtc::GetCroppedWindowRect +*!webrtc::MouseCursorMonitorWin::Capture +*!webrtc::MouseCursorMonitorTest_FromWindow_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3184 (6) +system call NtUserWindowFromPoint parameter value #0 +*!webrtc::MouseCursorMonitorWin::Capture +*!webrtc::MouseCursorMonitorTest_FromWindow_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3184 (7) +*!webrtc::MouseCursorMonitorWin::Capture +*!webrtc::MouseCursorMonitorTest_ShapeOnly_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3183 (1) +*!webrtc::RTPReceiverVideo::ReceiveGenericCodec +*!webrtc::RTPReceiverVideo::ParseVideoCodecSpecific +*!webrtc::RTPReceiverVideo::ParseRtpPacket +*!webrtc::RtpReceiverImpl::IncomingRtpPacket +*!RtxLoopBackTransport::SendPacket +*!webrtc::RTPSender::SendPacketToNetwork +*!webrtc::RTPSender::SendToNetwork +*!webrtc::RTPSenderVideo::SendVideoPacket +*!webrtc::RTPSenderVideo::SendGeneric +*!webrtc::RTPSenderVideo::SendVideo +*!webrtc::RTPSender::SendOutgoingData +*!webrtc::ModuleRtpRtcpImpl::SendOutgoingData +*!RtpRtcpRtxNackTest_LongNackList_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=name=https://code.google.com/p/webrtc/issues/detail?id=3183 (2) +*!webrtc::RTPReceiverVideo::ReceiveGenericCodec +*!webrtc::RTPReceiverVideo::ParseVideoCodecSpecific +*!webrtc::RTPReceiverVideo::ParseRtpPacket +*!webrtc::RtpReceiverImpl::IncomingRtpPacket +*!RtxLoopBackTransport::SendPacket +*!webrtc::RTPSender::SendPacketToNetwork +*!webrtc::RTPSender::PrepareAndSendPacket +*!webrtc::RTPSender::ReSendPacket +*!webrtc::RTPSender::OnReceivedNACK +*!webrtc::ModuleRtpRtcpImpl::OnReceivedNACK +*!webrtc::RTCPReceiver::TriggerCallbacksFromRTCPPacket +*!webrtc::ModuleRtpRtcpImpl::IncomingRtcpPacket +*!RtxLoopBackTransport::SendRTCPPacket +*!webrtc::RTCPSender::SendToNetwork +*!webrtc::RTCPSender::SendRTCP +*!webrtc::ModuleRtpRtcpImpl::SendNACK +*!RtpRtcpRtxNackTest_LongNackList_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=name=https://code.google.com/p/webrtc/issues/detail?id=3183 (3) +*!webrtc::RTPReceiverVideo::ReceiveGenericCodec +*!webrtc::RTPReceiverVideo::ParseVideoCodecSpecific +*!webrtc::RTPReceiverVideo::ParseRtpPacket +*!webrtc::RtpReceiverImpl::IncomingRtpPacket +*!RtxLoopBackTransport::SendPacket +*!webrtc::RTPSender::SendPacketToNetwork +*!webrtc::RTPSender::SendToNetwork +*!webrtc::RTPSenderVideo::SendVideoPacket +*!webrtc::RTPSenderVideo::SendGeneric +*!webrtc::RTPSenderVideo::SendVideo +*!webrtc::RTPSender::SendOutgoingData +*!webrtc::ModuleRtpRtcpImpl::SendOutgoingData +*!RtpRtcpRtxNackTest::RunRtxTest +*!RtpRtcpRtxNackTest_RtxNack_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3490 (1) +drmemorylib.dll!replace_memcmp +*!talk_base::AsyncWriteTest_TestWrite_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=3490 (2) +*!testing::internal::CmpHelperEQ<> +*!testing::internal::EqHelper<>::Compare<> +*!talk_base::AsyncWriteTest_TestWrite_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> diff --git a/tools/valgrind-webrtc/gtest_exclude/common_video_unittests.gtest-drmemory_win32.txt b/tools/valgrind-webrtc/gtest_exclude/common_video_unittests.gtest-drmemory_win32.txt new file mode 100644 index 000000000..d7767009b --- /dev/null +++ b/tools/valgrind-webrtc/gtest_exclude/common_video_unittests.gtest-drmemory_win32.txt @@ -0,0 +1,4 @@ +# Too slow on Dr Memory Full. +# https://code.google.com/p/webrtc/issues/detail?id=3247 +TestScaler.BiLinearScaleTest +TestScaler.BoxScaleTest diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-drmemory_win32.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-drmemory_win32.txt index ab8387293..985b3c276 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-drmemory_win32.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-drmemory_win32.txt @@ -1,3 +1,14 @@ -# Fails on Dr Memory Full. +# Times out before finishing on DrMemory Full +# https://code.google.com/p/webrtc/issues/detail?id=3375 +WebRtcVideoChannel2BaseTest.SimulateConference +WebRtcVideoChannel2BaseTest.TwoStreamsSendAndReceive +WebRtcVideoChannel2BaseTest.TwoStreamsReUseFirstStream + # https://code.google.com/p/webrtc/issues/detail?id=3158 WebRtcVideoMediaChannelTest.SendVp8HdAndReceiveAdaptedVp8Vga + +# https://code.google.com/p/webrtc/issues/detail?id=3482 +WebRtcVideoMediaChannelTest.GetStats + +#TODO(jiayl): https://code.google.com/p/webrtc/issues/detail?id=3492 +SctpDataMediaChannelTest.* diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-memcheck.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-memcheck.txt index 4727c59eb..9309510a7 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-memcheck.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-memcheck.txt @@ -1,2 +1,5 @@ -TODO(wu): https://code.google.com/p/webrtc/issues/detail?id=2380 -WebRtcVideoMediaChannelTest.TwoStreamsSendAndUnsignalledRecv \ No newline at end of file +#TODO(wu): https://code.google.com/p/webrtc/issues/detail?id=2380 +WebRtcVideoMediaChannelTest.TwoStreamsSendAndUnsignalledRecv + +#TODO(jiayl): https://code.google.com/p/webrtc/issues/detail?id=3492 +SctpDataMediaChannelTest.* diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt index 863d5859e..958893388 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt @@ -5,3 +5,8 @@ WebRtcVideoMediaChannelTest.TwoStreamsSendAndFailUnsignalledRecv WebRtcVideoMediaChannelTest.TwoStreamsSendAndFailUnsignalledRecvInOneToOne WebRtcVideoMediaChannelTest.TwoStreamsSendAndReceive WebRtcVideoMediaChannelTest.TwoStreamsSendAndUnsignalledRecv + +TODO(pbos): This suppression is overly broad, but offline talks with kjellander@ +indicate that we can move over to tsanv2 and deprecate tsanv1, which will remove +the need for tsanv1 suppressions. +WebRtcVideoChannel2BaseTest.* diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-drmemory_win32.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-drmemory_win32.txt index c879a244f..b0ac73e01 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-drmemory_win32.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-drmemory_win32.txt @@ -1,3 +1,6 @@ # Fails on Dr Memory Full. # https://code.google.com/p/webrtc/issues/detail?id=3158 -P2PTransportChannelTest.TestOPENToOPENAsGiceNoneSharedUfrag +P2PTransportChannel*.* +PortAllocatorTest.* +PortTest.* +PseudoTcpTest.TestSendBothUseLargeWindowScale diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-memcheck.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-memcheck.txt index 1ecdbfc53..1e2332960 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-memcheck.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_p2p_unittest.gtest-memcheck.txt @@ -3,4 +3,5 @@ P2PTransportChannelTest.* P2PTransportChannelSameNatTest.TestConesBehindSameCone PortTest.TestSendStunMessageAsIce PseudoTcpTest.TestSendBothUseLargeWindowScale - +# Issue 3447 +P2PTransportChannelMultihomedTest.TestFailover diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-drmemory_win32.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-drmemory_win32.txt index 38ef1c2ef..d41c231cf 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-drmemory_win32.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-drmemory_win32.txt @@ -1,3 +1,8 @@ -# Flakily fails on Dr Memory Light/Full. +# Flakily fails or crashes on Dr Memory Full. # https://code.google.com/p/webrtc/issues/detail?id=3158 -JsepPeerConnectionP2PTestClient.LocalP2PTest16To9 +DtmfSenderTest.* +JsepPeerConnectionP2PTestClient.* +PeerConnectionEndToEndTest.* +PeerConnectionInterfaceTest.* +# Issue 3453 +WebRtcSessionTest.TestReceiveSdesOfferCreateSdesAnswer diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-memcheck.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-memcheck.txt index f79fccc28..4d723c633 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-memcheck.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_peerconnection_unittest.gtest-memcheck.txt @@ -24,6 +24,10 @@ JsepPeerConnectionP2PTestClient.RegisterDataChannelObserver JsepPeerConnectionP2PTestClient.UpdateOfferWithRejectedContent PeerConnectionEndToEndTest.Call PeerConnectionEndToEndTest.CallWithLegacySdp +PeerConnectionEndToEndTest.CreateDataChannelBeforeNegotiate +PeerConnectionEndToEndTest.CreateDataChannelAfterNegotiate +PeerConnectionEndToEndTest.DataChannelIdAssignment +PeerConnectionEndToEndTest.MessageTransferBetweenTwoPairsOfDataChannels PeerConnectionInterfaceTest.DataChannelCloseWhenPeerConnectionClose PeerConnectionInterfaceTest.TestDataChannel PeerConnectionInterfaceTest.TestSendBinaryOnRtpDataChannel diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_unittest.gtest-drmemory_win32.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_unittest.gtest-drmemory_win32.txt new file mode 100644 index 000000000..5ba7b9aa9 --- /dev/null +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_unittest.gtest-drmemory_win32.txt @@ -0,0 +1,3 @@ +# Fails on Dr Memory Full. +# https://code.google.com/p/webrtc/issues/detail?id=3490 +AsyncWriteTest.TestWrite diff --git a/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-drmemory_win32.txt b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-drmemory_win32.txt index 9e5af0992..ff0d3169e 100644 --- a/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-drmemory_win32.txt +++ b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-drmemory_win32.txt @@ -1,12 +1,18 @@ -# Too slow to run with Dr Memory on Windows. -ApmTest.EchoCancellationReportsCorrectDelays -ApmTest.FloatAndIntInterfacesGiveIdenticalResults -ApmTest.IdenticalInputChannelsResultInIdenticalOutputChannels -ApmTest.VerifyDebugDumpFloat -ApmTest.VerifyDebugDumpInt -TestVideoSenderWithVp8.* -VideoSendersTest.* -VideoProcessingModuleTest.Denoising - -# https://code.google.com/p/webrtc/issues/detail?id=2323 -MouseCursorShapeTest.MatchCursors +# Too slow to run with Dr Memory on Windows. +ApmTest.EchoCancellationReportsCorrectDelays +ApmTest.FloatAndIntInterfacesGiveIdenticalResults +ApmTest.IdenticalInputChannelsResultInIdenticalOutputChannels +ApmTest.VerifyDebugDumpFloat +ApmTest.VerifyDebugDumpInt +CommonFormats/AudioProcessingTest* +TestScaler.PointScaleTest +TestScaler.BiLinearScaleTest +TestScaler.BoxScaleTest +TestVideoSenderWithVp8.* +TestVp8Impl.BaseUnitTest +VideoProcessingModuleTest.Denoising +VideoProcessorIntegrationTest.ProcessNoLossChangeBitRate +VideoSendersTest.* + +# https://code.google.com/p/webrtc/issues/detail?id=2323 +MouseCursorShapeTest.MatchCursors diff --git a/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-memcheck.txt b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-memcheck.txt new file mode 100644 index 000000000..8588487ae --- /dev/null +++ b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-memcheck.txt @@ -0,0 +1,2 @@ +# Tests that are too slow. +CommonFormats/AudioProcessingTest* diff --git a/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-tsan.txt b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-tsan.txt new file mode 100644 index 000000000..8588487ae --- /dev/null +++ b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-tsan.txt @@ -0,0 +1,2 @@ +# Tests that are too slow. +CommonFormats/AudioProcessingTest* diff --git a/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-tsan_win32.txt b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-tsan_win32.txt index f7b011599..af6b46156 100644 --- a/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-tsan_win32.txt +++ b/tools/valgrind-webrtc/gtest_exclude/modules_unittests.gtest-tsan_win32.txt @@ -1,16 +1,17 @@ -# Too slow to run with TSan on Windows. -ApmTest.EchoCancellationReportsCorrectDelays -ApmTest.IdenticalInputChannelsResultInIdenticalOutputChannels -RingBufferTest.RandomStressTest -RingBufferTest.RandomStressTestWithNullPtr -VideoProcessingModuleTest.ContentAnalysis -VideoProcessingModuleTest.Deflickering -VideoProcessingModuleTest.Denoising -TestVideoSenderWithVp8.* - -# https://code.google.com/p/webrtc/issues/detail?id=1938 -MouseCursorShapeTest.MatchCursors - -# https://code.google.com/p/webrtc/issues/detail?id=2329 -NetEqDecodingTest.TestBitExactness -NetEqDecodingTest.TestNetworkStatistics +# Too slow to run with TSan on Windows. +ApmTest.EchoCancellationReportsCorrectDelays +ApmTest.IdenticalInputChannelsResultInIdenticalOutputChannels +CommonFormats/AudioProcessingTest* +RingBufferTest.RandomStressTest +RingBufferTest.RandomStressTestWithNullPtr +VideoProcessingModuleTest.ContentAnalysis +VideoProcessingModuleTest.Deflickering +VideoProcessingModuleTest.Denoising +TestVideoSenderWithVp8.* + +# https://code.google.com/p/webrtc/issues/detail?id=1938 +MouseCursorShapeTest.MatchCursors + +# https://code.google.com/p/webrtc/issues/detail?id=2329 +NetEqDecodingTest.TestBitExactness +NetEqDecodingTest.TestNetworkStatistics diff --git a/tools/valgrind-webrtc/gtest_exclude/video_engine_tests.gtest-drmemory_win32.txt b/tools/valgrind-webrtc/gtest_exclude/video_engine_tests.gtest-drmemory_win32.txt index df482cacc..bfc6b549a 100644 --- a/tools/valgrind-webrtc/gtest_exclude/video_engine_tests.gtest-drmemory_win32.txt +++ b/tools/valgrind-webrtc/gtest_exclude/video_engine_tests.gtest-drmemory_win32.txt @@ -1,3 +1,6 @@ # Never completes on Dr Memory Full. # https://code.google.com/p/webrtc/issues/detail?id=3159 CallTest.SendsAndReceivesMultipleStreams +CallTest.ReceivesAndRetransmitsNack +# https://code.google.com/p/webrtc/issues/detail?id=3471 +VideoSendStreamTest.RetransmitsNackOverRtxWithPacing diff --git a/tools/valgrind-webrtc/memcheck/suppressions.txt b/tools/valgrind-webrtc/memcheck/suppressions.txt index e95abb850..6363f8828 100644 --- a/tools/valgrind-webrtc/memcheck/suppressions.txt +++ b/tools/valgrind-webrtc/memcheck/suppressions.txt @@ -9,6 +9,116 @@ #----------------------------------------------------------------------- # 1. webrtc stuff +{ + bug_3446 + Memcheck:Uninitialized + fun:vp8cx_pick_filter_level_fast + fun:vp8_loopfilter_frame + fun:encode_frame_to_data_rate + fun:vp8_get_compressed_data + fun:vp8e_encode + fun:vpx_codec_encode + ... + fun:_ZN6webrtc4test18VideoProcessorImpl12ProcessFrameEi + fun:_ZN6webrtc29VideoProcessorIntegrationTest22ProcessFramesAndVerifyENS_14QualityMetricsENS_11RateProfileENS_15CodecConfigParsEPNS_18RateControlMetricsE + fun:_ZN6webrtc70VideoProcessorIntegrationTest_ProcessNoLossSpatialResizeFrameDrop_Test8TestBodyEv +} +{ + bug_1976_1 + Memcheck:Unaddressable + fun:pthread_mutex_unlock + fun:_ZN9rtc15CriticalSection5LeaveEv + fun:_ZN9rtc9CritScopeD1Ev + ... + fun:_ZN9rtc6Thread15ProcessMessagesEi + fun:_ZN9rtc6Thread3RunEv + fun:_ZN9rtc6Thread6PreRunEPv +} +{ + bug_1976_2 + Memcheck:Leak + fun:calloc + obj:/usr/lib/x86_64-linux-gnu/libnss3.so + ... + fun:NSS_NoDB_Init + fun:_ZN9rtc10NSSContext13InitializeSSLEPFbPvE + fun:_ZN9rtc13InitializeSSLEPFbPvE + fun:_ZN9rtc10RandomTest13SetUpTestCaseEv + fun:_ZN7testing8TestCase16RunSetUpTestCaseEv +} +{ + bug_2100_3 + Memcheck:Uninitialized + fun:tls1_enc + fun:ssl3_get_record + fun:ssl3_read_bytes + fun:ssl3_read_internal + fun:ssl3_read + fun:SSL_read + fun:_ZN9rtc20OpenSSLStreamAdapter4ReadEPvmPmPi + ... +} +{ + bug_2100_4 + Memcheck:Uninitialized + fun:_ZN7testing8internal11CmpHelperEQIjhEENS_15AssertionResultEPKcS4_RKT_RKT0_ + fun:_ZN7testing8internal8EqHelperILb0EE7CompareIjhEENS_15AssertionResultEPKcS6_RKT_RKT0_ + fun:_ZN24SSLStreamAdapterTestDTLS8ReadDataEPN9rtc15StreamInterfaceE + ... +} +{ + bug_2100_5 + Memcheck:Uninitialized + fun:dtls1_process_record + fun:dtls1_get_record + fun:dtls1_read_bytes + fun:ssl3_read_internal + fun:ssl3_read + fun:SSL_read + fun:_ZN9rtc20OpenSSLStreamAdapter4ReadEPvmPmPi + ... +} +{ + BIO_new_mem_buf_1 + Memcheck:Leak + fun:malloc + fun:default_malloc_ex + fun:CRYPTO_malloc + fun:BUF_MEM_new + fun:mem_new + fun:BIO_set + fun:BIO_new + fun:BIO_new_mem_buf + fun:_ZN9rtc18OpenSSLCertificate13FromPEMStringERKSs + ... +} +{ + BIO_new_mem_buf_2 + Memcheck:Leak + fun:malloc + fun:default_malloc_ex + fun:CRYPTO_malloc + fun:BUF_MEM_new + fun:mem_new + fun:BIO_set + fun:BIO_new + fun:BIO_new_mem_buf + fun:_ZN9rtc15OpenSSLIdentity14FromPEMStringsERKSsS2_ +} +{ + SignalsCloseAfterForcedCloseAll + Memcheck:Leak + fun:_Znw* + fun:_ZN9rtc10HttpServer10Connection12BeginProcessEPNS_15StreamInterfaceE + ... +} +{ + DoNotDeleteTask2 + Memcheck:Leak + fun:_Znw* + ... + fun:_ZN9rtc41unstarted_task_test_DoNotDeleteTask2_Test8TestBodyEv +} { bug_716 Memcheck:Leak @@ -370,7 +480,7 @@ #----------------------------------------------------------------------- # 2. libjingle stuff (talk folder) { - bug_1976_1 + bug_1976_1_tmp Memcheck:Unaddressable fun:pthread_mutex_unlock fun:_ZN9talk_base15CriticalSection5LeaveEv @@ -381,7 +491,7 @@ fun:_ZN9talk_base6Thread6PreRunEPv } { - bug_1976_2 + bug_1976_2_tmp Memcheck:Leak fun:calloc obj:/usr/lib/x86_64-linux-gnu/libnss3.so @@ -421,7 +531,7 @@ ... } { - bug_2100_3 + bug_2100_3_tmp Memcheck:Uninitialized fun:tls1_enc fun:ssl3_get_record @@ -433,7 +543,7 @@ ... } { - bug_2100_4 + bug_2100_4_tmp Memcheck:Uninitialized fun:_ZN7testing8internal11CmpHelperEQIjhEENS_15AssertionResultEPKcS4_RKT_RKT0_ fun:_ZN7testing8internal8EqHelperILb0EE7CompareIjhEENS_15AssertionResultEPKcS6_RKT_RKT0_ @@ -441,7 +551,7 @@ ... } { - bug_2100_5 + bug_2100_5_tmp Memcheck:Uninitialized fun:dtls1_process_record fun:dtls1_get_record @@ -455,7 +565,7 @@ # For BIO_new_mem_buf # http://www.openssl.org/support/faq.html#PROG13 { - BIO_new_mem_buf_1 + BIO_new_mem_buf_1_tmp Memcheck:Leak fun:malloc fun:default_malloc_ex @@ -469,7 +579,7 @@ ... } { - BIO_new_mem_buf_2 + BIO_new_mem_buf_2_tmp Memcheck:Leak fun:malloc fun:default_malloc_ex @@ -483,7 +593,7 @@ } # For HttpServer.SignalsCloseAfterForcedCloseAll { - SignalsCloseAfterForcedCloseAll + SignalsCloseAfterForcedCloseAll_tmp Memcheck:Leak fun:_Znw* fun:_ZN9talk_base10HttpServer10Connection12BeginProcessEPNS_15StreamInterfaceE @@ -540,7 +650,7 @@ } # For tests that leaks by design. { - DoNotDeleteTask2 + DoNotDeleteTask2_tmp Memcheck:Leak fun:_Znw* ... @@ -588,3 +698,17 @@ fun:_ZN21VideoMediaChannelTestIN7cricket17WebRtcVideoEngineENS0_23WebRtcVideoMediaChannelEE36TwoStreamsSendAndFailUnsignalledRecvERKNS0_10VideoCodecE fun:_ZN69WebRtcVideoMediaChannelTest_TwoStreamsSendAndFailUnsignalledRecv_Test8TestBodyEv } +{ + bug_3478 + Memcheck:Leak + fun:_Znw* + fun:_ZNK9talk_base18FakeSSLCertificate12GetReferenceEv + fun:_ZN9talk_base18FakeSSLCertificate7DupCertES0_ + fun:_ZSt9transformIN9__gnu_cxx17__normal_iteratorIPKN9talk_base18FakeSSLCertificateESt6vectorIS3_SaIS3_EEEENS1_IPPNS2_14SSLCertificateES6_ISB_SaISB_EEEEPFPS3_S3_EET0_T_SK_SJ_T1_ + fun:_ZNK9talk_base18FakeSSLCertificate8GetChainEPPNS_12SSLCertChainE + fun:_ZN6webrtc14StatsCollector21AddCertificateReportsEPKN9talk_base14SSLCertificateE + fun:_ZN6webrtc14StatsCollector18ExtractSessionInfoEv + fun:_ZN6webrtc14StatsCollector11UpdateStatsENS_23PeerConnectionInterface16StatsOutputLevelE + fun:_ZN12_GLOBAL__N_118StatsCollectorTest22TestCertificateReportsERKN9talk_base18FakeSSLCertificateERKSt6vectorISsSaISsEES4_S9_ + fun:_ZN12_GLOBAL__N_156StatsCollectorTest_ChainedCertificateReportsCreated_Test8TestBodyEv +} diff --git a/tools/valgrind-webrtc/tsan/suppressions.txt b/tools/valgrind-webrtc/tsan/suppressions.txt index b9cac45ac..af1e6d2d4 100644 --- a/tools/valgrind-webrtc/tsan/suppressions.txt +++ b/tools/valgrind-webrtc/tsan/suppressions.txt @@ -9,6 +9,473 @@ #----------------------------------------------------------------------- # 1. webrtc stuff +{ + bug_1205_33 + ThreadSanitizer:Race + fun:webrtc::PeerConnectionProxy::~PeerConnectionProxy + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::Release + fun:rtc::scoped_refptr::~scoped_refptr + ... +} +{ + bug_1205_34 + ThreadSanitizer:Race + fun:rtc::AtomicOps::Increment + fun:rtc::RefCountedObject::AddRef + fun:rtc::scoped_refptr::scoped_refptr + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + ... +} +{ + bug_1205_35 + ThreadSanitizer:Race + fun:rtc::scoped_refptr::scoped_refptr + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + fun:webrtc::PeerConnectionFactory::CreatePeerConnection + ... +} +{ + bug_1205_36 + ThreadSanitizer:Race + fun:rtc::MessageHandler::~MessageHandler + fun:FakeAudioCaptureModule::~FakeAudioCaptureModule + ... +} +{ + bug_1205_39 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:FakeAudioCaptureModule::UpdateProcessing + fun:FakeAudioCaptureModule::StopRecording + fun:webrtc::VoEBaseImpl::StopSend + fun:webrtc::VoEBaseImpl::DeleteChannel + fun:cricket::WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel + ... +} +{ + bug_1205_41 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:FakeAudioCaptureModule::~FakeAudioCaptureModule + ... +} +{ + bug_2078_2 + ThreadSanitizer:Race + ... + fun:rtc::MemoryStreamBase::Read + ... + fun:cricket::RtpDumpReader::ReadPacket + ... +} +{ + bug_2078_3 + ThreadSanitizer:Race + fun:cricket::RtpSenderReceiver::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2078_4 + ThreadSanitizer:Race + fun:cricket::FileNetworkInterface::SendPacket + fun:cricket::RtpSenderReceiver::SendRtpPacket + fun:cricket::RtpSenderReceiver::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2078_7 + ThreadSanitizer:Race + fun:rtc::MemoryStreamBase::~MemoryStreamBase + fun:rtc::MemoryStream::~MemoryStream + ... +} +{ + bug_2078_8 + ThreadSanitizer:Race + fun:rtc::MemoryStreamBase::SetPosition + fun:rtc::StreamInterface::Rewind + fun:cricket::RtpTestUtility::VerifyTestPacketsFromStream + ... +} +{ + bug_2078_13 + ThreadSanitizer:Race + fun:std::_Vector_base::~_Vector_base + fun:std::vector::~vector + fun:cricket::RtpDumpPacket::~RtpDumpPacket + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_14 + ThreadSanitizer:Race + fun:cricket::RtpDumpReader::~RtpDumpReader + fun:cricket::RtpDumpLoopReader::~RtpDumpLoopReader + fun:cricket::RtpDumpLoopReader::~RtpDumpLoopReader + fun:rtc::scoped_ptr::~scoped_ptr + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_15 + ThreadSanitizer:Race + fun:rtc::FileStream::Close + fun:rtc::FileStream::~FileStream + fun:rtc::FileStream::~FileStream + fun:rtc::scoped_ptr::~scoped_ptr + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_16 + ThreadSanitizer:Race + fun:rtc::StreamInterface::~StreamInterface + fun:rtc::FileStream::~FileStream + fun:rtc::FileStream::~FileStream + fun:rtc::scoped_ptr::~scoped_ptr + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:cricket::RtpSenderReceiver::~RtpSenderReceiver + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2078_18 + ThreadSanitizer:Race + fun:rtc::MemoryStream::~MemoryStream + ... +} +{ + bug_2078_22 + ThreadSanitizer:Race + fun:rtc::MessageQueue::Quit + fun:rtc::Thread::Stop + ... +} +{ + bug_2078_23 + ThreadSanitizer:Race + fun:::FileVideoCapturerTest::VideoCapturerListener::OnFrameCaptured + fun:sigslot::_connection2::emit + ... + fun:cricket::FileVideoCapturer::ReadFrame + fun:cricket::FileVideoCapturer::FileReadThread::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:cricket::FileVideoCapturer::FileReadThread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2078_24 + ThreadSanitizer:Race + fun:rtc::MemoryStreamBase::SetPosition + fun:rtc::StreamInterface::Rewind + ... +} +{ + bug_2078_25 + ThreadSanitizer:Race + fun:cricket::FileNetworkInterface::SendPacket + fun:cricket::MediaChannel::DoSendPacket + fun:cricket::MediaChannel::SendPacket + fun:cricket::RtpSenderReceiver::SendRtpPacket + fun:cricket::RtpSenderReceiver::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2079_1 + ThreadSanitizer:Race + fun:rtc::VirtualSocketServer::AddPacketToNetwork + fun:rtc::VirtualSocketServer::SendUdp + fun:rtc::VirtualSocket::SendUdp + fun:rtc::VirtualSocket::SendTo + fun:rtc::AsyncUDPSocket::SendTo + fun:cricket::StunServer::SendResponse + fun:cricket::StunServer::OnBindingRequest + fun:cricket::StunServer::OnPacket + fun:sigslot::_connection4::emit + ... +} +{ + bug_2079_5 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:cricket::Transport::SetRole + fun:cricket::BaseSession::GetOrCreateTransportProxy + fun:cricket::BaseSession::CreateChannel + fun:cricket::FakeSession::CreateChannel + fun:cricket::VoiceChannel::Init + fun:cricket::ChannelManager::CreateVoiceChannel_w + ... +} +{ + bug_2079_6 + ThreadSanitizer:Race + fun:rtc::Thread::ReceiveSends + fun:rtc::Thread::Send + fun:rtc::Thread::Invoke + ... +} +{ + bug_2079_8 + ThreadSanitizer:Race + fun:rtc::Thread::Invoke + fun:cricket::ChannelManager::SetCaptureDevice + ... +} +{ + bug_2079_9 + ThreadSanitizer:Race + fun:rtc::Thread::Send + fun:cricket::Transport::SetIceRole + fun:cricket::BaseSession::GetOrCreateTransportProxy + fun:cricket::BaseSession::CreateChannel + fun:cricket::FakeSession::CreateChannel + fun:cricket::VoiceChannel::Init + fun:cricket::ChannelManager::CreateVoiceChannel_w + ... +} +{ + bug_2080_1 + ThreadSanitizer:Race + fun:rtc::MessageQueue::Quit + fun:rtc::SignalThread::Destroy + ... +} +{ + bug_2080_2 + ThreadSanitizer:Race + fun:rtc::MessageQueue::Quit + fun:rtc::AsyncHttpRequest::OnComplete + fun:sigslot::_connection2::emit + ... +} +{ + bug_2080_3 + ThreadSanitizer:Race + fun:rtc::LogMessage::UpdateMinLogSeverity + fun:rtc::LogMessage::AddLogToStream + ... +} +{ + bug_2080_8 + ThreadSanitizer:Race + ... + fun:rtc::AsyncUDPSocket::OnReadEvent + fun:sigslot::_connection1::emit + ... +} +{ + bug_2080_9 + ThreadSanitizer:Race + ... + fun:rtc::AsyncUDPSocket::OnWriteEvent + fun:sigslot::_connection1::emit + ... +} +{ + bug_2080_14 + ThreadSanitizer:Race + fun:rtc::IPAddress::IPAddress + fun:rtc::SocketAddress::ToSockAddrStorage + fun:rtc::PhysicalSocket::SendTo + fun:rtc::AsyncUDPSocket::SendTo + fun:rtc::NATServer::OnExternalPacket + ... +} +{ + bug_2080_16 + ThreadSanitizer:Race + ... + fun:rtc::IPAddress::operator< + ... +} +{ + bug_2080_18 + ThreadSanitizer:Race + fun:rtc::SocketAddress::port + ... +} +{ + bug_2080_19 + ThreadSanitizer:Race + fun:std::vector::_M_insert_aux + fun:std::vector::push_back + fun:rtc::TestClient::OnPacket + ... +} +{ + bug_2080_20 + ThreadSanitizer:Race + fun:rtc::VirtualSocketServer::AddPacketToNetwork + fun:rtc::VirtualSocketServer::SendUdp + fun:rtc::VirtualSocket::SendUdp + fun:rtc::VirtualSocket::SendTo + fun:rtc::AsyncUDPSocket::SendTo + fun:rtc::TestClient::SendTo + ... +} +{ + bug_2080_21 + ThreadSanitizer:Race + fun:rtc::SharedExclusiveTask::waiting_time_in_ms + ... +} +{ + bug_2080_22 + ThreadSanitizer:Race + fun:rtc::SharedExclusiveTask::~SharedExclusiveTask + fun:rtc::ReadTask::~ReadTask + ... +} +{ + bug_2080_24 + ThreadSanitizer:Race + ... + fun:OwnerThread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2080_25 + ThreadSanitizer:Race + fun:sigslot::has_slots::~has_slots + fun:OwnerThread::~OwnerThread + fun:OwnerThread::~OwnerThread + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2080_27 + ThreadSanitizer:Race + ... + fun:std::_Rb_tree::clear + fun:std::_Rb_tree::_M_erase_aux + fun:std::_Rb_tree::erase + fun:std::set::erase + fun:sigslot::has_slots::disconnect_all + fun:sigslot::has_slots::~has_slots + fun:OwnerThread::~OwnerThread + fun:OwnerThread::~OwnerThread + fun:rtc::scoped_ptr::~scoped_ptr + ... +} +{ + bug_2080_29 + ThreadSanitizer:Race + fun:rtc::Thread::Release + fun:ThreadTest_Release_Test::TestBody + ... +} +{ + bug_2080_30 + ThreadSanitizer:Race + fun:rtc::Thread::Invoke + fun:MessageQueueTest::IsLocked + fun:DeletedLockChecker::~DeletedLockChecker + ... +} +{ + bug_2080_31 + ThreadSanitizer:Race + fun:rtc::MessageHandler::~MessageHandler + fun:rtc::Thread::FunctorMessageHandler::~FunctorMessageHandler + ... +} +{ + bug_2080_32 + ThreadSanitizer:Race + fun:rtc::ReadTask::OnMessage + fun:rtc::MessageQueue::Dispatch + ... +} +{ + bug_2080_33 + ThreadSanitizer:Race + fun:rtc::SharedExclusiveLockTest_TestSharedShared_Test::TestBody + fun:testing::internal::HandleSehExceptionsInMethodIfSupported +} +{ + bug_2080_34 + ThreadSanitizer:Race + fun:rtc::WriteTask::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2080_35 + ThreadSanitizer:UnlockNonLocked + fun:pthread_mutex_unlock + fun:rtc::CriticalSection::Leave + fun:rtc::CritScope::~CritScope + ... + fun:rtc::AsyncFunctorMessageHandler::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2080_36 + ThreadSanitizer:Race + ... + fun:rtc::FunctorMessageHandler::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2931_1 + ThreadSanitizer:Race + ... + fun:rtc::FireAndForgetAsyncClosure::Execute + fun:rtc::AsyncInvoker::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:rtc::Thread::Run + fun:rtc::Thread::PreRun +} +{ + bug_2931_2 + ThreadSanitizer:Race + ... + fun:rtc::Callback0::HelperImpl::~HelperImpl + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::Release + fun:rtc::scoped_refptr::~scoped_refptr + fun:rtc::Callback0::~Callback0 + fun:rtc::FireAndForgetAsyncClosure::~FireAndForgetAsyncClosure + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::~RefCountedObject + fun:rtc::RefCountedObject::Release + fun:rtc::scoped_refptr::~scoped_refptr + fun:rtc::AsyncInvoker::OnMessage + fun:rtc::MessageQueue::Dispatch + fun:rtc::Thread::ProcessMessages + fun:AsyncInvokeTest_WithCallback_Test::TestBody + ... +} { bug_300 ThreadSanitizer:Race @@ -75,6 +542,13 @@ fun:webrtc::VideoEngine::SetTraceFilter ... } +{ + Benign TRACE_EVENT race (webrtc:3409) + ThreadSanitizer:Race + fun:webrtc::RTCPReceiver::HandleSenderReceiverReport + fun:webrtc::RTCPReceiver::IncomingRTCPPacket + ... +} #----------------------------------------------------------------------- # 2. libjingle stuff (talk folder) @@ -214,7 +688,7 @@ ... } { - bug_1205_33 + bug_1205_33_tmp ThreadSanitizer:Race fun:webrtc::PeerConnectionProxy::~PeerConnectionProxy fun:talk_base::RefCountedObject::~RefCountedObject @@ -224,7 +698,7 @@ ... } { - bug_1205_34 + bug_1205_34_tmp ThreadSanitizer:Race fun:talk_base::AtomicOps::Increment fun:talk_base::RefCountedObject::AddRef @@ -234,7 +708,7 @@ ... } { - bug_1205_35 + bug_1205_35_tmp ThreadSanitizer:Race fun:talk_base::scoped_refptr::scoped_refptr fun:webrtc::PeerConnectionFactory::CreatePeerConnection @@ -242,7 +716,7 @@ ... } { - bug_1205_36 + bug_1205_36_tmp ThreadSanitizer:Race fun:talk_base::MessageHandler::~MessageHandler fun:FakeAudioCaptureModule::~FakeAudioCaptureModule @@ -268,7 +742,7 @@ ... } { - bug_1205_39 + bug_1205_39_tmp ThreadSanitizer:Race fun:talk_base::Thread::Send fun:FakeAudioCaptureModule::UpdateProcessing @@ -285,7 +759,7 @@ ... } { - bug_1205_41 + bug_1205_41_tmp ThreadSanitizer:Race fun:talk_base::Thread::Send fun:FakeAudioCaptureModule::~FakeAudioCaptureModule @@ -315,7 +789,7 @@ ... } { - bug_2078_2 + bug_2078_2_tmp ThreadSanitizer:Race ... fun:talk_base::MemoryStreamBase::Read @@ -324,7 +798,7 @@ ... } { - bug_2078_3 + bug_2078_3_tmp ThreadSanitizer:Race fun:cricket::RtpSenderReceiver::OnMessage fun:talk_base::MessageQueue::Dispatch @@ -333,7 +807,7 @@ fun:talk_base::Thread::PreRun } { - bug_2078_4 + bug_2078_4_tmp ThreadSanitizer:Race fun:cricket::FileNetworkInterface::SendPacket fun:cricket::RtpSenderReceiver::SendRtpPacket @@ -358,14 +832,14 @@ ... } { - bug_2078_7 + bug_2078_7_tmp ThreadSanitizer:Race fun:talk_base::MemoryStreamBase::~MemoryStreamBase fun:talk_base::MemoryStream::~MemoryStream ... } { - bug_2078_8 + bug_2078_8_tmp ThreadSanitizer:Race fun:talk_base::MemoryStreamBase::SetPosition fun:talk_base::StreamInterface::Rewind @@ -373,7 +847,7 @@ ... } { - bug_2078_13 + bug_2078_13_tmp ThreadSanitizer:Race fun:std::_Vector_base::~_Vector_base fun:std::vector::~vector @@ -384,7 +858,7 @@ ... } { - bug_2078_14 + bug_2078_14_tmp ThreadSanitizer:Race fun:cricket::RtpDumpReader::~RtpDumpReader fun:cricket::RtpDumpLoopReader::~RtpDumpLoopReader @@ -396,7 +870,7 @@ ... } { - bug_2078_15 + bug_2078_15_tmp ThreadSanitizer:Race fun:talk_base::FileStream::Close fun:talk_base::FileStream::~FileStream @@ -408,7 +882,7 @@ ... } { - bug_2078_16 + bug_2078_16_tmp ThreadSanitizer:Race fun:talk_base::StreamInterface::~StreamInterface fun:talk_base::FileStream::~FileStream @@ -420,7 +894,7 @@ ... } { - bug_2078_18 + bug_2078_18_tmp ThreadSanitizer:Race fun:talk_base::MemoryStream::~MemoryStream ... @@ -432,14 +906,14 @@ ... } { - bug_2078_22 + bug_2078_22_tmp ThreadSanitizer:Race fun:talk_base::MessageQueue::Quit fun:talk_base::Thread::Stop ... } { - bug_2078_23 + bug_2078_23_tmp ThreadSanitizer:Race fun:::FileVideoCapturerTest::VideoCapturerListener::OnFrameCaptured fun:sigslot::_connection2::emit @@ -453,14 +927,14 @@ fun:talk_base::Thread::PreRun } { - bug_2078_24 + bug_2078_24_tmp ThreadSanitizer:Race fun:talk_base::MemoryStreamBase::SetPosition fun:talk_base::StreamInterface::Rewind ... } { - bug_2078_25 + bug_2078_25_tmp ThreadSanitizer:Race fun:cricket::FileNetworkInterface::SendPacket fun:cricket::MediaChannel::DoSendPacket @@ -473,7 +947,7 @@ fun:talk_base::Thread::PreRun } { - bug_2079_1 + bug_2079_1_tmp ThreadSanitizer:Race fun:talk_base::VirtualSocketServer::AddPacketToNetwork fun:talk_base::VirtualSocketServer::SendUdp @@ -493,7 +967,7 @@ ... } { - bug_2079_5 + bug_2079_5_tmp ThreadSanitizer:Race fun:talk_base::Thread::Send fun:cricket::Transport::SetRole @@ -505,7 +979,7 @@ ... } { - bug_2079_6 + bug_2079_6_tmp ThreadSanitizer:Race fun:talk_base::Thread::ReceiveSends fun:talk_base::Thread::Send @@ -519,14 +993,14 @@ ... } { - bug_2079_8 + bug_2079_8_tmp ThreadSanitizer:Race fun:talk_base::Thread::Invoke fun:cricket::ChannelManager::SetCaptureDevice ... } { - bug_2079_9 + bug_2079_9_tmp ThreadSanitizer:Race fun:talk_base::Thread::Send fun:cricket::Transport::SetIceRole @@ -538,14 +1012,14 @@ ... } { - bug_2080_1 + bug_2080_1_tmp ThreadSanitizer:Race fun:talk_base::MessageQueue::Quit fun:talk_base::SignalThread::Destroy ... } { - bug_2080_2 + bug_2080_2_tmp ThreadSanitizer:Race fun:talk_base::MessageQueue::Quit fun:talk_base::AsyncHttpRequest::OnComplete @@ -553,14 +1027,14 @@ ... } { - bug_2080_3 + bug_2080_3_tmp ThreadSanitizer:Race fun:talk_base::LogMessage::UpdateMinLogSeverity fun:talk_base::LogMessage::AddLogToStream ... } { - bug_2080_8 + bug_2080_8_tmp ThreadSanitizer:Race ... fun:talk_base::AsyncUDPSocket::OnReadEvent @@ -568,7 +1042,7 @@ ... } { - bug_2080_9 + bug_2080_9_tmp ThreadSanitizer:Race ... fun:talk_base::AsyncUDPSocket::OnWriteEvent @@ -582,7 +1056,7 @@ ... } { - bug_2080_14 + bug_2080_14_tmp ThreadSanitizer:Race fun:talk_base::IPAddress::IPAddress fun:talk_base::SocketAddress::ToSockAddrStorage @@ -592,20 +1066,20 @@ ... } { - bug_2080_16 + bug_2080_16_tmp ThreadSanitizer:Race ... fun:talk_base::IPAddress::operator< ... } { - bug_2080_18 + bug_2080_18_tmp ThreadSanitizer:Race fun:talk_base::SocketAddress::port ... } { - bug_2080_19 + bug_2080_19_tmp ThreadSanitizer:Race fun:std::vector::_M_insert_aux fun:std::vector::push_back @@ -613,7 +1087,7 @@ ... } { - bug_2080_20 + bug_2080_20_tmp ThreadSanitizer:Race fun:talk_base::VirtualSocketServer::AddPacketToNetwork fun:talk_base::VirtualSocketServer::SendUdp @@ -624,13 +1098,13 @@ ... } { - bug_2080_21 + bug_2080_21_tmp ThreadSanitizer:Race fun:talk_base::SharedExclusiveTask::waiting_time_in_ms ... } { - bug_2080_22 + bug_2080_22_tmp ThreadSanitizer:Race fun:talk_base::SharedExclusiveTask::~SharedExclusiveTask fun:talk_base::ReadTask::~ReadTask @@ -644,14 +1118,14 @@ ... } { - bug_2080_24 + bug_2080_24_tmp ThreadSanitizer:Race ... fun:OwnerThread::Run fun:talk_base::Thread::PreRun } { - bug_2080_25 + bug_2080_25_tmp ThreadSanitizer:Race fun:sigslot::has_slots::~has_slots fun:OwnerThread::~OwnerThread @@ -669,7 +1143,7 @@ ... } { - bug_2080_27 + bug_2080_27_tmp ThreadSanitizer:Race ... fun:std::_Rb_tree::clear @@ -684,14 +1158,14 @@ ... } { - bug_2080_29 + bug_2080_29_tmp ThreadSanitizer:Race fun:talk_base::Thread::Release fun:ThreadTest_Release_Test::TestBody ... } { - bug_2080_30 + bug_2080_30_tmp ThreadSanitizer:Race fun:talk_base::Thread::Invoke fun:MessageQueueTest::IsLocked @@ -699,27 +1173,27 @@ ... } { - bug_2080_31 + bug_2080_31_tmp ThreadSanitizer:Race fun:talk_base::MessageHandler::~MessageHandler fun:talk_base::Thread::FunctorMessageHandler::~FunctorMessageHandler ... } { - bug_2080_32 + bug_2080_32_tmp ThreadSanitizer:Race fun:talk_base::ReadTask::OnMessage fun:talk_base::MessageQueue::Dispatch ... } { - bug_2080_33 + bug_2080_33_tmp ThreadSanitizer:Race fun:talk_base::SharedExclusiveLockTest_TestSharedShared_Test::TestBody fun:testing::internal::HandleSehExceptionsInMethodIfSupported } { - bug_2080_34 + bug_2080_34_tmp ThreadSanitizer:Race fun:talk_base::WriteTask::OnMessage fun:talk_base::MessageQueue::Dispatch @@ -728,7 +1202,7 @@ fun:talk_base::Thread::PreRun } { - bug_2080_35 + bug_2080_35_tmp ThreadSanitizer:UnlockNonLocked fun:pthread_mutex_unlock fun:talk_base::CriticalSection::Leave @@ -741,7 +1215,7 @@ fun:talk_base::Thread::PreRun } { - bug_2080_36 + bug_2080_36_tmp ThreadSanitizer:Race ... fun:talk_base::FunctorMessageHandler::OnMessage @@ -751,7 +1225,7 @@ fun:talk_base::Thread::PreRun } { - bug_2931_1 + bug_2931_1_tmp ThreadSanitizer:Race ... fun:talk_base::FireAndForgetAsyncClosure::Execute @@ -762,7 +1236,7 @@ fun:talk_base::Thread::PreRun } { - bug_2931_2 + bug_2931_2_tmp ThreadSanitizer:Race ... fun:talk_base::Callback0::HelperImpl::~HelperImpl @@ -782,3 +1256,21 @@ fun:AsyncInvokeTest_WithCallback_Test::TestBody ... } +{ + bug_3372_1 + ThreadSanitizer:Race + fun:talk_base::LogMessage::Loggable + fun:cricket::WebRtcVideoEngine::Print + fun:webrtc::TraceImpl::WriteToFile + fun:webrtc::TraceImpl::Process + fun:webrtc::TraceImpl::Run + fun:webrtc::ThreadPosix::Run + fun:StartThread +} +{ + bug_3372_2 + ThreadSanitizer:Race + fun:std::string::find + fun:WebRtcVideoEngineTest_WebRtcShouldNotLog_Test::TestBody + fun:testing::internal::HandleSehExceptionsInMethodIfSupported +} diff --git a/tools/valgrind-webrtc/tsan_v2/suppressions.txt b/tools/valgrind-webrtc/tsan_v2/suppressions.txt index aa28bfaff..e783cd301 100644 --- a/tools/valgrind-webrtc/tsan_v2/suppressions.txt +++ b/tools/valgrind-webrtc/tsan_v2/suppressions.txt @@ -13,6 +13,9 @@ race:webrtc/modules/audio_processing/aec/aec_rdft.c # libjingle_p2p_unittest # See https://code.google.com/p/webrtc/issues/detail?id=2079 +race:webrtc/base/messagequeue.cc +race:webrtc/base/testclient.cc +race:webrtc/base/virtualsocketserver.cc race:talk/base/messagequeue.cc race:talk/base/testclient.cc race:talk/base/virtualsocketserver.cc @@ -20,7 +23,24 @@ race:talk/p2p/base/stunserver_unittest.cc # libjingle_unittest # See https://code.google.com/p/webrtc/issues/detail?id=2080 +race:webrtc/base/logging.cc +race:webrtc/base/sharedexclusivelock_unittest.cc +race:webrtc/base/signalthread_unittest.cc +race:webrtc/base/thread.cc race:talk/base/logging.cc race:talk/base/sharedexclusivelock_unittest.cc race:talk/base/signalthread_unittest.cc race:talk/base/thread.cc + +# third_party/usrsctp +# TODO(jiayl): https://code.google.com/p/webrtc/issues/detail?id=3492 +race:third_party/usrsctp/usrsctplib/user_sctp_timer_iterate.c + +# Potential deadlocks detected after roll in r6516. +# https://code.google.com/p/webrtc/issues/detail?id=3509 +deadlock:talk/base/criticalsection.h +deadlock:talk/base/sigslot.h +deadlock:webrtc/system_wrappers/source/critical_section_posix.cc +deadlock:webrtc/system_wrappers/source/rw_lock_posix.cc +deadlock:webrtc/system_wrappers/source/thread_posix.cc + diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn new file mode 100644 index 000000000..1e8c3289c --- /dev/null +++ b/webrtc/BUILD.gn @@ -0,0 +1,209 @@ +# Copyright (c) 2014 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. + +import("//build/config/arm.gni") +import("//build/config/crypto.gni") +import("//build/config/linux/pkg_config.gni") +import("build/webrtc.gni") + +# Contains the defines and includes in common.gypi that are duplicated both as +# target_defaults and direct_dependent_settings. +config("common_inherited_config") { + defines = [] + if (build_with_mozilla) { + defines += [ "WEBRTC_MOZILLA_BUILD" ] + } + if (build_with_chromium) { + defines = [ + "WEBRTC_CHROMIUM_BUILD", + "LOGGING_INSIDE_WEBRTC", + ] + include_dirs = [ + # overrides must be included first as that is the mechanism for + # selecting the override headers in Chromium. + "overrides", + # Allow includes to be prefixed with webrtc/ in case it is not an + # immediate subdirectory of the top-level. + "..", + ] + } + if (is_posix) { + defines += [ "WEBRTC_POSIX" ] + } + if (is_ios) { + defines += [ + "WEBRTC_MAC", + "WEBRTC_IOS", + ] + } + if (is_linux) { + defines += [ "WEBRTC_LINUX" ] + } + if (is_mac) { + defines += [ "WEBRTC_MAC" ] + } + if (is_win) { + defines += [ "WEBRTC_WIN" ] + } + if (is_android) { + defines += [ + "WEBRTC_LINUX", + "WEBRTC_ANDROID", + ] + if (enable_android_opensl) { + defines += [ "WEBRTC_ANDROID_OPENSLES" ] + } + } +} + +pkg_config("dbus-glib") { + packages = [ "dbus-glib-1" ] +} + +config("common_config") { + if (restrict_webrtc_logging) { + defines = [ "WEBRTC_RESTRICT_LOGGING" ] + } + + if (have_dbus_glib) { + defines += [ "HAVE_DBUS_GLIB" ] + # TODO(kjellander): Investigate this, it seems like include + # is still not found even if the execution of + # build/config/linux/pkg-config.py dbus-glib-1 returns correct include + # dirs on Linux. + all_dependent_configs = [ "dbus-glib" ] + } + + if (enable_video) { + defines += [ "WEBRTC_MODULE_UTILITY_VIDEO" ] + } + + if (!build_with_chromium) { + if (is_posix) { + # -Wextra is currently disabled in Chromium"s common.gypi. Enable + # for targets that can handle it. For Android/arm64 right now + # there will be an "enumeral and non-enumeral type in conditional + # expression" warning in android_tools/ndk_experimental"s version + # of stlport. + # See: https://code.google.com/p/chromium/issues/detail?id=379699 + if (cpu_arch != "arm64" || !is_android) { + cflags = [ + "-Wextra", + # We need to repeat some flags from Chromium"s common.gypi + # here that get overridden by -Wextra. + "-Wno-unused-parameter", + "-Wno-missing-field-initializers", + "-Wno-strict-overflow", + ] + cflags_cc = [ + "-Wnon-virtual-dtor", + # This is enabled for clang; enable for gcc as well. + "-Woverloaded-virtual", + ] + } + } + + if (is_clang) { + cflags += [ "-Wthread-safety" ] + } + } + + if (cpu_arch == "arm") { + defines += [ "WEBRTC_ARCH_ARM" ] + if (arm_version == 7) { + defines += [ "WEBRTC_ARCH_ARM_V7" ] + if (arm_use_neon) { + defines += [ "WEBRTC_ARCH_ARM_NEON" ] + } else { + defines += [ "WEBRTC_DETECT_ARM_NEON" ] + } + } + } + + if (cpu_arch == "mipsel") { + defines += [ "MIPS32_LE" ] + if (mips_fpu) { + defines += [ "MIPS_FPU_LE" ] + cflags += [ "-mhard-float" ] + } else { + cflags += [ "-msoft-float" ] + } + if (mips_arch_variant == "mips32r2") { + defines += [ "MIPS32_R2_LE" ] + cflags += [ "-mips32r2" ] + cflags_cc += [ "-mips32r2" ] + } + if (mips_dsp_rev == 1) { + defines += [ "MIPS_DSP_R1_LE" ] + cflags += [ "-mdsp" ] + cflags_cc += [ "-mdsp" ] + } else if (mips_dsp_rev == 2) { + defines += [ + "MIPS_DSP_R1_LE", + "MIPS_DSP_R2_LE", + ] + cflags += [ "-mdspr2" ] + cflags_cc += [ "-mdspr2" ] + } + } + + # TODO(kjellander): Handle warnings on Windows where WebRTC differ from the + # default warnings set in build/config/compiler/BUILD.gn. + + if (is_android && is_clang) { + # The Android NDK doesn"t provide optimized versions of these + # functions. Ensure they are disabled for all compilers. + cflags += [ + "-fno-builtin-cos", + "-fno-builtin-sin", + "-fno-builtin-cosf", + "-fno-builtin-sinf", + ] + } +} + +static_library("webrtc") { + sources = [ + "call.h", + "config.h", + "experiments.h", + "frame_callback.h", + "transport.h", + ] + + deps = [ + ":webrtc_common", + "base:webrtc_base", + "common_audio", + "common_video", + "modules/audio_coding", + "modules/audio_conference_mixer", + "modules/audio_device", + "modules/audio_processing", + "modules/bitrate_controller", + "modules/desktop_capture", + "modules/media_file", + "modules/rtp_rtcp", + "modules/utility", + "modules/video_capture", + "modules/video_coding", + "modules/video_processing", + "modules/video_render", + "system_wrappers", + "video", + "video_engine", + "voice_engine", + ] +} + +source_set("webrtc_common") { + sources = [ + "config.h", + "config.cc", + ] +} diff --git a/webrtc/OWNERS b/webrtc/OWNERS new file mode 100644 index 000000000..b806e212c --- /dev/null +++ b/webrtc/OWNERS @@ -0,0 +1,8 @@ +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/PRESUBMIT.py b/webrtc/PRESUBMIT.py index 4d4a3d50a..4132c1687 100644 --- a/webrtc/PRESUBMIT.py +++ b/webrtc/PRESUBMIT.py @@ -8,13 +8,13 @@ def _LicenseHeader(input_api): """Returns the license header regexp.""" - # Accept any year number from 2011 to the current year + # Accept any year number from 2003 to the current year current_year = int(input_api.time.strftime('%Y')) - allowed_years = (str(s) for s in reversed(xrange(2011, current_year + 1))) + allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1))) years_re = '(' + '|'.join(allowed_years) + ')' license_header = ( - r'.*? Copyright \(c\) %(year)s The WebRTC project authors\. ' - r'All Rights Reserved\.\n' + r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. ' + r'All [Rr]ights [Rr]eserved\.\n' r'.*?\n' r'.*? Use of this source code is governed by a BSD-style license\n' r'.*? that can be found in the LICENSE file in the root of the source\n' diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn new file mode 100644 index 000000000..f8fb0bd4f --- /dev/null +++ b/webrtc/base/BUILD.gn @@ -0,0 +1,720 @@ +# Copyright (c) 2014 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. + +import("//build/config/crypto.gni") +import("../build/webrtc.gni") + +config("webrtc_base_config") { + include_dirs = [ + "//third_party/jsoncpp/overrides/include", + "//third_party/jsoncpp/source/include", + ] + + defines = [ + "FEATURE_ENABLE_SSL", + "GTEST_RELATIVE_PATH", + ] + + # TODO(henrike): issue 3307, make webrtc_base build without disabling + # these flags. + cflags_cc = [ "-Wno-non-virtual-dtor" ] +} + +config("webrtc_base_chromium_config") { + defines = [ + "NO_MAIN_THREAD_WRAPPING", + "SSL_USE_NSS", + ] +} + +config("openssl_config") { + defines = [ + "SSL_USE_OPENSSL", + "HAVE_OPENSSL_SSL_H", + ] +} + +config("no_openssl_config") { + defines = [ + "SSL_USE_NSS", + "HAVE_NSS_SSL_H", + "SSL_USE_NSS_RNG", + ] +} + +config("android_config") { + defines = [ "HAVE_OPENSSL_SSL_H" ] +} + +config("no_android_config") { + defines = [ + "HAVE_NSS_SSL_H", + "SSL_USE_NSS_RNG", + ] +} + +config("ios_config") { + ldflags = [ + #"Foundation.framework", # Already included in //build/config:default_libs. + "Security.framework", + "SystemConfiguration.framework", + #"UIKit.framework", # Already included in //build/config:default_libs. + ] +} + +config("mac_config") { + ldflags = [ + "Cocoa.framework", + #"Foundation.framework", # Already included in //build/config:default_libs. + #"IOKit.framework", # Already included in //build/config:default_libs. + #"Security.framework", # Already included in //build/config:default_libs. + "SystemConfiguration.framework", + ] +} + +config("mac_x86_config") { + libs = [ + #"Carbon.framework", # Already included in //build/config:default_libs. + ] +} + +config("linux_system_ssl_config") { + visibility = ":*" # Only targets in this file can depend on this. + + # TODO(kjellander): Find out how to convert GYP include_dirs+ (i.e. insert + # first in the include path?). + include_dirs = [ "//net/third_party/nss/ssl" ] + + configs = [ "//third_party/nss:system_nss_no_ssl_config" ] +} + +# Provides the same functionality as the build/linux/system.gyp:ssl GYP target. +# This cannot be in build/linux/BUILD.gn since targets in build/ are not allowed +# to depend on targets outside of it. This could be replaced by the Chromium +# //crypto:platform target, but as WebRTC currently don't sync src/crypto from +# Chromium, it is not possible today. +config("linux_system_ssl") { + if (use_openssl) { + deps = [ "//third_party/openssl" ] + } else { + deps = [ "//net/third_party/nss/ssl:libssl" ] + + direct_dependent_configs = [ + ":linux_system_ssl_config", + ] + + if (is_clang) { + cflags = [ + # There is a broken header guard in /usr/include/nss/secmod.h: + # https://bugzilla.mozilla.org/show_bug.cgi?id=884072 + "-Wno-header-guard", + ] + } + } +} + +static_library("webrtc_base") { + cflags = [] + cflags_cc = [] + + configs += [ + "..:common_config", + ":webrtc_base_config", + ] + + direct_dependent_configs = [ + "..:common_inherited_config", + ":webrtc_base_config", + ] + + defines = [ + "LOGGING=1", + "USE_WEBRTC_DEV_BRANCH", + ] + + sources = [ + "asyncfile.cc", + "asyncfile.h", + "asynchttprequest.cc", + "asynchttprequest.h", + "asyncinvoker.cc", + "asyncinvoker.h", + "asyncinvoker-inl.h", + "asyncpacketsocket.h", + "asyncresolverinterface.h", + "asyncsocket.cc", + "asyncsocket.h", + "asynctcpsocket.cc", + "asynctcpsocket.h", + "asyncudpsocket.cc", + "asyncudpsocket.h", + "atomicops.h", + "autodetectproxy.cc", + "autodetectproxy.h", + "bandwidthsmoother.cc", + "bandwidthsmoother.h", + "base64.cc", + "base64.h", + "basicdefs.h", + "basictypes.h", + "bind.h", + "bind.h.pump", + "buffer.h", + "bytebuffer.cc", + "bytebuffer.h", + "byteorder.h", + "callback.h", + "callback.h.pump", + "checks.cc", + "checks.h", + "common.cc", + "common.h", + "constructormagic.h", + "cpumonitor.cc", + "cpumonitor.h", + "crc32.cc", + "crc32.h", + "criticalsection.h", + "cryptstring.h", + "dbus.cc", + "dbus.h", + "diskcache.cc", + "diskcache.h", + "event.cc", + "event.h", + "filelock.cc", + "filelock.h", + "fileutils.cc", + "fileutils.h", + "fileutils_mock.h", + "firewallsocketserver.cc", + "firewallsocketserver.h", + "flags.cc", + "flags.h", + "genericslot.h", + "genericslot.h.pump", + "gunit_prod.h", + "helpers.cc", + "helpers.h", + "httpbase.cc", + "httpbase.h", + "httpclient.cc", + "httpclient.h", + "httpcommon-inl.h", + "httpcommon.cc", + "httpcommon.h", + "httprequest.cc", + "httprequest.h", + "httpserver.cc", + "httpserver.h", + "ifaddrs-android.cc", + "ifaddrs-android.h", + "iosfilesystem.mm", + "ipaddress.cc", + "ipaddress.h", + "json.cc", + "json.h", + "latebindingsymboltable.cc", + "latebindingsymboltable.cc.def", + "latebindingsymboltable.h", + "latebindingsymboltable.h.def", + "libdbusglibsymboltable.cc", + "libdbusglibsymboltable.h", + "linux.cc", + "linux.h", + "linuxfdwalk.c", + "linuxfdwalk.h", + "linuxwindowpicker.cc", + "linuxwindowpicker.h", + "linked_ptr.h", + "logging.cc", + "logging.h", + "macasyncsocket.cc", + "macasyncsocket.h", + "maccocoasocketserver.h", + "maccocoasocketserver.mm", + "maccocoathreadhelper.h", + "maccocoathreadhelper.mm", + "macconversion.cc", + "macconversion.h", + "macsocketserver.cc", + "macsocketserver.h", + "macutils.cc", + "macutils.h", + "macwindowpicker.cc", + "macwindowpicker.h", + "mathutils.h", + "md5.cc", + "md5.h", + "md5digest.h", + "messagedigest.cc", + "messagedigest.h", + "messagehandler.cc", + "messagehandler.h", + "messagequeue.cc", + "messagequeue.h", + "multipart.cc", + "multipart.h", + "natserver.cc", + "natserver.h", + "natsocketfactory.cc", + "natsocketfactory.h", + "nattypes.cc", + "nattypes.h", + "nethelpers.cc", + "nethelpers.h", + "network.cc", + "network.h", + "nssidentity.cc", + "nssidentity.h", + "nssstreamadapter.cc", + "nssstreamadapter.h", + "nullsocketserver.h", + "openssl.h", + "openssladapter.cc", + "openssladapter.h", + "openssldigest.cc", + "openssldigest.h", + "opensslidentity.cc", + "opensslidentity.h", + "opensslstreamadapter.cc", + "opensslstreamadapter.h", + "optionsfile.cc", + "optionsfile.h", + "pathutils.cc", + "pathutils.h", + "physicalsocketserver.cc", + "physicalsocketserver.h", + "posix.cc", + "posix.h", + "profiler.cc", + "profiler.h", + "proxydetect.cc", + "proxydetect.h", + "proxyinfo.cc", + "proxyinfo.h", + "proxyserver.cc", + "proxyserver.h", + "ratelimiter.cc", + "ratelimiter.h", + "ratetracker.cc", + "ratetracker.h", + "refcount.h", + "referencecountedsingletonfactory.h", + "rollingaccumulator.h", + "schanneladapter.cc", + "schanneladapter.h", + "scoped_autorelease_pool.h", + "scoped_autorelease_pool.mm", + "scoped_ptr.h", + "scoped_ref_ptr.h", + "scopedptrcollection.h", + "sec_buffer.h", + "sha1.cc", + "sha1.h", + "sha1digest.h", + "sharedexclusivelock.cc", + "sharedexclusivelock.h", + "signalthread.cc", + "signalthread.h", + "sigslot.h", + "sigslotrepeater.h", + "socket.h", + "socketadapters.cc", + "socketadapters.h", + "socketaddress.cc", + "socketaddress.h", + "socketaddresspair.cc", + "socketaddresspair.h", + "socketfactory.h", + "socketpool.cc", + "socketpool.h", + "socketserver.h", + "socketstream.cc", + "socketstream.h", + "ssladapter.cc", + "ssladapter.h", + "sslconfig.h", + "sslfingerprint.cc", + "sslfingerprint.h", + "sslidentity.cc", + "sslidentity.h", + "sslroots.h", + "sslsocketfactory.cc", + "sslsocketfactory.h", + "sslstreamadapter.cc", + "sslstreamadapter.h", + "sslstreamadapterhelper.cc", + "sslstreamadapterhelper.h", + "stream.cc", + "stream.h", + "stringdigest.h", + "stringencode.cc", + "stringencode.h", + "stringutils.cc", + "stringutils.h", + "systeminfo.cc", + "systeminfo.h", + "task.cc", + "task.h", + "taskparent.cc", + "taskparent.h", + "taskrunner.cc", + "taskrunner.h", + "testclient.cc", + "testclient.h", + "thread.cc", + "thread.h", + "timeutils.cc", + "timeutils.h", + "timing.cc", + "timing.h", + "transformadapter.cc", + "transformadapter.h", + "unixfilesystem.cc", + "unixfilesystem.h", + "urlencode.cc", + "urlencode.h", + "versionparsing.cc", + "versionparsing.h", + "virtualsocketserver.cc", + "virtualsocketserver.h", + "win32.cc", + "win32.h", + "win32filesystem.cc", + "win32filesystem.h", + "win32regkey.cc", + "win32regkey.h", + "win32securityerrors.cc", + "win32socketinit.cc", + "win32socketinit.h", + "win32socketserver.cc", + "win32socketserver.h", + "win32window.cc", + "win32window.h", + "win32windowpicker.cc", + "win32windowpicker.h", + "window.h", + "windowpicker.h", + "windowpickerfactory.h", + "winfirewall.cc", + "winfirewall.h", + "winping.cc", + "winping.h", + "worker.cc", + "worker.h", + ] + + if (build_with_chromium) { + sources += [ + "../overrides/webrtc/base/basictypes.h", + "../overrides/webrtc/base/constructormagic.h", + "../overrides/webrtc/base/logging.cc", + "../overrides/webrtc/base/logging.h", + ] + if (is_win) { + sources += [ "../overrides/webrtc/base/win32socketinit.cc" ] + } + sources -= [ + "asyncinvoker.cc", + "asyncinvoker.h", + "asyncinvoker-inl.h", + "asyncresolverinterface.h", + "atomicops.h", + "bandwidthsmoother.cc", + "bandwidthsmoother.h", + "basictypes.h", + "bind.h", + "bind.h.pump", + "buffer.h", + "callback.h", + "callback.h.pump", + "constructormagic.h", + "dbus.cc", + "dbus.h", + "filelock.cc", + "filelock.h", + "fileutils_mock.h", + "genericslot.h", + "genericslot.h.pump", + "httpserver.cc", + "httpserver.h", + "json.cc", + "json.h", + "latebindingsymboltable.cc", + "latebindingsymboltable.cc.def", + "latebindingsymboltable.h", + "latebindingsymboltable.h.def", + "libdbusglibsymboltable.cc", + "libdbusglibsymboltable.h", + "linuxfdwalk.c", + "linuxfdwalk.h", + "linuxwindowpicker.cc", + "linuxwindowpicker.h", + "logging.cc", + "logging.h", + #"macasyncsocket.cc", + #"macasyncsocket.h", + #"maccocoasocketserver.h", + #"maccocoasocketserver.mm", + #"macsocketserver.cc", + #"macsocketserver.h", + #"macwindowpicker.cc", + #"macwindowpicker.h", + "mathutils.h", + "multipart.cc", + "multipart.h", + "natserver.cc", + "natserver.h", + "natsocketfactory.cc", + "natsocketfactory.h", + "nattypes.cc", + "nattypes.h", + "openssl.h", + "optionsfile.cc", + "optionsfile.h", + "posix.cc", + "posix.h", + "profiler.cc", + "profiler.h", + "proxyserver.cc", + "proxyserver.h", + "refcount.h", + "referencecountedsingletonfactory.h", + "rollingaccumulator.h", + #"safe_conversions.h", + #"safe_conversions_impl.h", + "scopedptrcollection.h", + "scoped_ref_ptr.h", + "sec_buffer.h", + "sharedexclusivelock.cc", + "sharedexclusivelock.h", + "sslconfig.h", + "sslroots.h", + "stringdigest.h", + #"testbase64.h", + "testclient.cc", + "testclient.h", + #"testutils.h", + "transformadapter.cc", + "transformadapter.h", + "versionparsing.cc", + "versionparsing.h", + "virtualsocketserver.cc", + "virtualsocketserver.h", + #"win32regkey.cc", + #"win32regkey.h", + #"win32socketinit.cc", + #"win32socketinit.h", + #"win32socketserver.cc", + #"win32socketserver.h", + #"win32toolhelp.h", + "window.h", + "windowpickerfactory.h", + "windowpicker.h", + ] + + include_dirs = [ + "../overrides", + "../../openssl/openssl/include", + ] + + direct_dependent_configs += [ ":webrtc_base_chromium_config" ] + } else { + if (is_win) { + sources += [ + "diskcache_win32.cc", + "diskcache_win32.h", + ] + } + + deps = [ "//third_party/jsoncpp" ] + } + + # TODO(henrike): issue 3307, make webrtc_base build without disabling + # these flags. + cflags += [ + "-Wno-extra", + "-Wno-all", + ] + cflags_cc += [ "-Wno-non-virtual-dtor" ] + + if (use_openssl) { + direct_dependent_configs += [ ":openssl_config" ] + + deps = [ "//third_party/openssl" ] + } else { + direct_dependent_configs += [ ":no_openssl_config" ] + } + + if (is_android) { + direct_dependent_configs += [ ":android_config" ] + + libs = [ + "log", + "GLESv2" + ] + } else { + direct_dependent_configs += [ ":no_android_config" ] + + sources -= [ + "ifaddrs-android.cc", + "ifaddrs-android.h", + ] + } + + if (is_ios) { + all_dependent_configs += [ ":ios_config" ] + + deps = [ "//net/third_party/nss/ssl:libssl" ] + } + + if (is_linux) { + libs = [ + "crypto", + "dl", + "rt", + "Xext", + "X11", + "Xcomposite", + "Xrender", + ] + configs += [ "//third_party/nss:system_nss_no_ssl_config" ] + } else { + sources -= [ + "dbus.cc", + "dbus.h", + "libdbusglibsymboltable.cc", + "libdbusglibsymboltable.h", + "linuxfdwalk.c", + "linuxfdwalk.h", + "linuxwindowpicker.cc", + "linuxwindowpicker.h", + ] + } + + if (is_mac) { + all_dependent_configs = [ ":mac_config" ] + + libs = [ + "crypto", # $(SDKROOT)/usr/lib/libcrypto.dylib + "ssl", # $(SDKROOT)/usr/lib/libssl.dylib + ] + if (cpu_arch == "x86") { + all_dependent_configs += [ ":mac_x86_config" ] + } + } else { + sources -= [ + "macasyncsocket.cc", + "macasyncsocket.h", + "maccocoasocketserver.h", + #"maccocoasocketserver.mm", # Seems to be excluded by default with GN. + "macconversion.cc", + "macconversion.h", + "macsocketserver.cc", + "macsocketserver.h", + "macutils.cc", + "macutils.h", + "macwindowpicker.cc", + "macwindowpicker.h", + ] + } + + if (is_win) { + libs = [ + "crypt32.lib", + "iphlpapi.lib", + "secur32.lib", + ] + + cflags += [ + # Suppress warnings about WIN32_LEAN_AND_MEAN. + "/wd4005", + "/wd4703", + ] + + defines += [ "_CRT_NONSTDC_NO_DEPRECATE" ] + } else { + sources -= [ + "schanneladapter.cc", + "schanneladapter.h", + "winping.cc", + "winping.h", + "winfirewall.cc", + "winfirewall.h", + # The files below were covered by a regex exclude in GYP. + "win32.cc", + "win32.h", + "win32filesystem.cc", + "win32filesystem.h", + "win32regkey.cc", + "win32regkey.h", + "win32securityerrors.cc", + "win32socketinit.cc", + "win32socketinit.h", + "win32socketserver.cc", + "win32socketserver.h", + "win32window.cc", + "win32window.h", + "win32windowpicker.cc", + "win32windowpicker.h", + ] + } + + if (is_posix) { + if (is_debug) { + defines += [ "_DEBUG" ] + } + } else { + sources -= [ + "latebindingsymboltable.cc", + "latebindingsymboltable.h", + "posix.cc", + "posix.h", + "unixfilesystem.cc", + "unixfilesystem.h", + ] + } + + if (is_ios || (is_mac && cpu_arch != "x86")) { + defines += [ "CARBON_DEPRECATED=YES" ] + } + + if (is_ios || !is_posix) { + sources -= [ + "openssl.h", + "openssladapter.cc", + "openssladapter.h", + "openssldigest.cc", + "openssldigest.h", + "opensslidentity.cc", + "opensslidentity.h", + "opensslstreamadapter.cc", + "opensslstreamadapter.h", + ] + } + + if (!is_linux && !is_android) { + sources -= [ + "linux.cc", + "linux.h", + ] + } + + if (is_mac || is_ios || is_win) { + deps += [ + "//net/third_party/nss/ssl:libssl", + "//third_party/nss:nspr", + "//third_party/nss:nss", + ] + } + + if (is_posix && !is_mac && !is_ios && !is_android) { + configs += [ ":linux_system_ssl" ] + } +} diff --git a/webrtc/base/OWNERS b/webrtc/base/OWNERS new file mode 100644 index 000000000..d292dfdb0 --- /dev/null +++ b/webrtc/base/OWNERS @@ -0,0 +1,9 @@ +henrike@webrtc.org +pwestin@webrtc.org +perkj@webrtc.org +henrika@webrtc.org +henrikg@webrtc.org +mflodman@webrtc.org +niklas.enbom@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/base/asyncfile.cc b/webrtc/base/asyncfile.cc new file mode 100644 index 000000000..ea904c554 --- /dev/null +++ b/webrtc/base/asyncfile.cc @@ -0,0 +1,21 @@ +/* + * Copyright 2010 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 "webrtc/base/asyncfile.h" + +namespace rtc { + +AsyncFile::AsyncFile() { +} + +AsyncFile::~AsyncFile() { +} + +} // namespace rtc diff --git a/webrtc/base/asyncfile.h b/webrtc/base/asyncfile.h new file mode 100644 index 000000000..dd4676688 --- /dev/null +++ b/webrtc/base/asyncfile.h @@ -0,0 +1,40 @@ +/* + * Copyright 2004 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_BASE_ASYNCFILE_H__ +#define WEBRTC_BASE_ASYNCFILE_H__ + +#include "webrtc/base/sigslot.h" + +namespace rtc { + +// Provides the ability to perform file I/O asynchronously. +// TODO: Create a common base class with AsyncSocket. +class AsyncFile { + public: + AsyncFile(); + virtual ~AsyncFile(); + + // Determines whether the file will receive read events. + virtual bool readable() = 0; + virtual void set_readable(bool value) = 0; + + // Determines whether the file will receive write events. + virtual bool writable() = 0; + virtual void set_writable(bool value) = 0; + + sigslot::signal1 SignalReadEvent; + sigslot::signal1 SignalWriteEvent; + sigslot::signal2 SignalCloseEvent; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCFILE_H__ diff --git a/webrtc/base/asynchttprequest.cc b/webrtc/base/asynchttprequest.cc new file mode 100644 index 000000000..23042f17d --- /dev/null +++ b/webrtc/base/asynchttprequest.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2004 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 "webrtc/base/asynchttprequest.h" + +namespace rtc { + +enum { + MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE, + MSG_LAUNCH_REQUEST +}; +static const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec + +/////////////////////////////////////////////////////////////////////////////// +// AsyncHttpRequest +/////////////////////////////////////////////////////////////////////////////// + +AsyncHttpRequest::AsyncHttpRequest(const std::string &user_agent) + : start_delay_(0), + firewall_(NULL), + port_(80), + secure_(false), + timeout_(kDefaultHTTPTimeout), + fail_redirect_(false), + factory_(Thread::Current()->socketserver(), user_agent), + pool_(&factory_), + client_(user_agent.c_str(), &pool_), + error_(HE_NONE) { + client_.SignalHttpClientComplete.connect(this, + &AsyncHttpRequest::OnComplete); +} + +AsyncHttpRequest::~AsyncHttpRequest() { +} + +void AsyncHttpRequest::OnWorkStart() { + if (start_delay_ <= 0) { + LaunchRequest(); + } else { + Thread::Current()->PostDelayed(start_delay_, this, MSG_LAUNCH_REQUEST); + } +} + +void AsyncHttpRequest::OnWorkStop() { + // worker is already quitting, no need to explicitly quit + LOG(LS_INFO) << "HttpRequest cancelled"; +} + +void AsyncHttpRequest::OnComplete(HttpClient* client, HttpErrorType error) { + Thread::Current()->Clear(this, MSG_TIMEOUT); + + set_error(error); + if (!error) { + LOG(LS_INFO) << "HttpRequest completed successfully"; + + std::string value; + if (client_.response().hasHeader(HH_LOCATION, &value)) { + response_redirect_ = value.c_str(); + } + } else { + LOG(LS_INFO) << "HttpRequest completed with error: " << error; + } + + worker()->Quit(); +} + +void AsyncHttpRequest::OnMessage(Message* message) { + switch (message->message_id) { + case MSG_TIMEOUT: + LOG(LS_INFO) << "HttpRequest timed out"; + client_.reset(); + worker()->Quit(); + break; + case MSG_LAUNCH_REQUEST: + LaunchRequest(); + break; + default: + SignalThread::OnMessage(message); + break; + } +} + +void AsyncHttpRequest::DoWork() { + // Do nothing while we wait for the request to finish. We only do this so + // that we can be a SignalThread; in the future this class should not be + // a SignalThread, since it does not need to spawn a new thread. + Thread::Current()->ProcessMessages(kForever); +} + +void AsyncHttpRequest::LaunchRequest() { + factory_.SetProxy(proxy_); + if (secure_) + factory_.UseSSL(host_.c_str()); + + bool transparent_proxy = (port_ == 80) && + ((proxy_.type == PROXY_HTTPS) || (proxy_.type == PROXY_UNKNOWN)); + if (transparent_proxy) { + client_.set_proxy(proxy_); + } + client_.set_fail_redirect(fail_redirect_); + client_.set_server(SocketAddress(host_, port_)); + + LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path; + + Thread::Current()->PostDelayed(timeout_, this, MSG_TIMEOUT); + client_.start(); +} + +} // namespace rtc diff --git a/webrtc/base/asynchttprequest.h b/webrtc/base/asynchttprequest.h new file mode 100644 index 000000000..b8d13db8f --- /dev/null +++ b/webrtc/base/asynchttprequest.h @@ -0,0 +1,104 @@ +/* + * Copyright 2004 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_BASE_ASYNCHTTPREQUEST_H_ +#define WEBRTC_BASE_ASYNCHTTPREQUEST_H_ + +#include +#include "webrtc/base/event.h" +#include "webrtc/base/httpclient.h" +#include "webrtc/base/signalthread.h" +#include "webrtc/base/socketpool.h" +#include "webrtc/base/sslsocketfactory.h" + +namespace rtc { + +class FirewallManager; + +/////////////////////////////////////////////////////////////////////////////// +// AsyncHttpRequest +// Performs an HTTP request on a background thread. Notifies on the foreground +// thread once the request is done (successfully or unsuccessfully). +/////////////////////////////////////////////////////////////////////////////// + +class AsyncHttpRequest : public SignalThread { + public: + explicit AsyncHttpRequest(const std::string &user_agent); + ~AsyncHttpRequest(); + + // If start_delay is less than or equal to zero, this starts immediately. + // Start_delay defaults to zero. + int start_delay() const { return start_delay_; } + void set_start_delay(int delay) { start_delay_ = delay; } + + const ProxyInfo& proxy() const { return proxy_; } + void set_proxy(const ProxyInfo& proxy) { + proxy_ = proxy; + } + void set_firewall(FirewallManager * firewall) { + firewall_ = firewall; + } + + // The DNS name of the host to connect to. + const std::string& host() { return host_; } + void set_host(const std::string& host) { host_ = host; } + + // The port to connect to on the target host. + int port() { return port_; } + void set_port(int port) { port_ = port; } + + // Whether the request should use SSL. + bool secure() { return secure_; } + void set_secure(bool secure) { secure_ = secure; } + + // Time to wait on the download, in ms. + int timeout() { return timeout_; } + void set_timeout(int timeout) { timeout_ = timeout; } + + // Fail redirects to allow analysis of redirect urls, etc. + bool fail_redirect() const { return fail_redirect_; } + void set_fail_redirect(bool redirect) { fail_redirect_ = redirect; } + + // Returns the redirect when redirection occurs + const std::string& response_redirect() { return response_redirect_; } + + HttpRequestData& request() { return client_.request(); } + HttpResponseData& response() { return client_.response(); } + HttpErrorType error() { return error_; } + + protected: + void set_error(HttpErrorType error) { error_ = error; } + virtual void OnWorkStart(); + virtual void OnWorkStop(); + void OnComplete(HttpClient* client, HttpErrorType error); + virtual void OnMessage(Message* message); + virtual void DoWork(); + + private: + void LaunchRequest(); + + int start_delay_; + ProxyInfo proxy_; + FirewallManager* firewall_; + std::string host_; + int port_; + bool secure_; + int timeout_; + bool fail_redirect_; + SslSocketFactory factory_; + ReuseSocketPool pool_; + HttpClient client_; + HttpErrorType error_; + std::string response_redirect_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCHTTPREQUEST_H_ diff --git a/webrtc/base/asynchttprequest_unittest.cc b/webrtc/base/asynchttprequest_unittest.cc new file mode 100644 index 000000000..0bfd795b1 --- /dev/null +++ b/webrtc/base/asynchttprequest_unittest.cc @@ -0,0 +1,233 @@ +/* + * Copyright 2004 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 "webrtc/base/asynchttprequest.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +static const SocketAddress kServerAddr("127.0.0.1", 0); +static const SocketAddress kServerHostnameAddr("localhost", 0); +static const char kServerGetPath[] = "/get"; +static const char kServerPostPath[] = "/post"; +static const char kServerResponse[] = "This is a test"; + +class TestHttpServer : public HttpServer, public sigslot::has_slots<> { + public: + TestHttpServer(Thread* thread, const SocketAddress& addr) : + socket_(thread->socketserver()->CreateAsyncSocket(addr.family(), + SOCK_STREAM)) { + socket_->Bind(addr); + socket_->Listen(5); + socket_->SignalReadEvent.connect(this, &TestHttpServer::OnAccept); + } + + SocketAddress address() const { return socket_->GetLocalAddress(); } + void Close() const { socket_->Close(); } + + private: + void OnAccept(AsyncSocket* socket) { + AsyncSocket* new_socket = socket_->Accept(NULL); + if (new_socket) { + HandleConnection(new SocketStream(new_socket)); + } + } + rtc::scoped_ptr socket_; +}; + +class AsyncHttpRequestTest : public testing::Test, + public sigslot::has_slots<> { + public: + AsyncHttpRequestTest() + : started_(false), + done_(false), + server_(Thread::Current(), kServerAddr) { + server_.SignalHttpRequest.connect(this, &AsyncHttpRequestTest::OnRequest); + } + + bool started() const { return started_; } + bool done() const { return done_; } + + AsyncHttpRequest* CreateGetRequest(const std::string& host, int port, + const std::string& path) { + rtc::AsyncHttpRequest* request = + new rtc::AsyncHttpRequest("unittest"); + request->SignalWorkDone.connect(this, + &AsyncHttpRequestTest::OnRequestDone); + request->request().verb = rtc::HV_GET; + request->set_host(host); + request->set_port(port); + request->request().path = path; + request->response().document.reset(new MemoryStream()); + return request; + } + AsyncHttpRequest* CreatePostRequest(const std::string& host, int port, + const std::string& path, + const std::string content_type, + StreamInterface* content) { + rtc::AsyncHttpRequest* request = + new rtc::AsyncHttpRequest("unittest"); + request->SignalWorkDone.connect(this, + &AsyncHttpRequestTest::OnRequestDone); + request->request().verb = rtc::HV_POST; + request->set_host(host); + request->set_port(port); + request->request().path = path; + request->request().setContent(content_type, content); + request->response().document.reset(new MemoryStream()); + return request; + } + + const TestHttpServer& server() const { return server_; } + + protected: + void OnRequest(HttpServer* server, HttpServerTransaction* t) { + started_ = true; + + if (t->request.path == kServerGetPath) { + t->response.set_success("text/plain", new MemoryStream(kServerResponse)); + } else if (t->request.path == kServerPostPath) { + // reverse the data and reply + size_t size; + StreamInterface* in = t->request.document.get(); + StreamInterface* out = new MemoryStream(); + in->GetSize(&size); + for (size_t i = 0; i < size; ++i) { + char ch; + in->SetPosition(size - i - 1); + in->Read(&ch, 1, NULL, NULL); + out->Write(&ch, 1, NULL, NULL); + } + out->Rewind(); + t->response.set_success("text/plain", out); + } else { + t->response.set_error(404); + } + server_.Respond(t); + } + void OnRequestDone(SignalThread* thread) { + done_ = true; + } + + private: + bool started_; + bool done_; + TestHttpServer server_; +}; + +TEST_F(AsyncHttpRequestTest, TestGetSuccess) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerGetPath); + EXPECT_FALSE(started()); + req->Start(); + EXPECT_TRUE_WAIT(started(), 5000); // Should have started by now. + EXPECT_TRUE_WAIT(done(), 5000); + std::string response; + EXPECT_EQ(200U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->Rewind(); + req->response().document->ReadLine(&response); + EXPECT_EQ(kServerResponse, response); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, TestGetNotFound) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + "/bad"); + req->Start(); + EXPECT_TRUE_WAIT(done(), 5000); + size_t size; + EXPECT_EQ(404U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->GetSize(&size); + EXPECT_EQ(0U, size); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, TestGetToNonServer) { + AsyncHttpRequest* req = CreateGetRequest( + "127.0.0.1", server().address().port(), + kServerGetPath); + // Stop the server before we send the request. + server().Close(); + req->Start(); + EXPECT_TRUE_WAIT(done(), 10000); + size_t size; + EXPECT_EQ(500U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->GetSize(&size); + EXPECT_EQ(0U, size); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, DISABLED_TestGetToInvalidHostname) { + AsyncHttpRequest* req = CreateGetRequest( + "invalid", server().address().port(), + kServerGetPath); + req->Start(); + EXPECT_TRUE_WAIT(done(), 5000); + size_t size; + EXPECT_EQ(500U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->GetSize(&size); + EXPECT_EQ(0U, size); + req->Release(); +} + +TEST_F(AsyncHttpRequestTest, TestPostSuccess) { + AsyncHttpRequest* req = CreatePostRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerPostPath, "text/plain", new MemoryStream("abcd1234")); + req->Start(); + EXPECT_TRUE_WAIT(done(), 5000); + std::string response; + EXPECT_EQ(200U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->Rewind(); + req->response().document->ReadLine(&response); + EXPECT_EQ("4321dcba", response); + req->Release(); +} + +// Ensure that we shut down properly even if work is outstanding. +TEST_F(AsyncHttpRequestTest, TestCancel) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerGetPath); + req->Start(); + req->Destroy(true); +} + +TEST_F(AsyncHttpRequestTest, TestGetSuccessDelay) { + AsyncHttpRequest* req = CreateGetRequest( + kServerHostnameAddr.hostname(), server().address().port(), + kServerGetPath); + req->set_start_delay(10); // Delay 10ms. + req->Start(); + Thread::SleepMs(5); + EXPECT_FALSE(started()); // Should not have started immediately. + EXPECT_TRUE_WAIT(started(), 5000); // Should have started by now. + EXPECT_TRUE_WAIT(done(), 5000); + std::string response; + EXPECT_EQ(200U, req->response().scode); + ASSERT_TRUE(req->response().document); + req->response().document->Rewind(); + req->response().document->ReadLine(&response); + EXPECT_EQ(kServerResponse, response); + req->Release(); +} + +} // namespace rtc diff --git a/webrtc/base/asyncinvoker-inl.h b/webrtc/base/asyncinvoker-inl.h new file mode 100644 index 000000000..733cb0e79 --- /dev/null +++ b/webrtc/base/asyncinvoker-inl.h @@ -0,0 +1,129 @@ +/* + * Copyright 2014 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_BASE_ASYNCINVOKER_INL_H_ +#define WEBRTC_BASE_ASYNCINVOKER_INL_H_ + +#include "webrtc/base/bind.h" +#include "webrtc/base/callback.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +class AsyncInvoker; + +// Helper class for AsyncInvoker. Runs a task and triggers a callback +// on the calling thread if necessary. Instances are ref-counted so their +// lifetime can be independent of AsyncInvoker. +class AsyncClosure : public RefCountInterface { + public: + virtual ~AsyncClosure() {} + // Runs the asynchronous task, and triggers a callback to the calling + // thread if needed. Should be called from the target thread. + virtual void Execute() = 0; +}; + +// Simple closure that doesn't trigger a callback for the calling thread. +template +class FireAndForgetAsyncClosure : public AsyncClosure { + public: + explicit FireAndForgetAsyncClosure(const FunctorT& functor) + : functor_(functor) {} + virtual void Execute() { + functor_(); + } + private: + FunctorT functor_; +}; + +// Base class for closures that may trigger a callback for the calling thread. +// Listens for the "destroyed" signals from the calling thread and the invoker, +// and cancels the callback to the calling thread if either is destroyed. +class NotifyingAsyncClosureBase : public AsyncClosure, + public sigslot::has_slots<> { + public: + virtual ~NotifyingAsyncClosureBase() { disconnect_all(); } + + protected: + NotifyingAsyncClosureBase(AsyncInvoker* invoker, Thread* calling_thread); + void TriggerCallback(); + void SetCallback(const Callback0& callback) { + CritScope cs(&crit_); + callback_ = callback; + } + bool CallbackCanceled() const { return calling_thread_ == NULL; } + + private: + Callback0 callback_; + CriticalSection crit_; + AsyncInvoker* invoker_; + Thread* calling_thread_; + + void CancelCallback(); +}; + +// Closures that have a non-void return value and require a callback. +template +class NotifyingAsyncClosure : public NotifyingAsyncClosureBase { + public: + NotifyingAsyncClosure(AsyncInvoker* invoker, + Thread* calling_thread, + const FunctorT& functor, + void (HostT::*callback)(ReturnT), + HostT* callback_host) + : NotifyingAsyncClosureBase(invoker, calling_thread), + functor_(functor), + callback_(callback), + callback_host_(callback_host) {} + virtual void Execute() { + ReturnT result = functor_(); + if (!CallbackCanceled()) { + SetCallback(Callback0(Bind(callback_, callback_host_, result))); + TriggerCallback(); + } + } + + private: + FunctorT functor_; + void (HostT::*callback_)(ReturnT); + HostT* callback_host_; +}; + +// Closures that have a void return value and require a callback. +template +class NotifyingAsyncClosure + : public NotifyingAsyncClosureBase { + public: + NotifyingAsyncClosure(AsyncInvoker* invoker, + Thread* calling_thread, + const FunctorT& functor, + void (HostT::*callback)(), + HostT* callback_host) + : NotifyingAsyncClosureBase(invoker, calling_thread), + functor_(functor) { + SetCallback(Callback0(Bind(callback, callback_host))); + } + virtual void Execute() { + functor_(); + TriggerCallback(); + } + + private: + FunctorT functor_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCINVOKER_INL_H_ diff --git a/webrtc/base/asyncinvoker.cc b/webrtc/base/asyncinvoker.cc new file mode 100644 index 000000000..ee423f110 --- /dev/null +++ b/webrtc/base/asyncinvoker.cc @@ -0,0 +1,91 @@ +/* + * Copyright 2014 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 "webrtc/base/asyncinvoker.h" + +namespace rtc { + +AsyncInvoker::AsyncInvoker() : destroying_(false) {} + +AsyncInvoker::~AsyncInvoker() { + destroying_ = true; + SignalInvokerDestroyed(); + // Messages for this need to be cleared *before* our destructor is complete. + MessageQueueManager::Clear(this); +} + +void AsyncInvoker::OnMessage(Message* msg) { + // Get the AsyncClosure shared ptr from this message's data. + ScopedRefMessageData* data = + static_cast*>(msg->pdata); + scoped_refptr closure = data->data(); + delete msg->pdata; + msg->pdata = NULL; + + // Execute the closure and trigger the return message if needed. + closure->Execute(); +} + +void AsyncInvoker::Flush(Thread* thread, uint32 id /*= MQID_ANY*/) { + if (destroying_) return; + + // Run this on |thread| to reduce the number of context switches. + if (Thread::Current() != thread) { + thread->Invoke(Bind(&AsyncInvoker::Flush, this, thread, id)); + return; + } + + MessageList removed; + thread->Clear(this, id, &removed); + for (MessageList::iterator it = removed.begin(); it != removed.end(); ++it) { + // This message was pending on this thread, so run it now. + thread->Send(it->phandler, + it->message_id, + it->pdata); + } +} + +void AsyncInvoker::DoInvoke(Thread* thread, AsyncClosure* closure, + uint32 id) { + if (destroying_) { + LOG(LS_WARNING) << "Tried to invoke while destroying the invoker."; + // Since this call transwers ownership of |closure|, we clean it up here. + delete closure; + return; + } + thread->Post(this, id, new ScopedRefMessageData(closure)); +} + +NotifyingAsyncClosureBase::NotifyingAsyncClosureBase(AsyncInvoker* invoker, + Thread* calling_thread) + : invoker_(invoker), calling_thread_(calling_thread) { + calling_thread->SignalQueueDestroyed.connect( + this, &NotifyingAsyncClosureBase::CancelCallback); + invoker->SignalInvokerDestroyed.connect( + this, &NotifyingAsyncClosureBase::CancelCallback); +} + +void NotifyingAsyncClosureBase::TriggerCallback() { + CritScope cs(&crit_); + if (!CallbackCanceled() && !callback_.empty()) { + invoker_->AsyncInvoke(calling_thread_, callback_); + } +} + +void NotifyingAsyncClosureBase::CancelCallback() { + // If the callback is triggering when this is called, block the + // destructor of the dying object here by waiting until the callback + // is done triggering. + CritScope cs(&crit_); + // calling_thread_ == NULL means do not trigger the callback. + calling_thread_ = NULL; +} + +} // namespace rtc diff --git a/webrtc/base/asyncinvoker.h b/webrtc/base/asyncinvoker.h new file mode 100644 index 000000000..ee9a03c80 --- /dev/null +++ b/webrtc/base/asyncinvoker.h @@ -0,0 +1,134 @@ +/* + * Copyright 2014 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_BASE_ASYNCINVOKER_H_ +#define WEBRTC_BASE_ASYNCINVOKER_H_ + +#include "webrtc/base/asyncinvoker-inl.h" +#include "webrtc/base/bind.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/scopedptrcollection.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Invokes function objects (aka functors) asynchronously on a Thread, and +// owns the lifetime of calls (ie, when this object is destroyed, calls in +// flight are cancelled). AsyncInvoker can optionally execute a user-specified +// function when the asynchronous call is complete, or operates in +// fire-and-forget mode otherwise. +// +// AsyncInvoker does not own the thread it calls functors on. +// +// A note about async calls and object lifetimes: users should +// be mindful of object lifetimes when calling functions asynchronously and +// ensure objects used by the function _cannot_ be deleted between the +// invocation and execution of the functor. AsyncInvoker is designed to +// help: any calls in flight will be cancelled when the AsyncInvoker used to +// make the call is destructed, and any calls executing will be allowed to +// complete before AsyncInvoker destructs. +// +// The easiest way to ensure lifetimes are handled correctly is to create a +// class that owns the Thread and AsyncInvoker objects, and then call its +// methods asynchronously as needed. +// +// Example: +// class MyClass { +// public: +// void FireAsyncTaskWithResult(Thread* thread, int x) { +// // Specify a callback to get the result upon completion. +// invoker_.AsyncInvoke( +// thread, Bind(&MyClass::AsyncTaskWithResult, this, x), +// &MyClass::OnTaskComplete, this); +// } +// void FireAnotherAsyncTask(Thread* thread) { +// // No callback specified means fire-and-forget. +// invoker_.AsyncInvoke( +// thread, Bind(&MyClass::AnotherAsyncTask, this)); +// +// private: +// int AsyncTaskWithResult(int x) { +// // Some long running process... +// return x * x; +// } +// void AnotherAsyncTask() { +// // Some other long running process... +// } +// void OnTaskComplete(int result) { result_ = result; } +// +// AsyncInvoker invoker_; +// int result_; +// }; +class AsyncInvoker : public MessageHandler { + public: + AsyncInvoker(); + virtual ~AsyncInvoker(); + + // Call |functor| asynchronously on |thread|, with no callback upon + // completion. Returns immediately. + template + void AsyncInvoke(Thread* thread, + const FunctorT& functor, + uint32 id = 0) { + AsyncClosure* closure = + new RefCountedObject >(functor); + DoInvoke(thread, closure, id); + } + + // Call |functor| asynchronously on |thread|, calling |callback| when done. + template + void AsyncInvoke(Thread* thread, + const FunctorT& functor, + void (HostT::*callback)(ReturnT), + HostT* callback_host, + uint32 id = 0) { + AsyncClosure* closure = + new RefCountedObject >( + this, Thread::Current(), functor, callback, callback_host); + DoInvoke(thread, closure, id); + } + + // Call |functor| asynchronously on |thread|, calling |callback| when done. + // Overloaded for void return. + template + void AsyncInvoke(Thread* thread, + const FunctorT& functor, + void (HostT::*callback)(), + HostT* callback_host, + uint32 id = 0) { + AsyncClosure* closure = + new RefCountedObject >( + this, Thread::Current(), functor, callback, callback_host); + DoInvoke(thread, closure, id); + } + + // Synchronously execute on |thread| all outstanding calls we own + // that are pending on |thread|, and wait for calls to complete + // before returning. Optionally filter by message id. + // The destructor will not wait for outstanding calls, so if that + // behavior is desired, call Flush() before destroying this object. + void Flush(Thread* thread, uint32 id = MQID_ANY); + + // Signaled when this object is destructed. + sigslot::signal0<> SignalInvokerDestroyed; + + private: + virtual void OnMessage(Message* msg); + void DoInvoke(Thread* thread, AsyncClosure* closure, uint32 id); + + bool destroying_; + + DISALLOW_COPY_AND_ASSIGN(AsyncInvoker); +}; + +} // namespace rtc + + +#endif // WEBRTC_BASE_ASYNCINVOKER_H_ diff --git a/webrtc/base/asyncpacketsocket.h b/webrtc/base/asyncpacketsocket.h new file mode 100644 index 000000000..dd91ea1f1 --- /dev/null +++ b/webrtc/base/asyncpacketsocket.h @@ -0,0 +1,140 @@ +/* + * Copyright 2004 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_BASE_ASYNCPACKETSOCKET_H_ +#define WEBRTC_BASE_ASYNCPACKETSOCKET_H_ + +#include "webrtc/base/dscp.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +// This structure holds the info needed to update the packet send time header +// extension, including the information needed to update the authentication tag +// after changing the value. +struct PacketTimeUpdateParams { + PacketTimeUpdateParams() + : rtp_sendtime_extension_id(-1), srtp_auth_tag_len(-1), + srtp_packet_index(-1) { + } + + int rtp_sendtime_extension_id; // extension header id present in packet. + std::vector srtp_auth_key; // Authentication key. + int srtp_auth_tag_len; // Authentication tag length. + int64 srtp_packet_index; // Required for Rtp Packet authentication. +}; + +// This structure holds meta information for the packet which is about to send +// over network. +struct PacketOptions { + PacketOptions() : dscp(DSCP_NO_CHANGE) {} + explicit PacketOptions(DiffServCodePoint dscp) : dscp(dscp) {} + + DiffServCodePoint dscp; + PacketTimeUpdateParams packet_time_params; +}; + +// This structure will have the information about when packet is actually +// received by socket. +struct PacketTime { + PacketTime() : timestamp(-1), not_before(-1) {} + PacketTime(int64 timestamp, int64 not_before) + : timestamp(timestamp), not_before(not_before) { + } + + int64 timestamp; // Receive time after socket delivers the data. + int64 not_before; // Earliest possible time the data could have arrived, + // indicating the potential error in the |timestamp| value, + // in case the system, is busy. For example, the time of + // the last select() call. + // If unknown, this value will be set to zero. +}; + +inline PacketTime CreatePacketTime(int64 not_before) { + return PacketTime(TimeMicros(), not_before); +} + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncPacketSocket : public sigslot::has_slots<> { + public: + enum State { + STATE_CLOSED, + STATE_BINDING, + STATE_BOUND, + STATE_CONNECTING, + STATE_CONNECTED + }; + + AsyncPacketSocket() { } + virtual ~AsyncPacketSocket() { } + + // Returns current local address. Address may be set to NULL if the + // socket is not bound yet (GetState() returns STATE_BINDING). + virtual SocketAddress GetLocalAddress() const = 0; + + // Returns remote address. Returns zeroes if this is not a client TCP socket. + virtual SocketAddress GetRemoteAddress() const = 0; + + // Send a packet. + virtual int Send(const void *pv, size_t cb, const PacketOptions& options) = 0; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, + const PacketOptions& options) = 0; + + // Close the socket. + virtual int Close() = 0; + + // Returns current state of the socket. + virtual State GetState() const = 0; + + // Get/set options. + virtual int GetOption(Socket::Option opt, int* value) = 0; + virtual int SetOption(Socket::Option opt, int value) = 0; + + // Get/Set current error. + // TODO: Remove SetError(). + virtual int GetError() const = 0; + virtual void SetError(int error) = 0; + + // Emitted each time a packet is read. Used only for UDP and + // connected TCP sockets. + sigslot::signal5 SignalReadPacket; + + // Emitted when the socket is currently able to send. + sigslot::signal1 SignalReadyToSend; + + // Emitted after address for the socket is allocated, i.e. binding + // is finished. State of the socket is changed from BINDING to BOUND + // (for UDP and server TCP sockets) or CONNECTING (for client TCP + // sockets). + sigslot::signal2 SignalAddressReady; + + // Emitted for client TCP sockets when state is changed from + // CONNECTING to CONNECTED. + sigslot::signal1 SignalConnect; + + // Emitted for client TCP sockets when state is changed from + // CONNECTED to CLOSED. + sigslot::signal2 SignalClose; + + // Used only for listening TCP sockets. + sigslot::signal2 SignalNewConnection; + + private: + DISALLOW_EVIL_CONSTRUCTORS(AsyncPacketSocket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCPACKETSOCKET_H_ diff --git a/webrtc/base/asyncresolverinterface.h b/webrtc/base/asyncresolverinterface.h new file mode 100644 index 000000000..4b401bd97 --- /dev/null +++ b/webrtc/base/asyncresolverinterface.h @@ -0,0 +1,47 @@ +/* + * Copyright 2013 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_BASE_ASYNCRESOLVERINTERFACE_H_ +#define WEBRTC_BASE_ASYNCRESOLVERINTERFACE_H_ + +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +// This interface defines the methods to resolve the address asynchronously. +class AsyncResolverInterface { + public: + AsyncResolverInterface() {} + virtual ~AsyncResolverInterface() {} + + // Start address resolve process. + virtual void Start(const SocketAddress& addr) = 0; + // Returns top most resolved address of |family| + virtual bool GetResolvedAddress(int family, SocketAddress* addr) const = 0; + // Returns error from resolver. + virtual int GetError() const = 0; + // Delete the resolver. + virtual void Destroy(bool wait) = 0; + // Returns top most resolved IPv4 address if address is resolved successfully. + // Otherwise returns address set in SetAddress. + SocketAddress address() const { + SocketAddress addr; + GetResolvedAddress(AF_INET, &addr); + return addr; + } + + // This signal is fired when address resolve process is completed. + sigslot::signal1 SignalDone; +}; + +} // namespace rtc + +#endif diff --git a/webrtc/base/asyncsocket.cc b/webrtc/base/asyncsocket.cc new file mode 100644 index 000000000..d565c6e99 --- /dev/null +++ b/webrtc/base/asyncsocket.cc @@ -0,0 +1,44 @@ +/* + * Copyright 2010 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 "webrtc/base/asyncsocket.h" + +namespace rtc { + +AsyncSocket::AsyncSocket() { +} + +AsyncSocket::~AsyncSocket() { +} + +AsyncSocketAdapter::AsyncSocketAdapter(AsyncSocket* socket) : socket_(NULL) { + Attach(socket); +} + +AsyncSocketAdapter::~AsyncSocketAdapter() { + delete socket_; +} + +void AsyncSocketAdapter::Attach(AsyncSocket* socket) { + ASSERT(!socket_); + socket_ = socket; + if (socket_) { + socket_->SignalConnectEvent.connect(this, + &AsyncSocketAdapter::OnConnectEvent); + socket_->SignalReadEvent.connect(this, + &AsyncSocketAdapter::OnReadEvent); + socket_->SignalWriteEvent.connect(this, + &AsyncSocketAdapter::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, + &AsyncSocketAdapter::OnCloseEvent); + } +} + +} // namespace rtc diff --git a/webrtc/base/asyncsocket.h b/webrtc/base/asyncsocket.h new file mode 100644 index 000000000..a6f3158e9 --- /dev/null +++ b/webrtc/base/asyncsocket.h @@ -0,0 +1,124 @@ +/* + * Copyright 2004 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_BASE_ASYNCSOCKET_H_ +#define WEBRTC_BASE_ASYNCSOCKET_H_ + +#include "webrtc/base/common.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socket.h" + +namespace rtc { + +// TODO: Remove Socket and rename AsyncSocket to Socket. + +// Provides the ability to perform socket I/O asynchronously. +class AsyncSocket : public Socket { + public: + AsyncSocket(); + virtual ~AsyncSocket(); + + virtual AsyncSocket* Accept(SocketAddress* paddr) = 0; + + // SignalReadEvent and SignalWriteEvent use multi_threaded_local to allow + // access concurrently from different thread. + // For example SignalReadEvent::connect will be called in AsyncUDPSocket ctor + // but at the same time the SocketDispatcher maybe signaling the read event. + // ready to read + sigslot::signal1 SignalReadEvent; + // ready to write + sigslot::signal1 SignalWriteEvent; + sigslot::signal1 SignalConnectEvent; // connected + sigslot::signal2 SignalCloseEvent; // closed +}; + +class AsyncSocketAdapter : public AsyncSocket, public sigslot::has_slots<> { + public: + // The adapted socket may explicitly be NULL, and later assigned using Attach. + // However, subclasses which support detached mode must override any methods + // that will be called during the detached period (usually GetState()), to + // avoid dereferencing a null pointer. + explicit AsyncSocketAdapter(AsyncSocket* socket); + virtual ~AsyncSocketAdapter(); + void Attach(AsyncSocket* socket); + virtual SocketAddress GetLocalAddress() const { + return socket_->GetLocalAddress(); + } + virtual SocketAddress GetRemoteAddress() const { + return socket_->GetRemoteAddress(); + } + virtual int Bind(const SocketAddress& addr) { + return socket_->Bind(addr); + } + virtual int Connect(const SocketAddress& addr) { + return socket_->Connect(addr); + } + virtual int Send(const void* pv, size_t cb) { + return socket_->Send(pv, cb); + } + virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) { + return socket_->SendTo(pv, cb, addr); + } + virtual int Recv(void* pv, size_t cb) { + return socket_->Recv(pv, cb); + } + virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) { + return socket_->RecvFrom(pv, cb, paddr); + } + virtual int Listen(int backlog) { + return socket_->Listen(backlog); + } + virtual AsyncSocket* Accept(SocketAddress* paddr) { + return socket_->Accept(paddr); + } + virtual int Close() { + return socket_->Close(); + } + virtual int GetError() const { + return socket_->GetError(); + } + virtual void SetError(int error) { + return socket_->SetError(error); + } + virtual ConnState GetState() const { + return socket_->GetState(); + } + virtual int EstimateMTU(uint16* mtu) { + return socket_->EstimateMTU(mtu); + } + virtual int GetOption(Option opt, int* value) { + return socket_->GetOption(opt, value); + } + virtual int SetOption(Option opt, int value) { + return socket_->SetOption(opt, value); + } + + protected: + virtual void OnConnectEvent(AsyncSocket* socket) { + SignalConnectEvent(this); + } + virtual void OnReadEvent(AsyncSocket* socket) { + SignalReadEvent(this); + } + virtual void OnWriteEvent(AsyncSocket* socket) { + SignalWriteEvent(this); + } + virtual void OnCloseEvent(AsyncSocket* socket, int err) { + SignalCloseEvent(this, err); + } + + AsyncSocket* socket_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCSOCKET_H_ diff --git a/webrtc/base/asynctcpsocket.cc b/webrtc/base/asynctcpsocket.cc new file mode 100644 index 000000000..0f7abd5a6 --- /dev/null +++ b/webrtc/base/asynctcpsocket.cc @@ -0,0 +1,299 @@ +/* + * Copyright 2004 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 "webrtc/base/asynctcpsocket.h" + +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +#if defined(WEBRTC_POSIX) +#include +#endif // WEBRTC_POSIX + +namespace rtc { + +static const size_t kMaxPacketSize = 64 * 1024; + +typedef uint16 PacketLength; +static const size_t kPacketLenSize = sizeof(PacketLength); + +static const size_t kBufSize = kMaxPacketSize + kPacketLenSize; + +static const int kListenBacklog = 5; + +// Binds and connects |socket| +AsyncSocket* AsyncTCPSocketBase::ConnectSocket( + rtc::AsyncSocket* socket, + const rtc::SocketAddress& bind_address, + const rtc::SocketAddress& remote_address) { + rtc::scoped_ptr owned_socket(socket); + if (socket->Bind(bind_address) < 0) { + LOG(LS_ERROR) << "Bind() failed with error " << socket->GetError(); + return NULL; + } + if (socket->Connect(remote_address) < 0) { + LOG(LS_ERROR) << "Connect() failed with error " << socket->GetError(); + return NULL; + } + return owned_socket.release(); +} + +AsyncTCPSocketBase::AsyncTCPSocketBase(AsyncSocket* socket, bool listen, + size_t max_packet_size) + : socket_(socket), + listen_(listen), + insize_(max_packet_size), + inpos_(0), + outsize_(max_packet_size), + outpos_(0) { + inbuf_ = new char[insize_]; + outbuf_ = new char[outsize_]; + + ASSERT(socket_.get() != NULL); + socket_->SignalConnectEvent.connect( + this, &AsyncTCPSocketBase::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &AsyncTCPSocketBase::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &AsyncTCPSocketBase::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &AsyncTCPSocketBase::OnCloseEvent); + + if (listen_) { + if (socket_->Listen(kListenBacklog) < 0) { + LOG(LS_ERROR) << "Listen() failed with error " << socket_->GetError(); + } + } +} + +AsyncTCPSocketBase::~AsyncTCPSocketBase() { + delete [] inbuf_; + delete [] outbuf_; +} + +SocketAddress AsyncTCPSocketBase::GetLocalAddress() const { + return socket_->GetLocalAddress(); +} + +SocketAddress AsyncTCPSocketBase::GetRemoteAddress() const { + return socket_->GetRemoteAddress(); +} + +int AsyncTCPSocketBase::Close() { + return socket_->Close(); +} + +AsyncTCPSocket::State AsyncTCPSocketBase::GetState() const { + switch (socket_->GetState()) { + case Socket::CS_CLOSED: + return STATE_CLOSED; + case Socket::CS_CONNECTING: + if (listen_) { + return STATE_BOUND; + } else { + return STATE_CONNECTING; + } + case Socket::CS_CONNECTED: + return STATE_CONNECTED; + default: + ASSERT(false); + return STATE_CLOSED; + } +} + +int AsyncTCPSocketBase::GetOption(Socket::Option opt, int* value) { + return socket_->GetOption(opt, value); +} + +int AsyncTCPSocketBase::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int AsyncTCPSocketBase::GetError() const { + return socket_->GetError(); +} + +void AsyncTCPSocketBase::SetError(int error) { + return socket_->SetError(error); +} + +int AsyncTCPSocketBase::SendTo(const void *pv, size_t cb, + const SocketAddress& addr, + const rtc::PacketOptions& options) { + if (addr == GetRemoteAddress()) + return Send(pv, cb, options); + + ASSERT(false); + socket_->SetError(ENOTCONN); + return -1; +} + +int AsyncTCPSocketBase::SendRaw(const void * pv, size_t cb) { + if (outpos_ + cb > outsize_) { + socket_->SetError(EMSGSIZE); + return -1; + } + + memcpy(outbuf_ + outpos_, pv, cb); + outpos_ += cb; + + return FlushOutBuffer(); +} + +int AsyncTCPSocketBase::FlushOutBuffer() { + int res = socket_->Send(outbuf_, outpos_); + if (res <= 0) { + return res; + } + if (static_cast(res) <= outpos_) { + outpos_ -= res; + } else { + ASSERT(false); + return -1; + } + if (outpos_ > 0) { + memmove(outbuf_, outbuf_ + res, outpos_); + } + return res; +} + +void AsyncTCPSocketBase::AppendToOutBuffer(const void* pv, size_t cb) { + ASSERT(outpos_ + cb < outsize_); + memcpy(outbuf_ + outpos_, pv, cb); + outpos_ += cb; +} + +void AsyncTCPSocketBase::OnConnectEvent(AsyncSocket* socket) { + SignalConnect(this); +} + +void AsyncTCPSocketBase::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket_.get() == socket); + + if (listen_) { + rtc::SocketAddress address; + rtc::AsyncSocket* new_socket = socket->Accept(&address); + if (!new_socket) { + // TODO: Do something better like forwarding the error + // to the user. + LOG(LS_ERROR) << "TCP accept failed with error " << socket_->GetError(); + return; + } + + HandleIncomingConnection(new_socket); + + // Prime a read event in case data is waiting. + new_socket->SignalReadEvent(new_socket); + } else { + int len = socket_->Recv(inbuf_ + inpos_, insize_ - inpos_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + if (!socket_->IsBlocking()) { + LOG(LS_ERROR) << "Recv() returned error: " << socket_->GetError(); + } + return; + } + + inpos_ += len; + + ProcessInput(inbuf_, &inpos_); + + if (inpos_ >= insize_) { + LOG(LS_ERROR) << "input buffer overflow"; + ASSERT(false); + inpos_ = 0; + } + } +} + +void AsyncTCPSocketBase::OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket_.get() == socket); + + if (outpos_ > 0) { + FlushOutBuffer(); + } + + if (outpos_ == 0) { + SignalReadyToSend(this); + } +} + +void AsyncTCPSocketBase::OnCloseEvent(AsyncSocket* socket, int error) { + SignalClose(this, error); +} + +// AsyncTCPSocket +// Binds and connects |socket| and creates AsyncTCPSocket for +// it. Takes ownership of |socket|. Returns NULL if bind() or +// connect() fail (|socket| is destroyed in that case). +AsyncTCPSocket* AsyncTCPSocket::Create( + AsyncSocket* socket, + const SocketAddress& bind_address, + const SocketAddress& remote_address) { + return new AsyncTCPSocket(AsyncTCPSocketBase::ConnectSocket( + socket, bind_address, remote_address), false); +} + +AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket, bool listen) + : AsyncTCPSocketBase(socket, listen, kBufSize) { +} + +int AsyncTCPSocket::Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) { + if (cb > kBufSize) { + SetError(EMSGSIZE); + return -1; + } + + // If we are blocking on send, then silently drop this packet + if (!IsOutBufferEmpty()) + return static_cast(cb); + + PacketLength pkt_len = HostToNetwork16(static_cast(cb)); + AppendToOutBuffer(&pkt_len, kPacketLenSize); + AppendToOutBuffer(pv, cb); + + int res = FlushOutBuffer(); + if (res <= 0) { + // drop packet if we made no progress + ClearOutBuffer(); + return res; + } + + // We claim to have sent the whole thing, even if we only sent partial + return static_cast(cb); +} + +void AsyncTCPSocket::ProcessInput(char * data, size_t* len) { + SocketAddress remote_addr(GetRemoteAddress()); + + while (true) { + if (*len < kPacketLenSize) + return; + + PacketLength pkt_len = rtc::GetBE16(data); + if (*len < kPacketLenSize + pkt_len) + return; + + SignalReadPacket(this, data + kPacketLenSize, pkt_len, remote_addr, + CreatePacketTime(0)); + + *len -= kPacketLenSize + pkt_len; + if (*len > 0) { + memmove(data, data + kPacketLenSize + pkt_len, *len); + } + } +} + +void AsyncTCPSocket::HandleIncomingConnection(AsyncSocket* socket) { + SignalNewConnection(this, new AsyncTCPSocket(socket, false)); +} + +} // namespace rtc diff --git a/webrtc/base/asynctcpsocket.h b/webrtc/base/asynctcpsocket.h new file mode 100644 index 000000000..ddee2615a --- /dev/null +++ b/webrtc/base/asynctcpsocket.h @@ -0,0 +1,100 @@ +/* + * Copyright 2004 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_BASE_ASYNCTCPSOCKET_H_ +#define WEBRTC_BASE_ASYNCTCPSOCKET_H_ + +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +// Simulates UDP semantics over TCP. Send and Recv packet sizes +// are preserved, and drops packets silently on Send, rather than +// buffer them in user space. +class AsyncTCPSocketBase : public AsyncPacketSocket { + public: + AsyncTCPSocketBase(AsyncSocket* socket, bool listen, size_t max_packet_size); + virtual ~AsyncTCPSocketBase(); + + // Pure virtual methods to send and recv data. + virtual int Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) = 0; + virtual void ProcessInput(char* data, size_t* len) = 0; + // Signals incoming connection. + virtual void HandleIncomingConnection(AsyncSocket* socket) = 0; + + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, + const rtc::PacketOptions& options); + virtual int Close(); + + virtual State GetState() const; + virtual int GetOption(Socket::Option opt, int* value); + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + protected: + // Binds and connects |socket| and creates AsyncTCPSocket for + // it. Takes ownership of |socket|. Returns NULL if bind() or + // connect() fail (|socket| is destroyed in that case). + static AsyncSocket* ConnectSocket(AsyncSocket* socket, + const SocketAddress& bind_address, + const SocketAddress& remote_address); + virtual int SendRaw(const void* pv, size_t cb); + int FlushOutBuffer(); + // Add data to |outbuf_|. + void AppendToOutBuffer(const void* pv, size_t cb); + + // Helper methods for |outpos_|. + bool IsOutBufferEmpty() const { return outpos_ == 0; } + void ClearOutBuffer() { outpos_ = 0; } + + private: + // Called by the underlying socket + void OnConnectEvent(AsyncSocket* socket); + void OnReadEvent(AsyncSocket* socket); + void OnWriteEvent(AsyncSocket* socket); + void OnCloseEvent(AsyncSocket* socket, int error); + + scoped_ptr socket_; + bool listen_; + char* inbuf_, * outbuf_; + size_t insize_, inpos_, outsize_, outpos_; + + DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocketBase); +}; + +class AsyncTCPSocket : public AsyncTCPSocketBase { + public: + // Binds and connects |socket| and creates AsyncTCPSocket for + // it. Takes ownership of |socket|. Returns NULL if bind() or + // connect() fail (|socket| is destroyed in that case). + static AsyncTCPSocket* Create(AsyncSocket* socket, + const SocketAddress& bind_address, + const SocketAddress& remote_address); + AsyncTCPSocket(AsyncSocket* socket, bool listen); + virtual ~AsyncTCPSocket() {} + + virtual int Send(const void* pv, size_t cb, + const rtc::PacketOptions& options); + virtual void ProcessInput(char* data, size_t* len); + virtual void HandleIncomingConnection(AsyncSocket* socket); + + private: + DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCTCPSOCKET_H_ diff --git a/webrtc/base/asynctcpsocket_unittest.cc b/webrtc/base/asynctcpsocket_unittest.cc new file mode 100644 index 000000000..b93175864 --- /dev/null +++ b/webrtc/base/asynctcpsocket_unittest.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2004 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 "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/virtualsocketserver.h" + +namespace rtc { + +class AsyncTCPSocketTest + : public testing::Test, + public sigslot::has_slots<> { + public: + AsyncTCPSocketTest() + : pss_(new rtc::PhysicalSocketServer), + vss_(new rtc::VirtualSocketServer(pss_.get())), + socket_(vss_->CreateAsyncSocket(SOCK_STREAM)), + tcp_socket_(new AsyncTCPSocket(socket_, true)), + ready_to_send_(false) { + tcp_socket_->SignalReadyToSend.connect(this, + &AsyncTCPSocketTest::OnReadyToSend); + } + + void OnReadyToSend(rtc::AsyncPacketSocket* socket) { + ready_to_send_ = true; + } + + protected: + scoped_ptr pss_; + scoped_ptr vss_; + AsyncSocket* socket_; + scoped_ptr tcp_socket_; + bool ready_to_send_; +}; + +TEST_F(AsyncTCPSocketTest, OnWriteEvent) { + EXPECT_FALSE(ready_to_send_); + socket_->SignalWriteEvent(socket_); + EXPECT_TRUE(ready_to_send_); +} + +} // namespace rtc diff --git a/webrtc/base/asyncudpsocket.cc b/webrtc/base/asyncudpsocket.cc new file mode 100644 index 000000000..3e2ecc4cd --- /dev/null +++ b/webrtc/base/asyncudpsocket.cc @@ -0,0 +1,122 @@ +/* + * Copyright 2004 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 "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +static const int BUF_SIZE = 64 * 1024; + +AsyncUDPSocket* AsyncUDPSocket::Create( + AsyncSocket* socket, + const SocketAddress& bind_address) { + scoped_ptr owned_socket(socket); + if (socket->Bind(bind_address) < 0) { + LOG(LS_ERROR) << "Bind() failed with error " << socket->GetError(); + return NULL; + } + return new AsyncUDPSocket(owned_socket.release()); +} + +AsyncUDPSocket* AsyncUDPSocket::Create(SocketFactory* factory, + const SocketAddress& bind_address) { + AsyncSocket* socket = + factory->CreateAsyncSocket(bind_address.family(), SOCK_DGRAM); + if (!socket) + return NULL; + return Create(socket, bind_address); +} + +AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket) + : socket_(socket) { + ASSERT(socket_); + size_ = BUF_SIZE; + buf_ = new char[size_]; + + // The socket should start out readable but not writable. + socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &AsyncUDPSocket::OnWriteEvent); +} + +AsyncUDPSocket::~AsyncUDPSocket() { + delete [] buf_; +} + +SocketAddress AsyncUDPSocket::GetLocalAddress() const { + return socket_->GetLocalAddress(); +} + +SocketAddress AsyncUDPSocket::GetRemoteAddress() const { + return socket_->GetRemoteAddress(); +} + +int AsyncUDPSocket::Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) { + return socket_->Send(pv, cb); +} + +int AsyncUDPSocket::SendTo(const void *pv, size_t cb, + const SocketAddress& addr, + const rtc::PacketOptions& options) { + return socket_->SendTo(pv, cb, addr); +} + +int AsyncUDPSocket::Close() { + return socket_->Close(); +} + +AsyncUDPSocket::State AsyncUDPSocket::GetState() const { + return STATE_BOUND; +} + +int AsyncUDPSocket::GetOption(Socket::Option opt, int* value) { + return socket_->GetOption(opt, value); +} + +int AsyncUDPSocket::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int AsyncUDPSocket::GetError() const { + return socket_->GetError(); +} + +void AsyncUDPSocket::SetError(int error) { + return socket_->SetError(error); +} + +void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket_.get() == socket); + + SocketAddress remote_addr; + int len = socket_->RecvFrom(buf_, size_, &remote_addr); + if (len < 0) { + // An error here typically means we got an ICMP error in response to our + // send datagram, indicating the remote address was unreachable. + // When doing ICE, this kind of thing will often happen. + // TODO: Do something better like forwarding the error to the user. + SocketAddress local_addr = socket_->GetLocalAddress(); + LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToSensitiveString() << "] " + << "receive failed with error " << socket_->GetError(); + return; + } + + // TODO: Make sure that we got all of the packet. + // If we did not, then we should resize our buffer to be large enough. + SignalReadPacket(this, buf_, static_cast(len), remote_addr, + CreatePacketTime(0)); +} + +void AsyncUDPSocket::OnWriteEvent(AsyncSocket* socket) { + SignalReadyToSend(this); +} + +} // namespace rtc diff --git a/webrtc/base/asyncudpsocket.h b/webrtc/base/asyncudpsocket.h new file mode 100644 index 000000000..ac64dca68 --- /dev/null +++ b/webrtc/base/asyncudpsocket.h @@ -0,0 +1,63 @@ +/* + * Copyright 2004 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_BASE_ASYNCUDPSOCKET_H_ +#define WEBRTC_BASE_ASYNCUDPSOCKET_H_ + +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncUDPSocket : public AsyncPacketSocket { + public: + // Binds |socket| and creates AsyncUDPSocket for it. Takes ownership + // of |socket|. Returns NULL if bind() fails (|socket| is destroyed + // in that case). + static AsyncUDPSocket* Create(AsyncSocket* socket, + const SocketAddress& bind_address); + // Creates a new socket for sending asynchronous UDP packets using an + // asynchronous socket from the given factory. + static AsyncUDPSocket* Create(SocketFactory* factory, + const SocketAddress& bind_address); + explicit AsyncUDPSocket(AsyncSocket* socket); + virtual ~AsyncUDPSocket(); + + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Send(const void *pv, size_t cb, + const rtc::PacketOptions& options); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, + const rtc::PacketOptions& options); + virtual int Close(); + + virtual State GetState() const; + virtual int GetOption(Socket::Option opt, int* value); + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + private: + // Called when the underlying socket is ready to be read from. + void OnReadEvent(AsyncSocket* socket); + // Called when the underlying socket is ready to send. + void OnWriteEvent(AsyncSocket* socket); + + scoped_ptr socket_; + char* buf_; + size_t size_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ASYNCUDPSOCKET_H_ diff --git a/webrtc/base/asyncudpsocket_unittest.cc b/webrtc/base/asyncudpsocket_unittest.cc new file mode 100644 index 000000000..bd65940fc --- /dev/null +++ b/webrtc/base/asyncudpsocket_unittest.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2004 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 "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/virtualsocketserver.h" + +namespace rtc { + +class AsyncUdpSocketTest + : public testing::Test, + public sigslot::has_slots<> { + public: + AsyncUdpSocketTest() + : pss_(new rtc::PhysicalSocketServer), + vss_(new rtc::VirtualSocketServer(pss_.get())), + socket_(vss_->CreateAsyncSocket(SOCK_DGRAM)), + udp_socket_(new AsyncUDPSocket(socket_)), + ready_to_send_(false) { + udp_socket_->SignalReadyToSend.connect(this, + &AsyncUdpSocketTest::OnReadyToSend); + } + + void OnReadyToSend(rtc::AsyncPacketSocket* socket) { + ready_to_send_ = true; + } + + protected: + scoped_ptr pss_; + scoped_ptr vss_; + AsyncSocket* socket_; + scoped_ptr udp_socket_; + bool ready_to_send_; +}; + +TEST_F(AsyncUdpSocketTest, OnWriteEvent) { + EXPECT_FALSE(ready_to_send_); + socket_->SignalWriteEvent(socket_); + EXPECT_TRUE(ready_to_send_); +} + +} // namespace rtc diff --git a/webrtc/base/atomicops.h b/webrtc/base/atomicops.h new file mode 100644 index 000000000..6096e8c08 --- /dev/null +++ b/webrtc/base/atomicops.h @@ -0,0 +1,149 @@ +/* + * Copyright 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_BASE_ATOMICOPS_H_ +#define WEBRTC_BASE_ATOMICOPS_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +// A single-producer, single-consumer, fixed-size queue. +// All methods not ending in Unsafe can be safely called without locking, +// provided that calls to consumer methods (Peek/Pop) or producer methods (Push) +// only happen on a single thread per method type. If multiple threads need to +// read simultaneously or write simultaneously, other synchronization is +// necessary. Synchronization is also required if a call into any Unsafe method +// could happen at the same time as a call to any other method. +template +class FixedSizeLockFreeQueue { + private: +// Atomic primitives and memory barrier +#if defined(__arm__) + typedef uint32 Atomic32; + + // Copied from google3/base/atomicops-internals-arm-v6plus.h + static inline void MemoryBarrier() { + asm volatile("dmb":::"memory"); + } + + // Adapted from google3/base/atomicops-internals-arm-v6plus.h + static inline void AtomicIncrement(volatile Atomic32* ptr) { + Atomic32 str_success, value; + asm volatile ( + "1:\n" + "ldrex %1, [%2]\n" + "add %1, %1, #1\n" + "strex %0, %1, [%2]\n" + "teq %0, #0\n" + "bne 1b" + : "=&r"(str_success), "=&r"(value) + : "r" (ptr) + : "cc", "memory"); + } +#elif !defined(SKIP_ATOMIC_CHECK) +#error "No atomic operations defined for the given architecture." +#endif + + public: + // Constructs an empty queue, with capacity 0. + FixedSizeLockFreeQueue() : pushed_count_(0), + popped_count_(0), + capacity_(0), + data_() {} + // Constructs an empty queue with the given capacity. + FixedSizeLockFreeQueue(size_t capacity) : pushed_count_(0), + popped_count_(0), + capacity_(capacity), + data_(new T[capacity]) {} + + // Pushes a value onto the queue. Returns true if the value was successfully + // pushed (there was space in the queue). This method can be safely called at + // the same time as PeekFront/PopFront. + bool PushBack(T value) { + if (capacity_ == 0) { + LOG(LS_WARNING) << "Queue capacity is 0."; + return false; + } + if (IsFull()) { + return false; + } + + data_[pushed_count_ % capacity_] = value; + // Make sure the data is written before the count is incremented, so other + // threads can't see the value exists before being able to read it. + MemoryBarrier(); + AtomicIncrement(&pushed_count_); + return true; + } + + // Retrieves the oldest value pushed onto the queue. Returns true if there was + // an item to peek (the queue was non-empty). This method can be safely called + // at the same time as PushBack. + bool PeekFront(T* value_out) { + if (capacity_ == 0) { + LOG(LS_WARNING) << "Queue capacity is 0."; + return false; + } + if (IsEmpty()) { + return false; + } + + *value_out = data_[popped_count_ % capacity_]; + return true; + } + + // Retrieves the oldest value pushed onto the queue and removes it from the + // queue. Returns true if there was an item to pop (the queue was non-empty). + // This method can be safely called at the same time as PushBack. + bool PopFront(T* value_out) { + if (PeekFront(value_out)) { + AtomicIncrement(&popped_count_); + return true; + } + return false; + } + + // Clears the current items in the queue and sets the new (fixed) size. This + // method cannot be called at the same time as any other method. + void ClearAndResizeUnsafe(int new_capacity) { + capacity_ = new_capacity; + data_.reset(new T[new_capacity]); + pushed_count_ = 0; + popped_count_ = 0; + } + + // Returns true if there is no space left in the queue for new elements. + int IsFull() const { return pushed_count_ == popped_count_ + capacity_; } + // Returns true if there are no elements in the queue. + int IsEmpty() const { return pushed_count_ == popped_count_; } + // Returns the current number of elements in the queue. This is always in the + // range [0, capacity] + size_t Size() const { return pushed_count_ - popped_count_; } + + // Returns the capacity of the queue (max size). + size_t capacity() const { return capacity_; } + + private: + volatile Atomic32 pushed_count_; + volatile Atomic32 popped_count_; + size_t capacity_; + rtc::scoped_ptr data_; + DISALLOW_COPY_AND_ASSIGN(FixedSizeLockFreeQueue); +}; + +} + +#endif // WEBRTC_BASE_ATOMICOPS_H_ diff --git a/webrtc/base/atomicops_unittest.cc b/webrtc/base/atomicops_unittest.cc new file mode 100644 index 000000000..5152c4de6 --- /dev/null +++ b/webrtc/base/atomicops_unittest.cc @@ -0,0 +1,79 @@ +/* + * Copyright 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. + */ + +#if !defined(__arm__) +// For testing purposes, define faked versions of the atomic operations +#include "webrtc/base/basictypes.h" +namespace rtc { +typedef uint32 Atomic32; +static inline void MemoryBarrier() { } +static inline void AtomicIncrement(volatile Atomic32* ptr) { + *ptr = *ptr + 1; +} +} +#define SKIP_ATOMIC_CHECK +#endif + +#include "webrtc/base/atomicops.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" + +TEST(FixedSizeLockFreeQueueTest, TestDefaultConstruct) { + rtc::FixedSizeLockFreeQueue queue; + EXPECT_EQ(0u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + EXPECT_FALSE(queue.PushBack(1)); + int val; + EXPECT_FALSE(queue.PopFront(&val)); +} + +TEST(FixedSizeLockFreeQueueTest, TestConstruct) { + rtc::FixedSizeLockFreeQueue queue(5); + EXPECT_EQ(5u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + int val; + EXPECT_FALSE(queue.PopFront(&val)); +} + +TEST(FixedSizeLockFreeQueueTest, TestPushPop) { + rtc::FixedSizeLockFreeQueue queue(2); + EXPECT_EQ(2u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + EXPECT_TRUE(queue.PushBack(1)); + EXPECT_EQ(1u, queue.Size()); + EXPECT_TRUE(queue.PushBack(2)); + EXPECT_EQ(2u, queue.Size()); + EXPECT_FALSE(queue.PushBack(3)); + EXPECT_EQ(2u, queue.Size()); + int val; + EXPECT_TRUE(queue.PopFront(&val)); + EXPECT_EQ(1, val); + EXPECT_EQ(1u, queue.Size()); + EXPECT_TRUE(queue.PopFront(&val)); + EXPECT_EQ(2, val); + EXPECT_EQ(0u, queue.Size()); + EXPECT_FALSE(queue.PopFront(&val)); + EXPECT_EQ(0u, queue.Size()); +} + +TEST(FixedSizeLockFreeQueueTest, TestResize) { + rtc::FixedSizeLockFreeQueue queue(2); + EXPECT_EQ(2u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + EXPECT_TRUE(queue.PushBack(1)); + EXPECT_EQ(1u, queue.Size()); + + queue.ClearAndResizeUnsafe(5); + EXPECT_EQ(5u, queue.capacity()); + EXPECT_EQ(0u, queue.Size()); + int val; + EXPECT_FALSE(queue.PopFront(&val)); +} diff --git a/webrtc/base/autodetectproxy.cc b/webrtc/base/autodetectproxy.cc new file mode 100644 index 000000000..bc54b9638 --- /dev/null +++ b/webrtc/base/autodetectproxy.cc @@ -0,0 +1,282 @@ +/* + * Copyright 2004 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 "webrtc/base/autodetectproxy.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/nethelpers.h" + +namespace rtc { + +static const ProxyType TEST_ORDER[] = { + PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN +}; + +static const int kSavedStringLimit = 128; + +static void SaveStringToStack(char *dst, + const std::string &src, + size_t dst_size) { + strncpy(dst, src.c_str(), dst_size - 1); + dst[dst_size - 1] = '\0'; +} + +AutoDetectProxy::AutoDetectProxy(const std::string& user_agent) + : agent_(user_agent), resolver_(NULL), socket_(NULL), next_(0) { +} + +AutoDetectProxy::~AutoDetectProxy() { + if (resolver_) { + resolver_->Destroy(false); + } +} + +void AutoDetectProxy::DoWork() { + // TODO: Try connecting to server_url without proxy first here? + if (!server_url_.empty()) { + LOG(LS_INFO) << "GetProxySettingsForUrl(" << server_url_ << ") - start"; + GetProxyForUrl(agent_.c_str(), server_url_.c_str(), &proxy_); + LOG(LS_INFO) << "GetProxySettingsForUrl - stop"; + } + Url url(proxy_.address.HostAsURIString()); + if (url.valid()) { + LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host"; + proxy_.address.SetIP(url.host()); + } + LOG(LS_INFO) << "AutoDetectProxy found proxy at " << proxy_.address; + if (proxy_.type == PROXY_UNKNOWN) { + LOG(LS_INFO) << "AutoDetectProxy initiating proxy classification"; + Next(); + // Process I/O until Stop() + Thread::Current()->ProcessMessages(kForever); + // Clean up the autodetect socket, from the thread that created it + delete socket_; + } + // TODO: If we found a proxy, try to use it to verify that it + // works by sending a request to server_url. This could either be + // done here or by the HttpPortAllocator. +} + +void AutoDetectProxy::OnMessage(Message *msg) { + if (MSG_UNRESOLVABLE == msg->message_id) { + // If we can't resolve the proxy, skip straight to failure. + Complete(PROXY_UNKNOWN); + } else if (MSG_TIMEOUT == msg->message_id) { + OnCloseEvent(socket_, ETIMEDOUT); + } else { + // This must be the ST_MSG_WORKER_DONE message that deletes the + // AutoDetectProxy object. We have observed crashes within this stack that + // seem to be highly reproducible for a small subset of users and thus are + // probably correlated with a specific proxy setting, so copy potentially + // relevant information onto the stack to make it available in Windows + // minidumps. + + // Save the user agent and the number of auto-detection passes that we + // needed. + char agent[kSavedStringLimit]; + SaveStringToStack(agent, agent_, sizeof agent); + + int next = next_; + + // Now the detected proxy config (minus the password field, which could be + // sensitive). + ProxyType type = proxy().type; + + char address_hostname[kSavedStringLimit]; + SaveStringToStack(address_hostname, + proxy().address.hostname(), + sizeof address_hostname); + + IPAddress address_ip = proxy().address.ipaddr(); + + uint16 address_port = proxy().address.port(); + + char autoconfig_url[kSavedStringLimit]; + SaveStringToStack(autoconfig_url, + proxy().autoconfig_url, + sizeof autoconfig_url); + + bool autodetect = proxy().autodetect; + + char bypass_list[kSavedStringLimit]; + SaveStringToStack(bypass_list, proxy().bypass_list, sizeof bypass_list); + + char username[kSavedStringLimit]; + SaveStringToStack(username, proxy().username, sizeof username); + + SignalThread::OnMessage(msg); + + // Log the gathered data at a log level that will never actually be enabled + // so that the compiler is forced to retain the data on the stack. + LOG(LS_SENSITIVE) << agent << " " << next << " " << type << " " + << address_hostname << " " << address_ip << " " + << address_port << " " << autoconfig_url << " " + << autodetect << " " << bypass_list << " " << username; + } +} + +void AutoDetectProxy::OnResolveResult(AsyncResolverInterface* resolver) { + if (resolver != resolver_) { + return; + } + int error = resolver_->GetError(); + if (error == 0) { + LOG(LS_VERBOSE) << "Resolved " << proxy_.address << " to " + << resolver_->address(); + proxy_.address = resolver_->address(); + if (!DoConnect()) { + Thread::Current()->Post(this, MSG_TIMEOUT); + } + } else { + LOG(LS_INFO) << "Failed to resolve " << resolver_->address(); + resolver_->Destroy(false); + resolver_ = NULL; + proxy_.address = SocketAddress(); + Thread::Current()->Post(this, MSG_UNRESOLVABLE); + } +} + +void AutoDetectProxy::Next() { + if (TEST_ORDER[next_] >= PROXY_UNKNOWN) { + Complete(PROXY_UNKNOWN); + return; + } + + LOG(LS_VERBOSE) << "AutoDetectProxy connecting to " + << proxy_.address.ToSensitiveString(); + + if (socket_) { + Thread::Current()->Clear(this, MSG_TIMEOUT); + Thread::Current()->Clear(this, MSG_UNRESOLVABLE); + socket_->Close(); + Thread::Current()->Dispose(socket_); + socket_ = NULL; + } + int timeout = 2000; + if (proxy_.address.IsUnresolvedIP()) { + // Launch an asyncresolver. This thread will spin waiting for it. + timeout += 2000; + if (!resolver_) { + resolver_ = new AsyncResolver(); + } + resolver_->SignalDone.connect(this, &AutoDetectProxy::OnResolveResult); + resolver_->Start(proxy_.address); + } else { + if (!DoConnect()) { + Thread::Current()->Post(this, MSG_TIMEOUT); + return; + } + } + Thread::Current()->PostDelayed(timeout, this, MSG_TIMEOUT); +} + +bool AutoDetectProxy::DoConnect() { + if (resolver_) { + resolver_->Destroy(false); + resolver_ = NULL; + } + socket_ = + Thread::Current()->socketserver()->CreateAsyncSocket( + proxy_.address.family(), SOCK_STREAM); + if (!socket_) { + LOG(LS_VERBOSE) << "Unable to create socket for " << proxy_.address; + return false; + } + socket_->SignalConnectEvent.connect(this, &AutoDetectProxy::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &AutoDetectProxy::OnReadEvent); + socket_->SignalCloseEvent.connect(this, &AutoDetectProxy::OnCloseEvent); + socket_->Connect(proxy_.address); + return true; +} + +void AutoDetectProxy::Complete(ProxyType type) { + Thread::Current()->Clear(this, MSG_TIMEOUT); + Thread::Current()->Clear(this, MSG_UNRESOLVABLE); + if (socket_) { + socket_->Close(); + } + + proxy_.type = type; + LoggingSeverity sev = (proxy_.type == PROXY_UNKNOWN) ? LS_ERROR : LS_INFO; + LOG_V(sev) << "AutoDetectProxy detected " + << proxy_.address.ToSensitiveString() + << " as type " << proxy_.type; + + Thread::Current()->Quit(); +} + +void AutoDetectProxy::OnConnectEvent(AsyncSocket * socket) { + std::string probe; + + switch (TEST_ORDER[next_]) { + case PROXY_HTTPS: + probe.assign("CONNECT www.google.com:443 HTTP/1.0\r\n" + "User-Agent: "); + probe.append(agent_); + probe.append("\r\n" + "Host: www.google.com\r\n" + "Content-Length: 0\r\n" + "Proxy-Connection: Keep-Alive\r\n" + "\r\n"); + break; + case PROXY_SOCKS5: + probe.assign("\005\001\000", 3); + break; + default: + ASSERT(false); + return; + } + + LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_] + << " sending " << probe.size() << " bytes"; + socket_->Send(probe.data(), probe.size()); +} + +void AutoDetectProxy::OnReadEvent(AsyncSocket * socket) { + char data[257]; + int len = socket_->Recv(data, 256); + if (len > 0) { + data[len] = 0; + LOG(LS_VERBOSE) << "AutoDetectProxy read " << len << " bytes"; + } + + switch (TEST_ORDER[next_]) { + case PROXY_HTTPS: + if ((len >= 2) && (data[0] == '\x05')) { + Complete(PROXY_SOCKS5); + return; + } + if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) { + Complete(PROXY_HTTPS); + return; + } + break; + case PROXY_SOCKS5: + if ((len >= 2) && (data[0] == '\x05')) { + Complete(PROXY_SOCKS5); + return; + } + break; + default: + ASSERT(false); + return; + } + + ++next_; + Next(); +} + +void AutoDetectProxy::OnCloseEvent(AsyncSocket * socket, int error) { + LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error; + ++next_; + Next(); +} + +} // namespace rtc diff --git a/webrtc/base/autodetectproxy.h b/webrtc/base/autodetectproxy.h new file mode 100644 index 000000000..45e9c40be --- /dev/null +++ b/webrtc/base/autodetectproxy.h @@ -0,0 +1,90 @@ +/* + * Copyright 2004 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_BASE_AUTODETECTPROXY_H_ +#define WEBRTC_BASE_AUTODETECTPROXY_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/proxydetect.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/signalthread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// AutoDetectProxy +/////////////////////////////////////////////////////////////////////////////// + +class AsyncResolverInterface; +class AsyncSocket; + +class AutoDetectProxy : public SignalThread { + public: + explicit AutoDetectProxy(const std::string& user_agent); + + const ProxyInfo& proxy() const { return proxy_; } + + void set_server_url(const std::string& url) { + server_url_ = url; + } + void set_proxy(const SocketAddress& proxy) { + proxy_.type = PROXY_UNKNOWN; + proxy_.address = proxy; + } + void set_auth_info(bool use_auth, const std::string& username, + const CryptString& password) { + if (use_auth) { + proxy_.username = username; + proxy_.password = password; + } + } + // Default implementation of GetProxySettingsForUrl. Override for special + // implementation. + virtual bool GetProxyForUrl(const char* agent, const char* url, + rtc::ProxyInfo* proxy) { + return GetProxySettingsForUrl(agent, url, proxy, true); + } + enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE, + MSG_UNRESOLVABLE, + ADP_MSG_FIRST_AVAILABLE}; + + protected: + virtual ~AutoDetectProxy(); + + // SignalThread Interface + virtual void DoWork(); + virtual void OnMessage(Message *msg); + + void Next(); + void Complete(ProxyType type); + + void OnConnectEvent(AsyncSocket * socket); + void OnReadEvent(AsyncSocket * socket); + void OnCloseEvent(AsyncSocket * socket, int error); + void OnResolveResult(AsyncResolverInterface* resolver); + bool DoConnect(); + + private: + std::string agent_; + std::string server_url_; + ProxyInfo proxy_; + AsyncResolverInterface* resolver_; + AsyncSocket* socket_; + int next_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(AutoDetectProxy); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_AUTODETECTPROXY_H_ diff --git a/webrtc/base/autodetectproxy_unittest.cc b/webrtc/base/autodetectproxy_unittest.cc new file mode 100644 index 000000000..80f220f2f --- /dev/null +++ b/webrtc/base/autodetectproxy_unittest.cc @@ -0,0 +1,131 @@ +/* + * Copyright 2004 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 "webrtc/base/autodetectproxy.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" + +namespace rtc { + +static const char kUserAgent[] = ""; +static const char kPath[] = "/"; +static const char kHost[] = "relay.google.com"; +static const uint16 kPort = 443; +static const bool kSecure = true; +// At most, AutoDetectProxy should take ~6 seconds. Each connect step is +// allotted 2 seconds, with the initial resolution + connect given an +// extra 2 seconds. The slowest case is: +// 1) Resolution + HTTPS takes full 4 seconds and fails (but resolution +// succeeds). +// 2) SOCKS5 takes the full 2 seconds. +// Socket creation time seems unbounded, and has been observed to take >1 second +// on a linux machine under load. As such, we allow for 10 seconds for timeout, +// though could still end up with some flakiness. +static const int kTimeoutMs = 10000; + +class AutoDetectProxyTest : public testing::Test, public sigslot::has_slots<> { + public: + AutoDetectProxyTest() : auto_detect_proxy_(NULL), done_(false) {} + + protected: + bool Create(const std::string &user_agent, + const std::string &path, + const std::string &host, + uint16 port, + bool secure, + bool startnow) { + auto_detect_proxy_ = new AutoDetectProxy(user_agent); + EXPECT_TRUE(auto_detect_proxy_ != NULL); + if (!auto_detect_proxy_) { + return false; + } + Url host_url(path, host, port); + host_url.set_secure(secure); + auto_detect_proxy_->set_server_url(host_url.url()); + auto_detect_proxy_->SignalWorkDone.connect( + this, + &AutoDetectProxyTest::OnWorkDone); + if (startnow) { + auto_detect_proxy_->Start(); + } + return true; + } + + bool Run(int timeout_ms) { + EXPECT_TRUE_WAIT(done_, timeout_ms); + return done_; + } + + void SetProxy(const SocketAddress& proxy) { + auto_detect_proxy_->set_proxy(proxy); + } + + void Start() { + auto_detect_proxy_->Start(); + } + + void TestCopesWithProxy(const SocketAddress& proxy) { + // Tests that at least autodetect doesn't crash for a given proxy address. + ASSERT_TRUE(Create(kUserAgent, + kPath, + kHost, + kPort, + kSecure, + false)); + SetProxy(proxy); + Start(); + ASSERT_TRUE(Run(kTimeoutMs)); + } + + private: + void OnWorkDone(rtc::SignalThread *thread) { + AutoDetectProxy *auto_detect_proxy = + static_cast(thread); + EXPECT_TRUE(auto_detect_proxy == auto_detect_proxy_); + auto_detect_proxy_ = NULL; + auto_detect_proxy->Release(); + done_ = true; + } + + AutoDetectProxy *auto_detect_proxy_; + bool done_; +}; + +TEST_F(AutoDetectProxyTest, TestDetectUnresolvedProxy) { + TestCopesWithProxy(rtc::SocketAddress("localhost", 9999)); +} + +TEST_F(AutoDetectProxyTest, TestDetectUnresolvableProxy) { + TestCopesWithProxy(rtc::SocketAddress("invalid", 9999)); +} + +TEST_F(AutoDetectProxyTest, TestDetectIPv6Proxy) { + TestCopesWithProxy(rtc::SocketAddress("::1", 9999)); +} + +TEST_F(AutoDetectProxyTest, TestDetectIPv4Proxy) { + TestCopesWithProxy(rtc::SocketAddress("127.0.0.1", 9999)); +} + +// Test that proxy detection completes successfully. (Does not actually verify +// the correct detection result since we don't know what proxy to expect on an +// arbitrary machine.) +TEST_F(AutoDetectProxyTest, TestProxyDetection) { + ASSERT_TRUE(Create(kUserAgent, + kPath, + kHost, + kPort, + kSecure, + true)); + ASSERT_TRUE(Run(kTimeoutMs)); +} + +} // namespace rtc diff --git a/webrtc/base/bandwidthsmoother.cc b/webrtc/base/bandwidthsmoother.cc new file mode 100644 index 000000000..0cbf3f3d1 --- /dev/null +++ b/webrtc/base/bandwidthsmoother.cc @@ -0,0 +1,84 @@ +/* + * Copyright 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 "webrtc/base/bandwidthsmoother.h" + +#include + +namespace rtc { + +BandwidthSmoother::BandwidthSmoother(int initial_bandwidth_guess, + uint32 time_between_increase, + double percent_increase, + size_t samples_count_to_average, + double min_sample_count_percent) + : time_between_increase_(time_between_increase), + percent_increase_(rtc::_max(1.0, percent_increase)), + time_at_last_change_(0), + bandwidth_estimation_(initial_bandwidth_guess), + accumulator_(samples_count_to_average), + min_sample_count_percent_( + rtc::_min(1.0, + rtc::_max(0.0, min_sample_count_percent))) { +} + +// Samples a new bandwidth measurement +// returns true if the bandwidth estimation changed +bool BandwidthSmoother::Sample(uint32 sample_time, int bandwidth) { + if (bandwidth < 0) { + return false; + } + + accumulator_.AddSample(bandwidth); + + if (accumulator_.count() < static_cast( + accumulator_.max_count() * min_sample_count_percent_)) { + // We have not collected enough samples yet. + return false; + } + + // Replace bandwidth with the mean of sampled bandwidths. + const int mean_bandwidth = static_cast(accumulator_.ComputeMean()); + + if (mean_bandwidth < bandwidth_estimation_) { + time_at_last_change_ = sample_time; + bandwidth_estimation_ = mean_bandwidth; + return true; + } + + const int old_bandwidth_estimation = bandwidth_estimation_; + const double increase_threshold_d = percent_increase_ * bandwidth_estimation_; + if (increase_threshold_d > INT_MAX) { + // If bandwidth goes any higher we would overflow. + return false; + } + + const int increase_threshold = static_cast(increase_threshold_d); + if (mean_bandwidth < increase_threshold) { + time_at_last_change_ = sample_time; + // The value of bandwidth_estimation remains the same if we don't exceed + // percent_increase_ * bandwidth_estimation_ for at least + // time_between_increase_ time. + } else if (sample_time >= time_at_last_change_ + time_between_increase_) { + time_at_last_change_ = sample_time; + if (increase_threshold == 0) { + // Bandwidth_estimation_ must be zero. Assume a jump from zero to a + // positive bandwidth means we have regained connectivity. + bandwidth_estimation_ = mean_bandwidth; + } else { + bandwidth_estimation_ = increase_threshold; + } + } + // Else don't make a change. + + return old_bandwidth_estimation != bandwidth_estimation_; +} + +} // namespace rtc diff --git a/webrtc/base/bandwidthsmoother.h b/webrtc/base/bandwidthsmoother.h new file mode 100644 index 000000000..cf5a25dfa --- /dev/null +++ b/webrtc/base/bandwidthsmoother.h @@ -0,0 +1,59 @@ +/* + * Copyright 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_BASE_BANDWIDTHSMOOTHER_H_ +#define WEBRTC_BASE_BANDWIDTHSMOOTHER_H_ + +#include "webrtc/base/rollingaccumulator.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +// The purpose of BandwidthSmoother is to smooth out bandwidth +// estimations so that 'trstate' messages can be triggered when we +// are "sure" there is sufficient bandwidth. To avoid frequent fluctuations, +// we take a slightly pessimistic view of our bandwidth. We only increase +// our estimation when we have sampled bandwidth measurements of values +// at least as large as the current estimation * percent_increase +// for at least time_between_increase time. If a sampled bandwidth +// is less than our current estimation we immediately decrease our estimation +// to that sampled value. +// We retain the initial bandwidth guess as our current bandwidth estimation +// until we have received (min_sample_count_percent * samples_count_to_average) +// number of samples. Min_sample_count_percent must be in range [0, 1]. +class BandwidthSmoother { + public: + BandwidthSmoother(int initial_bandwidth_guess, + uint32 time_between_increase, + double percent_increase, + size_t samples_count_to_average, + double min_sample_count_percent); + + // Samples a new bandwidth measurement. + // bandwidth is expected to be non-negative. + // returns true if the bandwidth estimation changed + bool Sample(uint32 sample_time, int bandwidth); + + int get_bandwidth_estimation() const { + return bandwidth_estimation_; + } + + private: + uint32 time_between_increase_; + double percent_increase_; + uint32 time_at_last_change_; + int bandwidth_estimation_; + RollingAccumulator accumulator_; + double min_sample_count_percent_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BANDWIDTHSMOOTHER_H_ diff --git a/webrtc/base/bandwidthsmoother_unittest.cc b/webrtc/base/bandwidthsmoother_unittest.cc new file mode 100644 index 000000000..132c0b13a --- /dev/null +++ b/webrtc/base/bandwidthsmoother_unittest.cc @@ -0,0 +1,116 @@ +/* + * Copyright 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 "webrtc/base/bandwidthsmoother.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +static const int kTimeBetweenIncrease = 10; +static const double kPercentIncrease = 1.1; +static const size_t kSamplesCountToAverage = 2; +static const double kMinSampleCountPercent = 1.0; + +TEST(BandwidthSmootherTest, TestSampleIncrease) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + int bandwidth_sample = 1000; + EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation()); + bandwidth_sample = + static_cast(bandwidth_sample * kPercentIncrease); + EXPECT_FALSE(mon.Sample(9, bandwidth_sample)); + EXPECT_TRUE(mon.Sample(10, bandwidth_sample)); + EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation()); + int next_expected_est = + static_cast(bandwidth_sample * kPercentIncrease); + bandwidth_sample *= 2; + EXPECT_TRUE(mon.Sample(20, bandwidth_sample)); + EXPECT_EQ(next_expected_est, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleIncreaseFromZero) { + BandwidthSmoother mon(0, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + const int kBandwidthSample = 1000; + EXPECT_EQ(0, mon.get_bandwidth_estimation()); + EXPECT_FALSE(mon.Sample(9, kBandwidthSample)); + EXPECT_TRUE(mon.Sample(10, kBandwidthSample)); + EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleDecrease) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + const int kBandwidthSample = 999; + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_FALSE(mon.Sample(1, kBandwidthSample)); + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_TRUE(mon.Sample(2, kBandwidthSample)); + EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleTooFewSamples) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + 10, // 10 samples. + 0.5); // 5 min samples. + + const int kBandwidthSample = 500; + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_FALSE(mon.Sample(1, kBandwidthSample)); + EXPECT_FALSE(mon.Sample(2, kBandwidthSample)); + EXPECT_FALSE(mon.Sample(3, kBandwidthSample)); + EXPECT_FALSE(mon.Sample(4, kBandwidthSample)); + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); + EXPECT_TRUE(mon.Sample(5, kBandwidthSample)); + EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleRollover) { + const int kHugeBandwidth = 2000000000; // > INT_MAX/1.1 + BandwidthSmoother mon(kHugeBandwidth, + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + EXPECT_FALSE(mon.Sample(10, INT_MAX)); + EXPECT_FALSE(mon.Sample(11, INT_MAX)); + EXPECT_EQ(kHugeBandwidth, mon.get_bandwidth_estimation()); +} + +TEST(BandwidthSmootherTest, TestSampleNegative) { + BandwidthSmoother mon(1000, // initial_bandwidth_guess + kTimeBetweenIncrease, + kPercentIncrease, + kSamplesCountToAverage, + kMinSampleCountPercent); + + EXPECT_FALSE(mon.Sample(10, -1)); + EXPECT_FALSE(mon.Sample(11, -1)); + EXPECT_EQ(1000, mon.get_bandwidth_estimation()); +} + +} // namespace rtc diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp new file mode 100644 index 000000000..f05c3251d --- /dev/null +++ b/webrtc/base/base.gyp @@ -0,0 +1,764 @@ +# Copyright (c) 2014 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. + +{ + 'includes': [ '../build/common.gypi', ], + 'conditions': [ + ['os_posix == 1 and OS != "mac" and OS != "ios"', { + 'conditions': [ + ['sysroot!=""', { + 'variables': { + 'pkg-config': '../../../build/linux/pkg-config-wrapper "<(sysroot)" "<(target_arch)"', + }, + }, { + 'variables': { + 'pkg-config': 'pkg-config' + }, + }], + ], + }], + ], + 'targets': [ + { + 'target_name': 'webrtc_base', + 'type': 'static_library', + 'defines': [ + 'FEATURE_ENABLE_SSL', + 'GTEST_RELATIVE_PATH', + 'LOGGING=1', + 'USE_WEBRTC_DEV_BRANCH', + ], + 'sources': [ + 'asyncfile.cc', + 'asyncfile.h', + 'asynchttprequest.cc', + 'asynchttprequest.h', + 'asyncinvoker.cc', + 'asyncinvoker.h', + 'asyncinvoker-inl.h', + 'asyncpacketsocket.h', + 'asyncresolverinterface.h', + 'asyncsocket.cc', + 'asyncsocket.h', + 'asynctcpsocket.cc', + 'asynctcpsocket.h', + 'asyncudpsocket.cc', + 'asyncudpsocket.h', + 'atomicops.h', + 'autodetectproxy.cc', + 'autodetectproxy.h', + 'bandwidthsmoother.cc', + 'bandwidthsmoother.h', + 'base64.cc', + 'base64.h', + 'basicdefs.h', + 'basictypes.h', + 'bind.h', + 'bind.h.pump', + 'buffer.h', + 'bytebuffer.cc', + 'bytebuffer.h', + 'byteorder.h', + 'callback.h', + 'callback.h.pump', + 'checks.cc', + 'checks.h', + 'common.cc', + 'common.h', + 'constructormagic.h', + 'cpumonitor.cc', + 'cpumonitor.h', + 'crc32.cc', + 'crc32.h', + 'criticalsection.h', + 'cryptstring.h', + 'dbus.cc', + 'dbus.h', + 'diskcache.cc', + 'diskcache.h', + 'diskcache_win32.cc', + 'diskcache_win32.h', + 'event.cc', + 'event.h', + 'filelock.cc', + 'filelock.h', + 'fileutils.cc', + 'fileutils.h', + 'fileutils_mock.h', + 'firewallsocketserver.cc', + 'firewallsocketserver.h', + 'flags.cc', + 'flags.h', + 'gunit_prod.h', + 'helpers.cc', + 'helpers.h', + 'httpbase.cc', + 'httpbase.h', + 'httpclient.cc', + 'httpclient.h', + 'httpcommon-inl.h', + 'httpcommon.cc', + 'httpcommon.h', + 'httprequest.cc', + 'httprequest.h', + 'httpserver.cc', + 'httpserver.h', + 'ifaddrs-android.cc', + 'ifaddrs-android.h', + 'iosfilesystem.mm', + 'ipaddress.cc', + 'ipaddress.h', + 'json.cc', + 'json.h', + 'latebindingsymboltable.cc', + 'latebindingsymboltable.cc.def', + 'latebindingsymboltable.h', + 'latebindingsymboltable.h.def', + 'libdbusglibsymboltable.cc', + 'libdbusglibsymboltable.h', + 'linux.cc', + 'linux.h', + 'linuxfdwalk.c', + 'linuxfdwalk.h', + 'linuxwindowpicker.cc', + 'linuxwindowpicker.h', + 'linked_ptr.h', + 'logging.cc', + 'logging.h', + 'macasyncsocket.cc', + 'macasyncsocket.h', + 'maccocoasocketserver.h', + 'maccocoasocketserver.mm', + 'maccocoathreadhelper.h', + 'maccocoathreadhelper.mm', + 'macconversion.cc', + 'macconversion.h', + 'macsocketserver.cc', + 'macsocketserver.h', + 'macutils.cc', + 'macutils.h', + 'macwindowpicker.cc', + 'macwindowpicker.h', + 'mathutils.h', + 'md5.cc', + 'md5.h', + 'md5digest.h', + 'messagedigest.cc', + 'messagedigest.h', + 'messagehandler.cc', + 'messagehandler.h', + 'messagequeue.cc', + 'messagequeue.h', + 'multipart.cc', + 'multipart.h', + 'natserver.cc', + 'natserver.h', + 'natsocketfactory.cc', + 'natsocketfactory.h', + 'nattypes.cc', + 'nattypes.h', + 'nethelpers.cc', + 'nethelpers.h', + 'network.cc', + 'network.h', + 'nssidentity.cc', + 'nssidentity.h', + 'nssstreamadapter.cc', + 'nssstreamadapter.h', + 'nullsocketserver.h', + 'openssl.h', + 'openssladapter.cc', + 'openssladapter.h', + 'openssldigest.cc', + 'openssldigest.h', + 'opensslidentity.cc', + 'opensslidentity.h', + 'opensslstreamadapter.cc', + 'opensslstreamadapter.h', + 'optionsfile.cc', + 'optionsfile.h', + 'pathutils.cc', + 'pathutils.h', + 'physicalsocketserver.cc', + 'physicalsocketserver.h', + 'posix.cc', + 'posix.h', + 'profiler.cc', + 'profiler.h', + 'proxydetect.cc', + 'proxydetect.h', + 'proxyinfo.cc', + 'proxyinfo.h', + 'proxyserver.cc', + 'proxyserver.h', + 'ratelimiter.cc', + 'ratelimiter.h', + 'ratetracker.cc', + 'ratetracker.h', + 'refcount.h', + 'referencecountedsingletonfactory.h', + 'rollingaccumulator.h', + 'safe_conversions.h', + 'safe_conversions_impl.h', + 'schanneladapter.cc', + 'schanneladapter.h', + 'scoped_autorelease_pool.h', + 'scoped_autorelease_pool.mm', + 'scoped_ptr.h', + 'scoped_ref_ptr.h', + 'scopedptrcollection.h', + 'sec_buffer.h', + 'sha1.cc', + 'sha1.h', + 'sha1digest.h', + 'sharedexclusivelock.cc', + 'sharedexclusivelock.h', + 'signalthread.cc', + 'signalthread.h', + 'sigslot.h', + 'sigslotrepeater.h', + 'socket.h', + 'socketadapters.cc', + 'socketadapters.h', + 'socketaddress.cc', + 'socketaddress.h', + 'socketaddresspair.cc', + 'socketaddresspair.h', + 'socketfactory.h', + 'socketpool.cc', + 'socketpool.h', + 'socketserver.h', + 'socketstream.cc', + 'socketstream.h', + 'ssladapter.cc', + 'ssladapter.h', + 'sslconfig.h', + 'sslfingerprint.cc', + 'sslfingerprint.h', + 'sslidentity.cc', + 'sslidentity.h', + 'sslroots.h', + 'sslsocketfactory.cc', + 'sslsocketfactory.h', + 'sslstreamadapter.cc', + 'sslstreamadapter.h', + 'sslstreamadapterhelper.cc', + 'sslstreamadapterhelper.h', + 'stream.cc', + 'stream.h', + 'stringdigest.h', + 'stringencode.cc', + 'stringencode.h', + 'stringutils.cc', + 'stringutils.h', + 'systeminfo.cc', + 'systeminfo.h', + 'task.cc', + 'task.h', + 'taskparent.cc', + 'taskparent.h', + 'taskrunner.cc', + 'taskrunner.h', + 'testclient.cc', + 'testclient.h', + 'thread.cc', + 'thread.h', + 'thread_checker.h', + 'thread_checker_impl.cc', + 'thread_checker_impl.h', + 'timeutils.cc', + 'timeutils.h', + 'timing.cc', + 'timing.h', + 'transformadapter.cc', + 'transformadapter.h', + 'unixfilesystem.cc', + 'unixfilesystem.h', + 'urlencode.cc', + 'urlencode.h', + 'versionparsing.cc', + 'versionparsing.h', + 'virtualsocketserver.cc', + 'virtualsocketserver.h', + 'win32.cc', + 'win32.h', + 'win32filesystem.cc', + 'win32filesystem.h', + 'win32regkey.cc', + 'win32regkey.h', + 'win32securityerrors.cc', + 'win32socketinit.cc', + 'win32socketinit.h', + 'win32socketserver.cc', + 'win32socketserver.h', + 'win32window.cc', + 'win32window.h', + 'win32windowpicker.cc', + 'win32windowpicker.h', + 'window.h', + 'windowpicker.h', + 'windowpickerfactory.h', + 'winfirewall.cc', + 'winfirewall.h', + 'winping.cc', + 'winping.h', + 'worker.cc', + 'worker.h', + '../overrides/webrtc/base/basictypes.h', + '../overrides/webrtc/base/constructormagic.h', + '../overrides/webrtc/base/logging.cc', + '../overrides/webrtc/base/logging.h', + '../overrides/webrtc/base/win32socketinit.cc', + ], + # TODO(henrike): issue 3307, make webrtc_base build without disabling + # these flags. + 'cflags!': [ + '-Wextra', + '-Wall', + ], + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'direct_dependent_settings': { + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'defines': [ + 'FEATURE_ENABLE_SSL', + 'GTEST_RELATIVE_PATH', + ], + }, + 'include_dirs': [ + '../../third_party/jsoncpp/overrides/include', + '../../third_party/jsoncpp/source/include', + ], + 'conditions': [ + ['build_with_chromium==1', { + 'include_dirs': [ + '../overrides', + '../../openssl/openssl/include', + ], + 'sources!': [ + 'asyncinvoker.cc', + 'asyncinvoker.h', + 'asyncinvoker-inl.h', + 'asyncresolverinterface.h', + 'atomicops.h', + 'bandwidthsmoother.cc', + 'bandwidthsmoother.h', + 'basictypes.h', + 'bind.h', + 'bind.h.pump', + 'buffer.h', + 'callback.h', + 'callback.h.pump', + 'constructormagic.h', + 'dbus.cc', + 'dbus.h', + 'diskcache_win32.cc', + 'diskcache_win32.h', + 'filelock.cc', + 'filelock.h', + 'fileutils_mock.h', + 'genericslot.h', + 'genericslot.h.pump', + 'httpserver.cc', + 'httpserver.h', + 'json.cc', + 'json.h', + 'latebindingsymboltable.cc', + 'latebindingsymboltable.cc.def', + 'latebindingsymboltable.h', + 'latebindingsymboltable.h.def', + 'libdbusglibsymboltable.cc', + 'libdbusglibsymboltable.h', + 'linuxfdwalk.c', + 'linuxfdwalk.h', + 'linuxwindowpicker.cc', + 'linuxwindowpicker.h', + 'logging.cc', + 'logging.h', + 'macasyncsocket.cc', + 'macasyncsocket.h', + 'maccocoasocketserver.h', + 'maccocoasocketserver.mm', + 'macsocketserver.cc', + 'macsocketserver.h', + 'macwindowpicker.cc', + 'macwindowpicker.h', + 'mathutils.h', + 'multipart.cc', + 'multipart.h', + 'natserver.cc', + 'natserver.h', + 'natsocketfactory.cc', + 'natsocketfactory.h', + 'nattypes.cc', + 'nattypes.h', + 'openssl.h', + 'optionsfile.cc', + 'optionsfile.h', + 'posix.cc', + 'posix.h', + 'profiler.cc', + 'profiler.h', + 'proxyserver.cc', + 'proxyserver.h', + 'refcount.h', + 'referencecountedsingletonfactory.h', + 'rollingaccumulator.h', + 'safe_conversions.h', + 'safe_conversions_impl.h', + 'scopedptrcollection.h', + 'scoped_ref_ptr.h', + 'sec_buffer.h', + 'sharedexclusivelock.cc', + 'sharedexclusivelock.h', + 'sslconfig.h', + 'sslroots.h', + 'stringdigest.h', + 'testbase64.h', + 'testclient.cc', + 'testclient.h', + 'transformadapter.cc', + 'transformadapter.h', + 'versionparsing.cc', + 'versionparsing.h', + 'virtualsocketserver.cc', + 'virtualsocketserver.h', + 'win32regkey.cc', + 'win32regkey.h', + 'win32socketinit.cc', + 'win32socketinit.h', + 'win32socketserver.cc', + 'win32socketserver.h', + 'window.h', + 'windowpickerfactory.h', + 'windowpicker.h', + ], + 'defines': [ + 'NO_MAIN_THREAD_WRAPPING', + 'SSL_USE_NSS', + ], + 'direct_dependent_settings': { + 'defines': [ + 'NO_MAIN_THREAD_WRAPPING', + 'SSL_USE_NSS', + ], + }, + }, { + 'conditions': [ + ['build_json==1', { + 'dependencies': [ + '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp', + ], + }, { + 'include_dirs': [ + '<(json_root)', + ], + 'defines': [ + # When defined changes the include path for json.h to where it + # is expected to be when building json outside of the standalone + # build. + 'WEBRTC_EXTERNAL_JSON', + ], + }], + ], + 'sources!': [ + '../overrides/webrtc/base/basictypes.h', + '../overrides/webrtc/base/constructormagic.h', + '../overrides/webrtc/base/win32socketinit.cc', + '../overrides/webrtc/base/logging.cc', + '../overrides/webrtc/base/logging.h', + ], + }], + ['use_openssl==1', { + 'defines': [ + 'SSL_USE_OPENSSL', + 'HAVE_OPENSSL_SSL_H', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SSL_USE_OPENSSL', + 'HAVE_OPENSSL_SSL_H', + ], + }, + 'conditions': [ + ['build_ssl==1', { + 'dependencies': [ + '<(DEPTH)/third_party/openssl/openssl.gyp:openssl', + ], + }, { + 'include_dirs': [ + '<(ssl_root)', + ], + }], + ], + }, { + 'defines': [ + 'SSL_USE_NSS', + 'HAVE_NSS_SSL_H', + 'SSL_USE_NSS_RNG', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SSL_USE_NSS', + 'HAVE_NSS_SSL_H', + 'SSL_USE_NSS_RNG', + ], + }, + }], + ['OS == "android"', { + 'defines': [ + 'HAVE_OPENSSL_SSL_H' + ], + 'direct_dependent_settings': { + 'defines': [ + 'HAVE_OPENSSL_SSL_H' + ], + }, + 'link_settings': { + 'libraries': [ + '-llog', + '-lGLESv2', + ], + }, + }, { + 'defines': [ + 'HAVE_NSS_SSL_H' + 'SSL_USE_NSS_RNG', + ], + 'direct_dependent_settings': { + 'defines': [ + 'HAVE_NSS_SSL_H' + 'SSL_USE_NSS_RNG', + ], + }, + 'sources!': [ + 'ifaddrs-android.cc', + 'ifaddrs-android.h', + ], + }], + ['OS=="ios"', { + 'all_dependent_settings': { + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '-framework Foundation', + '-framework Security', + '-framework SystemConfiguration', + '-framework UIKit', + ], + }, + }, + 'conditions': [ + ['build_ssl==1', { + 'dependencies': [ + '<(DEPTH)/net/third_party/nss/ssl.gyp:libssl', + ] + }, { + 'include_dirs': [ + '<(ssl_root)', + ], + }], + ], + }], + ['OS=="linux"', { + 'link_settings': { + 'libraries': [ + '-lcrypto', + '-ldl', + '-lrt', + '-lXext', + '-lX11', + '-lXcomposite', + '-lXrender', + ], + }, + 'conditions': [ + ['build_ssl==1', { + 'link_settings': { + 'libraries': [ + ' + +#include "webrtc/base/common.h" + +using std::vector; + +namespace rtc { + +static const char kPad = '='; +static const unsigned char pd = 0xFD; // Padding +static const unsigned char sp = 0xFE; // Whitespace +static const unsigned char il = 0xFF; // Illegal base64 character + +const char Base64::Base64Table[] = +// 0000000000111111111122222222223333333333444444444455555555556666 +// 0123456789012345678901234567890123456789012345678901234567890123 + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Decode Table gives the index of any valid base64 character in the +// Base64 table +// 65 == A, 97 == a, 48 == 0, 43 == +, 47 == / + +const unsigned char Base64::DecodeTable[] = { +// 0 1 2 3 4 5 6 7 8 9 + il,il,il,il,il,il,il,il,il,sp, // 0 - 9 + sp,sp,sp,sp,il,il,il,il,il,il, // 10 - 19 + il,il,il,il,il,il,il,il,il,il, // 20 - 29 + il,il,sp,il,il,il,il,il,il,il, // 30 - 39 + il,il,il,62,il,il,il,63,52,53, // 40 - 49 + 54,55,56,57,58,59,60,61,il,il, // 50 - 59 + il,pd,il,il,il, 0, 1, 2, 3, 4, // 60 - 69 + 5, 6, 7, 8, 9,10,11,12,13,14, // 70 - 79 + 15,16,17,18,19,20,21,22,23,24, // 80 - 89 + 25,il,il,il,il,il,il,26,27,28, // 90 - 99 + 29,30,31,32,33,34,35,36,37,38, // 100 - 109 + 39,40,41,42,43,44,45,46,47,48, // 110 - 119 + 49,50,51,il,il,il,il,il,il,il, // 120 - 129 + il,il,il,il,il,il,il,il,il,il, // 130 - 139 + il,il,il,il,il,il,il,il,il,il, // 140 - 149 + il,il,il,il,il,il,il,il,il,il, // 150 - 159 + il,il,il,il,il,il,il,il,il,il, // 160 - 169 + il,il,il,il,il,il,il,il,il,il, // 170 - 179 + il,il,il,il,il,il,il,il,il,il, // 180 - 189 + il,il,il,il,il,il,il,il,il,il, // 190 - 199 + il,il,il,il,il,il,il,il,il,il, // 200 - 209 + il,il,il,il,il,il,il,il,il,il, // 210 - 219 + il,il,il,il,il,il,il,il,il,il, // 220 - 229 + il,il,il,il,il,il,il,il,il,il, // 230 - 239 + il,il,il,il,il,il,il,il,il,il, // 240 - 249 + il,il,il,il,il,il // 250 - 255 +}; + +bool Base64::IsBase64Char(char ch) { + return (('A' <= ch) && (ch <= 'Z')) || + (('a' <= ch) && (ch <= 'z')) || + (('0' <= ch) && (ch <= '9')) || + (ch == '+') || (ch == '/'); +} + +bool Base64::GetNextBase64Char(char ch, char* next_ch) { + if (next_ch == NULL) { + return false; + } + const char* p = strchr(Base64Table, ch); + if (!p) + return false; + ++p; + *next_ch = (*p) ? *p : Base64Table[0]; + return true; +} + +bool Base64::IsBase64Encoded(const std::string& str) { + for (size_t i = 0; i < str.size(); ++i) { + if (!IsBase64Char(str.at(i))) + return false; + } + return true; +} + +void Base64::EncodeFromArray(const void* data, size_t len, + std::string* result) { + ASSERT(NULL != result); + result->clear(); + result->resize(((len + 2) / 3) * 4); + const unsigned char* byte_data = static_cast(data); + + unsigned char c; + size_t i = 0; + size_t dest_ix = 0; + while (i < len) { + c = (byte_data[i] >> 2) & 0x3f; + (*result)[dest_ix++] = Base64Table[c]; + + c = (byte_data[i] << 4) & 0x3f; + if (++i < len) { + c |= (byte_data[i] >> 4) & 0x0f; + } + (*result)[dest_ix++] = Base64Table[c]; + + if (i < len) { + c = (byte_data[i] << 2) & 0x3f; + if (++i < len) { + c |= (byte_data[i] >> 6) & 0x03; + } + (*result)[dest_ix++] = Base64Table[c]; + } else { + (*result)[dest_ix++] = kPad; + } + + if (i < len) { + c = byte_data[i] & 0x3f; + (*result)[dest_ix++] = Base64Table[c]; + ++i; + } else { + (*result)[dest_ix++] = kPad; + } + } +} + +size_t Base64::GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads, + const char* data, size_t len, size_t* dpos, + unsigned char qbuf[4], bool* padded) +{ + size_t byte_len = 0, pad_len = 0, pad_start = 0; + for (; (byte_len < 4) && (*dpos < len); ++*dpos) { + qbuf[byte_len] = DecodeTable[static_cast(data[*dpos])]; + if ((il == qbuf[byte_len]) || (illegal_pads && (pd == qbuf[byte_len]))) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore illegal characters + } else if (sp == qbuf[byte_len]) { + if (parse_flags == DO_PARSE_STRICT) + break; + // Ignore spaces + } else if (pd == qbuf[byte_len]) { + if (byte_len < 2) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore unexpected padding + } else if (byte_len + pad_len >= 4) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore extra pads + } else { + if (1 == ++pad_len) { + pad_start = *dpos; + } + } + } else { + if (pad_len > 0) { + if (parse_flags != DO_PARSE_ANY) + break; + // Ignore pads which are followed by data + pad_len = 0; + } + ++byte_len; + } + } + for (size_t i = byte_len; i < 4; ++i) { + qbuf[i] = 0; + } + if (4 == byte_len + pad_len) { + *padded = true; + } else { + *padded = false; + if (pad_len) { + // Roll back illegal padding + *dpos = pad_start; + } + } + return byte_len; +} + +bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + std::string* result, size_t* data_used) { + return DecodeFromArrayTemplate( + data, len, flags, result, data_used); +} + +bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + vector* result, size_t* data_used) { + return DecodeFromArrayTemplate >(data, len, flags, result, + data_used); +} + +template +bool Base64::DecodeFromArrayTemplate(const char* data, size_t len, + DecodeFlags flags, T* result, + size_t* data_used) +{ + ASSERT(NULL != result); + ASSERT(flags <= (DO_PARSE_MASK | DO_PAD_MASK | DO_TERM_MASK)); + + const DecodeFlags parse_flags = flags & DO_PARSE_MASK; + const DecodeFlags pad_flags = flags & DO_PAD_MASK; + const DecodeFlags term_flags = flags & DO_TERM_MASK; + ASSERT(0 != parse_flags); + ASSERT(0 != pad_flags); + ASSERT(0 != term_flags); + + result->clear(); + result->reserve(len); + + size_t dpos = 0; + bool success = true, padded; + unsigned char c, qbuf[4]; + while (dpos < len) { + size_t qlen = GetNextQuantum(parse_flags, (DO_PAD_NO == pad_flags), + data, len, &dpos, qbuf, &padded); + c = (qbuf[0] << 2) | ((qbuf[1] >> 4) & 0x3); + if (qlen >= 2) { + result->push_back(c); + c = ((qbuf[1] << 4) & 0xf0) | ((qbuf[2] >> 2) & 0xf); + if (qlen >= 3) { + result->push_back(c); + c = ((qbuf[2] << 6) & 0xc0) | qbuf[3]; + if (qlen >= 4) { + result->push_back(c); + c = 0; + } + } + } + if (qlen < 4) { + if ((DO_TERM_ANY != term_flags) && (0 != c)) { + success = false; // unused bits + } + if ((DO_PAD_YES == pad_flags) && !padded) { + success = false; // expected padding + } + break; + } + } + if ((DO_TERM_BUFFER == term_flags) && (dpos != len)) { + success = false; // unused chars + } + if (data_used) { + *data_used = dpos; + } + return success; +} + +} // namespace rtc diff --git a/webrtc/base/base64.h b/webrtc/base/base64.h new file mode 100644 index 000000000..d5a7dd84c --- /dev/null +++ b/webrtc/base/base64.h @@ -0,0 +1,104 @@ + +//********************************************************************* +//* C_Base64 - a simple base64 encoder and decoder. +//* +//* Copyright (c) 1999, Bob Withers - bwit@pobox.com +//* +//* This code may be freely used for any purpose, either personal +//* or commercial, provided the authors copyright notice remains +//* intact. +//********************************************************************* + +#ifndef WEBRTC_BASE_BASE64_H__ +#define WEBRTC_BASE_BASE64_H__ + +#include +#include + +namespace rtc { + +class Base64 +{ +public: + enum DecodeOption { + DO_PARSE_STRICT = 1, // Parse only base64 characters + DO_PARSE_WHITE = 2, // Parse only base64 and whitespace characters + DO_PARSE_ANY = 3, // Parse all characters + DO_PARSE_MASK = 3, + + DO_PAD_YES = 4, // Padding is required + DO_PAD_ANY = 8, // Padding is optional + DO_PAD_NO = 12, // Padding is disallowed + DO_PAD_MASK = 12, + + DO_TERM_BUFFER = 16, // Must termiante at end of buffer + DO_TERM_CHAR = 32, // May terminate at any character boundary + DO_TERM_ANY = 48, // May terminate at a sub-character bit offset + DO_TERM_MASK = 48, + + // Strictest interpretation + DO_STRICT = DO_PARSE_STRICT | DO_PAD_YES | DO_TERM_BUFFER, + + DO_LAX = DO_PARSE_ANY | DO_PAD_ANY | DO_TERM_CHAR, + }; + typedef int DecodeFlags; + + static bool IsBase64Char(char ch); + + // Get the char next to the |ch| from the Base64Table. + // If the |ch| is the last one in the Base64Table then returns + // the first one from the table. + // Expects the |ch| be a base64 char. + // The result will be saved in |next_ch|. + // Returns true on success. + static bool GetNextBase64Char(char ch, char* next_ch); + + // Determines whether the given string consists entirely of valid base64 + // encoded characters. + static bool IsBase64Encoded(const std::string& str); + + static void EncodeFromArray(const void* data, size_t len, + std::string* result); + static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + std::string* result, size_t* data_used); + static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags, + std::vector* result, size_t* data_used); + + // Convenience Methods + static inline std::string Encode(const std::string& data) { + std::string result; + EncodeFromArray(data.data(), data.size(), &result); + return result; + } + static inline std::string Decode(const std::string& data, DecodeFlags flags) { + std::string result; + DecodeFromArray(data.data(), data.size(), flags, &result, NULL); + return result; + } + static inline bool Decode(const std::string& data, DecodeFlags flags, + std::string* result, size_t* data_used) + { + return DecodeFromArray(data.data(), data.size(), flags, result, data_used); + } + static inline bool Decode(const std::string& data, DecodeFlags flags, + std::vector* result, size_t* data_used) + { + return DecodeFromArray(data.data(), data.size(), flags, result, data_used); + } + +private: + static const char Base64Table[]; + static const unsigned char DecodeTable[]; + + static size_t GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads, + const char* data, size_t len, size_t* dpos, + unsigned char qbuf[4], bool* padded); + template + static bool DecodeFromArrayTemplate(const char* data, size_t len, + DecodeFlags flags, T* result, + size_t* data_used); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BASE64_H__ diff --git a/webrtc/base/base64_unittest.cc b/webrtc/base/base64_unittest.cc new file mode 100644 index 000000000..c4d407244 --- /dev/null +++ b/webrtc/base/base64_unittest.cc @@ -0,0 +1,1001 @@ +/* + * Copyright 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 "webrtc/base/common.h" +#include "webrtc/base/base64.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/stream.h" + +#include "webrtc/base/testbase64.h" + +using namespace std; +using namespace rtc; + +static struct { + size_t plain_length; + const char* plaintext; + const char* cyphertext; +} base64_tests[] = { + + // Basic bit patterns; + // values obtained with "echo -n '...' | uuencode -m test" + + { 1, "\000", "AA==" }, + { 1, "\001", "AQ==" }, + { 1, "\002", "Ag==" }, + { 1, "\004", "BA==" }, + { 1, "\010", "CA==" }, + { 1, "\020", "EA==" }, + { 1, "\040", "IA==" }, + { 1, "\100", "QA==" }, + { 1, "\200", "gA==" }, + + { 1, "\377", "/w==" }, + { 1, "\376", "/g==" }, + { 1, "\375", "/Q==" }, + { 1, "\373", "+w==" }, + { 1, "\367", "9w==" }, + { 1, "\357", "7w==" }, + { 1, "\337", "3w==" }, + { 1, "\277", "vw==" }, + { 1, "\177", "fw==" }, + { 2, "\000\000", "AAA=" }, + { 2, "\000\001", "AAE=" }, + { 2, "\000\002", "AAI=" }, + { 2, "\000\004", "AAQ=" }, + { 2, "\000\010", "AAg=" }, + { 2, "\000\020", "ABA=" }, + { 2, "\000\040", "ACA=" }, + { 2, "\000\100", "AEA=" }, + { 2, "\000\200", "AIA=" }, + { 2, "\001\000", "AQA=" }, + { 2, "\002\000", "AgA=" }, + { 2, "\004\000", "BAA=" }, + { 2, "\010\000", "CAA=" }, + { 2, "\020\000", "EAA=" }, + { 2, "\040\000", "IAA=" }, + { 2, "\100\000", "QAA=" }, + { 2, "\200\000", "gAA=" }, + + { 2, "\377\377", "//8=" }, + { 2, "\377\376", "//4=" }, + { 2, "\377\375", "//0=" }, + { 2, "\377\373", "//s=" }, + { 2, "\377\367", "//c=" }, + { 2, "\377\357", "/+8=" }, + { 2, "\377\337", "/98=" }, + { 2, "\377\277", "/78=" }, + { 2, "\377\177", "/38=" }, + { 2, "\376\377", "/v8=" }, + { 2, "\375\377", "/f8=" }, + { 2, "\373\377", "+/8=" }, + { 2, "\367\377", "9/8=" }, + { 2, "\357\377", "7/8=" }, + { 2, "\337\377", "3/8=" }, + { 2, "\277\377", "v/8=" }, + { 2, "\177\377", "f/8=" }, + + { 3, "\000\000\000", "AAAA" }, + { 3, "\000\000\001", "AAAB" }, + { 3, "\000\000\002", "AAAC" }, + { 3, "\000\000\004", "AAAE" }, + { 3, "\000\000\010", "AAAI" }, + { 3, "\000\000\020", "AAAQ" }, + { 3, "\000\000\040", "AAAg" }, + { 3, "\000\000\100", "AABA" }, + { 3, "\000\000\200", "AACA" }, + { 3, "\000\001\000", "AAEA" }, + { 3, "\000\002\000", "AAIA" }, + { 3, "\000\004\000", "AAQA" }, + { 3, "\000\010\000", "AAgA" }, + { 3, "\000\020\000", "ABAA" }, + { 3, "\000\040\000", "ACAA" }, + { 3, "\000\100\000", "AEAA" }, + { 3, "\000\200\000", "AIAA" }, + { 3, "\001\000\000", "AQAA" }, + { 3, "\002\000\000", "AgAA" }, + { 3, "\004\000\000", "BAAA" }, + { 3, "\010\000\000", "CAAA" }, + { 3, "\020\000\000", "EAAA" }, + { 3, "\040\000\000", "IAAA" }, + { 3, "\100\000\000", "QAAA" }, + { 3, "\200\000\000", "gAAA" }, + + { 3, "\377\377\377", "////" }, + { 3, "\377\377\376", "///+" }, + { 3, "\377\377\375", "///9" }, + { 3, "\377\377\373", "///7" }, + { 3, "\377\377\367", "///3" }, + { 3, "\377\377\357", "///v" }, + { 3, "\377\377\337", "///f" }, + { 3, "\377\377\277", "//+/" }, + { 3, "\377\377\177", "//9/" }, + { 3, "\377\376\377", "//7/" }, + { 3, "\377\375\377", "//3/" }, + { 3, "\377\373\377", "//v/" }, + { 3, "\377\367\377", "//f/" }, + { 3, "\377\357\377", "/+//" }, + { 3, "\377\337\377", "/9//" }, + { 3, "\377\277\377", "/7//" }, + { 3, "\377\177\377", "/3//" }, + { 3, "\376\377\377", "/v//" }, + { 3, "\375\377\377", "/f//" }, + { 3, "\373\377\377", "+///" }, + { 3, "\367\377\377", "9///" }, + { 3, "\357\377\377", "7///" }, + { 3, "\337\377\377", "3///" }, + { 3, "\277\377\377", "v///" }, + { 3, "\177\377\377", "f///" }, + + // Random numbers: values obtained with + // + // #! /bin/bash + // dd bs=$1 count=1 if=/dev/random of=/tmp/bar.random + // od -N $1 -t o1 /tmp/bar.random + // uuencode -m test < /tmp/bar.random + // + // where $1 is the number of bytes (2, 3) + + { 2, "\243\361", "o/E=" }, + { 2, "\024\167", "FHc=" }, + { 2, "\313\252", "y6o=" }, + { 2, "\046\041", "JiE=" }, + { 2, "\145\236", "ZZ4=" }, + { 2, "\254\325", "rNU=" }, + { 2, "\061\330", "Mdg=" }, + { 2, "\245\032", "pRo=" }, + { 2, "\006\000", "BgA=" }, + { 2, "\375\131", "/Vk=" }, + { 2, "\303\210", "w4g=" }, + { 2, "\040\037", "IB8=" }, + { 2, "\261\372", "sfo=" }, + { 2, "\335\014", "3Qw=" }, + { 2, "\233\217", "m48=" }, + { 2, "\373\056", "+y4=" }, + { 2, "\247\232", "p5o=" }, + { 2, "\107\053", "Rys=" }, + { 2, "\204\077", "hD8=" }, + { 2, "\276\211", "vok=" }, + { 2, "\313\110", "y0g=" }, + { 2, "\363\376", "8/4=" }, + { 2, "\251\234", "qZw=" }, + { 2, "\103\262", "Q7I=" }, + { 2, "\142\312", "Yso=" }, + { 2, "\067\211", "N4k=" }, + { 2, "\220\001", "kAE=" }, + { 2, "\152\240", "aqA=" }, + { 2, "\367\061", "9zE=" }, + { 2, "\133\255", "W60=" }, + { 2, "\176\035", "fh0=" }, + { 2, "\032\231", "Gpk=" }, + + { 3, "\013\007\144", "Cwdk" }, + { 3, "\030\112\106", "GEpG" }, + { 3, "\047\325\046", "J9Um" }, + { 3, "\310\160\022", "yHAS" }, + { 3, "\131\100\237", "WUCf" }, + { 3, "\064\342\134", "NOJc" }, + { 3, "\010\177\004", "CH8E" }, + { 3, "\345\147\205", "5WeF" }, + { 3, "\300\343\360", "wOPw" }, + { 3, "\061\240\201", "MaCB" }, + { 3, "\225\333\044", "ldsk" }, + { 3, "\215\137\352", "jV/q" }, + { 3, "\371\147\160", "+Wdw" }, + { 3, "\030\320\051", "GNAp" }, + { 3, "\044\174\241", "JHyh" }, + { 3, "\260\127\037", "sFcf" }, + { 3, "\111\045\033", "SSUb" }, + { 3, "\202\114\107", "gkxH" }, + { 3, "\057\371\042", "L/ki" }, + { 3, "\223\247\244", "k6ek" }, + { 3, "\047\216\144", "J45k" }, + { 3, "\203\070\327", "gzjX" }, + { 3, "\247\140\072", "p2A6" }, + { 3, "\124\115\116", "VE1O" }, + { 3, "\157\162\050", "b3Io" }, + { 3, "\357\223\004", "75ME" }, + { 3, "\052\117\156", "Kk9u" }, + { 3, "\347\154\000", "52wA" }, + { 3, "\303\012\142", "wwpi" }, + { 3, "\060\035\362", "MB3y" }, + { 3, "\130\226\361", "WJbx" }, + { 3, "\173\013\071", "ews5" }, + { 3, "\336\004\027", "3gQX" }, + { 3, "\357\366\234", "7/ac" }, + { 3, "\353\304\111", "68RJ" }, + { 3, "\024\264\131", "FLRZ" }, + { 3, "\075\114\251", "PUyp" }, + { 3, "\315\031\225", "zRmV" }, + { 3, "\154\201\276", "bIG+" }, + { 3, "\200\066\072", "gDY6" }, + { 3, "\142\350\267", "Yui3" }, + { 3, "\033\000\166", "GwB2" }, + { 3, "\210\055\077", "iC0/" }, + { 3, "\341\037\124", "4R9U" }, + { 3, "\161\103\152", "cUNq" }, + { 3, "\270\142\131", "uGJZ" }, + { 3, "\337\076\074", "3z48" }, + { 3, "\375\106\362", "/Uby" }, + { 3, "\227\301\127", "l8FX" }, + { 3, "\340\002\234", "4AKc" }, + { 3, "\121\064\033", "UTQb" }, + { 3, "\157\134\143", "b1xj" }, + { 3, "\247\055\327", "py3X" }, + { 3, "\340\142\005", "4GIF" }, + { 3, "\060\260\143", "MLBj" }, + { 3, "\075\203\170", "PYN4" }, + { 3, "\143\160\016", "Y3AO" }, + { 3, "\313\013\063", "ywsz" }, + { 3, "\174\236\135", "fJ5d" }, + { 3, "\103\047\026", "QycW" }, + { 3, "\365\005\343", "9QXj" }, + { 3, "\271\160\223", "uXCT" }, + { 3, "\362\255\172", "8q16" }, + { 3, "\113\012\015", "SwoN" }, + + // various lengths, generated by this python script: + // + // from string import lowercase as lc + // for i in range(27): + // print '{ %2d, "%s",%s "%s" },' % (i, lc[:i], ' ' * (26-i), + // lc[:i].encode('base64').strip()) + + { 0, "abcdefghijklmnopqrstuvwxyz", "" }, + { 1, "abcdefghijklmnopqrstuvwxyz", "YQ==" }, + { 2, "abcdefghijklmnopqrstuvwxyz", "YWI=" }, + { 3, "abcdefghijklmnopqrstuvwxyz", "YWJj" }, + { 4, "abcdefghijklmnopqrstuvwxyz", "YWJjZA==" }, + { 5, "abcdefghijklmnopqrstuvwxyz", "YWJjZGU=" }, + { 6, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVm" }, + { 7, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZw==" }, + { 8, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2g=" }, + { 9, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hp" }, + { 10, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpag==" }, + { 11, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpams=" }, + { 12, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamts" }, + { 13, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbQ==" }, + { 14, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW4=" }, + { 15, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5v" }, + { 16, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcA==" }, + { 17, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHE=" }, + { 18, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFy" }, + { 19, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFycw==" }, + { 20, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3Q=" }, + { 21, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1" }, + { 22, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dg==" }, + { 23, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnc=" }, + { 24, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4" }, + { 25, "abcdefghijklmnopqrstuvwxy", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eQ==" }, + { 26, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=" }, +}; +#if 0 +static struct { + const char* plaintext; + const char* cyphertext; +} base64_strings[] = { + + // The first few Google quotes + // Cyphertext created with "uuencode - GNU sharutils 4.2.1" + { + "Everyone! We're teetering on the brink of disaster." + " - Sergey Brin, 6/24/99, regarding the company's state " + "after the unleashing of Netscape/Google search", + + "RXZlcnlvbmUhICBXZSdyZSB0ZWV0ZXJpbmcgb24gdGhlIGJyaW5rIG9mIGRp" + "c2FzdGVyLiAtIFNlcmdleSBCcmluLCA2LzI0Lzk5LCByZWdhcmRpbmcgdGhl" + "IGNvbXBhbnkncyBzdGF0ZSBhZnRlciB0aGUgdW5sZWFzaGluZyBvZiBOZXRz" + "Y2FwZS9Hb29nbGUgc2VhcmNo" }, + + { + "I'm not sure why we're still alive, but we seem to be." + " - Larry Page, 6/24/99, while hiding in the kitchenette " + "during the Netscape traffic overflow", + + "SSdtIG5vdCBzdXJlIHdoeSB3ZSdyZSBzdGlsbCBhbGl2ZSwgYnV0IHdlIHNl" + "ZW0gdG8gYmUuIC0gTGFycnkgUGFnZSwgNi8yNC85OSwgd2hpbGUgaGlkaW5n" + "IGluIHRoZSBraXRjaGVuZXR0ZSBkdXJpbmcgdGhlIE5ldHNjYXBlIHRyYWZm" + "aWMgb3ZlcmZsb3c" }, + + { + "I think kids want porn." + " - Sergey Brin, 6/99, on why Google shouldn't prioritize a " + "filtered search for children and families", + + "SSB0aGluayBraWRzIHdhbnQgcG9ybi4gLSBTZXJnZXkgQnJpbiwgNi85OSwg" + "b24gd2h5IEdvb2dsZSBzaG91bGRuJ3QgcHJpb3JpdGl6ZSBhIGZpbHRlcmVk" + "IHNlYXJjaCBmb3IgY2hpbGRyZW4gYW5kIGZhbWlsaWVz" }, +}; +#endif +// Compare bytes 0..len-1 of x and y. If not equal, abort with verbose error +// message showing position and numeric value that differed. +// Handles embedded nulls just like any other byte. +// Only added because string.compare() in gcc-3.3.3 seems to misbehave with +// embedded nulls. +// TODO: switch back to string.compare() if/when gcc is fixed +#define EXPECT_EQ_ARRAY(len, x, y, msg) \ + for (size_t j = 0; j < len; ++j) { \ + if (x[j] != y[j]) { \ + LOG(LS_ERROR) << "" # x << " != " # y \ + << " byte " << j << " msg: " << msg; \ + } \ + } + +size_t Base64Escape(const unsigned char *src, size_t szsrc, char *dest, + size_t szdest) { + std::string escaped; + Base64::EncodeFromArray((const char *)src, szsrc, &escaped); + memcpy(dest, escaped.data(), min(escaped.size(), szdest)); + return escaped.size(); +} + +size_t Base64Unescape(const char *src, size_t szsrc, char *dest, + size_t szdest) { + std::string unescaped; + EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, &unescaped, + NULL)); + memcpy(dest, unescaped.data(), min(unescaped.size(), szdest)); + return unescaped.size(); +} + +size_t Base64Unescape(const char *src, size_t szsrc, string *s) { + EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, s, NULL)); + return s->size(); +} + +TEST(Base64, EncodeDecodeBattery) { + LOG(LS_VERBOSE) << "Testing base-64"; + + size_t i; + + // Check the short strings; this tests the math (and boundaries) + for( i = 0; i < sizeof(base64_tests) / sizeof(base64_tests[0]); ++i ) { + char encode_buffer[100]; + size_t encode_length; + char decode_buffer[100]; + size_t decode_length; + size_t cypher_length; + + LOG(LS_VERBOSE) << "B64: " << base64_tests[i].cyphertext; + + const unsigned char* unsigned_plaintext = + reinterpret_cast(base64_tests[i].plaintext); + + cypher_length = strlen(base64_tests[i].cyphertext); + + // The basic escape function: + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = Base64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + sizeof(encode_buffer)); + // Is it of the expected length? + EXPECT_EQ(encode_length, cypher_length); + + // Is it the expected encoded value? + EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext); + + // If we encode it into a buffer of exactly the right length... + memset(encode_buffer, 0, sizeof(encode_buffer)); + encode_length = Base64Escape(unsigned_plaintext, + base64_tests[i].plain_length, + encode_buffer, + cypher_length); + // Is it still of the expected length? + EXPECT_EQ(encode_length, cypher_length); + + // And is the value still correct? (i.e., not losing the last byte) + EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext); + + // If we decode it back: + memset(decode_buffer, 0, sizeof(decode_buffer)); + decode_length = Base64Unescape(encode_buffer, + cypher_length, + decode_buffer, + sizeof(decode_buffer)); + + // Is it of the expected length? + EXPECT_EQ(decode_length, base64_tests[i].plain_length); + + // Is it the expected decoded value? + EXPECT_EQ(0, memcmp(decode_buffer, base64_tests[i].plaintext, decode_length)); + + // Our decoder treats the padding '=' characters at the end as + // optional. If encode_buffer has any, run some additional + // tests that fiddle with them. + char* first_equals = strchr(encode_buffer, '='); + if (first_equals) { + // How many equals signs does the string start with? + int equals = (*(first_equals+1) == '=') ? 2 : 1; + + // Try chopping off the equals sign(s) entirely. The decoder + // should still be okay with this. + string decoded2("this junk should also be ignored"); + *first_equals = '\0'; + EXPECT_NE(0U, Base64Unescape(encode_buffer, first_equals-encode_buffer, + &decoded2)); + EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded2.size(), decoded2.data(), base64_tests[i].plaintext, i); + + size_t len; + + // try putting some extra stuff after the equals signs, or in between them + if (equals == 2) { + sprintfn(first_equals, 6, " = = "); + len = first_equals - encode_buffer + 5; + } else { + sprintfn(first_equals, 6, " = "); + len = first_equals - encode_buffer + 3; + } + decoded2.assign("this junk should be ignored"); + EXPECT_NE(0U, Base64Unescape(encode_buffer, len, &decoded2)); + EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length); + EXPECT_EQ_ARRAY(decoded2.size(), decoded2, base64_tests[i].plaintext, i); + } + } +} + +// here's a weird case: a giant base64 encoded stream which broke our base64 +// decoding. Let's test it explicitly. +const char SpecificTest[] = + "/9j/4AAQSkZJRgABAgEASABIAAD/4Q0HRXhpZgAATU0AKgAAAAgADAEOAAIAAAAgAAAAngEPAAI\n" + "AAAAFAAAAvgEQAAIAAAAJAAAAwwESAAMAAAABAAEAAAEaAAUAAAABAAAAzAEbAAUAAAABAAAA1A\n" + "EoAAMAAAABAAIAAAExAAIAAAAUAAAA3AEyAAIAAAAUAAAA8AE8AAIAAAAQAAABBAITAAMAAAABA\n" + "AIAAIdpAAQAAAABAAABFAAAAsQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAFNPTlkA\n" + "RFNDLVAyMDAAAAAASAAAAAEAAABIAAAAAUFkb2JlIFBob3Rvc2hvcCA3LjAAMjAwNzowMTozMCA\n" + "yMzoxMDowNABNYWMgT1MgWCAxMC40LjgAAByCmgAFAAAAAQAAAmqCnQAFAAAAAQAAAnKIIgADAA\n" + "AAAQACAACIJwADAAAAAQBkAACQAAAHAAAABDAyMjCQAwACAAAAFAAAAnqQBAACAAAAFAAAAo6RA\n" + "QAHAAAABAECAwCRAgAFAAAAAQAAAqKSBAAKAAAAAQAAAqqSBQAFAAAAAQAAArKSBwADAAAAAQAF\n" + "AACSCAADAAAAAQAAAACSCQADAAAAAQAPAACSCgAFAAAAAQAAArqgAAAHAAAABDAxMDCgAQADAAA\n" + "AAf//AACgAgAEAAAAAQAAAGSgAwAEAAAAAQAAAGSjAAAHAAAAAQMAAACjAQAHAAAAAQEAAACkAQ\n" + "ADAAAAAQAAAACkAgADAAAAAQAAAACkAwADAAAAAQAAAACkBgADAAAAAQAAAACkCAADAAAAAQAAA\n" + "ACkCQADAAAAAQAAAACkCgADAAAAAQAAAAAAAAAAAAAACgAAAZAAAAAcAAAACjIwMDc6MDE6MjAg\n" + "MjM6MDU6NTIAMjAwNzowMToyMCAyMzowNTo1MgAAAAAIAAAAAQAAAAAAAAAKAAAAMAAAABAAAAB\n" + "PAAAACgAAAAYBAwADAAAAAQAGAAABGgAFAAAAAQAAAxIBGwAFAAAAAQAAAxoBKAADAAAAAQACAA\n" + "ACAQAEAAAAAQAAAyICAgAEAAAAAQAACd0AAAAAAAAASAAAAAEAAABIAAAAAf/Y/+AAEEpGSUYAA\n" + "QIBAEgASAAA/+0ADEFkb2JlX0NNAAL/7gAOQWRvYmUAZIAAAAAB/9sAhAAMCAgICQgMCQkMEQsK\n" + "CxEVDwwMDxUYExMVExMYEQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQ0LCw0\n" + "ODRAODhAUDg4OFBQODg4OFBEMDAwMDBERDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA\n" + "wMDAz/wAARCABkAGQDASIAAhEBAxEB/90ABAAH/8QBPwAAAQUBAQEBAQEAAAAAAAAAAwABAgQFB\n" + "gcICQoLAQABBQEBAQEBAQAAAAAAAAABAAIDBAUGBwgJCgsQAAEEAQMCBAIFBwYIBQMMMwEAAhED\n" + "BCESMQVBUWETInGBMgYUkaGxQiMkFVLBYjM0coLRQwclklPw4fFjczUWorKDJkSTVGRFwqN0Nhf\n" + "SVeJl8rOEw9N14/NGJ5SkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9xEAAg\n" + "IBAgQEAwQFBgcHBgU1AQACEQMhMRIEQVFhcSITBTKBkRShsUIjwVLR8DMkYuFygpJDUxVjczTxJ\n" + "QYWorKDByY1wtJEk1SjF2RFVTZ0ZeLys4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm\n" + "9ic3R1dnd4eXp7fH/9oADAMBAAIRAxEAPwDy7bKNTUXNLz9EaJPDWMjxH4ozhtpYwaACT8ShaaW\n" + "bW0uEc9/JFfjj0Q4Hk/PRDxwX7y47W9z/AN9Cv4+O3ILK2DcRqT2CaSvEbcl1Jbz37KG1dBldLo\n" + "qaS4l9xGjG9v6yoDAdYIaIjUk+AREgo4y5sapirb8Yl0NHHdKvBNm4yA1o5Pc+SPEFvCWqB3HZF\n" + "Hj2SbWQ/afGFP0bHP8ATY0uc4w1o1JPkkimGiS2KvqlnmBkOZQTyydzgPMM9v8A0lp4v1Nx9gF1\n" + "tpdqJaGtH/S3I0i3lISXW/8AMqnd/O2bfg2eUkqVYf/Q8zuncO4Bj7lZ+n7f5Mj5KsJcY8NUZ4d\n" + "uEDVo1HkeU0rg3Om4H2rabCWUN7DQuK1n5FWKW4uCwG92gDRJBS6exhxmMboQI+Cv4WFTQ42Bs2\n" + "fvnkkqEmy2YxoMMbpVzaz6jt+RbpHZs8lzkHqrasKkYOKP0jgDfZ4N/wDM1tNrcWfSPmRyq9uNV\n" + "DnFg2s97i7UkjxKVrq0eVz3spZsja+ASDzwsh9jnOk/JFzb3XZD3v1c4yT8UACTCniKDUnKz5Nj\n" + "G33XV1DV73BrT8dF23SejV4zg9g33cOsPb+SxVvqv9ViwNy8vS0iWs/daf8A0Y5dpTi1sADGxCR\n" + "K1o0YBEmInlXWYbDBcDLdPJXa8f71Yrx2jnUoAqLnfZK5hJaW2vdwEk5a/wD/0fN6Ia/e76IiVf\n" + "xavUL7CPpnT4LNbYXAVjuQt/AqDmNYO/Kjnoy4hr5J8SwMhrRMaeSvbsxrfUazcOw4UX0Cisem2\n" + "SBoD4+Kz8nC6llbSLCRrubJA8kwUWbUDa29X1PMa7aQWjuDC0MXMdbDbhI7eazBiUfZ6GOYRe1s\n" + "WvGgJ8Vbw2+m4Bx9s6JpNHuuGo1FF53r/SHYua61gLse0lzXeBP5rkvqx0o5vVWz7WY49QkiQSP\n" + "oN/tLoevW/ogxv0HA7tJ0AnhT+pdDGYVl/wCdcTPkGn2NU0JWNWvlgAbHV6fEqdu2gR/r2WlWwt\n" + "AA5VXAEsLXTqJafArQY5rRr9LiPBJiZsZCI1pJjxCi0j4oncSICSkWwzwkjeaSch//0vO7sP7Lm\n" + "enO9ogtd5FbPT3Q5pCpZVc4ld3Lmn3O8j9EI2BYdunKjOobMQIyI+rusc2wx4d0eutwGnHh/uQc\n" + "Ha7ladj6mVANGvcqOgz0Go7HJ12/GEHcwvB/dPY6ImbbaMaASGuIBjkN7qofs9Ubg9g7OI9p/t/\n" + "RTSmhTHr0v6eSz6UgCPP2/wAVu9Ex2V49dVY2iACB4BZeVXQ/AJ3gzGnnOi2+kACpru8flUsNmt\n" + "zHRf6xfWCnoeAfTh2ZaQKazx/Ke7+QxcKz61fWA2uuObaC4zGhaPJrXBL64ZFmR124O09ENraPK\n" + "N3/AH5GqxIrZVUyp2K2vfdkENsDnxuex9m4Ox9n82xSgNd9D+p/XR1npgseR9ppOy4Dx/NfH/CL\n" + "oQJGunmvMv8AFq3KHVcq3HkYQbD2nuSf0I/rMavSg6TLjLigQhJ7Z58v9QkmlsTOqSCn/9PzL7R\n" + "d6Qq3n0wZ2zotXpT9xLfFYvkr/S7jXeB8E0jRkhKpC3q8LcJ/kmCrTnkuAPCq4do9Q/ytVbuAeY\n" + "Gg5lQybQK+82GBqEQUA1kOHPYf3LLsoyN36G5w8iUfHxepbXE2l0cApALgLHzBq9UxhTXU5hMC1\n" + "ktnSCup6S4Ctk+C5XqVGcaHPfuiuHkeTTuWz0+9zaKiH6CC0/yXBSQ2a/MxojV57634rq+v2PLY\n" + "be1r2nsYG13/AFKxbfCBMcr0brGAzrGEwCG31ncx0SfBzf7S4+zoHUWWsJq3hz9oLfcBH77R9H+\n" + "0pA13u/qPgDp/Q6ri39JlfpXkDx+h/msWn1L6wdO6bSbcrIbU2Q0xLnSe21kuVejJspbVS5+4bd\n" + "ocBAkD/orG+tP1ar67Wy7GtZTm1SCXfRsb+a18fRe38x6SG3/44H1Z3f0y2I+l6DoSXD/8xPrDs\n" + "3enVu3bdnqN3R+//USSVo//1PLohhce+gRWS0Nsby3lRgFkKxQyW7SgUh3em5Tbq2uB9wWw1wey\n" + "J1XGV2XYdm5k7e4WzidXY9oMwo5RZ4T6Hd1ixwfp96PWbAJBVTHzK7O6Ky5oJB1HZMqmUEFlkGy\n" + "xpa4zI1Hkq31dy7bMN9BAc3HeWAnnbyxEycmuup1jiAGglZ31PyrmZ9tQg1WtNj54EHR3/S2qTH\n" + "1Yc5GgD1FFtzPdWGkd2AyflogZmRmsz6PSrbXbdo+txOrP337f3fzVo15DK2uyrTtqpBOnBKx6b\n" + "7MjJsz7tHWOAYP3WD6LU6cqGjFCNl1MmvLcxv6YtDTLSAqP27LrdtYHXFnJZI+Tp3MWg68OpDPv\n" + "UMUM2lkQBoouKQ6swjE9Nml+1sz1PW+z6xt27zuj+skrX2ZvqR5z8kkuOfdPt43/1fMm/grFG6f\n" + "Lss9JA7JG7tnZs/SfJUrfS3foJ9TvHCopJsV8nWx/t24bJn8Fo/5TjWJXMJIS+i+G36TsZ/7Q9P\n" + "8ATfzfeOFofVSZv2/zvt+O3X/v65dJPjt/BiyfN1/wn0zre79nVej/ADG8ep4x2/6Srjd6TdviF\n" + "52ko8m6/Ht9X1KnftEo+POwxzK8mSTF46vrH6T1/OEl5Okkl//Z/+0uHFBob3Rvc2hvcCAzLjAA\n" + "OEJJTQQEAAAAAAArHAIAAAIAAhwCeAAfICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAA\n" + "4QklNBCUAAAAAABD7Caa9B0wqNp2P4sxXqayFOEJJTQPqAAAAAB2wPD94bWwgdmVyc2lvbj0iMS\n" + "4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUgQ\n" + "29tcHV0ZXIvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Q\n" + "cm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk\n" + "+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1Ib3Jpem9udGFsUmVzPC9rZXk+Cgk8ZGljdD\n" + "4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCTxzdHJpbmc+Y\n" + "29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCTxrZXk+Y29tLmFwcGxlLnByaW50\n" + "LnRpY2tldC5pdGVtQXJyYXk8L2tleT4KCQk8YXJyYXk+CgkJCTxkaWN0PgoJCQkJPGtleT5jb20\n" + "uYXBwbGUucHJpbnQuUGFnZUZvcm1hdC5QTUhvcml6b250YWxSZXM8L2tleT4KCQkJCTxyZWFsPj\n" + "cyPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJC\n" + "QkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJpbmc+CgkJCQk8a2V5PmNv\n" + "bS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGRhdGU+MjAwNy0wMS0zMFQ\n" + "yMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuc3RhdGVGbG\n" + "FnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY3Q+CgkJPC9hcnJheT4KC\n" + "TwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwv\n" + "a2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4\n" + "KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS\n" + "5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KC\n" + "QkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwva2V5PgoJ\n" + "CQkJPGludGVnZXI+MTwvaW50ZWdlcj4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5\n" + "jbGllbnQ8L2tleT4KCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW\n" + "5nPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lm1vZERhdGU8L2tleT4KCQkJCTxkY\n" + "XRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQu\n" + "dGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJPGludGVnZXI+MDwvaW50ZWdlcj4KCQkJPC9kaWN\n" + "0PgoJCTwvYXJyYXk+Cgk8L2RpY3Q+Cgk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0Ll\n" + "BNU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZ\n" + "WF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4K\n" + "CQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5Pgo\n" + "JCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1TY2FsaW5nPC\n" + "9rZXk+CgkJCQk8cmVhbD4xPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0L\n" + "mNsaWVudDwva2V5PgoJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJp\n" + "bmc+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGR\n" + "hdGU+MjAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC\n" + "50aWNrZXQuc3RhdGVGbGFnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY\n" + "3Q+CgkJPC9hcnJheT4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQu\n" + "UE1WZXJ0aWNhbFJlczwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n" + "0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cm\n" + "luZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFyc\n" + "mF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1WZXJ0\n" + "aWNhbFJlczwva2V5PgoJCQkJPHJlYWw+NzI8L3JlYWw+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcml\n" + "udC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbm\n" + "FnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZ\n" + "Xk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT4KCQkJCTxrZXk+Y29tLmFw\n" + "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI\n" + "+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5jb20uYXBwbGUucHJpbnQuUG\n" + "FnZUZvcm1hdC5QTVZlcnRpY2FsU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwb\n" + "GUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGlu\n" + "Z21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF\n" + "5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2\n" + "VGb3JtYXQuUE1WZXJ0aWNhbFNjYWxpbmc8L2tleT4KCQkJCTxyZWFsPjE8L3JlYWw+CgkJCQk8a\n" + "2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5h\n" + "cHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n" + "pY2tldC5tb2REYXRlPC9rZXk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT\n" + "4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpb\n" + "nRlZ2VyPjA8L2ludGVnZXI+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5j\n" + "b20uYXBwbGUucHJpbnQuc3ViVGlja2V0LnBhcGVyX2luZm9fdGlja2V0PC9rZXk+Cgk8ZGljdD4\n" + "KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUmVjdDwva2\n" + "V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5P\n" + "goJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJPGtleT5j\n" + "b20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGl\n" + "jdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUm\n" + "VjdDwva2V5PgoJCQkJCTxhcnJheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhb\n" + "D4wLjA8L3JlYWw+CgkJCQkJCTxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFs\n" + "PgoJCQkJCTwvYXJyYXk+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDw\n" + "va2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQ\n" + "kJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+M\n" + "jAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlj\n" + "a2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q\n" + "+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYX\n" + "QuUE1BZGp1c3RlZFBhcGVyUmVjdDwva2V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wc\n" + "mludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21h\n" + "bmFnZXI8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTw\n" + "va2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYW\n" + "dlRm9ybWF0LlBNQWRqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZ\n" + "WFsPi0xODwvcmVhbD4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3Jl\n" + "YWw+CgkJCQkJCTxyZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmF\n" + "wcGxlLnByaW50LnRpY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcm\n" + "ludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQub\n" + "W9kRGF0ZTwva2V5PgoJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJ\n" + "CTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWd\n" + "lcj4wPC9pbnRlZ2VyPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5Pm\n" + "NvbS5hcHBsZS5wcmludC5QYXBlckluZm8uUE1QYXBlck5hbWU8L2tleT4KCQk8ZGljdD4KCQkJP\n" + "GtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20u\n" + "YXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcml\n" + "udC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZX\n" + "k+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVBhcGVyTmFtZTwva2V5PgoJCQkJCTxzdHJpb\n" + "mc+bmEtbGV0dGVyPC9zdHJpbmc+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNs\n" + "aWVudDwva2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50LnBtLlBvc3RTY3JpcHQ8L3N\n" + "0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQ\n" + "kJCTxkYXRlPjIwMDMtMDctMDFUMTc6NDk6MzZaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFwcGxlL\n" + "nByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4xPC9pbnRlZ2VyPgoJ\n" + "CQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5\n" + "QYXBlckluZm8uUE1VbmFkanVzdGVkUGFnZVJlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb2\n" + "0uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUuc\n" + "HJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNr\n" + "ZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmF\n" + "wcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYWdlUmVjdDwva2V5PgoJCQkJCTxhcn\n" + "JheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhbD4wLjA8L3JlYWw+CgkJCQkJC\n" + "TxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFsPgoJCQkJCTwvYXJyYXk+CgkJ\n" + "CQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJCQkJCTxzdHJpbmc\n" + "+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLn\n" + "ByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+MjAwNy0wMS0zMFQyMjowODo0M\n" + "Vo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LnN0YXRlRmxhZzwva2V5\n" + "PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9\n" + "kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYXBlcl\n" + "JlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b\n" + "3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5n\n" + "PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJ\n" + "heT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYW\n" + "RqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZWFsPi0xODwvcmVhb\n" + "D4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3JlYWw+CgkJCQkJCTxy\n" + "ZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n" + "pY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZX\n" + "I8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5P\n" + "goJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFw\n" + "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4wPC9pbnRlZ2V\n" + "yPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcm\n" + "ludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJPGRpY3Q+CgkJCTxrZXk+Y29tL\n" + "mFwcGxlLnByaW50LnRpY2tldC5jcmVhdG9yPC9rZXk+CgkJCTxzdHJpbmc+Y29tLmFwcGxlLnBy\n" + "aW50LnBtLlBvc3RTY3JpcHQ8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n" + "0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcH\n" + "BsZS5wcmludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJCQkJPHN0cmluZz5VU\n" + "yBMZXR0ZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50\n" + "PC9rZXk+CgkJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5\n" + "nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPG\n" + "RhdGU+MjAwMy0wNy0wMVQxNzo0OTozNlo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpb\n" + "nQudGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8\n" + "L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2t\n" + "ldC5BUElWZXJzaW9uPC9rZXk+CgkJPHN0cmluZz4wMC4yMDwvc3RyaW5nPgoJCTxrZXk+Y29tLm\n" + "FwcGxlLnByaW50LnRpY2tldC5wcml2YXRlTG9jazwva2V5PgoJCTxmYWxzZS8+CgkJPGtleT5jb\n" + "20uYXBwbGUucHJpbnQudGlja2V0LnR5cGU8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmlu\n" + "dC5QYXBlckluZm9UaWNrZXQ8L3N0cmluZz4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW5\n" + "0LnRpY2tldC5BUElWZXJzaW9uPC9rZXk+Cgk8c3RyaW5nPjAwLjIwPC9zdHJpbmc+Cgk8a2V5Pm\n" + "NvbS5hcHBsZS5wcmludC50aWNrZXQucHJpdmF0ZUxvY2s8L2tleT4KCTxmYWxzZS8+Cgk8a2V5P\n" + "mNvbS5hcHBsZS5wcmludC50aWNrZXQudHlwZTwva2V5PgoJPHN0cmluZz5jb20uYXBwbGUucHJp\n" + "bnQuUGFnZUZvcm1hdFRpY2tldDwvc3RyaW5nPgo8L2RpY3Q+CjwvcGxpc3Q+CjhCSU0D6QAAAAA\n" + "AeAADAAAASABIAAAAAALeAkD/7v/uAwYCUgNnBSgD/AACAAAASABIAAAAAALYAigAAQAAAGQAAA\n" + "ABAAMDAwAAAAF//wABAAEAAAAAAAAAAAAAAABoCAAZAZAAAAAAACAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAADhCSU0D7QAAAAAAEABIAAAAAQABAEgAAAABAAE4QklNBCYAAAAAAA4AAAAAAAAA\n" + "AAAAP4AAADhCSU0EDQAAAAAABAAAAB44QklNBBkAAAAAAAQAAAAeOEJJTQPzAAAAAAAJAAAAAAA\n" + "AAAABADhCSU0ECgAAAAAAAQAAOEJJTScQAAAAAAAKAAEAAAAAAAAAAThCSU0D9QAAAAAASAAvZm\n" + "YAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAA\n" + "QAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP//\n" + "//////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA///\n" + "//////////////////////////wPoAAA4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQ\n" + "QeAAAAAAAEAAAAADhCSU0EGgAAAAADRQAAAAYAAAAAAAAAAAAAAGQAAABkAAAACABEAFMAQwAwA\n" + "DIAMwAyADUAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAGQAAABkAAAAAAAAAAAA\n" + "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHN\n" + "PYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAA\n" + "AAQnRvbWxvbmcAAABkAAAAAFJnaHRsb25nAAAAZAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABA\n" + "AAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAG\n" + "b3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQA\n" + "AAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAA\n" + "BUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAZAAAAABSZ2h0bG9uZ\n" + "wAAAGQAAAADdXJsVEVYVAAAAAEAAAAAAABudWxsVEVYVAAAAAEAAAAAAABNc2dlVEVYVAAAAAEA\n" + "AAAAAAZhbHRUYWdURVhUAAAAAQAAAAAADmNlbGxUZXh0SXNIVE1MYm9vbAEAAAAIY2VsbFRleHR\n" + "URVhUAAAAAQAAAAAACWhvcnpBbGlnbmVudW0AAAAPRVNsaWNlSG9yekFsaWduAAAAB2RlZmF1bH\n" + "QAAAAJdmVydEFsaWduZW51bQAAAA9FU2xpY2VWZXJ0QWxpZ24AAAAHZGVmYXVsdAAAAAtiZ0Nvb\n" + "G9yVHlwZWVudW0AAAARRVNsaWNlQkdDb2xvclR5cGUAAAAATm9uZQAAAAl0b3BPdXRzZXRsb25n\n" + "AAAAAAAAAApsZWZ0T3V0c2V0bG9uZwAAAAAAAAAMYm90dG9tT3V0c2V0bG9uZwAAAAAAAAALcml\n" + "naHRPdXRzZXRsb25nAAAAAAA4QklNBBEAAAAAAAEBADhCSU0EFAAAAAAABAAAAAE4QklNBAwAAA\n" + "AACfkAAAABAAAAZAAAAGQAAAEsAAB1MAAACd0AGAAB/9j/4AAQSkZJRgABAgEASABIAAD/7QAMQ\n" + "WRvYmVfQ00AAv/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUT\n" + "ExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4\n" + "ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZA\n" + "MBIgACEQEDEQH/3QAEAAf/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBA\n" + "QEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEy\n" + "BhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80Y\n" + "nlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBT\n" + "UBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTV\n" + "KMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/\n" + "2gAMAwEAAhEDEQA/APLtso1NRc0vP0Rok8NYyPEfijOG2ljBoAJPxKFppZtbS4Rz38kV+OPRDge\n" + "T89EPHBfvLjtb3P8A30K/j47cgsrYNxGpPYJpK8RtyXUlvPfsobV0GV0uippLiX3EaMb2/rKgMB\n" + "1ghoiNST4BESCjjLmxqmKtvxiXQ0cd0q8E2bjIDWjk9z5I8QW8JaoHcdkUePZJtZD9p8YU/Rsc/\n" + "wBNjS5zjDWjUk+SSKYaJLYq+qWeYGQ5lBPLJ3OA8wz2/wDSWni/U3H2AXW2l2oloa0f9LcjSLeU\n" + "hJdb/wAyqd387Zt+DZ5SSpVh/9DzO6dw7gGPuVn6ft/kyPkqwlxjw1Rnh24QNWjUeR5TSuDc6bg\n" + "fatpsJZQ3sNC4rWfkVYpbi4LAb3aANEkFLp7GHGYxuhAj4K/hYVNDjYGzZ++eSSoSbLZjGgwxul\n" + "XNrPqO35FukdmzyXOQeqtqwqRg4o/SOAN9ng3/AMzW02txZ9I+ZHKr241UOcWDaz3uLtSSPEpWu\n" + "rR5XPeylmyNr4BIPPCyH2Oc6T8kXNvddkPe/VzjJPxQAJMKeIoNScrPk2MbfddXUNXvcGtPx0Xb\n" + "dJ6NXjOD2Dfdw6w9v5LFW+q/1WLA3Ly9LSJaz91p/wDRjl2lOLWwAMbEJErWjRgESYieVdZhsMF\n" + "wMt08ldrx/vVivHaOdSgCoud9krmElpba93ASTlr/AP/R83ohr97voiJV/Fq9QvsI+mdPgs1thc\n" + "BWO5C38CoOY1g78qOejLiGvknxLAyGtExp5K9uzGt9RrNw7DhRfQKKx6bZIGgPj4rPycLqWVtIs\n" + "JGu5skDyTBRZtQNrb1fU8xrtpBaO4MLQxcx1sNuEjt5rMGJR9noY5hF7Wxa8aAnxVvDb6bgHH2z\n" + "omk0e64ajUUXnev9Idi5rrWAux7SXNd4E/muS+rHSjm9VbPtZjj1CSJBI+g3+0uh69b+iDG/QcD\n" + "u0nQCeFP6l0MZhWX/AJ1xM+QafY1TQlY1a+WABsdXp8Sp27aBH+vZaVbC0ADlVcASwtdOolp8Ct\n" + "BjmtGv0uI8EmJmxkIjWkmPEKLSPiidxIgJKRbDPCSN5pJyH//S87uw/suZ6c72iC13kVs9PdDmk\n" + "KllVziV3cuafc7yP0QjYFh26cqM6hsxAjIj6u6xzbDHh3R663AaceH+5BwdruVp2PqZUA0a9yo6\n" + "DPQajscnXb8YQdzC8H909joiZttoxoBIa4gGOQ3uqh+z1RuD2Ds4j2n+39FNKaFMevS/p5LPpSA\n" + "I8/b/ABW70THZXj11VjaIAIHgFl5VdD8AneDMaec6Lb6QAKmu7x+VSw2a3MdF/rF9YKeh4B9OHZ\n" + "lpAprPH8p7v5DFwrPrV9YDa645toLjMaFo8mtcEvrhkWZHXbg7T0Q2to8o3f8AfkarEitlVTKnY\n" + "ra992QQ2wOfG57H2bg7H2fzbFKA130P6n9dHWemCx5H2mk7LgPH818f8IuhAka6ea8y/wAWrcod\n" + "VyrceRhBsPae5J/Qj+sxq9KDpMuMuKBCEntnny/1CSaWxM6pIKf/0/MvtF3pCrefTBnbOi1elP3\n" + "Et8Vi+Sv9LuNd4HwTSNGSEqkLerwtwn+SYKtOeS4A8Krh2j1D/K1Vu4B5gaDmVDJtAr7zYYGoRB\n" + "QDWQ4c9h/csuyjI3fobnDyJR8fF6ltcTaXRwCkAuAsfMGr1TGFNdTmEwLWS2dIK6npLgK2T4Lle\n" + "pUZxoc9+6K4eR5NO5bPT73NoqIfoILT/JcFJDZr8zGiNXnvrfiur6/Y8tht7WvaexgbXf8AUrFt\n" + "8IExyvRusYDOsYTAIbfWdzHRJ8HN/tLj7OgdRZawmreHP2gt9wEfvtH0f7SkDXe7+o+AOn9DquL\n" + "f0mV+leQPH6H+axafUvrB07ptJtyshtTZDTEudJ7bWS5V6MmyltVLn7ht2hwECQP+isb60/Vqvr\n" + "tbLsa1lObVIJd9Gxv5rXx9F7fzHpIbf/jgfVnd/TLYj6XoOhJcP/zE+sOzd6dW7dt2eo3dH7/9R\n" + "JJWj//U8uiGFx76BFZLQ2xvLeVGAWQrFDJbtKBSHd6blNura4H3BbDXB7InVcZXZdh2bmTt7hbO\n" + "J1dj2gzCjlFnhPod3WLHB+n3o9ZsAkFVMfMrs7orLmgkHUdkyqZQQWWQbLGlrjMjUeSrfV3Ltsw\n" + "30EBzcd5YCedvLETJya66nWOIAaCVnfU/KuZn21CDVa02PngQdHf9LapMfVhzkaAPUUW3M91YaR\n" + "3YDJ+WiBmZGazPo9Kttdt2j63E6s/fft/d/NWjXkMra7KtO2qkE6cErHpvsyMmzPu0dY4Bg/dYP\n" + "otTpyoaMUI2XUya8tzG/pi0NMtICo/bsut21gdcWclkj5OncxaDrw6kM+9QxQzaWRAGii4pDqzC\n" + "MT02aX7WzPU9b7PrG3bvO6P6yStfZm+pHnPySS4590+3jf/V8yb+CsUbp8uyz0kDskbu2dmz9J8\n" + "lSt9Ld+gn1O8cKikmxXydbH+3bhsmfwWj/lONYlcwkhL6L4bfpOxn/tD0/wBN/N944Wh9VJm/b/\n" + "O+347df+/rl0k+O38GLJ83X/CfTOt7v2dV6P8AMbx6njHb/pKuN3pN2+IXnaSjybr8e31fUqd+0\n" + "Sj487DHMryZJMXjq+sfpPX84SXk6SSX/9kAOEJJTQQhAAAAAABVAAAAAQEAAAAPAEEAZABvAGIA\n" + "ZQAgAFAAaABvAHQAbwBzAGgAbwBwAAAAEwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAA\n" + "gADcALgAwAAAAAQA4QklNBAYAAAAAAAcABQAAAAEBAP/hFWdodHRwOi8vbnMuYWRvYmUuY29tL3\n" + "hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prY\n" + "zlkJz8+Cjw/YWRvYmUteGFwLWZpbHRlcnMgZXNjPSJDUiI/Pgo8eDp4YXBtZXRhIHhtbG5zOng9\n" + "J2Fkb2JlOm5zOm1ldGEvJyB4OnhhcHRrPSdYTVAgdG9vbGtpdCAyLjguMi0zMywgZnJhbWV3b3J\n" + "rIDEuNSc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi\n" + "1yZGYtc3ludGF4LW5zIycgeG1sbnM6aVg9J2h0dHA6Ly9ucy5hZG9iZS5jb20vaVgvMS4wLyc+C\n" + "gogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXVpZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05\n" + "MWQ1NDAzZjkyZjknCiAgeG1sbnM6cGRmPSdodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvJz4\n" + "KICA8IS0tIHBkZjpTdWJqZWN0IGlzIGFsaWFzZWQgLS0+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCi\n" + "A8cmRmOkRlc2NyaXB0aW9uIGFib3V0PSd1dWlkOjIyZDAyYjBhLWIyNDktMTFkYi04YWY4LTkxZ\n" + "DU0MDNmOTJmOScKICB4bWxuczpwaG90b3Nob3A9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9z\n" + "aG9wLzEuMC8nPgogIDwhLS0gcGhvdG9zaG9wOkNhcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwvcmR\n" + "mOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGEtYj\n" + "I0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcD0naHR0cDovL25zLmFkb2JlL\n" + "mNvbS94YXAvMS4wLyc+CiAgPCEtLSB4YXA6RGVzY3JpcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwv\n" + "cmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGE\n" + "tYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcE1NPSdodHRwOi8vbnMuYW\n" + "RvYmUuY29tL3hhcC8xLjAvbW0vJz4KICA8eGFwTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwa\n" + "G90b3Nob3A6MjJkMDJiMDYtYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5PC94YXBNTTpEb2N1\n" + "bWVudElEPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXV\n" + "pZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05MWQ1NDAzZjkyZjknCiAgeG1sbnM6ZGM9J2h0dH\n" + "A6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvJz4KICA8ZGM6ZGVzY3JpcHRpb24+CiAgIDxyZ\n" + "GY6QWx0PgogICAgPHJkZjpsaSB4bWw6bGFuZz0neC1kZWZhdWx0Jz4gICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgIDwvcmRmOkFsdD4KICA8L2RjOmRlc2NyaXB0aW9\n" + "uPgogPC9yZGY6RGVzY3JpcHRpb24+Cgo8L3JkZjpSREY+CjwveDp4YXBtZXRhPgogICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA\n" + "ogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n" + "ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n" + "AgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n" + "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAg\n" + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n" + "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIC\n" + "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0ndyc/P\n" + "v/uAA5BZG9iZQBkQAAAAAH/2wCEAAQDAwMDAwQDAwQGBAMEBgcFBAQFBwgGBgcGBggKCAkJCQkI\n" + "CgoMDAwMDAoMDAwMDAwMDAwMDAwMDAwMDAwMDAwBBAUFCAcIDwoKDxQODg4UFA4ODg4UEQwMDAw\n" + "MEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZAMBEQACEQEDEQ\n" + "H/3QAEAA3/xAGiAAAABwEBAQEBAAAAAAAAAAAEBQMCBgEABwgJCgsBAAICAwEBAQEBAAAAAAAAA\n" + "AEAAgMEBQYHCAkKCxAAAgEDAwIEAgYHAwQCBgJzAQIDEQQABSESMUFRBhNhInGBFDKRoQcVsUIj\n" + "wVLR4TMWYvAkcoLxJUM0U5KismNzwjVEJ5OjszYXVGR0w9LiCCaDCQoYGYSURUaktFbTVSga8uP\n" + "zxNTk9GV1hZWltcXV5fVmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9zhIWGh4iJiouMjY6PgpOUlZaX\n" + "mJmam5ydnp+So6SlpqeoqaqrrK2ur6EQACAgECAwUFBAUGBAgDA20BAAIRAwQhEjFBBVETYSIGc\n" + "YGRMqGx8BTB0eEjQhVSYnLxMyQ0Q4IWklMlomOywgdz0jXiRIMXVJMICQoYGSY2RRonZHRVN/Kj\n" + "s8MoKdPj84SUpLTE1OT0ZXWFlaW1xdXl9UZWZnaGlqa2xtbm9kdXZ3eHl6e3x9fn9zhIWGh4iJi\n" + "ouMjY6Pg5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6vr/2gAMAwEAAhEDEQA/APBnplwPAdR+GB\n" + "KY6dYtNG1w39yh4+xb+zIksgEfFaRSSoIx8f7RPRRkSWQimM+lRmwWVXFWYigHxUUVoMiJM+Fj0\n" + "tg0RBegLE0Wu+3c+GTBazFCGI7HtSp9slbFYYzyoBsegw2hY1Afl3wqqRqahk+0tDgKpgu4DAUU\n" + "+HY+GRS2ePiMKtUB3G+KGuONq//Q8OzpFbW5WnxMop4k9crG5ZnZNJkEOn21utVRYw7HxZtz+OR\n" + "vdsrZ2lRtci4aVxFEQA0neg/ZXxJpTITNNuOFss0vSotYNvZ2qGRkPKSTqiU8Sdqk5SZU5Ix8XJ\n" + "NNZ8k6bp8TtM73OputUtYq0Unux/hkRkJOzZLCAN2KR+VpbtSkCBaDnIzdlWu59u+XeJTjeASk8\n" + "+juZOESEAVqx8BvU/PJibScTrTy09560hkWOGFd2YgFnPQKD19zhOSkxw2l8Vm6XAiYb8gg+k5O\n" + "9mnhoon9H3cs5s7WF5pp29OGGMFndyaAKBuTiEEPQLD8h/NDmNdYlttNkYjlbFjcXCr3LLH8II8\n" + "C2WUGviZvon/OPWkm3RNSv72SYllMkKxQRV67CQMSKYQAxMkR/wBC56d61P0heel4cYuVOXWvTp\n" + "h4Qjjf/9Hw5qBYyISaqjBV+QpvkAzKcki4HomnIxck/wBhtlR2bhunvlDywddMUl4zW+kQ9FQ8X\n" + "nfuSewrtmPkycPvc/DhMhvyegXOrWWhmLQPKlsj6xIAiLCoZkY96nv7npmJvI2XOjQFMl0fyRqM\n" + "NoxvZvrGt33wlATwiMnVnY1LEdSfuyXF3KIDmUu88w2XlnTl8raAlb2ZFfVL0jdYRtQnxc7BfDC\n" + "OaJR7nm3me5tdOtjbMvp3ZRXkV6chVQRX79hmVjgZG+jgZ5jHGhzecXF5LPL6jEjstSSaDM51Ka\n" + "6MZ9S1C0sEBe8uZo4YCBXdjxGw60wEWyEqfUHkT8vLXRJFuLdTcaqfhlvWUErtukZ3ABPUjIXTE\n" + "m3rGmeV2Tk5UKz/AG/E/wAcgZKya20C3b02kjYtH8AqCygbkUH0nLYlgUb+gbWtPbpXt/n2ybB/\n" + "/9Lw4oaVxGd+PxH3qBkGaY3KyiSP01IkiUclH8sg+LKydm6INvZvKsFu+kWtvD8LRoFNRup6moO\n" + "aqd277HsGW+XPLmn6XM17FF6l7vW4fd2Zuu+RFls2tmUNrLJb7TSBertGQGqetDkxE0na0pvtHs\n" + "QkszWyiGAG5laYlnkeMVHJj8sA5rPk+SvMepTalqlxd3B5zTOXdj/MxqafLpm5xioh5nPK5kpRG\n" + "pkcKAST0A6k5NpfUP5K/ki1ssHmHzF+71KRQ8Nud/Qibb/kYw6/yjbrXISlSH07YaHbWyxx2kXE\n" + "KACB2zHJtLI7XSelBRvH2xCpvaaTDHXkOTVBPcUG2479RlsdmJVPRtvV+ylenQ0y62FP/9PxRpo\n" + "WG5FxKKxKFDA+GVS5NsebLdFsRePc3siVW4f4QR0QVAGYeSXR2unhtZ6s60K6jt+MMSFwtF2+xX\n" + "wr7eGUGLlRPQMsE2vxQm7itxKg3VCfT2+nb8cDYaCDtfOXmCCcROrQrUhkkCHYn6emRMqZxjbLd\n" + "F1+W/4xajHzjNCtQKMffETWUdngX5p+QZ9A8xS6hbo0ui37NNDPT7DOalHpsCD08Rmyw5ARTpdV\n" + "gIPEF35MeRn80ed4S5EdrpKm9kZ15K0iH92hB7Me/tmS60vt/QrCYyekiBdgSTXcjqV9q9MokFD\n" + "N7S3aFVVR8RoK9zldqndvAY6nffr/AGYQqLhjdpCoIAZW22HavU/LJBUP9WblX0xTw7fOmWsX/9\n" + "Tw7FdvMqWkQ3Z1qfED+mQIbI77PX/LFis9vBajZm2Y+x65rMh3t30Bsze400aVaIbSLk6r8CMRT\n" + "l/NmOcllnGDD9Y8uecNfEEiXrMgDGWAyGOOu5WlB+vMrHODTlxZCdjsyFdB006VpVtLasurQxBL\n" + "64WiLI4/aFT1ANOXemV5piR2b9NiljB4yyHy9CLOVI5GJhB+CvXY9R8xmINzs5HNZ+Z96BZpbxA\n" + "fVJo39UFefwopYgL4nMiMd2qZoIn/AJx00u3t/Lt7qpp9Yv5GLf5MUTERqfbvmzBeezjd9H+VlL\n" + "wSQzBqsvOGQD7L12rXsemPNxmXQSxxIPU2nFV4HYqR1xEUWj4ZAxBryr2G+J2VGDZlLrxUH6KZA\n" + "Fkqb15VFelfwy+2FP8A/9Xxlf6AdA182Yk9eFeLxSjoVfcfSMo4uIOfkweFOnpvlWYrLEwNFAA+\n" + "nMOYdrhFvQLeSO7coBXiK8iKiv07Zj8Ac4QtNrW1njUcKcT+yAR/xGmR4WcsStLpTuPU9IFaEsV\n" + "BP3k4m2AgBzSwyQNcIwNTE1aI3wnam9O2Ug7s5Ckk/NDndeVXa2H78MqqV6jmeBp9+ZWKXqDjZ4\n" + "+gvVvy30qCy0qzsLRBCnBI2VdgUTqPvOZ7y+Q7pz+bn5q6d+VflZxZlJ/NN4ypptk5qtB9qRwDX\n" + "gn/AAx2y2ItpfKFv+eH5qNeTajJ5ovVaVywSqvEtTUKqupAA6D2y0BNPtv/AJx//M5PzL8mJeXT\n" + "L+ndPf6rqarSpkAqsnEAAeoN6DpkJRYci9lROSgSUUH9o9K5Tw0ztfSHnXkOtK9q+PHwydq//9b\n" + "yxrVoZNBtNSA5zRMPXmH8j0CLXuBmHE+qneamHpEuqYeV7pzFVTRgQK5XMNmnlb1vyyY5QA1OwJ\n" + "+eUF2seTOLu5s7azVIVAkpVn/hhnIALG73Yz5jvb1dICqzpDNIqyFD8SxH7R28cxibZCiWOsdJs\n" + "PTM6XNstPhnkjIhcHuJBVfvOCiUSn0TfWrTTLjyw8guA/PifTO3xcxxA8a5ZAbimvJP0m3p/kFF\n" + "WxhmpWQJ9NW3zZPHz5vlb/nIDVbrWfzO1RJhxGnpDaRL/khA1T7ktmSOTAJhZaAUtLawsbayl8v\n" + "xWi3Gpay0cF3HPcFRJJHJMXVrcJ8UaAFG5LWjF8tAYW9H/wCcOo9bTzxrt/owkTyksZW5gkIKvI\n" + "7k26nvyReRJHyyBWT7dWQyOWlbnK2526e1O1MqIUFE84uPLkOdK9RXI0E2/wD/1/DA1bURZLY/W\n" + "ZDZqwb0eXw7dMgIi7bjllVXsz7yNcfWC0Vd3Ip92Y2UOz0cnsPlwyx8xQ/u24sMxCadoJp9LOXk\n" + "VX/uwRUE0BI8cokbLMyoKouHu2MaKGXw7fLDwgoGSkbHpaNZyLLHRSKcFFQQRvUdMlwUFOQyLzr\n" + "ztpCaba6fPau4ijv4OURY8AjVFKV7ZZiO+7Vnh6XvXkSWNbW2WTb92KDxIFMzwHlZc3zX+fuizW\n" + "f5p3ty8XGDU4YLmCQiisyII3+4rvl8UB5ffEghRGvOm7AbnvWvjk1fen/ONPldPKP5aWOpPCfr2\n" + "uE31y6q2wbaMEn+VAMDSdyzrzj+avlHyTp0l/r2rxWFuHWJuIeacu4qFCRgsajfBwsty89/6Gr/\n" + "ACa9an+JL/hSnrfoubhXwpXpjwhaL//Q8E1AqtcAZMs8l6i1nqMa1oSVP0VynKLDmaWdSfQXl69\n" + "jF1Jv8MhDb5rpB3AO7INRRLhhGp4R05FgaGvTMU8200xS70zVDMRp2pTIOvBmB3PgQP15kxIcnD\n" + "LH/EEz0rRvOJhldr9pQtCqyd6VrShGTqw5d4ARv9jHfOGl+ZJNMluLkyenaFbiRdqFYW5nrWuwO\n" + "MKB5MdSMRxnhlu9N8p6lLFpti63FUjCtFJTrDKvse2bEDZ4XJ9RZB+YPli2/Mjy5bxoUi1a0YS2\n" + "85UOwIXiy9jRu+TBppfOF1+V3m22vrdpNPM8cs/oo0VJlUqQPjValR3+IZNNvtLS9Yu9Mi0/TJr\n" + "kyp6QhWVVCIWRATsKBemwwFrDzT87fybs/wA1bW21PRb+DTvNlgGSRp6iC8i3KJJx+y6n7D0Pwm\n" + "hxBZXT55/6Fi/Nf0PW+qWXq+t6X1X67F6vD/ftK04V/wBl344U8b//0fBapxheVh9ocV+nviqY2\n" + "/qQJDew/bioWHiuQ8m0bbvaPKGtQ6jaxSo9JloCK75gZI0Xb4sgkHo8MouoAvP94BsRmGY7uWJU\n" + "gzbypOQpNOvIdK4Nw2WCE2tXulTkjEEbdafgclxMhFBas93dwyQzsWDghlJFONKHJCZtjOFBJfy\n" + "j1y9vPL9zpbIs0WkXL2sUjA8hDXlGCRXtt07ZuYvL5KJeo6bfajbkzWkcToR8dqshZ6in2fhNK/\n" + "PDTUlXmHVvMdr5o0v9H2kdrqGpfu7m0nkY87Uf7tkKAU4/s03ynLkEBbfihx7dGT6va67LbRMNR\n" + "aKOBuUTKgIBXoK1BOYR1M3aQ0mOt9yxUeZNdtJhFapLqMluSXkg5oxJrUMW5KevQ9MmNXXNqOiH\n" + "Rr/Hmv8A1r9I/oj95w+r+j9Yf1+NP5+nXtTD+dF8tkfkOlv/0vC3ph7f0/alcVTbS4A8QibuKb5\n" + "RI05EBYRFpdX3ly79a2qYCavH/EY7TCYyMD5PSdD8+wXUSn1ArDqOhBzFlipz4ZwWbaV5htbsgF\n" + "qg9crMXKErGyYwajFGzxyHlGSePbbwyqg5UZlCaxrFpaWU95LIqrEjMAT4Dp9OShGy1ZslBhv/A\n" + "Dj9rd/a+aL+xUK+m38L3d0HrxRo2HFtu5D8c27y8t30raarbWkU+u6g4gsNORn+EcUaSh2Pc0/4\n" + "lgtAjezzbT9SutY1i782al8Nxdyotqh6xWybIg+jc5q8s+I27bFDgFPQp9RE+nrag70+L6crrZu\n" + "4jajokdv6LW/Dii1Wo61PXKQN3KPK0L+h4/rnD/K5V78a5LhXxd3/0/DMXXtwxVNtL9Xkaf3f7N\n" + "etfbKMjdjtkZ9D6ufrlK0+HpX8coF9HJ26sXvfqXrf7i/U+uften/d/wCyrmQL6uOav0pvpP8Ai\n" + "b1F+rV59+vH6a5XLhcjH4nRmY/xpxHP0/UptWvT6Mx/RbmjxWK+aP8AFf1M/pCv1Kvxen9inavf\n" + "MrFwXtzcLUeLXq5Mv/I3nz1b0v8AjofuKVry9KrUpTanOlf9jmQ68va/zH9b/COn/o7/AI431mP\n" + "65SvLh+zWvbl9rMfNfC34K4kmj9T6lD6FKclp/DNYXZx5srsPrHor6nXvkgxTPS/U+rv6dPU5mt\n" + "fngFN5ulv+l/pL/Lp/scerHo//2Q==\n"; + +static std::string gCommandLine; + +TEST(Base64, LargeSample) { + LOG(LS_VERBOSE) << "Testing specific base64 file"; + + char unescaped[64 * 1024]; + + // unescape that massive blob above + size_t size = Base64Unescape(SpecificTest, + sizeof(SpecificTest), + unescaped, + sizeof(unescaped)); + + EXPECT_EQ(size, sizeof(testbase64)); + EXPECT_EQ(0, memcmp(testbase64, unescaped, sizeof(testbase64))); +} + +bool DecodeTest(const char* encoded, size_t expect_unparsed, + const char* decoded, Base64::DecodeFlags flags) +{ + std::string result; + size_t consumed = 0, encoded_len = strlen(encoded); + bool success = Base64::DecodeFromArray(encoded, encoded_len, flags, + &result, &consumed); + size_t unparsed = encoded_len - consumed; + EXPECT_EQ(expect_unparsed, unparsed) << "\"" << encoded + << "\" -> \"" << decoded + << "\""; + EXPECT_STREQ(decoded, result.c_str()); + return success; +} + +#define Flags(x,y,z) \ + Base64::DO_PARSE_##x | Base64::DO_PAD_##y | Base64::DO_TERM_##z + +TEST(Base64, DecodeParseOptions) { + // Trailing whitespace + EXPECT_TRUE (DecodeTest("YWJjZA== ", 1, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(ANY, YES, CHAR))); + + // Embedded whitespace + EXPECT_FALSE(DecodeTest("YWJjZA= =", 3, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(ANY, YES, CHAR))); + + // Embedded non-base64 characters + EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA=*=", 0, "abcd", Flags(ANY, YES, CHAR))); + + // Unexpected padding characters + EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a", Flags(STRICT, YES, CHAR))); + EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a", Flags(WHITE, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YW=JjZA==", 0, "abcd", Flags(ANY, YES, CHAR))); +} + +TEST(Base64, DecodePadOptions) { + // Padding + EXPECT_TRUE (DecodeTest("YWJjZA==", 0, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA==", 0, "abcd", Flags(STRICT, ANY, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA==", 2, "abcd", Flags(STRICT, NO, CHAR))); + + // Incomplete padding + EXPECT_FALSE(DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, ANY, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, NO, CHAR))); + + // No padding + EXPECT_FALSE(DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, YES, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, ANY, CHAR))); + EXPECT_TRUE (DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, NO, CHAR))); +} + +TEST(Base64, DecodeTerminateOptions) { + // Complete quantum + EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, BUFFER))); + EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, CHAR))); + EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, ANY))); + + // Complete quantum with trailing data + EXPECT_FALSE(DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, BUFFER))); + EXPECT_TRUE (DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, CHAR))); + EXPECT_TRUE (DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, ANY))); + + // Incomplete quantum + EXPECT_FALSE(DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, BUFFER))); + EXPECT_FALSE(DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, CHAR))); + EXPECT_TRUE (DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, ANY))); +} + +TEST(Base64, GetNextBase64Char) { + // The table looks like this: + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + char next_char; + EXPECT_TRUE(Base64::GetNextBase64Char('A', &next_char)); + EXPECT_EQ('B', next_char); + EXPECT_TRUE(Base64::GetNextBase64Char('Z', &next_char)); + EXPECT_EQ('a', next_char); + EXPECT_TRUE(Base64::GetNextBase64Char('/', &next_char)); + EXPECT_EQ('A', next_char); + EXPECT_FALSE(Base64::GetNextBase64Char('&', &next_char)); + EXPECT_FALSE(Base64::GetNextBase64Char('Z', NULL)); +} diff --git a/webrtc/base/base_tests.gyp b/webrtc/base/base_tests.gyp new file mode 100644 index 000000000..c5cc7b809 --- /dev/null +++ b/webrtc/base/base_tests.gyp @@ -0,0 +1,156 @@ +# Copyright (c) 2013 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. +{ + 'includes': [ '../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'webrtc_base_tests_utils', + 'type': 'static_library', + 'sources': [ + 'unittest_main.cc', + # Also use this as a convenient dumping ground for misc files that are + # included by multiple targets below. + 'fakecpumonitor.h', + 'fakenetwork.h', + 'fakesslidentity.h', + 'faketaskrunner.h', + 'gunit.h', + 'testbase64.h', + 'testechoserver.h', + 'testutils.h', + 'win32toolhelp.h', + ], + 'dependencies': [ + 'base.gyp:webrtc_base', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + }, + { + 'target_name': 'webrtc_base_tests', + 'type': 'executable', + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + 'base.gyp:webrtc_base', + 'webrtc_base_tests_utils', + ], + 'sources': [ + 'asynchttprequest_unittest.cc', + 'atomicops_unittest.cc', + 'autodetectproxy_unittest.cc', + 'bandwidthsmoother_unittest.cc', + 'base64_unittest.cc', + 'basictypes_unittest.cc', + 'bind_unittest.cc', + 'buffer_unittest.cc', + 'bytebuffer_unittest.cc', + 'byteorder_unittest.cc', + 'callback_unittest.cc', + 'cpumonitor_unittest.cc', + 'crc32_unittest.cc', + 'criticalsection_unittest.cc', + 'event_unittest.cc', + 'filelock_unittest.cc', + 'fileutils_unittest.cc', + 'helpers_unittest.cc', + 'httpbase_unittest.cc', + 'httpcommon_unittest.cc', + 'httpserver_unittest.cc', + 'ipaddress_unittest.cc', + 'logging_unittest.cc', + 'md5digest_unittest.cc', + 'messagedigest_unittest.cc', + 'messagequeue_unittest.cc', + 'multipart_unittest.cc', + 'nat_unittest.cc', + 'network_unittest.cc', + 'nullsocketserver_unittest.cc', + 'optionsfile_unittest.cc', + 'pathutils_unittest.cc', + 'physicalsocketserver_unittest.cc', + 'profiler_unittest.cc', + 'proxy_unittest.cc', + 'proxydetect_unittest.cc', + 'ratelimiter_unittest.cc', + 'ratetracker_unittest.cc', + 'referencecountedsingletonfactory_unittest.cc', + 'rollingaccumulator_unittest.cc', + 'scopedptrcollection_unittest.cc', + 'sha1digest_unittest.cc', + 'sharedexclusivelock_unittest.cc', + 'signalthread_unittest.cc', + 'sigslot_unittest.cc', + 'sigslottester.h', + 'sigslottester.h.pump', + 'socket_unittest.cc', + 'socket_unittest.h', + 'socketaddress_unittest.cc', + 'stream_unittest.cc', + 'stringencode_unittest.cc', + 'stringutils_unittest.cc', + # TODO(ronghuawu): Reenable this test. + # 'systeminfo_unittest.cc', + 'task_unittest.cc', + 'testclient_unittest.cc', + 'thread_checker_unittest.cc', + 'thread_unittest.cc', + 'timeutils_unittest.cc', + 'urlencode_unittest.cc', + 'versionparsing_unittest.cc', + 'virtualsocket_unittest.cc', + # TODO(ronghuawu): Reenable this test. + # 'windowpicker_unittest.cc', + ], + 'conditions': [ + ['OS=="linux"', { + 'sources': [ + 'latebindingsymboltable_unittest.cc', + # TODO(ronghuawu): Reenable this test. + # 'linux_unittest.cc', + 'linuxfdwalk_unittest.cc', + ], + }], + ['OS=="win"', { + 'sources': [ + 'win32_unittest.cc', + 'win32regkey_unittest.cc', + 'win32socketserver_unittest.cc', + 'win32toolhelp_unittest.cc', + 'win32window_unittest.cc', + 'win32windowpicker_unittest.cc', + 'winfirewall_unittest.cc', + ], + 'sources!': [ + # TODO(ronghuawu): Fix TestUdpReadyToSendIPv6 on windows bot + # then reenable these tests. + 'physicalsocketserver_unittest.cc', + 'socket_unittest.cc', + 'win32socketserver_unittest.cc', + 'win32windowpicker_unittest.cc', + ], + }], + ['OS=="mac"', { + 'sources': [ + 'macsocketserver_unittest.cc', + 'macutils_unittest.cc', + ], + }], + ['os_posix==1', { + 'sources': [ + 'sslidentity_unittest.cc', + 'sslstreamadapter_unittest.cc', + ], + }], + ['OS=="ios" or (OS=="mac" and target_arch!="ia32")', { + 'defines': [ + 'CARBON_DEPRECATED=YES', + ], + }], + ], # conditions + }, + ], +} diff --git a/webrtc/base/basicdefs.h b/webrtc/base/basicdefs.h new file mode 100644 index 000000000..1dee2ae65 --- /dev/null +++ b/webrtc/base/basicdefs.h @@ -0,0 +1,20 @@ +/* + * Copyright 2004 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_BASE_BASICDEFS_H_ +#define WEBRTC_BASE_BASICDEFS_H_ + +#if HAVE_CONFIG_H +#include "config.h" // NOLINT +#endif + +#define ARRAY_SIZE(x) (static_cast(sizeof(x) / sizeof(x[0]))) + +#endif // WEBRTC_BASE_BASICDEFS_H_ diff --git a/webrtc/base/basictypes.h b/webrtc/base/basictypes.h new file mode 100644 index 000000000..7649a43f8 --- /dev/null +++ b/webrtc/base/basictypes.h @@ -0,0 +1,134 @@ +/* + * Copyright 2004 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_BASE_BASICTYPES_H_ +#define WEBRTC_BASE_BASICTYPES_H_ + +#include // for NULL, size_t + +#if !(defined(_MSC_VER) && (_MSC_VER < 1600)) +#include // for uintptr_t +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" // NOLINT +#endif + +#include "webrtc/base/constructormagic.h" + +#if !defined(INT_TYPES_DEFINED) +#define INT_TYPES_DEFINED +#ifdef COMPILER_MSVC +typedef unsigned __int64 uint64; +typedef __int64 int64; +#ifndef INT64_C +#define INT64_C(x) x ## I64 +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## UI64 +#endif +#define INT64_F "I64" +#else // COMPILER_MSVC +// On Mac OS X, cssmconfig.h defines uint64 as uint64_t +// TODO(fbarchard): Use long long for compatibility with chromium on BSD/OSX. +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +typedef uint64_t uint64; +typedef int64_t int64; +#ifndef INT64_C +#define INT64_C(x) x ## LL +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## ULL +#endif +#define INT64_F "l" +#elif defined(__LP64__) +typedef unsigned long uint64; // NOLINT +typedef long int64; // NOLINT +#ifndef INT64_C +#define INT64_C(x) x ## L +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## UL +#endif +#define INT64_F "l" +#else // __LP64__ +typedef unsigned long long uint64; // NOLINT +typedef long long int64; // NOLINT +#ifndef INT64_C +#define INT64_C(x) x ## LL +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## ULL +#endif +#define INT64_F "ll" +#endif // __LP64__ +#endif // COMPILER_MSVC +typedef unsigned int uint32; +typedef int int32; +typedef unsigned short uint16; // NOLINT +typedef short int16; // NOLINT +typedef unsigned char uint8; +typedef signed char int8; +#endif // INT_TYPES_DEFINED + +// Detect compiler is for x86 or x64. +#if defined(__x86_64__) || defined(_M_X64) || \ + defined(__i386__) || defined(_M_IX86) +#define CPU_X86 1 +#endif +// Detect compiler is for arm. +#if defined(__arm__) || defined(_M_ARM) +#define CPU_ARM 1 +#endif +#if defined(CPU_X86) && defined(CPU_ARM) +#error CPU_X86 and CPU_ARM both defined. +#endif +#if !defined(ARCH_CPU_BIG_ENDIAN) && !defined(ARCH_CPU_LITTLE_ENDIAN) +// x86, arm or GCC provided __BYTE_ORDER__ macros +#if CPU_X86 || CPU_ARM || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define ARCH_CPU_LITTLE_ENDIAN +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define ARCH_CPU_BIG_ENDIAN +#else +#error ARCH_CPU_BIG_ENDIAN or ARCH_CPU_LITTLE_ENDIAN should be defined. +#endif +#endif +#if defined(ARCH_CPU_BIG_ENDIAN) && defined(ARCH_CPU_LITTLE_ENDIAN) +#error ARCH_CPU_BIG_ENDIAN and ARCH_CPU_LITTLE_ENDIAN both defined. +#endif + +#if defined(WEBRTC_WIN) +typedef int socklen_t; +#endif + +// The following only works for C++ +#ifdef __cplusplus +namespace rtc { + template inline T _min(T a, T b) { return (a > b) ? b : a; } + template inline T _max(T a, T b) { return (a < b) ? b : a; } + + // For wait functions that take a number of milliseconds, kForever indicates + // unlimited time. + const int kForever = -1; +} + +#define ALIGNP(p, t) \ + (reinterpret_cast(((reinterpret_cast(p) + \ + ((t) - 1)) & ~((t) - 1)))) +#define RTC_IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a) - 1))) + +// Use these to declare and define a static local variable (static T;) so that +// it is leaked so that its destructors are not called at exit. +#define LIBJINGLE_DEFINE_STATIC_LOCAL(type, name, arguments) \ + static type& name = *new type arguments + +#endif // __cplusplus +#endif // WEBRTC_BASE_BASICTYPES_H_ diff --git a/webrtc/base/basictypes_unittest.cc b/webrtc/base/basictypes_unittest.cc new file mode 100644 index 000000000..20515ecf9 --- /dev/null +++ b/webrtc/base/basictypes_unittest.cc @@ -0,0 +1,75 @@ +/* + * Copyright 2012 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 "webrtc/base/basictypes.h" + +#include "webrtc/base/gunit.h" + +namespace rtc { + +TEST(BasicTypesTest, Endian) { + uint16 v16 = 0x1234u; + uint8 first_byte = *reinterpret_cast(&v16); +#if defined(ARCH_CPU_LITTLE_ENDIAN) + EXPECT_EQ(0x34u, first_byte); +#elif defined(ARCH_CPU_BIG_ENDIAN) + EXPECT_EQ(0x12u, first_byte); +#endif +} + +TEST(BasicTypesTest, SizeOfTypes) { + int8 i8 = -1; + uint8 u8 = 1u; + int16 i16 = -1; + uint16 u16 = 1u; + int32 i32 = -1; + uint32 u32 = 1u; + int64 i64 = -1; + uint64 u64 = 1u; + EXPECT_EQ(1u, sizeof(i8)); + EXPECT_EQ(1u, sizeof(u8)); + EXPECT_EQ(2u, sizeof(i16)); + EXPECT_EQ(2u, sizeof(u16)); + EXPECT_EQ(4u, sizeof(i32)); + EXPECT_EQ(4u, sizeof(u32)); + EXPECT_EQ(8u, sizeof(i64)); + EXPECT_EQ(8u, sizeof(u64)); + EXPECT_GT(0, i8); + EXPECT_LT(0u, u8); + EXPECT_GT(0, i16); + EXPECT_LT(0u, u16); + EXPECT_GT(0, i32); + EXPECT_LT(0u, u32); + EXPECT_GT(0, i64); + EXPECT_LT(0u, u64); +} + +TEST(BasicTypesTest, SizeOfConstants) { + EXPECT_EQ(8u, sizeof(INT64_C(0))); + EXPECT_EQ(8u, sizeof(UINT64_C(0))); + EXPECT_EQ(8u, sizeof(INT64_C(0x1234567887654321))); + EXPECT_EQ(8u, sizeof(UINT64_C(0x8765432112345678))); +} + +// Test CPU_ macros +#if !defined(CPU_ARM) && defined(__arm__) +#error expected CPU_ARM to be defined. +#endif +#if !defined(CPU_X86) && (defined(WEBRTC_WIN) || defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)) +#error expected CPU_X86 to be defined. +#endif +#if !defined(ARCH_CPU_LITTLE_ENDIAN) && \ + (defined(WEBRTC_WIN) || defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(CPU_X86)) +#error expected ARCH_CPU_LITTLE_ENDIAN to be defined. +#endif + +// TODO(fbarchard): Test all macros in basictypes.h + +} // namespace rtc diff --git a/webrtc/base/bind.h b/webrtc/base/bind.h new file mode 100644 index 000000000..2e3104edf --- /dev/null +++ b/webrtc/base/bind.h @@ -0,0 +1,587 @@ +// This file was GENERATED by command: +// pump.py bind.h.pump +// DO NOT EDIT BY HAND!!! + +/* + * Copyright 2012 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. + */ + +// To generate bind.h from bind.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump + +// Bind() is an overloaded function that converts method calls into function +// objects (aka functors). It captures any arguments to the method by value +// when Bind is called, producing a stateful, nullary function object. Care +// should be taken about the lifetime of objects captured by Bind(); the +// returned functor knows nothing about the lifetime of the method's object or +// any arguments passed by pointer, and calling the functor with a destroyed +// object will surely do bad things. +// +// Example usage: +// struct Foo { +// int Test1() { return 42; } +// int Test2() const { return 52; } +// int Test3(int x) { return x*x; } +// float Test4(int x, float y) { return x + y; } +// }; +// +// int main() { +// Foo foo; +// cout << rtc::Bind(&Foo::Test1, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test2, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test3, &foo, 3)() << endl; +// cout << rtc::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl; +// } + +#ifndef WEBRTC_BASE_BIND_H_ +#define WEBRTC_BASE_BIND_H_ + +#define NONAME + +namespace rtc { +namespace detail { +// This is needed because the template parameters in Bind can't be resolved +// if they're used both as parameters of the function pointer type and as +// parameters to Bind itself: the function pointer parameters are exact +// matches to the function prototype, but the parameters to bind have +// references stripped. This trick allows the compiler to dictate the Bind +// parameter types rather than deduce them. +template struct identity { typedef T type; }; +} // namespace detail + +template +class MethodFunctor0 { + public: + MethodFunctor0(MethodT method, ObjectT* object) + : method_(method), object_(object) {} + R operator()() const { + return (object_->*method_)(); } + private: + MethodT method_; + ObjectT* object_; +}; + +template +class Functor0 { + public: + explicit Functor0(const FunctorT& functor) + : functor_(functor) {} + R operator()() const { + return functor_(); } + private: + FunctorT functor_; +}; + + +#define FP_T(x) R (ObjectT::*x)() + +template +MethodFunctor0 +Bind(FP_T(method), ObjectT* object) { + return MethodFunctor0( + method, object); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)() const + +template +MethodFunctor0 +Bind(FP_T(method), const ObjectT* object) { + return MethodFunctor0( + method, object); +} + +#undef FP_T +#define FP_T(x) R (*x)() + +template +Functor0 +Bind(FP_T(function)) { + return Functor0( + function); +} + +#undef FP_T + +template +class MethodFunctor1 { + public: + MethodFunctor1(MethodT method, ObjectT* object, + P1 p1) + : method_(method), object_(object), + p1_(p1) {} + R operator()() const { + return (object_->*method_)(p1_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; +}; + +template +class Functor1 { + public: + Functor1(const FunctorT& functor, P1 p1) + : functor_(functor), + p1_(p1) {} + R operator()() const { + return functor_(p1_); } + private: + FunctorT functor_; + P1 p1_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1) + +template +MethodFunctor1 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1) { + return MethodFunctor1( + method, object, p1); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1) const + +template +MethodFunctor1 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1) { + return MethodFunctor1( + method, object, p1); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1) + +template +Functor1 +Bind(FP_T(function), + typename detail::identity::type p1) { + return Functor1( + function, p1); +} + +#undef FP_T + +template +class MethodFunctor2 { + public: + MethodFunctor2(MethodT method, ObjectT* object, + P1 p1, + P2 p2) + : method_(method), object_(object), + p1_(p1), + p2_(p2) {} + R operator()() const { + return (object_->*method_)(p1_, p2_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; +}; + +template +class Functor2 { + public: + Functor2(const FunctorT& functor, P1 p1, P2 p2) + : functor_(functor), + p1_(p1), + p2_(p2) {} + R operator()() const { + return functor_(p1_, p2_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2) + +template +MethodFunctor2 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2) { + return MethodFunctor2( + method, object, p1, p2); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2) const + +template +MethodFunctor2 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2) { + return MethodFunctor2( + method, object, p1, p2); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2) + +template +Functor2 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2) { + return Functor2( + function, p1, p2); +} + +#undef FP_T + +template +class MethodFunctor3 { + public: + MethodFunctor3(MethodT method, ObjectT* object, + P1 p1, + P2 p2, + P3 p3) + : method_(method), object_(object), + p1_(p1), + p2_(p2), + p3_(p3) {} + R operator()() const { + return (object_->*method_)(p1_, p2_, p3_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; + P3 p3_; +}; + +template +class Functor3 { + public: + Functor3(const FunctorT& functor, P1 p1, P2 p2, P3 p3) + : functor_(functor), + p1_(p1), + p2_(p2), + p3_(p3) {} + R operator()() const { + return functor_(p1_, p2_, p3_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; + P3 p3_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3) + +template +MethodFunctor3 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3) { + return MethodFunctor3( + method, object, p1, p2, p3); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3) const + +template +MethodFunctor3 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3) { + return MethodFunctor3( + method, object, p1, p2, p3); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2, P3) + +template +Functor3 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3) { + return Functor3( + function, p1, p2, p3); +} + +#undef FP_T + +template +class MethodFunctor4 { + public: + MethodFunctor4(MethodT method, ObjectT* object, + P1 p1, + P2 p2, + P3 p3, + P4 p4) + : method_(method), object_(object), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4) {} + R operator()() const { + return (object_->*method_)(p1_, p2_, p3_, p4_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; +}; + +template +class Functor4 { + public: + Functor4(const FunctorT& functor, P1 p1, P2 p2, P3 p3, P4 p4) + : functor_(functor), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4) {} + R operator()() const { + return functor_(p1_, p2_, p3_, p4_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4) + +template +MethodFunctor4 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4) { + return MethodFunctor4( + method, object, p1, p2, p3, p4); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4) const + +template +MethodFunctor4 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4) { + return MethodFunctor4( + method, object, p1, p2, p3, p4); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2, P3, P4) + +template +Functor4 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4) { + return Functor4( + function, p1, p2, p3, p4); +} + +#undef FP_T + +template +class MethodFunctor5 { + public: + MethodFunctor5(MethodT method, ObjectT* object, + P1 p1, + P2 p2, + P3 p3, + P4 p4, + P5 p5) + : method_(method), object_(object), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5) {} + R operator()() const { + return (object_->*method_)(p1_, p2_, p3_, p4_, p5_); } + private: + MethodT method_; + ObjectT* object_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; +}; + +template +class Functor5 { + public: + Functor5(const FunctorT& functor, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) + : functor_(functor), + p1_(p1), + p2_(p2), + p3_(p3), + p4_(p4), + p5_(p5) {} + R operator()() const { + return functor_(p1_, p2_, p3_, p4_, p5_); } + private: + FunctorT functor_; + P1 p1_; + P2 p2_; + P3 p3_; + P4 p4_; + P5 p5_; +}; + + +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5) + +template +MethodFunctor5 +Bind(FP_T(method), ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4, + typename detail::identity::type p5) { + return MethodFunctor5( + method, object, p1, p2, p3, p4, p5); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5) const + +template +MethodFunctor5 +Bind(FP_T(method), const ObjectT* object, + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4, + typename detail::identity::type p5) { + return MethodFunctor5( + method, object, p1, p2, p3, p4, p5); +} + +#undef FP_T +#define FP_T(x) R (*x)(P1, P2, P3, P4, P5) + +template +Functor5 +Bind(FP_T(function), + typename detail::identity::type p1, + typename detail::identity::type p2, + typename detail::identity::type p3, + typename detail::identity::type p4, + typename detail::identity::type p5) { + return Functor5( + function, p1, p2, p3, p4, p5); +} + +#undef FP_T + +} // namespace rtc + +#undef NONAME + +#endif // WEBRTC_BASE_BIND_H_ diff --git a/webrtc/base/bind.h.pump b/webrtc/base/bind.h.pump new file mode 100644 index 000000000..b5663c45d --- /dev/null +++ b/webrtc/base/bind.h.pump @@ -0,0 +1,138 @@ +/* + * Copyright 2012 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. + */ + +// To generate bind.h from bind.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump + +// Bind() is an overloaded function that converts method calls into function +// objects (aka functors). It captures any arguments to the method by value +// when Bind is called, producing a stateful, nullary function object. Care +// should be taken about the lifetime of objects captured by Bind(); the +// returned functor knows nothing about the lifetime of the method's object or +// any arguments passed by pointer, and calling the functor with a destroyed +// object will surely do bad things. +// +// Example usage: +// struct Foo { +// int Test1() { return 42; } +// int Test2() const { return 52; } +// int Test3(int x) { return x*x; } +// float Test4(int x, float y) { return x + y; } +// }; +// +// int main() { +// Foo foo; +// cout << rtc::Bind(&Foo::Test1, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test2, &foo)() << endl; +// cout << rtc::Bind(&Foo::Test3, &foo, 3)() << endl; +// cout << rtc::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl; +// } + +#ifndef WEBRTC_BASE_BIND_H_ +#define WEBRTC_BASE_BIND_H_ + +#define NONAME + +namespace rtc { +namespace detail { +// This is needed because the template parameters in Bind can't be resolved +// if they're used both as parameters of the function pointer type and as +// parameters to Bind itself: the function pointer parameters are exact +// matches to the function prototype, but the parameters to bind have +// references stripped. This trick allows the compiler to dictate the Bind +// parameter types rather than deduce them. +template struct identity { typedef T type; }; +} // namespace detail + +$var n = 5 +$range i 0..n +$for i [[ +$range j 1..i + +template +class MethodFunctor$i { + public: + MethodFunctor$i(MethodT method, ObjectT* object$for j [[, + P$j p$j]]) + : method_(method), object_(object)$for j [[, + p$(j)_(p$j)]] {} + R operator()() const { + return (object_->*method_)($for j , [[p$(j)_]]); } + private: + MethodT method_; + ObjectT* object_;$for j [[ + + P$j p$(j)_;]] + +}; + +template +class Functor$i { + public: + $if i == 0 [[explicit ]] +Functor$i(const FunctorT& functor$for j [[, P$j p$j]]) + : functor_(functor)$for j [[, + p$(j)_(p$j)]] {} + R operator()() const { + return functor_($for j , [[p$(j)_]]); } + private: + FunctorT functor_;$for j [[ + + P$j p$(j)_;]] + +}; + + +#define FP_T(x) R (ObjectT::*x)($for j , [[P$j]]) + +template +MethodFunctor$i +Bind(FP_T(method), ObjectT* object$for j [[, + typename detail::identity::type p$j]]) { + return MethodFunctor$i( + method, object$for j [[, p$j]]); +} + +#undef FP_T +#define FP_T(x) R (ObjectT::*x)($for j , [[P$j]]) const + +template +MethodFunctor$i +Bind(FP_T(method), const ObjectT* object$for j [[, + typename detail::identity::type p$j]]) { + return MethodFunctor$i( + method, object$for j [[, p$j]]); +} + +#undef FP_T +#define FP_T(x) R (*x)($for j , [[P$j]]) + +template +Functor$i +Bind(FP_T(function)$for j [[, + typename detail::identity::type p$j]]) { + return Functor$i( + function$for j [[, p$j]]); +} + +#undef FP_T + +]] + +} // namespace rtc + +#undef NONAME + +#endif // WEBRTC_BASE_BIND_H_ diff --git a/webrtc/base/bind_unittest.cc b/webrtc/base/bind_unittest.cc new file mode 100644 index 000000000..ed8dd5cf2 --- /dev/null +++ b/webrtc/base/bind_unittest.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2004 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 "webrtc/base/bind.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +namespace { + +struct MethodBindTester { + void NullaryVoid() { ++call_count; } + int NullaryInt() { ++call_count; return 1; } + int NullaryConst() const { ++call_count; return 2; } + void UnaryVoid(int dummy) { ++call_count; } + template T Identity(T value) { ++call_count; return value; } + int UnaryByRef(int& value) const { ++call_count; return ++value; } // NOLINT + int Multiply(int a, int b) const { ++call_count; return a * b; } + mutable int call_count; +}; + +int Return42() { return 42; } +int Negate(int a) { return -a; } +int Multiply(int a, int b) { return a * b; } + +} // namespace + +TEST(BindTest, BindToMethod) { + MethodBindTester object = {0}; + EXPECT_EQ(0, object.call_count); + Bind(&MethodBindTester::NullaryVoid, &object)(); + EXPECT_EQ(1, object.call_count); + EXPECT_EQ(1, Bind(&MethodBindTester::NullaryInt, &object)()); + EXPECT_EQ(2, object.call_count); + EXPECT_EQ(2, Bind(&MethodBindTester::NullaryConst, + static_cast(&object))()); + EXPECT_EQ(3, object.call_count); + Bind(&MethodBindTester::UnaryVoid, &object, 5)(); + EXPECT_EQ(4, object.call_count); + EXPECT_EQ(100, Bind(&MethodBindTester::Identity, &object, 100)()); + EXPECT_EQ(5, object.call_count); + const std::string string_value("test string"); + EXPECT_EQ(string_value, Bind(&MethodBindTester::Identity, + &object, string_value)()); + EXPECT_EQ(6, object.call_count); + int value = 11; + EXPECT_EQ(12, Bind(&MethodBindTester::UnaryByRef, &object, value)()); + EXPECT_EQ(12, value); + EXPECT_EQ(7, object.call_count); + EXPECT_EQ(56, Bind(&MethodBindTester::Multiply, &object, 7, 8)()); + EXPECT_EQ(8, object.call_count); +} + +TEST(BindTest, BindToFunction) { + EXPECT_EQ(42, Bind(&Return42)()); + EXPECT_EQ(3, Bind(&Negate, -3)()); + EXPECT_EQ(56, Bind(&Multiply, 8, 7)()); +} + +} // namespace rtc diff --git a/webrtc/base/buffer.h b/webrtc/base/buffer.h new file mode 100644 index 000000000..dbe7b1aa7 --- /dev/null +++ b/webrtc/base/buffer.h @@ -0,0 +1,102 @@ +/* + * Copyright 2004 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_BASE_BUFFER_H_ +#define WEBRTC_BASE_BUFFER_H_ + +#include + +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +// Basic buffer class, can be grown and shrunk dynamically. +// Unlike std::string/vector, does not initialize data when expanding capacity. +class Buffer { + public: + Buffer() { + Construct(NULL, 0, 0); + } + Buffer(const void* data, size_t length) { + Construct(data, length, length); + } + Buffer(const void* data, size_t length, size_t capacity) { + Construct(data, length, capacity); + } + Buffer(const Buffer& buf) { + Construct(buf.data(), buf.length(), buf.length()); + } + + const char* data() const { return data_.get(); } + char* data() { return data_.get(); } + // TODO: should this be size(), like STL? + size_t length() const { return length_; } + size_t capacity() const { return capacity_; } + + Buffer& operator=(const Buffer& buf) { + if (&buf != this) { + Construct(buf.data(), buf.length(), buf.length()); + } + return *this; + } + bool operator==(const Buffer& buf) const { + return (length_ == buf.length() && + memcmp(data_.get(), buf.data(), length_) == 0); + } + bool operator!=(const Buffer& buf) const { + return !operator==(buf); + } + + void SetData(const void* data, size_t length) { + ASSERT(data != NULL || length == 0); + SetLength(length); + memcpy(data_.get(), data, length); + } + void AppendData(const void* data, size_t length) { + ASSERT(data != NULL || length == 0); + size_t old_length = length_; + SetLength(length_ + length); + memcpy(data_.get() + old_length, data, length); + } + void SetLength(size_t length) { + SetCapacity(length); + length_ = length; + } + void SetCapacity(size_t capacity) { + if (capacity > capacity_) { + rtc::scoped_ptr data(new char[capacity]); + memcpy(data.get(), data_.get(), length_); + data_.swap(data); + capacity_ = capacity; + } + } + + void TransferTo(Buffer* buf) { + ASSERT(buf != NULL); + buf->data_.reset(data_.release()); + buf->length_ = length_; + buf->capacity_ = capacity_; + Construct(NULL, 0, 0); + } + + protected: + void Construct(const void* data, size_t length, size_t capacity) { + data_.reset(new char[capacity_ = capacity]); + SetData(data, length); + } + + scoped_ptr data_; + size_t length_; + size_t capacity_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BUFFER_H_ diff --git a/webrtc/base/buffer_unittest.cc b/webrtc/base/buffer_unittest.cc new file mode 100644 index 000000000..71b3f89e3 --- /dev/null +++ b/webrtc/base/buffer_unittest.cc @@ -0,0 +1,143 @@ +/* + * Copyright 2004 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 "webrtc/base/buffer.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +static const char kTestData[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF +}; + +TEST(BufferTest, TestConstructDefault) { + Buffer buf; + EXPECT_EQ(0U, buf.length()); + EXPECT_EQ(0U, buf.capacity()); + EXPECT_EQ(Buffer(), buf); +} + +TEST(BufferTest, TestConstructEmptyWithCapacity) { + Buffer buf(NULL, 0, 256U); + EXPECT_EQ(0U, buf.length()); + EXPECT_EQ(256U, buf.capacity()); + EXPECT_EQ(Buffer(), buf); +} + +TEST(BufferTest, TestConstructData) { + Buffer buf(kTestData, sizeof(kTestData)); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf); +} + +TEST(BufferTest, TestConstructDataWithCapacity) { + Buffer buf(kTestData, sizeof(kTestData), 256U); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(256U, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf); +} + +TEST(BufferTest, TestConstructCopy) { + Buffer buf1(kTestData, sizeof(kTestData), 256), buf2(buf1); + EXPECT_EQ(sizeof(kTestData), buf2.length()); + EXPECT_EQ(sizeof(kTestData), buf2.capacity()); // capacity isn't copied + EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(buf1, buf2); +} + +TEST(BufferTest, TestAssign) { + Buffer buf1, buf2(kTestData, sizeof(kTestData), 256); + EXPECT_NE(buf1, buf2); + buf1 = buf2; + EXPECT_EQ(sizeof(kTestData), buf1.length()); + EXPECT_EQ(sizeof(kTestData), buf1.capacity()); // capacity isn't copied + EXPECT_EQ(0, memcmp(buf1.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(buf1, buf2); +} + +TEST(BufferTest, TestSetData) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestAppendData) { + Buffer buf(kTestData, sizeof(kTestData)); + buf.AppendData(kTestData, sizeof(kTestData)); + EXPECT_EQ(2 * sizeof(kTestData), buf.length()); + EXPECT_EQ(2 * sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData), + kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetLengthSmaller) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + buf.SetLength(sizeof(kTestData) / 2); + EXPECT_EQ(sizeof(kTestData) / 2, buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData) / 2)); +} + +TEST(BufferTest, TestSetLengthLarger) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + buf.SetLength(sizeof(kTestData) * 2); + EXPECT_EQ(sizeof(kTestData) * 2, buf.length()); + EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetCapacitySmaller) { + Buffer buf; + buf.SetData(kTestData, sizeof(kTestData)); + buf.SetCapacity(sizeof(kTestData) / 2); // should be ignored + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData), buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetCapacityLarger) { + Buffer buf(kTestData, sizeof(kTestData)); + buf.SetCapacity(sizeof(kTestData) * 2); + EXPECT_EQ(sizeof(kTestData), buf.length()); + EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestSetCapacityThenSetLength) { + Buffer buf(kTestData, sizeof(kTestData)); + buf.SetCapacity(sizeof(kTestData) * 4); + memcpy(buf.data() + sizeof(kTestData), kTestData, sizeof(kTestData)); + buf.SetLength(sizeof(kTestData) * 2); + EXPECT_EQ(sizeof(kTestData) * 2, buf.length()); + EXPECT_EQ(sizeof(kTestData) * 4, buf.capacity()); + EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData))); + EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData), + kTestData, sizeof(kTestData))); +} + +TEST(BufferTest, TestTransfer) { + Buffer buf1(kTestData, sizeof(kTestData), 256U), buf2; + buf1.TransferTo(&buf2); + EXPECT_EQ(0U, buf1.length()); + EXPECT_EQ(0U, buf1.capacity()); + EXPECT_EQ(sizeof(kTestData), buf2.length()); + EXPECT_EQ(256U, buf2.capacity()); // capacity does transfer + EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData))); +} + +} // namespace rtc diff --git a/webrtc/base/bytebuffer.cc b/webrtc/base/bytebuffer.cc new file mode 100644 index 000000000..6133759e5 --- /dev/null +++ b/webrtc/base/bytebuffer.cc @@ -0,0 +1,234 @@ +/* + * Copyright 2004 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 "webrtc/base/bytebuffer.h" + +#include +#include + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" + +namespace rtc { + +static const int DEFAULT_SIZE = 4096; + +ByteBuffer::ByteBuffer() { + Construct(NULL, DEFAULT_SIZE, ORDER_NETWORK); +} + +ByteBuffer::ByteBuffer(ByteOrder byte_order) { + Construct(NULL, DEFAULT_SIZE, byte_order); +} + +ByteBuffer::ByteBuffer(const char* bytes, size_t len) { + Construct(bytes, len, ORDER_NETWORK); +} + +ByteBuffer::ByteBuffer(const char* bytes, size_t len, ByteOrder byte_order) { + Construct(bytes, len, byte_order); +} + +ByteBuffer::ByteBuffer(const char* bytes) { + Construct(bytes, strlen(bytes), ORDER_NETWORK); +} + +void ByteBuffer::Construct(const char* bytes, size_t len, + ByteOrder byte_order) { + version_ = 0; + start_ = 0; + size_ = len; + byte_order_ = byte_order; + bytes_ = new char[size_]; + + if (bytes) { + end_ = len; + memcpy(bytes_, bytes, end_); + } else { + end_ = 0; + } +} + +ByteBuffer::~ByteBuffer() { + delete[] bytes_; +} + +bool ByteBuffer::ReadUInt8(uint8* val) { + if (!val) return false; + + return ReadBytes(reinterpret_cast(val), 1); +} + +bool ByteBuffer::ReadUInt16(uint16* val) { + if (!val) return false; + + uint16 v; + if (!ReadBytes(reinterpret_cast(&v), 2)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost16(v) : v; + return true; + } +} + +bool ByteBuffer::ReadUInt24(uint32* val) { + if (!val) return false; + + uint32 v = 0; + char* read_into = reinterpret_cast(&v); + if (byte_order_ == ORDER_NETWORK || IsHostBigEndian()) { + ++read_into; + } + + if (!ReadBytes(read_into, 3)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost32(v) : v; + return true; + } +} + +bool ByteBuffer::ReadUInt32(uint32* val) { + if (!val) return false; + + uint32 v; + if (!ReadBytes(reinterpret_cast(&v), 4)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost32(v) : v; + return true; + } +} + +bool ByteBuffer::ReadUInt64(uint64* val) { + if (!val) return false; + + uint64 v; + if (!ReadBytes(reinterpret_cast(&v), 8)) { + return false; + } else { + *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost64(v) : v; + return true; + } +} + +bool ByteBuffer::ReadString(std::string* val, size_t len) { + if (!val) return false; + + if (len > Length()) { + return false; + } else { + val->append(bytes_ + start_, len); + start_ += len; + return true; + } +} + +bool ByteBuffer::ReadBytes(char* val, size_t len) { + if (len > Length()) { + return false; + } else { + memcpy(val, bytes_ + start_, len); + start_ += len; + return true; + } +} + +void ByteBuffer::WriteUInt8(uint8 val) { + WriteBytes(reinterpret_cast(&val), 1); +} + +void ByteBuffer::WriteUInt16(uint16 val) { + uint16 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork16(val) : val; + WriteBytes(reinterpret_cast(&v), 2); +} + +void ByteBuffer::WriteUInt24(uint32 val) { + uint32 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork32(val) : val; + char* start = reinterpret_cast(&v); + if (byte_order_ == ORDER_NETWORK || IsHostBigEndian()) { + ++start; + } + WriteBytes(start, 3); +} + +void ByteBuffer::WriteUInt32(uint32 val) { + uint32 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork32(val) : val; + WriteBytes(reinterpret_cast(&v), 4); +} + +void ByteBuffer::WriteUInt64(uint64 val) { + uint64 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork64(val) : val; + WriteBytes(reinterpret_cast(&v), 8); +} + +void ByteBuffer::WriteString(const std::string& val) { + WriteBytes(val.c_str(), val.size()); +} + +void ByteBuffer::WriteBytes(const char* val, size_t len) { + memcpy(ReserveWriteBuffer(len), val, len); +} + +char* ByteBuffer::ReserveWriteBuffer(size_t len) { + if (Length() + len > Capacity()) + Resize(Length() + len); + + char* start = bytes_ + end_; + end_ += len; + return start; +} + +void ByteBuffer::Resize(size_t size) { + size_t len = _min(end_ - start_, size); + if (size <= size_) { + // Don't reallocate, just move data backwards + memmove(bytes_, bytes_ + start_, len); + } else { + // Reallocate a larger buffer. + size_ = _max(size, 3 * size_ / 2); + char* new_bytes = new char[size_]; + memcpy(new_bytes, bytes_ + start_, len); + delete [] bytes_; + bytes_ = new_bytes; + } + start_ = 0; + end_ = len; + ++version_; +} + +bool ByteBuffer::Consume(size_t size) { + if (size > Length()) + return false; + start_ += size; + return true; +} + +ByteBuffer::ReadPosition ByteBuffer::GetReadPosition() const { + return ReadPosition(start_, version_); +} + +bool ByteBuffer::SetReadPosition(const ReadPosition &position) { + if (position.version_ != version_) { + return false; + } + start_ = position.start_; + return true; +} + +void ByteBuffer::Clear() { + memset(bytes_, 0, size_); + start_ = end_ = 0; + ++version_; +} + +} // namespace rtc diff --git a/webrtc/base/bytebuffer.h b/webrtc/base/bytebuffer.h new file mode 100644 index 000000000..1934f418e --- /dev/null +++ b/webrtc/base/bytebuffer.h @@ -0,0 +1,119 @@ +/* + * Copyright 2004 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_BASE_BYTEBUFFER_H_ +#define WEBRTC_BASE_BYTEBUFFER_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +class ByteBuffer { + public: + + enum ByteOrder { + ORDER_NETWORK = 0, // Default, use network byte order (big endian). + ORDER_HOST, // Use the native order of the host. + }; + + // |byte_order| defines order of bytes in the buffer. + ByteBuffer(); + explicit ByteBuffer(ByteOrder byte_order); + ByteBuffer(const char* bytes, size_t len); + ByteBuffer(const char* bytes, size_t len, ByteOrder byte_order); + + // Initializes buffer from a zero-terminated string. + explicit ByteBuffer(const char* bytes); + + ~ByteBuffer(); + + const char* Data() const { return bytes_ + start_; } + size_t Length() const { return end_ - start_; } + size_t Capacity() const { return size_ - start_; } + ByteOrder Order() const { return byte_order_; } + + // Read a next value from the buffer. Return false if there isn't + // enough data left for the specified type. + bool ReadUInt8(uint8* val); + bool ReadUInt16(uint16* val); + bool ReadUInt24(uint32* val); + bool ReadUInt32(uint32* val); + bool ReadUInt64(uint64* val); + bool ReadBytes(char* val, size_t len); + + // Appends next |len| bytes from the buffer to |val|. Returns false + // if there is less than |len| bytes left. + bool ReadString(std::string* val, size_t len); + + // Write value to the buffer. Resizes the buffer when it is + // neccessary. + void WriteUInt8(uint8 val); + void WriteUInt16(uint16 val); + void WriteUInt24(uint32 val); + void WriteUInt32(uint32 val); + void WriteUInt64(uint64 val); + void WriteString(const std::string& val); + void WriteBytes(const char* val, size_t len); + + // Reserves the given number of bytes and returns a char* that can be written + // into. Useful for functions that require a char* buffer and not a + // ByteBuffer. + char* ReserveWriteBuffer(size_t len); + + // Resize the buffer to the specified |size|. This invalidates any remembered + // seek positions. + void Resize(size_t size); + + // Moves current position |size| bytes forward. Returns false if + // there is less than |size| bytes left in the buffer. Consume doesn't + // permanently remove data, so remembered read positions are still valid + // after this call. + bool Consume(size_t size); + + // Clears the contents of the buffer. After this, Length() will be 0. + void Clear(); + + // Used with GetReadPosition/SetReadPosition. + class ReadPosition { + friend class ByteBuffer; + ReadPosition(size_t start, int version) + : start_(start), version_(version) { } + size_t start_; + int version_; + }; + + // Remembers the current read position for a future SetReadPosition. Any + // calls to Shift or Resize in the interim will invalidate the position. + ReadPosition GetReadPosition() const; + + // If the given position is still valid, restores that read position. + bool SetReadPosition(const ReadPosition &position); + + private: + void Construct(const char* bytes, size_t size, ByteOrder byte_order); + + char* bytes_; + size_t size_; + size_t start_; + size_t end_; + int version_; + ByteOrder byte_order_; + + // There are sensible ways to define these, but they aren't needed in our code + // base. + DISALLOW_COPY_AND_ASSIGN(ByteBuffer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_BYTEBUFFER_H_ diff --git a/webrtc/base/bytebuffer_unittest.cc b/webrtc/base/bytebuffer_unittest.cc new file mode 100644 index 000000000..f4b0504ef --- /dev/null +++ b/webrtc/base/bytebuffer_unittest.cc @@ -0,0 +1,211 @@ +/* + * Copyright 2004 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 "webrtc/base/bytebuffer.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +TEST(ByteBufferTest, TestByteOrder) { + uint16 n16 = 1; + uint32 n32 = 1; + uint64 n64 = 1; + + EXPECT_EQ(n16, NetworkToHost16(HostToNetwork16(n16))); + EXPECT_EQ(n32, NetworkToHost32(HostToNetwork32(n32))); + EXPECT_EQ(n64, NetworkToHost64(HostToNetwork64(n64))); + + if (IsHostBigEndian()) { + // The host is the network (big) endian. + EXPECT_EQ(n16, HostToNetwork16(n16)); + EXPECT_EQ(n32, HostToNetwork32(n32)); + EXPECT_EQ(n64, HostToNetwork64(n64)); + + // GetBE converts big endian to little endian here. + EXPECT_EQ(n16 >> 8, GetBE16(&n16)); + EXPECT_EQ(n32 >> 24, GetBE32(&n32)); + EXPECT_EQ(n64 >> 56, GetBE64(&n64)); + } else { + // The host is little endian. + EXPECT_NE(n16, HostToNetwork16(n16)); + EXPECT_NE(n32, HostToNetwork32(n32)); + EXPECT_NE(n64, HostToNetwork64(n64)); + + // GetBE converts little endian to big endian here. + EXPECT_EQ(GetBE16(&n16), HostToNetwork16(n16)); + EXPECT_EQ(GetBE32(&n32), HostToNetwork32(n32)); + EXPECT_EQ(GetBE64(&n64), HostToNetwork64(n64)); + + // GetBE converts little endian to big endian here. + EXPECT_EQ(n16 << 8, GetBE16(&n16)); + EXPECT_EQ(n32 << 24, GetBE32(&n32)); + EXPECT_EQ(n64 << 56, GetBE64(&n64)); + } +} + +TEST(ByteBufferTest, TestBufferLength) { + ByteBuffer buffer; + size_t size = 0; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt8(1); + ++size; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt16(1); + size += 2; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt24(1); + size += 3; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt32(1); + size += 4; + EXPECT_EQ(size, buffer.Length()); + + buffer.WriteUInt64(1); + size += 8; + EXPECT_EQ(size, buffer.Length()); + + EXPECT_TRUE(buffer.Consume(0)); + EXPECT_EQ(size, buffer.Length()); + + EXPECT_TRUE(buffer.Consume(4)); + size -= 4; + EXPECT_EQ(size, buffer.Length()); +} + +TEST(ByteBufferTest, TestGetSetReadPosition) { + ByteBuffer buffer("ABCDEF", 6); + EXPECT_EQ(6U, buffer.Length()); + ByteBuffer::ReadPosition pos(buffer.GetReadPosition()); + EXPECT_TRUE(buffer.SetReadPosition(pos)); + EXPECT_EQ(6U, buffer.Length()); + std::string read; + EXPECT_TRUE(buffer.ReadString(&read, 3)); + EXPECT_EQ("ABC", read); + EXPECT_EQ(3U, buffer.Length()); + EXPECT_TRUE(buffer.SetReadPosition(pos)); + EXPECT_EQ(6U, buffer.Length()); + read.clear(); + EXPECT_TRUE(buffer.ReadString(&read, 3)); + EXPECT_EQ("ABC", read); + EXPECT_EQ(3U, buffer.Length()); + // For a resize by writing Capacity() number of bytes. + size_t capacity = buffer.Capacity(); + buffer.ReserveWriteBuffer(buffer.Capacity()); + EXPECT_EQ(capacity + 3U, buffer.Length()); + EXPECT_FALSE(buffer.SetReadPosition(pos)); + read.clear(); + EXPECT_TRUE(buffer.ReadString(&read, 3)); + EXPECT_EQ("DEF", read); +} + +TEST(ByteBufferTest, TestReadWriteBuffer) { + ByteBuffer::ByteOrder orders[2] = { ByteBuffer::ORDER_HOST, + ByteBuffer::ORDER_NETWORK }; + for (size_t i = 0; i < ARRAY_SIZE(orders); i++) { + ByteBuffer buffer(orders[i]); + EXPECT_EQ(orders[i], buffer.Order()); + uint8 ru8; + EXPECT_FALSE(buffer.ReadUInt8(&ru8)); + + // Write and read uint8. + uint8 wu8 = 1; + buffer.WriteUInt8(wu8); + EXPECT_TRUE(buffer.ReadUInt8(&ru8)); + EXPECT_EQ(wu8, ru8); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint16. + uint16 wu16 = (1 << 8) + 1; + buffer.WriteUInt16(wu16); + uint16 ru16; + EXPECT_TRUE(buffer.ReadUInt16(&ru16)); + EXPECT_EQ(wu16, ru16); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint24. + uint32 wu24 = (3 << 16) + (2 << 8) + 1; + buffer.WriteUInt24(wu24); + uint32 ru24; + EXPECT_TRUE(buffer.ReadUInt24(&ru24)); + EXPECT_EQ(wu24, ru24); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint32. + uint32 wu32 = (4 << 24) + (3 << 16) + (2 << 8) + 1; + buffer.WriteUInt32(wu32); + uint32 ru32; + EXPECT_TRUE(buffer.ReadUInt32(&ru32)); + EXPECT_EQ(wu32, ru32); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read uint64. + uint32 another32 = (8 << 24) + (7 << 16) + (6 << 8) + 5; + uint64 wu64 = (static_cast(another32) << 32) + wu32; + buffer.WriteUInt64(wu64); + uint64 ru64; + EXPECT_TRUE(buffer.ReadUInt64(&ru64)); + EXPECT_EQ(wu64, ru64); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read string. + std::string write_string("hello"); + buffer.WriteString(write_string); + std::string read_string; + EXPECT_TRUE(buffer.ReadString(&read_string, write_string.size())); + EXPECT_EQ(write_string, read_string); + EXPECT_EQ(0U, buffer.Length()); + + // Write and read bytes + char write_bytes[] = "foo"; + buffer.WriteBytes(write_bytes, 3); + char read_bytes[3]; + EXPECT_TRUE(buffer.ReadBytes(read_bytes, 3)); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(write_bytes[i], read_bytes[i]); + } + EXPECT_EQ(0U, buffer.Length()); + + // Write and read reserved buffer space + char* write_dst = buffer.ReserveWriteBuffer(3); + memcpy(write_dst, write_bytes, 3); + memset(read_bytes, 0, 3); + EXPECT_TRUE(buffer.ReadBytes(read_bytes, 3)); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(write_bytes[i], read_bytes[i]); + } + EXPECT_EQ(0U, buffer.Length()); + + // Write and read in order. + buffer.WriteUInt8(wu8); + buffer.WriteUInt16(wu16); + buffer.WriteUInt24(wu24); + buffer.WriteUInt32(wu32); + buffer.WriteUInt64(wu64); + EXPECT_TRUE(buffer.ReadUInt8(&ru8)); + EXPECT_EQ(wu8, ru8); + EXPECT_TRUE(buffer.ReadUInt16(&ru16)); + EXPECT_EQ(wu16, ru16); + EXPECT_TRUE(buffer.ReadUInt24(&ru24)); + EXPECT_EQ(wu24, ru24); + EXPECT_TRUE(buffer.ReadUInt32(&ru32)); + EXPECT_EQ(wu32, ru32); + EXPECT_TRUE(buffer.ReadUInt64(&ru64)); + EXPECT_EQ(wu64, ru64); + EXPECT_EQ(0U, buffer.Length()); + } +} + +} // namespace rtc diff --git a/webrtc/base/byteorder.h b/webrtc/base/byteorder.h new file mode 100644 index 000000000..d907d9e41 --- /dev/null +++ b/webrtc/base/byteorder.h @@ -0,0 +1,168 @@ +/* + * Copyright 2004 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_BASE_BYTEORDER_H_ +#define WEBRTC_BASE_BYTEORDER_H_ + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) +#include +#endif + +#if defined(WEBRTC_WIN) +#include +#endif + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Reading and writing of little and big-endian numbers from memory +// TODO: Optimized versions, with direct read/writes of +// integers in host-endian format, when the platform supports it. + +inline void Set8(void* memory, size_t offset, uint8 v) { + static_cast(memory)[offset] = v; +} + +inline uint8 Get8(const void* memory, size_t offset) { + return static_cast(memory)[offset]; +} + +inline void SetBE16(void* memory, uint16 v) { + Set8(memory, 0, static_cast(v >> 8)); + Set8(memory, 1, static_cast(v >> 0)); +} + +inline void SetBE32(void* memory, uint32 v) { + Set8(memory, 0, static_cast(v >> 24)); + Set8(memory, 1, static_cast(v >> 16)); + Set8(memory, 2, static_cast(v >> 8)); + Set8(memory, 3, static_cast(v >> 0)); +} + +inline void SetBE64(void* memory, uint64 v) { + Set8(memory, 0, static_cast(v >> 56)); + Set8(memory, 1, static_cast(v >> 48)); + Set8(memory, 2, static_cast(v >> 40)); + Set8(memory, 3, static_cast(v >> 32)); + Set8(memory, 4, static_cast(v >> 24)); + Set8(memory, 5, static_cast(v >> 16)); + Set8(memory, 6, static_cast(v >> 8)); + Set8(memory, 7, static_cast(v >> 0)); +} + +inline uint16 GetBE16(const void* memory) { + return static_cast((Get8(memory, 0) << 8) | + (Get8(memory, 1) << 0)); +} + +inline uint32 GetBE32(const void* memory) { + return (static_cast(Get8(memory, 0)) << 24) | + (static_cast(Get8(memory, 1)) << 16) | + (static_cast(Get8(memory, 2)) << 8) | + (static_cast(Get8(memory, 3)) << 0); +} + +inline uint64 GetBE64(const void* memory) { + return (static_cast(Get8(memory, 0)) << 56) | + (static_cast(Get8(memory, 1)) << 48) | + (static_cast(Get8(memory, 2)) << 40) | + (static_cast(Get8(memory, 3)) << 32) | + (static_cast(Get8(memory, 4)) << 24) | + (static_cast(Get8(memory, 5)) << 16) | + (static_cast(Get8(memory, 6)) << 8) | + (static_cast(Get8(memory, 7)) << 0); +} + +inline void SetLE16(void* memory, uint16 v) { + Set8(memory, 0, static_cast(v >> 0)); + Set8(memory, 1, static_cast(v >> 8)); +} + +inline void SetLE32(void* memory, uint32 v) { + Set8(memory, 0, static_cast(v >> 0)); + Set8(memory, 1, static_cast(v >> 8)); + Set8(memory, 2, static_cast(v >> 16)); + Set8(memory, 3, static_cast(v >> 24)); +} + +inline void SetLE64(void* memory, uint64 v) { + Set8(memory, 0, static_cast(v >> 0)); + Set8(memory, 1, static_cast(v >> 8)); + Set8(memory, 2, static_cast(v >> 16)); + Set8(memory, 3, static_cast(v >> 24)); + Set8(memory, 4, static_cast(v >> 32)); + Set8(memory, 5, static_cast(v >> 40)); + Set8(memory, 6, static_cast(v >> 48)); + Set8(memory, 7, static_cast(v >> 56)); +} + +inline uint16 GetLE16(const void* memory) { + return static_cast((Get8(memory, 0) << 0) | + (Get8(memory, 1) << 8)); +} + +inline uint32 GetLE32(const void* memory) { + return (static_cast(Get8(memory, 0)) << 0) | + (static_cast(Get8(memory, 1)) << 8) | + (static_cast(Get8(memory, 2)) << 16) | + (static_cast(Get8(memory, 3)) << 24); +} + +inline uint64 GetLE64(const void* memory) { + return (static_cast(Get8(memory, 0)) << 0) | + (static_cast(Get8(memory, 1)) << 8) | + (static_cast(Get8(memory, 2)) << 16) | + (static_cast(Get8(memory, 3)) << 24) | + (static_cast(Get8(memory, 4)) << 32) | + (static_cast(Get8(memory, 5)) << 40) | + (static_cast(Get8(memory, 6)) << 48) | + (static_cast(Get8(memory, 7)) << 56); +} + +// Check if the current host is big endian. +inline bool IsHostBigEndian() { + static const int number = 1; + return 0 == *reinterpret_cast(&number); +} + +inline uint16 HostToNetwork16(uint16 n) { + uint16 result; + SetBE16(&result, n); + return result; +} + +inline uint32 HostToNetwork32(uint32 n) { + uint32 result; + SetBE32(&result, n); + return result; +} + +inline uint64 HostToNetwork64(uint64 n) { + uint64 result; + SetBE64(&result, n); + return result; +} + +inline uint16 NetworkToHost16(uint16 n) { + return GetBE16(&n); +} + +inline uint32 NetworkToHost32(uint32 n) { + return GetBE32(&n); +} + +inline uint64 NetworkToHost64(uint64 n) { + return GetBE64(&n); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_BYTEORDER_H_ diff --git a/webrtc/base/byteorder_unittest.cc b/webrtc/base/byteorder_unittest.cc new file mode 100644 index 000000000..f4e7df3b7 --- /dev/null +++ b/webrtc/base/byteorder_unittest.cc @@ -0,0 +1,83 @@ +/* + * Copyright 2012 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 "webrtc/base/byteorder.h" + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +// Test memory set functions put values into memory in expected order. +TEST(ByteOrderTest, TestSet) { + uint8 buf[8] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + Set8(buf, 0, 0xfb); + Set8(buf, 1, 0x12); + EXPECT_EQ(0xfb, buf[0]); + EXPECT_EQ(0x12, buf[1]); + SetBE16(buf, 0x1234); + EXPECT_EQ(0x12, buf[0]); + EXPECT_EQ(0x34, buf[1]); + SetLE16(buf, 0x1234); + EXPECT_EQ(0x34, buf[0]); + EXPECT_EQ(0x12, buf[1]); + SetBE32(buf, 0x12345678); + EXPECT_EQ(0x12, buf[0]); + EXPECT_EQ(0x34, buf[1]); + EXPECT_EQ(0x56, buf[2]); + EXPECT_EQ(0x78, buf[3]); + SetLE32(buf, 0x12345678); + EXPECT_EQ(0x78, buf[0]); + EXPECT_EQ(0x56, buf[1]); + EXPECT_EQ(0x34, buf[2]); + EXPECT_EQ(0x12, buf[3]); + SetBE64(buf, UINT64_C(0x0123456789abcdef)); + EXPECT_EQ(0x01, buf[0]); + EXPECT_EQ(0x23, buf[1]); + EXPECT_EQ(0x45, buf[2]); + EXPECT_EQ(0x67, buf[3]); + EXPECT_EQ(0x89, buf[4]); + EXPECT_EQ(0xab, buf[5]); + EXPECT_EQ(0xcd, buf[6]); + EXPECT_EQ(0xef, buf[7]); + SetLE64(buf, UINT64_C(0x0123456789abcdef)); + EXPECT_EQ(0xef, buf[0]); + EXPECT_EQ(0xcd, buf[1]); + EXPECT_EQ(0xab, buf[2]); + EXPECT_EQ(0x89, buf[3]); + EXPECT_EQ(0x67, buf[4]); + EXPECT_EQ(0x45, buf[5]); + EXPECT_EQ(0x23, buf[6]); + EXPECT_EQ(0x01, buf[7]); +} + +// Test memory get functions get values from memory in expected order. +TEST(ByteOrderTest, TestGet) { + uint8 buf[8]; + buf[0] = 0x01u; + buf[1] = 0x23u; + buf[2] = 0x45u; + buf[3] = 0x67u; + buf[4] = 0x89u; + buf[5] = 0xabu; + buf[6] = 0xcdu; + buf[7] = 0xefu; + EXPECT_EQ(0x01u, Get8(buf, 0)); + EXPECT_EQ(0x23u, Get8(buf, 1)); + EXPECT_EQ(0x0123u, GetBE16(buf)); + EXPECT_EQ(0x2301u, GetLE16(buf)); + EXPECT_EQ(0x01234567u, GetBE32(buf)); + EXPECT_EQ(0x67452301u, GetLE32(buf)); + EXPECT_EQ(UINT64_C(0x0123456789abcdef), GetBE64(buf)); + EXPECT_EQ(UINT64_C(0xefcdab8967452301), GetLE64(buf)); +} + +} // namespace rtc + diff --git a/webrtc/base/callback.h b/webrtc/base/callback.h new file mode 100644 index 000000000..949510e95 --- /dev/null +++ b/webrtc/base/callback.h @@ -0,0 +1,261 @@ +// This file was GENERATED by command: +// pump.py callback.h.pump +// DO NOT EDIT BY HAND!!! + +/* + * Copyright 2012 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. + */ + +// To generate callback.h from callback.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py callback.h.pump + +// Callbacks are callable object containers. They can hold a function pointer +// or a function object and behave like a value type. Internally, data is +// reference-counted, making copies and pass-by-value inexpensive. +// +// Callbacks are typed using template arguments. The format is: +// CallbackN +// where N is the number of arguments supplied to the callable object. +// Callbacks are invoked using operator(), just like a function or a function +// object. Default-constructed callbacks are "empty," and executing an empty +// callback does nothing. A callback can be made empty by assigning it from +// a default-constructed callback. +// +// Callbacks are similar in purpose to std::function (which isn't available on +// all platforms we support) and a lightweight alternative to sigslots. Since +// they effectively hide the type of the object they call, they're useful in +// breaking dependencies between objects that need to interact with one another. +// Notably, they can hold the results of Bind(), std::bind*, etc, without +// needing +// to know the resulting object type of those calls. +// +// Sigslots, on the other hand, provide a fuller feature set, such as multiple +// subscriptions to a signal, optional thread-safety, and lifetime tracking of +// slots. When these features are needed, choose sigslots. +// +// Example: +// int sqr(int x) { return x * x; } +// struct AddK { +// int k; +// int operator()(int x) const { return x + k; } +// } add_k = {5}; +// +// Callback1 my_callback; +// cout << my_callback.empty() << endl; // true +// +// my_callback = Callback1(&sqr); +// cout << my_callback.empty() << endl; // false +// cout << my_callback(3) << endl; // 9 +// +// my_callback = Callback1(add_k); +// cout << my_callback(10) << endl; // 15 +// +// my_callback = Callback1(); +// cout << my_callback.empty() << endl; // true + +#ifndef WEBRTC_BASE_CALLBACK_H_ +#define WEBRTC_BASE_CALLBACK_H_ + +#include "webrtc/base/logging.h" +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ref_ptr.h" + +namespace rtc { + +template +class Callback0 { + public: + // Default copy operations are appropriate for this class. + Callback0() {} + template Callback0(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()() { + if (empty()) + return R(); + return helper_->Run(); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run() = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run() { + return functor_(); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback1 { + public: + // Default copy operations are appropriate for this class. + Callback1() {} + template Callback1(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1) { + if (empty()) + return R(); + return helper_->Run(p1); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1) { + return functor_(p1); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback2 { + public: + // Default copy operations are appropriate for this class. + Callback2() {} + template Callback2(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2) { + if (empty()) + return R(); + return helper_->Run(p1, p2); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2) { + return functor_(p1, p2); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback3 { + public: + // Default copy operations are appropriate for this class. + Callback3() {} + template Callback3(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2, P3 p3) { + if (empty()) + return R(); + return helper_->Run(p1, p2, p3); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2, P3 p3) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2, P3 p3) { + return functor_(p1, p2, p3); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback4 { + public: + // Default copy operations are appropriate for this class. + Callback4() {} + template Callback4(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2, P3 p3, P4 p4) { + if (empty()) + return R(); + return helper_->Run(p1, p2, p3, p4); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4) { + return functor_(p1, p2, p3, p4); + } + T functor_; + }; + scoped_refptr helper_; +}; + +template +class Callback5 { + public: + // Default copy operations are appropriate for this class. + Callback5() {} + template Callback5(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { + if (empty()) + return R(); + return helper_->Run(p1, p2, p3, p4, p5); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { + return functor_(p1, p2, p3, p4, p5); + } + T functor_; + }; + scoped_refptr helper_; +}; +} // namespace rtc + +#endif // WEBRTC_BASE_CALLBACK_H_ diff --git a/webrtc/base/callback.h.pump b/webrtc/base/callback.h.pump new file mode 100644 index 000000000..86957df52 --- /dev/null +++ b/webrtc/base/callback.h.pump @@ -0,0 +1,103 @@ +/* + * Copyright 2012 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. + */ + +// To generate callback.h from callback.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py callback.h.pump + +// Callbacks are callable object containers. They can hold a function pointer +// or a function object and behave like a value type. Internally, data is +// reference-counted, making copies and pass-by-value inexpensive. +// +// Callbacks are typed using template arguments. The format is: +// CallbackN +// where N is the number of arguments supplied to the callable object. +// Callbacks are invoked using operator(), just like a function or a function +// object. Default-constructed callbacks are "empty," and executing an empty +// callback does nothing. A callback can be made empty by assigning it from +// a default-constructed callback. +// +// Callbacks are similar in purpose to std::function (which isn't available on +// all platforms we support) and a lightweight alternative to sigslots. Since +// they effectively hide the type of the object they call, they're useful in +// breaking dependencies between objects that need to interact with one another. +// Notably, they can hold the results of Bind(), std::bind*, etc, without needing +// to know the resulting object type of those calls. +// +// Sigslots, on the other hand, provide a fuller feature set, such as multiple +// subscriptions to a signal, optional thread-safety, and lifetime tracking of +// slots. When these features are needed, choose sigslots. +// +// Example: +// int sqr(int x) { return x * x; } +// struct AddK { +// int k; +// int operator()(int x) const { return x + k; } +// } add_k = {5}; +// +// Callback1 my_callback; +// cout << my_callback.empty() << endl; // true +// +// my_callback = Callback1(&sqr); +// cout << my_callback.empty() << endl; // false +// cout << my_callback(3) << endl; // 9 +// +// my_callback = Callback1(add_k); +// cout << my_callback(10) << endl; // 15 +// +// my_callback = Callback1(); +// cout << my_callback.empty() << endl; // true + +#ifndef WEBRTC_BASE_CALLBACK_H_ +#define WEBRTC_BASE_CALLBACK_H_ + +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ref_ptr.h" + +namespace rtc { + +$var n = 5 +$range i 0..n +$for i [[ +$range j 1..i + +template +class Callback$i { + public: + // Default copy operations are appropriate for this class. + Callback$i() {} + template Callback$i(const T& functor) + : helper_(new RefCountedObject< HelperImpl >(functor)) {} + R operator()($for j , [[P$j p$j]]) { + if (empty()) + return R(); + return helper_->Run($for j , [[p$j]]); + } + bool empty() const { return !helper_; } + + private: + struct Helper : RefCountInterface { + virtual ~Helper() {} + virtual R Run($for j , [[P$j p$j]]) = 0; + }; + template struct HelperImpl : Helper { + explicit HelperImpl(const T& functor) : functor_(functor) {} + virtual R Run($for j , [[P$j p$j]]) { + return functor_($for j , [[p$j]]); + } + T functor_; + }; + scoped_refptr helper_; +}; + +]] +} // namespace rtc + +#endif // WEBRTC_BASE_CALLBACK_H_ diff --git a/webrtc/base/callback_unittest.cc b/webrtc/base/callback_unittest.cc new file mode 100644 index 000000000..66c939140 --- /dev/null +++ b/webrtc/base/callback_unittest.cc @@ -0,0 +1,81 @@ +/* + * Copyright 2004 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 "webrtc/base/bind.h" +#include "webrtc/base/callback.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +namespace { + +void f() {} +int g() { return 42; } +int h(int x) { return x * x; } +void i(int& x) { x *= x; } // NOLINT: Testing refs + +struct BindTester { + int a() { return 24; } + int b(int x) const { return x * x; } +}; + +} // namespace + +TEST(CallbackTest, VoidReturn) { + Callback0 cb; + EXPECT_TRUE(cb.empty()); + cb(); // Executing an empty callback should not crash. + cb = Callback0(&f); + EXPECT_FALSE(cb.empty()); + cb(); +} + +TEST(CallbackTest, IntReturn) { + Callback0 cb; + EXPECT_TRUE(cb.empty()); + cb = Callback0(&g); + EXPECT_FALSE(cb.empty()); + EXPECT_EQ(42, cb()); + EXPECT_EQ(42, cb()); +} + +TEST(CallbackTest, OneParam) { + Callback1 cb1(&h); + EXPECT_FALSE(cb1.empty()); + EXPECT_EQ(9, cb1(-3)); + EXPECT_EQ(100, cb1(10)); + + // Try clearing a callback. + cb1 = Callback1(); + EXPECT_TRUE(cb1.empty()); + + // Try a callback with a ref parameter. + Callback1 cb2(&i); + int x = 3; + cb2(x); + EXPECT_EQ(9, x); + cb2(x); + EXPECT_EQ(81, x); +} + +TEST(CallbackTest, WithBind) { + BindTester t; + Callback0 cb1 = Bind(&BindTester::a, &t); + EXPECT_EQ(24, cb1()); + EXPECT_EQ(24, cb1()); + cb1 = Bind(&BindTester::b, &t, 10); + EXPECT_EQ(100, cb1()); + EXPECT_EQ(100, cb1()); + cb1 = Bind(&BindTester::b, &t, 5); + EXPECT_EQ(25, cb1()); + EXPECT_EQ(25, cb1()); +} + +} // namespace rtc diff --git a/webrtc/base/checks.cc b/webrtc/base/checks.cc new file mode 100644 index 000000000..67f5003de --- /dev/null +++ b/webrtc/base/checks.cc @@ -0,0 +1,34 @@ +/* + * Copyright 2006 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 "webrtc/base/checks.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +void Fatal(const char* file, int line, const char* format, ...) { + char msg[256]; + + va_list arguments; + va_start(arguments, format); + vsnprintf(msg, sizeof(msg), format, arguments); + va_end(arguments); + + LOG(LS_ERROR) << "\n\n#\n# Fatal error in " << file + << ", line " << line << "\n#" << msg + << "\n#\n"; + abort(); +} + +} // namespace rtc diff --git a/webrtc/base/checks.h b/webrtc/base/checks.h new file mode 100644 index 000000000..5a2841ae9 --- /dev/null +++ b/webrtc/base/checks.h @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 module contains some basic debugging facilities. +// Originally comes from shared/commandlineflags/checks.h + +#ifndef WEBRTC_BASE_CHECKS_H_ +#define WEBRTC_BASE_CHECKS_H_ + +#include + +namespace rtc { + +// Prints an error message to stderr and aborts execution. +void Fatal(const char* file, int line, const char* format, ...); + +} // namespace rtc + +// The UNREACHABLE macro is very useful during development. +#define UNREACHABLE() \ + rtc::Fatal(__FILE__, __LINE__, "unreachable code") + +#endif // WEBRTC_BASE_CHECKS_H_ diff --git a/webrtc/base/common.cc b/webrtc/base/common.cc new file mode 100644 index 000000000..c6f1b7531 --- /dev/null +++ b/webrtc/base/common.cc @@ -0,0 +1,72 @@ +/* + * Copyright 2004 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 + +#if WEBRTC_WIN +#define WIN32_LEAN_AND_MEAN +#include +#endif // WEBRTC_WIN + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +#include +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +////////////////////////////////////////////////////////////////////// +// Assertions +////////////////////////////////////////////////////////////////////// + +namespace rtc { + +void Break() { +#if WEBRTC_WIN + ::DebugBreak(); +#else // !WEBRTC_WIN + // On POSIX systems, SIGTRAP signals debuggers to break without killing the + // process. If a debugger isn't attached, the uncaught SIGTRAP will crash the + // app. + raise(SIGTRAP); +#endif + // If a debugger wasn't attached, we will have crashed by this point. If a + // debugger is attached, we'll continue from here. +} + +static AssertLogger custom_assert_logger_ = NULL; + +void SetCustomAssertLogger(AssertLogger logger) { + custom_assert_logger_ = logger; +} + +void LogAssert(const char* function, const char* file, int line, + const char* expression) { + if (custom_assert_logger_) { + custom_assert_logger_(function, file, line, expression); + } else { + LOG(LS_ERROR) << file << "(" << line << ")" << ": ASSERT FAILED: " + << expression << " @ " << function; + } +} + +bool IsOdd(int n) { + return (n & 0x1); +} + +bool IsEven(int n) { + return !IsOdd(n); +} + +} // namespace rtc diff --git a/webrtc/base/common.h b/webrtc/base/common.h new file mode 100644 index 000000000..afb747d7f --- /dev/null +++ b/webrtc/base/common.h @@ -0,0 +1,204 @@ +/* + * Copyright 2004 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_BASE_COMMON_H_ // NOLINT +#define WEBRTC_BASE_COMMON_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" + +#if defined(_MSC_VER) +// warning C4355: 'this' : used in base member initializer list +#pragma warning(disable:4355) +#endif + +////////////////////////////////////////////////////////////////////// +// General Utilities +////////////////////////////////////////////////////////////////////// + +#ifndef RTC_UNUSED +#define RTC_UNUSED(x) RtcUnused(static_cast(&x)) +#define RTC_UNUSED2(x, y) RtcUnused(static_cast(&x)); \ + RtcUnused(static_cast(&y)) +#define RTC_UNUSED3(x, y, z) RtcUnused(static_cast(&x)); \ + RtcUnused(static_cast(&y)); \ + RtcUnused(static_cast(&z)) +#define RTC_UNUSED4(x, y, z, a) RtcUnused(static_cast(&x)); \ + RtcUnused(static_cast(&y)); \ + RtcUnused(static_cast(&z)); \ + RtcUnused(static_cast(&a)) +#define RTC_UNUSED5(x, y, z, a, b) RtcUnused(static_cast(&x)); \ + RtcUnused(static_cast(&y)); \ + RtcUnused(static_cast(&z)); \ + RtcUnused(static_cast(&a)); \ + RtcUnused(static_cast(&b)) +inline void RtcUnused(const void*) {} +#endif // RTC_UNUSED + +#if !defined(WEBRTC_WIN) + +#ifndef strnicmp +#define strnicmp(x, y, n) strncasecmp(x, y, n) +#endif + +#ifndef stricmp +#define stricmp(x, y) strcasecmp(x, y) +#endif + +// TODO(fbarchard): Remove this. std::max should be used everywhere in the code. +// NOMINMAX must be defined where we include . +#define stdmax(x, y) std::max(x, y) +#else +#define stdmax(x, y) rtc::_max(x, y) +#endif + +#define ARRAY_SIZE(x) (static_cast(sizeof(x) / sizeof(x[0]))) + +///////////////////////////////////////////////////////////////////////////// +// Assertions +///////////////////////////////////////////////////////////////////////////// + +#ifndef ENABLE_DEBUG +#define ENABLE_DEBUG _DEBUG +#endif // !defined(ENABLE_DEBUG) + +// Even for release builds, allow for the override of LogAssert. Though no +// macro is provided, this can still be used for explicit runtime asserts +// and allow applications to override the assert behavior. + +namespace rtc { + + +// If a debugger is attached, triggers a debugger breakpoint. If a debugger is +// not attached, forces program termination. +void Break(); + +// LogAssert writes information about an assertion to the log. It's called by +// Assert (and from the ASSERT macro in debug mode) before any other action +// is taken (e.g. breaking the debugger, abort()ing, etc.). +void LogAssert(const char* function, const char* file, int line, + const char* expression); + +typedef void (*AssertLogger)(const char* function, + const char* file, + int line, + const char* expression); + +// Sets a custom assert logger to be used instead of the default LogAssert +// behavior. To clear the custom assert logger, pass NULL for |logger| and the +// default behavior will be restored. Only one custom assert logger can be set +// at a time, so this should generally be set during application startup and +// only by one component. +void SetCustomAssertLogger(AssertLogger logger); + +bool IsOdd(int n); + +bool IsEven(int n); + +} // namespace rtc + +#if ENABLE_DEBUG + +namespace rtc { + +inline bool Assert(bool result, const char* function, const char* file, + int line, const char* expression) { + if (!result) { + LogAssert(function, file, line, expression); + Break(); + return false; + } + return true; +} + +} // namespace rtc + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#define __FUNCTION__ "" +#endif + +#ifndef ASSERT +#define ASSERT(x) \ + (void)rtc::Assert((x), __FUNCTION__, __FILE__, __LINE__, #x) +#endif + +#ifndef VERIFY +#define VERIFY(x) rtc::Assert((x), __FUNCTION__, __FILE__, __LINE__, #x) +#endif + +#else // !ENABLE_DEBUG + +namespace rtc { + +inline bool ImplicitCastToBool(bool result) { return result; } + +} // namespace rtc + +#ifndef ASSERT +#define ASSERT(x) (void)0 +#endif + +#ifndef VERIFY +#define VERIFY(x) rtc::ImplicitCastToBool(x) +#endif + +#endif // !ENABLE_DEBUG + +#define COMPILE_TIME_ASSERT(expr) char CTA_UNIQUE_NAME[expr] +#define CTA_UNIQUE_NAME CTA_MAKE_NAME(__LINE__) +#define CTA_MAKE_NAME(line) CTA_MAKE_NAME2(line) +#define CTA_MAKE_NAME2(line) constraint_ ## line + +// Forces compiler to inline, even against its better judgement. Use wisely. +#if defined(__GNUC__) +#define FORCE_INLINE __attribute__((always_inline)) +#elif defined(WEBRTC_WIN) +#define FORCE_INLINE __forceinline +#else +#define FORCE_INLINE +#endif + +// Borrowed from Chromium's base/compiler_specific.h. +// Annotate a virtual method indicating it must be overriding a virtual +// method in the parent class. +// Use like: +// virtual void foo() OVERRIDE; +#if defined(WEBRTC_WIN) +#define OVERRIDE override +#elif defined(__clang__) +// Clang defaults to C++03 and warns about using override. Squelch that. +// Intentionally no push/pop here so all users of OVERRIDE ignore the warning +// too. This is like passing -Wno-c++11-extensions, except that GCC won't die +// (because it won't see this pragma). +#pragma clang diagnostic ignored "-Wc++11-extensions" +#define OVERRIDE override +#elif defined(__GNUC__) && __cplusplus >= 201103 && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 +// GCC 4.7 supports explicit virtual overrides when C++11 support is enabled. +#define OVERRIDE override +#else +#define OVERRIDE +#endif + +// Annotate a function indicating the caller must examine the return value. +// Use like: +// int foo() WARN_UNUSED_RESULT; +// To explicitly ignore a result, see |ignore_result()| in . +// TODO(ajm): Hack to avoid multiple definitions until the base/ of webrtc and +// libjingle are merged. +#if !defined(WARN_UNUSED_RESULT) +#if defined(__GNUC__) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif +#endif // WARN_UNUSED_RESULT + +#endif // WEBRTC_BASE_COMMON_H_ // NOLINT diff --git a/webrtc/base/compile_assert.h b/webrtc/base/compile_assert.h new file mode 100644 index 000000000..6d4249c8f --- /dev/null +++ b/webrtc/base/compile_assert.h @@ -0,0 +1,82 @@ +/* + * Copyright 2013 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. + */ + +// COMPILE_ASSERT macro, borrowed from google3/base/macros.h. +#ifndef WEBRTC_BASE_COMPILE_ASSERT_H_ +#define WEBRTC_BASE_COMPILE_ASSERT_H_ + +// The COMPILE_ASSERT macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// COMPILE_ASSERT(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +// TODO(ajm): Hack to avoid multiple definitions until the base/ of webrtc and +// libjingle are merged. +#if !defined(COMPILE_ASSERT) +template +struct CompileAssert { +}; + +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] // NOLINT +#endif // COMPILE_ASSERT + +// Implementation details of COMPILE_ASSERT: +// +// - COMPILE_ASSERT works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outer parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert +// +// instead, these compilers will refuse to compile +// +// COMPILE_ASSERT(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + +#endif // WEBRTC_BASE_COMPILE_ASSERT_H_ diff --git a/webrtc/system_wrappers/interface/constructor_magic.h b/webrtc/base/constructormagic.h similarity index 67% rename from webrtc/system_wrappers/interface/constructor_magic.h rename to webrtc/base/constructormagic.h index b2aabc574..c20be2b32 100644 --- a/webrtc/system_wrappers/interface/constructor_magic.h +++ b/webrtc/base/constructormagic.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * Copyright 2004 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 @@ -8,34 +8,25 @@ * be found in the AUTHORS file in the root of the source tree. */ -/* - * WebRtc - * Copy from third_party/libjingle/source/talk/base/constructormagic.h - */ - -#ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CONSTRUCTOR_MAGIC_H_ -#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CONSTRUCTOR_MAGIC_H_ +#ifndef WEBRTC_BASE_CONSTRUCTORMAGIC_H_ +#define WEBRTC_BASE_CONSTRUCTORMAGIC_H_ -#ifndef DISALLOW_ASSIGN #define DISALLOW_ASSIGN(TypeName) \ void operator=(const TypeName&) -#endif -#ifndef DISALLOW_COPY_AND_ASSIGN // A macro to disallow the evil copy constructor and operator= functions -// This should be used in the private: declarations for a class +// This should be used in the private: declarations for a class. +// Undefine this, just in case. Some third-party includes have their own +// version. +#undef DISALLOW_COPY_AND_ASSIGN #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ DISALLOW_ASSIGN(TypeName) -#endif -#ifndef DISALLOW_EVIL_CONSTRUCTORS // Alternative, less-accurate legacy name. #define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ DISALLOW_COPY_AND_ASSIGN(TypeName) -#endif -#ifndef DISALLOW_IMPLICIT_CONSTRUCTORS // A macro to disallow all the implicit constructors, namely the // default constructor, copy constructor and operator= functions. // @@ -45,6 +36,6 @@ #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ TypeName(); \ DISALLOW_EVIL_CONSTRUCTORS(TypeName) -#endif -#endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CONSTRUCTOR_MAGIC_H_ + +#endif // WEBRTC_BASE_CONSTRUCTORMAGIC_H_ diff --git a/webrtc/base/cpumonitor.cc b/webrtc/base/cpumonitor.cc new file mode 100644 index 000000000..c881b48c5 --- /dev/null +++ b/webrtc/base/cpumonitor.cc @@ -0,0 +1,423 @@ +/* + * Copyright 2010 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 "webrtc/base/cpumonitor.h" + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/systeminfo.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif + +#if defined(WEBRTC_POSIX) +#include +#endif + +#if defined(WEBRTC_MAC) +#include +#include +#include +#include +#include +#endif // defined(WEBRTC_MAC) + +#if defined(WEBRTC_LINUX) +#include +#include +#include +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#endif // defined(WEBRTC_LINUX) + +#if defined(WEBRTC_MAC) +static uint64 TimeValueTToInt64(const time_value_t &time_value) { + return rtc::kNumMicrosecsPerSec * time_value.seconds + + time_value.microseconds; +} +#endif // defined(WEBRTC_MAC) + +// How CpuSampler works +// When threads switch, the time they spent is accumulated to system counters. +// The time can be treated as user, kernel or idle. +// user time is applications. +// kernel time is the OS, including the thread switching code itself. +// typically kernel time indicates IO. +// idle time is a process that wastes time when nothing is ready to run. +// +// User time is broken down by process (application). One of the applications +// is the current process. When you add up all application times, this is +// system time. If only your application is running, system time should be the +// same as process time. +// +// All cores contribute to these accumulators. A dual core process is able to +// process twice as many cycles as a single core. The actual code efficiency +// may be worse, due to contention, but the available cycles is exactly twice +// as many, and the cpu load will reflect the efficiency. Hyperthreads behave +// the same way. The load will reflect 200%, but the actual amount of work +// completed will be much less than a true dual core. +// +// Total available performance is the sum of all accumulators. +// If you tracked this for 1 second, it would essentially give you the clock +// rate - number of cycles per second. +// Speed step / Turbo Boost is not considered, so infact more processing time +// may be available. + +namespace rtc { + +// Note Tests on Windows show 600 ms is minimum stable interval for Windows 7. +static const int32 kDefaultInterval = 950; // Slightly under 1 second. + +CpuSampler::CpuSampler() + : min_load_interval_(kDefaultInterval) +#if defined(WEBRTC_WIN) + , get_system_times_(NULL), + nt_query_system_information_(NULL), + force_fallback_(false) +#endif + { +} + +CpuSampler::~CpuSampler() { +} + +// Set minimum interval in ms between computing new load values. Default 950. +void CpuSampler::set_load_interval(int min_load_interval) { + min_load_interval_ = min_load_interval; +} + +bool CpuSampler::Init() { + sysinfo_.reset(new SystemInfo); + cpus_ = sysinfo_->GetMaxCpus(); + if (cpus_ == 0) { + return false; + } +#if defined(WEBRTC_WIN) + // Note that GetSystemTimes is available in Windows XP SP1 or later. + // http://msdn.microsoft.com/en-us/library/ms724400.aspx + // NtQuerySystemInformation is used as a fallback. + if (!force_fallback_) { + get_system_times_ = GetProcAddress(GetModuleHandle(L"kernel32.dll"), + "GetSystemTimes"); + } + nt_query_system_information_ = GetProcAddress(GetModuleHandle(L"ntdll.dll"), + "NtQuerySystemInformation"); + if ((get_system_times_ == NULL) && (nt_query_system_information_ == NULL)) { + return false; + } +#endif +#if defined(WEBRTC_LINUX) + Pathname sname("/proc/stat"); + sfile_.reset(Filesystem::OpenFile(sname, "rb")); + if (!sfile_) { + LOG_ERR(LS_ERROR) << "open proc/stat failed:"; + return false; + } + if (!sfile_->DisableBuffering()) { + LOG_ERR(LS_ERROR) << "could not disable buffering for proc/stat"; + return false; + } +#endif // defined(WEBRTC_LINUX) + GetProcessLoad(); // Initialize values. + GetSystemLoad(); + // Help next user call return valid data by recomputing load. + process_.prev_load_time_ = 0u; + system_.prev_load_time_ = 0u; + return true; +} + +float CpuSampler::UpdateCpuLoad(uint64 current_total_times, + uint64 current_cpu_times, + uint64 *prev_total_times, + uint64 *prev_cpu_times) { + float result = 0.f; + if (current_total_times < *prev_total_times || + current_cpu_times < *prev_cpu_times) { + LOG(LS_ERROR) << "Inconsistent time values are passed. ignored"; + } else { + const uint64 cpu_diff = current_cpu_times - *prev_cpu_times; + const uint64 total_diff = current_total_times - *prev_total_times; + result = (total_diff == 0ULL ? 0.f : + static_cast(1.0f * cpu_diff / total_diff)); + if (result > static_cast(cpus_)) { + result = static_cast(cpus_); + } + *prev_total_times = current_total_times; + *prev_cpu_times = current_cpu_times; + } + return result; +} + +float CpuSampler::GetSystemLoad() { + uint32 timenow = Time(); + int elapsed = static_cast(TimeDiff(timenow, system_.prev_load_time_)); + if (min_load_interval_ != 0 && system_.prev_load_time_ != 0u && + elapsed < min_load_interval_) { + return system_.prev_load_; + } +#if defined(WEBRTC_WIN) + uint64 total_times, cpu_times; + + typedef BOOL (_stdcall *GST_PROC)(LPFILETIME, LPFILETIME, LPFILETIME); + typedef NTSTATUS (WINAPI *QSI_PROC)(SYSTEM_INFORMATION_CLASS, + PVOID, ULONG, PULONG); + + GST_PROC get_system_times = reinterpret_cast(get_system_times_); + QSI_PROC nt_query_system_information = reinterpret_cast( + nt_query_system_information_); + + if (get_system_times) { + FILETIME idle_time, kernel_time, user_time; + if (!get_system_times(&idle_time, &kernel_time, &user_time)) { + LOG(LS_ERROR) << "::GetSystemTimes() failed: " << ::GetLastError(); + return 0.f; + } + // kernel_time includes Kernel idle time, so no need to + // include cpu_time as total_times + total_times = ToUInt64(kernel_time) + ToUInt64(user_time); + cpu_times = total_times - ToUInt64(idle_time); + + } else { + if (nt_query_system_information) { + ULONG returned_length = 0; + scoped_ptr processor_info( + new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[cpus_]); + nt_query_system_information( + ::SystemProcessorPerformanceInformation, + reinterpret_cast(processor_info.get()), + cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), + &returned_length); + + if (returned_length != + (cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION))) { + LOG(LS_ERROR) << "NtQuerySystemInformation has unexpected size"; + return 0.f; + } + + uint64 current_idle = 0; + uint64 current_kernel = 0; + uint64 current_user = 0; + for (int ix = 0; ix < cpus_; ++ix) { + current_idle += processor_info[ix].IdleTime.QuadPart; + current_kernel += processor_info[ix].UserTime.QuadPart; + current_user += processor_info[ix].KernelTime.QuadPart; + } + total_times = current_kernel + current_user; + cpu_times = total_times - current_idle; + } else { + return 0.f; + } + } +#endif // WEBRTC_WIN + +#if defined(WEBRTC_MAC) + mach_port_t mach_host = mach_host_self(); + host_cpu_load_info_data_t cpu_info; + mach_msg_type_number_t info_count = HOST_CPU_LOAD_INFO_COUNT; + kern_return_t kr = host_statistics(mach_host, HOST_CPU_LOAD_INFO, + reinterpret_cast(&cpu_info), + &info_count); + mach_port_deallocate(mach_task_self(), mach_host); + if (KERN_SUCCESS != kr) { + LOG(LS_ERROR) << "::host_statistics() failed"; + return 0.f; + } + + const uint64 cpu_times = cpu_info.cpu_ticks[CPU_STATE_NICE] + + cpu_info.cpu_ticks[CPU_STATE_SYSTEM] + + cpu_info.cpu_ticks[CPU_STATE_USER]; + const uint64 total_times = cpu_times + cpu_info.cpu_ticks[CPU_STATE_IDLE]; +#endif // defined(WEBRTC_MAC) + +#if defined(WEBRTC_LINUX) + if (!sfile_) { + LOG(LS_ERROR) << "Invalid handle for proc/stat"; + return 0.f; + } + std::string statbuf; + sfile_->SetPosition(0); + if (!sfile_->ReadLine(&statbuf)) { + LOG_ERR(LS_ERROR) << "Could not read proc/stat file"; + return 0.f; + } + + unsigned long long user; + unsigned long long nice; + unsigned long long system; + unsigned long long idle; + if (sscanf(statbuf.c_str(), "cpu %Lu %Lu %Lu %Lu", + &user, &nice, + &system, &idle) != 4) { + LOG_ERR(LS_ERROR) << "Could not parse cpu info"; + return 0.f; + } + const uint64 cpu_times = nice + system + user; + const uint64 total_times = cpu_times + idle; +#endif // defined(WEBRTC_LINUX) + +#if defined(__native_client__) + // TODO(ryanpetrie): Implement this via PPAPI when it's available. + const uint64 cpu_times = 0; + const uint64 total_times = 0; +#endif // defined(__native_client__) + + system_.prev_load_time_ = timenow; + system_.prev_load_ = UpdateCpuLoad(total_times, + cpu_times * cpus_, + &system_.prev_total_times_, + &system_.prev_cpu_times_); + return system_.prev_load_; +} + +float CpuSampler::GetProcessLoad() { + uint32 timenow = Time(); + int elapsed = static_cast(TimeDiff(timenow, process_.prev_load_time_)); + if (min_load_interval_ != 0 && process_.prev_load_time_ != 0u && + elapsed < min_load_interval_) { + return process_.prev_load_; + } +#if defined(WEBRTC_WIN) + FILETIME current_file_time; + ::GetSystemTimeAsFileTime(¤t_file_time); + + FILETIME create_time, exit_time, kernel_time, user_time; + if (!::GetProcessTimes(::GetCurrentProcess(), + &create_time, &exit_time, &kernel_time, &user_time)) { + LOG(LS_ERROR) << "::GetProcessTimes() failed: " << ::GetLastError(); + return 0.f; + } + + const uint64 total_times = + ToUInt64(current_file_time) - ToUInt64(create_time); + const uint64 cpu_times = + (ToUInt64(kernel_time) + ToUInt64(user_time)); +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) + // Common to both OSX and Linux. + struct timeval tv; + gettimeofday(&tv, NULL); + const uint64 total_times = tv.tv_sec * kNumMicrosecsPerSec + tv.tv_usec; +#endif + +#if defined(WEBRTC_MAC) + // Get live thread usage. + task_thread_times_info task_times_info; + mach_msg_type_number_t info_count = TASK_THREAD_TIMES_INFO_COUNT; + + if (KERN_SUCCESS != task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, + reinterpret_cast(&task_times_info), + &info_count)) { + LOG(LS_ERROR) << "::task_info(TASK_THREAD_TIMES_INFO) failed"; + return 0.f; + } + + // Get terminated thread usage. + task_basic_info task_term_info; + info_count = TASK_BASIC_INFO_COUNT; + if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, + reinterpret_cast(&task_term_info), + &info_count)) { + LOG(LS_ERROR) << "::task_info(TASK_BASIC_INFO) failed"; + return 0.f; + } + + const uint64 cpu_times = (TimeValueTToInt64(task_times_info.user_time) + + TimeValueTToInt64(task_times_info.system_time) + + TimeValueTToInt64(task_term_info.user_time) + + TimeValueTToInt64(task_term_info.system_time)); +#endif // defined(WEBRTC_MAC) + +#if defined(WEBRTC_LINUX) + rusage usage; + if (getrusage(RUSAGE_SELF, &usage) < 0) { + LOG_ERR(LS_ERROR) << "getrusage failed"; + return 0.f; + } + + const uint64 cpu_times = + (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * kNumMicrosecsPerSec + + usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; +#endif // defined(WEBRTC_LINUX) + +#if defined(__native_client__) + // TODO(ryanpetrie): Implement this via PPAPI when it's available. + const uint64 cpu_times = 0; +#endif // defined(__native_client__) + + process_.prev_load_time_ = timenow; + process_.prev_load_ = UpdateCpuLoad(total_times, + cpu_times, + &process_.prev_total_times_, + &process_.prev_cpu_times_); + return process_.prev_load_; +} + +int CpuSampler::GetMaxCpus() const { + return cpus_; +} + +int CpuSampler::GetCurrentCpus() { + return sysinfo_->GetCurCpus(); +} + +/////////////////////////////////////////////////////////////////// +// Implementation of class CpuMonitor. +CpuMonitor::CpuMonitor(Thread* thread) + : monitor_thread_(thread) { +} + +CpuMonitor::~CpuMonitor() { + Stop(); +} + +void CpuMonitor::set_thread(Thread* thread) { + ASSERT(monitor_thread_ == NULL || monitor_thread_ == thread); + monitor_thread_ = thread; +} + +bool CpuMonitor::Start(int period_ms) { + if (!monitor_thread_ || !sampler_.Init()) return false; + + monitor_thread_->SignalQueueDestroyed.connect( + this, &CpuMonitor::OnMessageQueueDestroyed); + + period_ms_ = period_ms; + monitor_thread_->PostDelayed(period_ms_, this); + + return true; +} + +void CpuMonitor::Stop() { + if (monitor_thread_) { + monitor_thread_->Clear(this); + } +} + +void CpuMonitor::OnMessage(Message* msg) { + int max_cpus = sampler_.GetMaxCpus(); + int current_cpus = sampler_.GetCurrentCpus(); + float process_load = sampler_.GetProcessLoad(); + float system_load = sampler_.GetSystemLoad(); + SignalUpdate(current_cpus, max_cpus, process_load, system_load); + + if (monitor_thread_) { + monitor_thread_->PostDelayed(period_ms_, this); + } +} + +} // namespace rtc diff --git a/webrtc/base/cpumonitor.h b/webrtc/base/cpumonitor.h new file mode 100644 index 000000000..39b09b380 --- /dev/null +++ b/webrtc/base/cpumonitor.h @@ -0,0 +1,123 @@ +/* + * Copyright 2010 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_BASE_CPUMONITOR_H_ +#define WEBRTC_BASE_CPUMONITOR_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#if defined(WEBRTC_LINUX) +#include "webrtc/base/stream.h" +#endif // defined(WEBRTC_LINUX) + +namespace rtc { +class Thread; +class SystemInfo; + +struct CpuStats { + CpuStats() + : prev_total_times_(0), + prev_cpu_times_(0), + prev_load_(0.f), + prev_load_time_(0u) { + } + + uint64 prev_total_times_; + uint64 prev_cpu_times_; + float prev_load_; // Previous load value. + uint32 prev_load_time_; // Time previous load value was taken. +}; + +// CpuSampler samples the process and system load. +class CpuSampler { + public: + CpuSampler(); + ~CpuSampler(); + + // Initialize CpuSampler. Returns true if successful. + bool Init(); + + // Set minimum interval in ms between computing new load values. + // Default 950 ms. Set to 0 to disable interval. + void set_load_interval(int min_load_interval); + + // Return CPU load of current process as a float from 0 to 1. + float GetProcessLoad(); + + // Return CPU load of current process as a float from 0 to 1. + float GetSystemLoad(); + + // Return number of cpus. Includes hyperthreads. + int GetMaxCpus() const; + + // Return current number of cpus available to this process. + int GetCurrentCpus(); + + // For testing. Allows forcing of fallback to using NTDLL functions. + void set_force_fallback(bool fallback) { +#if defined(WEBRTC_WIN) + force_fallback_ = fallback; +#endif + } + + private: + float UpdateCpuLoad(uint64 current_total_times, + uint64 current_cpu_times, + uint64 *prev_total_times, + uint64 *prev_cpu_times); + CpuStats process_; + CpuStats system_; + int cpus_; + int min_load_interval_; // Minimum time between computing new load. + scoped_ptr sysinfo_; +#if defined(WEBRTC_WIN) + void* get_system_times_; + void* nt_query_system_information_; + bool force_fallback_; +#endif +#if defined(WEBRTC_LINUX) + // File for reading /proc/stat + scoped_ptr sfile_; +#endif // defined(WEBRTC_LINUX) +}; + +// CpuMonitor samples and signals the CPU load periodically. +class CpuMonitor + : public rtc::MessageHandler, public sigslot::has_slots<> { + public: + explicit CpuMonitor(Thread* thread); + virtual ~CpuMonitor(); + void set_thread(Thread* thread); + + bool Start(int period_ms); + void Stop(); + // Signal parameters are current cpus, max cpus, process load and system load. + sigslot::signal4 SignalUpdate; + + protected: + // Override virtual method of parent MessageHandler. + virtual void OnMessage(rtc::Message* msg); + // Clear the monitor thread and stop sending it messages if the thread goes + // away before our lifetime. + void OnMessageQueueDestroyed() { monitor_thread_ = NULL; } + + private: + Thread* monitor_thread_; + CpuSampler sampler_; + int period_ms_; + + DISALLOW_COPY_AND_ASSIGN(CpuMonitor); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_CPUMONITOR_H_ diff --git a/webrtc/base/cpumonitor_unittest.cc b/webrtc/base/cpumonitor_unittest.cc new file mode 100644 index 000000000..6d9af5aec --- /dev/null +++ b/webrtc/base/cpumonitor_unittest.cc @@ -0,0 +1,388 @@ +/* + * Copyright 2010 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 + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/cpumonitor.h" +#include "webrtc/base/flags.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/timing.h" + +namespace rtc { + +static const int kMaxCpus = 1024; +static const int kSettleTime = 100; // Amount of time to between tests. +static const int kIdleTime = 500; // Amount of time to be idle in ms. +static const int kBusyTime = 1000; // Amount of time to be busy in ms. +static const int kLongInterval = 2000; // Interval longer than busy times + +class BusyThread : public rtc::Thread { + public: + BusyThread(double load, double duration, double interval) : + load_(load), duration_(duration), interval_(interval) { + } + virtual ~BusyThread() { + Stop(); + } + void Run() { + Timing time; + double busy_time = interval_ * load_ / 100.0; + for (;;) { + time.BusyWait(busy_time); + time.IdleWait(interval_ - busy_time); + if (duration_) { + duration_ -= interval_; + if (duration_ <= 0) { + break; + } + } + } + } + private: + double load_; + double duration_; + double interval_; +}; + +class CpuLoadListener : public sigslot::has_slots<> { + public: + CpuLoadListener() + : current_cpus_(0), + cpus_(0), + process_load_(.0f), + system_load_(.0f), + count_(0) { + } + + void OnCpuLoad(int current_cpus, int cpus, float proc_load, float sys_load) { + current_cpus_ = current_cpus; + cpus_ = cpus; + process_load_ = proc_load; + system_load_ = sys_load; + ++count_; + } + + int current_cpus() const { return current_cpus_; } + int cpus() const { return cpus_; } + float process_load() const { return process_load_; } + float system_load() const { return system_load_; } + int count() const { return count_; } + + private: + int current_cpus_; + int cpus_; + float process_load_; + float system_load_; + int count_; +}; + +// Set affinity (which cpu to run on), but respecting FLAG_affinity: +// -1 means no affinity - run on whatever cpu is available. +// 0 .. N means run on specific cpu. The tool will create N threads and call +// SetThreadAffinity on 0 to N - 1 as cpu. FLAG_affinity sets the first cpu +// so the range becomes affinity to affinity + N - 1 +// Note that this function affects Windows scheduling, effectively giving +// the thread with affinity for a specified CPU more priority on that CPU. +bool SetThreadAffinity(BusyThread* t, int cpu, int affinity) { +#if defined(WEBRTC_WIN) + if (affinity >= 0) { + return ::SetThreadAffinityMask(t->GetHandle(), + 1 << (cpu + affinity)) != FALSE; + } +#endif + return true; +} + +bool SetThreadPriority(BusyThread* t, int prio) { + if (!prio) { + return true; + } + bool ok = t->SetPriority(static_cast(prio)); + if (!ok) { + std::cout << "Error setting thread priority." << std::endl; + } + return ok; +} + +int CpuLoad(double cpuload, double duration, int numthreads, + int priority, double interval, int affinity) { + int ret = 0; + std::vector threads; + for (int i = 0; i < numthreads; ++i) { + threads.push_back(new BusyThread(cpuload, duration, interval)); + // NOTE(fbarchard): Priority must be done before Start. + if (!SetThreadPriority(threads[i], priority) || + !threads[i]->Start() || + !SetThreadAffinity(threads[i], i, affinity)) { + ret = 1; + break; + } + } + // Wait on each thread + if (ret == 0) { + for (int i = 0; i < numthreads; ++i) { + threads[i]->Stop(); + } + } + + for (int i = 0; i < numthreads; ++i) { + delete threads[i]; + } + return ret; +} + +// Make 2 CPUs busy +static void CpuTwoBusyLoop(int busytime) { + CpuLoad(100.0, busytime / 1000.0, 2, 1, 0.050, -1); +} + +// Make 1 CPUs busy +static void CpuBusyLoop(int busytime) { + CpuLoad(100.0, busytime / 1000.0, 1, 1, 0.050, -1); +} + +// Make 1 use half CPU time. +static void CpuHalfBusyLoop(int busytime) { + CpuLoad(50.0, busytime / 1000.0, 1, 1, 0.050, -1); +} + +void TestCpuSampler(bool test_proc, bool test_sys, bool force_fallback) { + CpuSampler sampler; + sampler.set_force_fallback(force_fallback); + EXPECT_TRUE(sampler.Init()); + sampler.set_load_interval(100); + int cpus = sampler.GetMaxCpus(); + + // Test1: CpuSampler under idle situation. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + Thread::SleepMs(kIdleTime); + + float proc_idle = 0.f, sys_idle = 0.f; + if (test_proc) { + proc_idle = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_idle = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_idle; + EXPECT_GE(proc_idle, 0.f); + EXPECT_LE(proc_idle, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_idle; + EXPECT_GE(sys_idle, 0.f); + EXPECT_LE(sys_idle, static_cast(cpus)); + } + + // Test2: CpuSampler with main process at 50% busy. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + CpuHalfBusyLoop(kBusyTime); + + float proc_halfbusy = 0.f, sys_halfbusy = 0.f; + if (test_proc) { + proc_halfbusy = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_halfbusy = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Halfbusy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_halfbusy; + EXPECT_GE(proc_halfbusy, 0.f); + EXPECT_LE(proc_halfbusy, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Halfbusy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_halfbusy; + EXPECT_GE(sys_halfbusy, 0.f); + EXPECT_LE(sys_halfbusy, static_cast(cpus)); + } + + // Test3: CpuSampler with main process busy. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + CpuBusyLoop(kBusyTime); + + float proc_busy = 0.f, sys_busy = 0.f; + if (test_proc) { + proc_busy = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_busy = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Busy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_busy; + EXPECT_GE(proc_busy, 0.f); + EXPECT_LE(proc_busy, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Busy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_busy; + EXPECT_GE(sys_busy, 0.f); + EXPECT_LE(sys_busy, static_cast(cpus)); + } + + // Test4: CpuSampler with 2 cpus process busy. + if (cpus >= 2) { + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + CpuTwoBusyLoop(kBusyTime); + + float proc_twobusy = 0.f, sys_twobusy = 0.f; + if (test_proc) { + proc_twobusy = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_twobusy = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad 2 CPU Busy:" + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_twobusy; + EXPECT_GE(proc_twobusy, 0.f); + EXPECT_LE(proc_twobusy, static_cast(cpus)); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad 2 CPU Busy: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_twobusy; + EXPECT_GE(sys_twobusy, 0.f); + EXPECT_LE(sys_twobusy, static_cast(cpus)); + } + } + + // Test5: CpuSampler with idle process after being busy. + Thread::SleepMs(kSettleTime); + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + Thread::SleepMs(kIdleTime); + + if (test_proc) { + proc_idle = sampler.GetProcessLoad(); + } + if (test_sys) { + sys_idle = sampler.GetSystemLoad(); + } + if (test_proc) { + LOG(LS_INFO) << "ProcessLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << proc_idle; + EXPECT_GE(proc_idle, 0.f); + EXPECT_LE(proc_idle, proc_busy); + } + if (test_sys) { + LOG(LS_INFO) << "SystemLoad Idle: " + << std::setiosflags(std::ios_base::fixed) + << std::setprecision(2) << std::setw(6) << sys_idle; + EXPECT_GE(sys_idle, 0.f); + EXPECT_LE(sys_idle, static_cast(cpus)); + } +} + +TEST(CpuMonitorTest, TestCpus) { + CpuSampler sampler; + EXPECT_TRUE(sampler.Init()); + int current_cpus = sampler.GetCurrentCpus(); + int cpus = sampler.GetMaxCpus(); + LOG(LS_INFO) << "Current Cpus: " << std::setw(9) << current_cpus; + LOG(LS_INFO) << "Maximum Cpus: " << std::setw(9) << cpus; + EXPECT_GT(cpus, 0); + EXPECT_LE(cpus, kMaxCpus); + EXPECT_GT(current_cpus, 0); + EXPECT_LE(current_cpus, cpus); +} + +#if defined(WEBRTC_WIN) +// Tests overall system CpuSampler using legacy OS fallback code if applicable. +TEST(CpuMonitorTest, TestGetSystemLoadForceFallback) { + TestCpuSampler(false, true, true); +} +#endif + +// Tests both process and system functions in use at same time. +TEST(CpuMonitorTest, TestGetBothLoad) { + TestCpuSampler(true, true, false); +} + +// Tests a query less than the interval produces the same value. +TEST(CpuMonitorTest, TestInterval) { + CpuSampler sampler; + EXPECT_TRUE(sampler.Init()); + + // Test1: Set interval to large value so sampler will not update. + sampler.set_load_interval(kLongInterval); + + sampler.GetProcessLoad(); + sampler.GetSystemLoad(); + + float proc_orig = sampler.GetProcessLoad(); + float sys_orig = sampler.GetSystemLoad(); + + Thread::SleepMs(kIdleTime); + + float proc_halftime = sampler.GetProcessLoad(); + float sys_halftime = sampler.GetSystemLoad(); + + EXPECT_EQ(proc_orig, proc_halftime); + EXPECT_EQ(sys_orig, sys_halftime); +} + +TEST(CpuMonitorTest, TestCpuMonitor) { + CpuMonitor monitor(Thread::Current()); + CpuLoadListener listener; + monitor.SignalUpdate.connect(&listener, &CpuLoadListener::OnCpuLoad); + EXPECT_TRUE(monitor.Start(10)); + // We have checked cpu load more than twice. + EXPECT_TRUE_WAIT(listener.count() > 2, 1000); + EXPECT_GT(listener.current_cpus(), 0); + EXPECT_GT(listener.cpus(), 0); + EXPECT_GE(listener.process_load(), .0f); + EXPECT_GE(listener.system_load(), .0f); + + monitor.Stop(); + // Wait 20 ms to ake sure all signals are delivered. + Thread::Current()->ProcessMessages(20); + int old_count = listener.count(); + Thread::Current()->ProcessMessages(20); + // Verfy no more siganls. + EXPECT_EQ(old_count, listener.count()); +} + +} // namespace rtc diff --git a/webrtc/base/crc32.cc b/webrtc/base/crc32.cc new file mode 100644 index 000000000..d643a25a4 --- /dev/null +++ b/webrtc/base/crc32.cc @@ -0,0 +1,52 @@ +/* + * Copyright 2012 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 "webrtc/base/crc32.h" + +#include "webrtc/base/basicdefs.h" + +namespace rtc { + +// This implementation is based on the sample implementation in RFC 1952. + +// CRC32 polynomial, in reversed form. +// See RFC 1952, or http://en.wikipedia.org/wiki/Cyclic_redundancy_check +static const uint32 kCrc32Polynomial = 0xEDB88320; +static uint32 kCrc32Table[256] = { 0 }; + +static void EnsureCrc32TableInited() { + if (kCrc32Table[ARRAY_SIZE(kCrc32Table) - 1]) + return; // already inited + for (uint32 i = 0; i < ARRAY_SIZE(kCrc32Table); ++i) { + uint32 c = i; + for (size_t j = 0; j < 8; ++j) { + if (c & 1) { + c = kCrc32Polynomial ^ (c >> 1); + } else { + c >>= 1; + } + } + kCrc32Table[i] = c; + } +} + +uint32 UpdateCrc32(uint32 start, const void* buf, size_t len) { + EnsureCrc32TableInited(); + + uint32 c = start ^ 0xFFFFFFFF; + const uint8* u = static_cast(buf); + for (size_t i = 0; i < len; ++i) { + c = kCrc32Table[(c ^ u[i]) & 0xFF] ^ (c >> 8); + } + return c ^ 0xFFFFFFFF; +} + +} // namespace rtc + diff --git a/webrtc/base/crc32.h b/webrtc/base/crc32.h new file mode 100644 index 000000000..99b4cac89 --- /dev/null +++ b/webrtc/base/crc32.h @@ -0,0 +1,34 @@ +/* + * Copyright 2012 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_BASE_CRC32_H_ +#define WEBRTC_BASE_CRC32_H_ + +#include + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Updates a CRC32 checksum with |len| bytes from |buf|. |initial| holds the +// checksum result from the previous update; for the first call, it should be 0. +uint32 UpdateCrc32(uint32 initial, const void* buf, size_t len); + +// Computes a CRC32 checksum using |len| bytes from |buf|. +inline uint32 ComputeCrc32(const void* buf, size_t len) { + return UpdateCrc32(0, buf, len); +} +inline uint32 ComputeCrc32(const std::string& str) { + return ComputeCrc32(str.c_str(), str.size()); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_CRC32_H_ diff --git a/webrtc/base/crc32_unittest.cc b/webrtc/base/crc32_unittest.cc new file mode 100644 index 000000000..0bfdeeea0 --- /dev/null +++ b/webrtc/base/crc32_unittest.cc @@ -0,0 +1,35 @@ +/* + * Copyright 2012 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 "webrtc/base/crc32.h" +#include "webrtc/base/gunit.h" + +#include + +namespace rtc { + +TEST(Crc32Test, TestBasic) { + EXPECT_EQ(0U, ComputeCrc32("")); + EXPECT_EQ(0x352441C2U, ComputeCrc32("abc")); + EXPECT_EQ(0x171A3F5FU, + ComputeCrc32("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")); +} + +TEST(Crc32Test, TestMultipleUpdates) { + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + uint32 c = 0; + for (size_t i = 0; i < input.size(); ++i) { + c = UpdateCrc32(c, &input[i], 1); + } + EXPECT_EQ(0x171A3F5FU, c); +} + +} // namespace rtc diff --git a/webrtc/base/criticalsection.h b/webrtc/base/criticalsection.h new file mode 100644 index 000000000..a950a47f5 --- /dev/null +++ b/webrtc/base/criticalsection.h @@ -0,0 +1,179 @@ +/* + * Copyright 2004 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_BASE_CRITICALSECTION_H__ +#define WEBRTC_BASE_CRITICALSECTION_H__ + +#include "webrtc/base/constructormagic.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#if defined(WEBRTC_POSIX) +#include +#endif + +#ifdef _DEBUG +#define CS_TRACK_OWNER 1 +#endif // _DEBUG + +#if CS_TRACK_OWNER +#define TRACK_OWNER(x) x +#else // !CS_TRACK_OWNER +#define TRACK_OWNER(x) +#endif // !CS_TRACK_OWNER + +namespace rtc { + +#if defined(WEBRTC_WIN) +class CriticalSection { + public: + CriticalSection() { + InitializeCriticalSection(&crit_); + // Windows docs say 0 is not a valid thread id + TRACK_OWNER(thread_ = 0); + } + ~CriticalSection() { + DeleteCriticalSection(&crit_); + } + void Enter() { + EnterCriticalSection(&crit_); + TRACK_OWNER(thread_ = GetCurrentThreadId()); + } + bool TryEnter() { + if (TryEnterCriticalSection(&crit_) != FALSE) { + TRACK_OWNER(thread_ = GetCurrentThreadId()); + return true; + } + return false; + } + void Leave() { + TRACK_OWNER(thread_ = 0); + LeaveCriticalSection(&crit_); + } + +#if CS_TRACK_OWNER + bool CurrentThreadIsOwner() const { return thread_ == GetCurrentThreadId(); } +#endif // CS_TRACK_OWNER + + private: + CRITICAL_SECTION crit_; + TRACK_OWNER(DWORD thread_); // The section's owning thread id +}; +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) +class CriticalSection { + public: + CriticalSection() { + pthread_mutexattr_t mutex_attribute; + pthread_mutexattr_init(&mutex_attribute); + pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex_, &mutex_attribute); + pthread_mutexattr_destroy(&mutex_attribute); + TRACK_OWNER(thread_ = 0); + } + ~CriticalSection() { + pthread_mutex_destroy(&mutex_); + } + void Enter() { + pthread_mutex_lock(&mutex_); + TRACK_OWNER(thread_ = pthread_self()); + } + bool TryEnter() { + if (pthread_mutex_trylock(&mutex_) == 0) { + TRACK_OWNER(thread_ = pthread_self()); + return true; + } + return false; + } + void Leave() { + TRACK_OWNER(thread_ = 0); + pthread_mutex_unlock(&mutex_); + } + +#if CS_TRACK_OWNER + bool CurrentThreadIsOwner() const { return pthread_equal(thread_, pthread_self()); } +#endif // CS_TRACK_OWNER + + private: + pthread_mutex_t mutex_; + TRACK_OWNER(pthread_t thread_); +}; +#endif // WEBRTC_POSIX + +// CritScope, for serializing execution through a scope. +class CritScope { + public: + explicit CritScope(CriticalSection *pcrit) { + pcrit_ = pcrit; + pcrit_->Enter(); + } + ~CritScope() { + pcrit_->Leave(); + } + private: + CriticalSection *pcrit_; + DISALLOW_COPY_AND_ASSIGN(CritScope); +}; + +// Tries to lock a critical section on construction via +// CriticalSection::TryEnter, and unlocks on destruction if the +// lock was taken. Never blocks. +// +// IMPORTANT: Unlike CritScope, the lock may not be owned by this thread in +// subsequent code. Users *must* check locked() to determine if the +// lock was taken. If you're not calling locked(), you're doing it wrong! +class TryCritScope { + public: + explicit TryCritScope(CriticalSection *pcrit) { + pcrit_ = pcrit; + locked_ = pcrit_->TryEnter(); + } + ~TryCritScope() { + if (locked_) { + pcrit_->Leave(); + } + } + bool locked() const { + return locked_; + } + private: + CriticalSection *pcrit_; + bool locked_; + DISALLOW_COPY_AND_ASSIGN(TryCritScope); +}; + +// TODO: Move this to atomicops.h, which can't be done easily because of +// complex compile rules. +class AtomicOps { + public: +#if defined(WEBRTC_WIN) + // Assumes sizeof(int) == sizeof(LONG), which it is on Win32 and Win64. + static int Increment(int* i) { + return ::InterlockedIncrement(reinterpret_cast(i)); + } + static int Decrement(int* i) { + return ::InterlockedDecrement(reinterpret_cast(i)); + } +#else + static int Increment(int* i) { + return __sync_add_and_fetch(i, 1); + } + static int Decrement(int* i) { + return __sync_sub_and_fetch(i, 1); + } +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_CRITICALSECTION_H__ diff --git a/webrtc/base/criticalsection_unittest.cc b/webrtc/base/criticalsection_unittest.cc new file mode 100644 index 000000000..e1b05cb01 --- /dev/null +++ b/webrtc/base/criticalsection_unittest.cc @@ -0,0 +1,146 @@ +/* + * Copyright 2014 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 "webrtc/base/criticalsection.h" +#include "webrtc/base/event.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/scopedptrcollection.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +namespace { + +const int kLongTime = 10000; // 10 seconds +const int kNumThreads = 16; +const int kOperationsToRun = 1000; + +template +class AtomicOpRunner : public MessageHandler { + public: + explicit AtomicOpRunner(int initial_value) + : value_(initial_value), + threads_active_(0), + start_event_(true, false), + done_event_(true, false) {} + + int value() const { return value_; } + + bool Run() { + // Signal all threads to start. + start_event_.Set(); + + // Wait for all threads to finish. + return done_event_.Wait(kLongTime); + } + + void SetExpectedThreadCount(int count) { + threads_active_ = count; + } + + virtual void OnMessage(Message* msg) { + std::vector values; + values.reserve(kOperationsToRun); + + // Wait to start. + ASSERT_TRUE(start_event_.Wait(kLongTime)); + + // Generate a bunch of values by updating value_ atomically. + for (int i = 0; i < kOperationsToRun; ++i) { + values.push_back(T::AtomicOp(&value_)); + } + + { // Add them all to the set. + CritScope cs(&all_values_crit_); + for (size_t i = 0; i < values.size(); ++i) { + std::pair::iterator, bool> result = + all_values_.insert(values[i]); + // Each value should only be taken by one thread, so if this value + // has already been added, something went wrong. + EXPECT_TRUE(result.second) + << "Thread=" << Thread::Current() << " value=" << values[i]; + } + } + + // Signal that we're done. + if (AtomicOps::Decrement(&threads_active_) == 0) { + done_event_.Set(); + } + } + + private: + int value_; + int threads_active_; + CriticalSection all_values_crit_; + std::set all_values_; + Event start_event_; + Event done_event_; +}; + +struct IncrementOp { + static int AtomicOp(int* i) { return AtomicOps::Increment(i); } +}; + +struct DecrementOp { + static int AtomicOp(int* i) { return AtomicOps::Decrement(i); } +}; + +void StartThreads(ScopedPtrCollection* threads, + MessageHandler* handler) { + for (int i = 0; i < kNumThreads; ++i) { + Thread* thread = new Thread(); + thread->Start(); + thread->Post(handler); + threads->PushBack(thread); + } +} + +} // namespace + +TEST(AtomicOpsTest, Simple) { + int value = 0; + EXPECT_EQ(1, AtomicOps::Increment(&value)); + EXPECT_EQ(1, value); + EXPECT_EQ(2, AtomicOps::Increment(&value)); + EXPECT_EQ(2, value); + EXPECT_EQ(1, AtomicOps::Decrement(&value)); + EXPECT_EQ(1, value); + EXPECT_EQ(0, AtomicOps::Decrement(&value)); + EXPECT_EQ(0, value); +} + +TEST(AtomicOpsTest, Increment) { + // Create and start lots of threads. + AtomicOpRunner runner(0); + ScopedPtrCollection threads; + StartThreads(&threads, &runner); + runner.SetExpectedThreadCount(kNumThreads); + + // Release the hounds! + EXPECT_TRUE(runner.Run()); + EXPECT_EQ(kOperationsToRun * kNumThreads, runner.value()); +} + +TEST(AtomicOpsTest, Decrement) { + // Create and start lots of threads. + AtomicOpRunner runner(kOperationsToRun * kNumThreads); + ScopedPtrCollection threads; + StartThreads(&threads, &runner); + runner.SetExpectedThreadCount(kNumThreads); + + // Release the hounds! + EXPECT_TRUE(runner.Run()); + EXPECT_EQ(0, runner.value()); +} + +} // namespace rtc diff --git a/webrtc/base/cryptstring.h b/webrtc/base/cryptstring.h new file mode 100644 index 000000000..f43057d8f --- /dev/null +++ b/webrtc/base/cryptstring.h @@ -0,0 +1,183 @@ +/* + * Copyright 2004 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_BASE_CRYPTSTRING_H_ +#define _WEBRTC_BASE_CRYPTSTRING_H_ + +#include + +#include +#include + +#include "webrtc/base/linked_ptr.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class CryptStringImpl { +public: + virtual ~CryptStringImpl() {} + virtual size_t GetLength() const = 0; + virtual void CopyTo(char * dest, bool nullterminate) const = 0; + virtual std::string UrlEncode() const = 0; + virtual CryptStringImpl * Copy() const = 0; + virtual void CopyRawTo(std::vector * dest) const = 0; +}; + +class EmptyCryptStringImpl : public CryptStringImpl { +public: + virtual ~EmptyCryptStringImpl() {} + virtual size_t GetLength() const { return 0; } + virtual void CopyTo(char * dest, bool nullterminate) const { + if (nullterminate) { + *dest = '\0'; + } + } + virtual std::string UrlEncode() const { return ""; } + virtual CryptStringImpl * Copy() const { return new EmptyCryptStringImpl(); } + virtual void CopyRawTo(std::vector * dest) const { + dest->clear(); + } +}; + +class CryptString { +public: + CryptString() : impl_(new EmptyCryptStringImpl()) {} + size_t GetLength() const { return impl_->GetLength(); } + void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); } + CryptString(const CryptString & other) : impl_(other.impl_->Copy()) {} + explicit CryptString(const CryptStringImpl & impl) : impl_(impl.Copy()) {} + CryptString & operator=(const CryptString & other) { + if (this != &other) { + impl_.reset(other.impl_->Copy()); + } + return *this; + } + void Clear() { impl_.reset(new EmptyCryptStringImpl()); } + std::string UrlEncode() const { return impl_->UrlEncode(); } + void CopyRawTo(std::vector * dest) const { + return impl_->CopyRawTo(dest); + } + +private: + scoped_ptr impl_; +}; + + +// Used for constructing strings where a password is involved and we +// need to ensure that we zero memory afterwards +class FormatCryptString { +public: + FormatCryptString() { + storage_ = new char[32]; + capacity_ = 32; + length_ = 0; + storage_[0] = 0; + } + + void Append(const std::string & text) { + Append(text.data(), text.length()); + } + + void Append(const char * data, size_t length) { + EnsureStorage(length_ + length + 1); + memcpy(storage_ + length_, data, length); + length_ += length; + storage_[length_] = '\0'; + } + + void Append(const CryptString * password) { + size_t len = password->GetLength(); + EnsureStorage(length_ + len + 1); + password->CopyTo(storage_ + length_, true); + length_ += len; + } + + size_t GetLength() { + return length_; + } + + const char * GetData() { + return storage_; + } + + + // Ensures storage of at least n bytes + void EnsureStorage(size_t n) { + if (capacity_ >= n) { + return; + } + + size_t old_capacity = capacity_; + char * old_storage = storage_; + + for (;;) { + capacity_ *= 2; + if (capacity_ >= n) + break; + } + + storage_ = new char[capacity_]; + + if (old_capacity) { + memcpy(storage_, old_storage, length_); + + // zero memory in a way that an optimizer won't optimize it out + old_storage[0] = 0; + for (size_t i = 1; i < old_capacity; i++) { + old_storage[i] = old_storage[i - 1]; + } + delete[] old_storage; + } + } + + ~FormatCryptString() { + if (capacity_) { + storage_[0] = 0; + for (size_t i = 1; i < capacity_; i++) { + storage_[i] = storage_[i - 1]; + } + } + delete[] storage_; + } +private: + char * storage_; + size_t capacity_; + size_t length_; +}; + +class InsecureCryptStringImpl : public CryptStringImpl { + public: + std::string& password() { return password_; } + const std::string& password() const { return password_; } + + virtual ~InsecureCryptStringImpl() {} + virtual size_t GetLength() const { return password_.size(); } + virtual void CopyTo(char * dest, bool nullterminate) const { + memcpy(dest, password_.data(), password_.size()); + if (nullterminate) dest[password_.size()] = 0; + } + virtual std::string UrlEncode() const { return password_; } + virtual CryptStringImpl * Copy() const { + InsecureCryptStringImpl * copy = new InsecureCryptStringImpl; + copy->password() = password_; + return copy; + } + virtual void CopyRawTo(std::vector * dest) const { + dest->resize(password_.size()); + memcpy(&dest->front(), password_.data(), password_.size()); + } + private: + std::string password_; +}; + +} + +#endif // _WEBRTC_BASE_CRYPTSTRING_H_ diff --git a/webrtc/base/dbus.cc b/webrtc/base/dbus.cc new file mode 100644 index 000000000..b8392f9a2 --- /dev/null +++ b/webrtc/base/dbus.cc @@ -0,0 +1,396 @@ +/* + * Copyright 2004 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. + */ + +#ifdef HAVE_DBUS_GLIB + +#include "webrtc/base/dbus.h" + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Avoid static object construction/destruction on startup/shutdown. +static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT; +static LibDBusGlibSymbolTable *g_dbus_symbol = NULL; + +// Releases DBus-Glib symbols. +static void ReleaseDBusGlibSymbol() { + if (g_dbus_symbol != NULL) { + delete g_dbus_symbol; + g_dbus_symbol = NULL; + } +} + +// Loads DBus-Glib symbols. +static void InitializeDBusGlibSymbol() { + // This is thread safe. + if (NULL == g_dbus_symbol) { + g_dbus_symbol = new LibDBusGlibSymbolTable(); + + // Loads dbus-glib + if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) { + LOG(LS_WARNING) << "Failed to load dbus-glib symbol table."; + ReleaseDBusGlibSymbol(); + } else { + // Nothing we can do if atexit() failed. Just ignore its returned value. + atexit(ReleaseDBusGlibSymbol); + } + } +} + +inline static LibDBusGlibSymbolTable *GetSymbols() { + return DBusMonitor::GetDBusGlibSymbolTable(); +} + +// Implementation of class DBusSigMessageData +DBusSigMessageData::DBusSigMessageData(DBusMessage *message) + : TypedMessageData(message) { + GetSymbols()->dbus_message_ref()(data()); +} + +DBusSigMessageData::~DBusSigMessageData() { + GetSymbols()->dbus_message_unref()(data()); +} + +// Implementation of class DBusSigFilter + +// Builds a DBus filter string from given DBus path, interface and member. +std::string DBusSigFilter::BuildFilterString(const std::string &path, + const std::string &interface, + const std::string &member) { + std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'"); + if (!path.empty()) { + ret += ("," DBUS_PATH "='"); + ret += path; + ret += "'"; + } + if (!interface.empty()) { + ret += ("," DBUS_INTERFACE "='"); + ret += interface; + ret += "'"; + } + if (!member.empty()) { + ret += ("," DBUS_MEMBER "='"); + ret += member; + ret += "'"; + } + return ret; +} + +// Forwards the message to the given instance. +DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn, + DBusMessage *message, + void *instance) { + ASSERT(instance); + if (instance) { + return static_cast(instance)->Callback(message); + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +// Posts a message to caller thread. +DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) { + if (caller_thread_) { + caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message)); + } + // Don't "eat" the message here. Let it pop up. + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +// From MessageHandler. +void DBusSigFilter::OnMessage(Message *message) { + if (message != NULL && DSM_SIGNAL == message->message_id) { + DBusSigMessageData *msg = + static_cast(message->pdata); + if (msg) { + ProcessSignal(msg->data()); + delete msg; + } + } +} + +// Definition of private class DBusMonitoringThread. +// It creates a worker-thread to listen signals on DBus. The worker-thread will +// be running in a priate GMainLoop forever until either Stop() has been invoked +// or it hits an error. +class DBusMonitor::DBusMonitoringThread : public rtc::Thread { + public: + explicit DBusMonitoringThread(DBusMonitor *monitor, + GMainContext *context, + GMainLoop *mainloop, + std::vector *filter_list) + : monitor_(monitor), + context_(context), + mainloop_(mainloop), + connection_(NULL), + idle_source_(NULL), + filter_list_(filter_list) { + ASSERT(monitor_); + ASSERT(context_); + ASSERT(mainloop_); + ASSERT(filter_list_); + } + + virtual ~DBusMonitoringThread() { + Stop(); + } + + // Override virtual method of Thread. Context: worker-thread. + virtual void Run() { + ASSERT(NULL == connection_); + + // Setup DBus connection and start monitoring. + monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING); + if (!Setup()) { + LOG(LS_ERROR) << "DBus monitoring setup failed."; + monitor_->OnMonitoringStatusChanged(DMS_FAILED); + CleanUp(); + return; + } + monitor_->OnMonitoringStatusChanged(DMS_RUNNING); + g_main_loop_run(mainloop_); + monitor_->OnMonitoringStatusChanged(DMS_STOPPED); + + // Done normally. Clean up DBus connection. + CleanUp(); + return; + } + + // Override virtual method of Thread. Context: caller-thread. + virtual void Stop() { + ASSERT(NULL == idle_source_); + // Add an idle source and let the gmainloop quit on idle. + idle_source_ = g_idle_source_new(); + if (idle_source_) { + g_source_set_callback(idle_source_, &Idle, this, NULL); + g_source_attach(idle_source_, context_); + } else { + LOG(LS_ERROR) << "g_idle_source_new() failed."; + QuitGMainloop(); // Try to quit anyway. + } + + Thread::Stop(); // Wait for the thread. + } + + private: + // Registers all DBus filters. + void RegisterAllFilters() { + ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()( + connection_)); + + for (std::vector::iterator it = filter_list_->begin(); + it != filter_list_->end(); ++it) { + DBusSigFilter *filter = (*it); + if (!filter) { + LOG(LS_ERROR) << "DBusSigFilter list corrupted."; + continue; + } + + GetSymbols()->dbus_bus_add_match()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), + filter->filter().c_str(), NULL); + + if (!GetSymbols()->dbus_connection_add_filter()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), + &DBusSigFilter::DBusCallback, filter, NULL)) { + LOG(LS_ERROR) << "dbus_connection_add_filter() failed." + << "Filter: " << filter->filter(); + continue; + } + } + } + + // Unregisters all DBus filters. + void UnRegisterAllFilters() { + ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()( + connection_)); + + for (std::vector::iterator it = filter_list_->begin(); + it != filter_list_->end(); ++it) { + DBusSigFilter *filter = (*it); + if (!filter) { + LOG(LS_ERROR) << "DBusSigFilter list corrupted."; + continue; + } + GetSymbols()->dbus_connection_remove_filter()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), + &DBusSigFilter::DBusCallback, filter); + } + } + + // Sets up the monitoring thread. + bool Setup() { + g_main_context_push_thread_default(context_); + + // Start connection to dbus. + // If dbus daemon is not running, returns false immediately. + connection_ = GetSymbols()->dbus_g_bus_get_private()(monitor_->type_, + context_, NULL); + if (NULL == connection_) { + LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection."; + return false; + } + if (NULL == GetSymbols()->dbus_g_connection_get_connection()(connection_)) { + LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. " + << "DBus daemon is probably not running."; + return false; + } + + // Application don't exit if DBus daemon die. + GetSymbols()->dbus_connection_set_exit_on_disconnect()( + GetSymbols()->dbus_g_connection_get_connection()(connection_), FALSE); + + // Connect all filters. + RegisterAllFilters(); + + return true; + } + + // Cleans up the monitoring thread. + void CleanUp() { + if (idle_source_) { + // We did an attach() with the GSource, so we need to destroy() it. + g_source_destroy(idle_source_); + // We need to unref() the GSource to end the last reference we got. + g_source_unref(idle_source_); + idle_source_ = NULL; + } + if (connection_) { + if (GetSymbols()->dbus_g_connection_get_connection()(connection_)) { + UnRegisterAllFilters(); + GetSymbols()->dbus_connection_close()( + GetSymbols()->dbus_g_connection_get_connection()(connection_)); + } + GetSymbols()->dbus_g_connection_unref()(connection_); + connection_ = NULL; + } + g_main_loop_unref(mainloop_); + mainloop_ = NULL; + g_main_context_unref(context_); + context_ = NULL; + } + + // Handles callback on Idle. We only add this source when ready to stop. + static gboolean Idle(gpointer data) { + static_cast(data)->QuitGMainloop(); + return TRUE; + } + + // We only hit this when ready to quit. + void QuitGMainloop() { + g_main_loop_quit(mainloop_); + } + + DBusMonitor *monitor_; + + GMainContext *context_; + GMainLoop *mainloop_; + DBusGConnection *connection_; + GSource *idle_source_; + + std::vector *filter_list_; +}; + +// Implementation of class DBusMonitor + +// Returns DBus-Glib symbol handle. Initialize it first if hasn't. +LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() { + // This is multi-thread safe. + pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol); + + return g_dbus_symbol; +}; + +// Creates an instance of DBusMonitor +DBusMonitor *DBusMonitor::Create(DBusBusType type) { + if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) { + return NULL; + } + return new DBusMonitor(type); +} + +DBusMonitor::DBusMonitor(DBusBusType type) + : type_(type), + status_(DMS_NOT_INITIALIZED), + monitoring_thread_(NULL) { + ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION); +} + +DBusMonitor::~DBusMonitor() { + StopMonitoring(); +} + +bool DBusMonitor::AddFilter(DBusSigFilter *filter) { + if (monitoring_thread_) { + return false; + } + if (!filter) { + return false; + } + filter_list_.push_back(filter); + return true; +} + +bool DBusMonitor::StartMonitoring() { + if (!monitoring_thread_) { + g_type_init(); + g_thread_init(NULL); + GetSymbols()->dbus_g_thread_init()(); + + GMainContext *context = g_main_context_new(); + if (NULL == context) { + LOG(LS_ERROR) << "g_main_context_new() failed."; + return false; + } + + GMainLoop *mainloop = g_main_loop_new(context, FALSE); + if (NULL == mainloop) { + LOG(LS_ERROR) << "g_main_loop_new() failed."; + g_main_context_unref(context); + return false; + } + + monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop, + &filter_list_); + if (monitoring_thread_ == NULL) { + LOG(LS_ERROR) << "Failed to create DBus monitoring thread."; + g_main_context_unref(context); + g_main_loop_unref(mainloop); + return false; + } + monitoring_thread_->Start(); + } + return true; +} + +bool DBusMonitor::StopMonitoring() { + if (monitoring_thread_) { + monitoring_thread_->Stop(); + monitoring_thread_ = NULL; + } + return true; +} + +DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() { + return status_; +} + +void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) { + status_ = status; +} + +#undef LATE + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB diff --git a/webrtc/base/dbus.h b/webrtc/base/dbus.h new file mode 100644 index 000000000..fb90638bc --- /dev/null +++ b/webrtc/base/dbus.h @@ -0,0 +1,168 @@ +/* + * Copyright 2004 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_BASE_DBUS_H_ +#define WEBRTC_BASE_DBUS_H_ + +#ifdef HAVE_DBUS_GLIB + +#include + +#include +#include + +#include "webrtc/base/libdbusglibsymboltable.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#define DBUS_TYPE "type" +#define DBUS_SIGNAL "signal" +#define DBUS_PATH "path" +#define DBUS_INTERFACE "interface" +#define DBUS_MEMBER "member" + +#ifdef CHROMEOS +#define CROS_PM_PATH "/" +#define CROS_PM_INTERFACE "org.chromium.PowerManager" +#define CROS_SIG_POWERCHANGED "PowerStateChanged" +#define CROS_VALUE_SLEEP "mem" +#define CROS_VALUE_RESUME "on" +#else +#define UP_PATH "/org/freedesktop/UPower" +#define UP_INTERFACE "org.freedesktop.UPower" +#define UP_SIG_SLEEPING "Sleeping" +#define UP_SIG_RESUMING "Resuming" +#endif // CHROMEOS + +// Wraps a DBus messages. +class DBusSigMessageData : public TypedMessageData { + public: + explicit DBusSigMessageData(DBusMessage *message); + ~DBusSigMessageData(); +}; + +// DBusSigFilter is an abstract class that defines the interface of DBus +// signal handling. +// The subclasses implement ProcessSignal() for various purposes. +// When a DBus signal comes, a DSM_SIGNAL message is posted to the caller thread +// which will then invokes ProcessSignal(). +class DBusSigFilter : protected MessageHandler { + public: + enum DBusSigMessage { DSM_SIGNAL }; + + // This filter string should ususally come from BuildFilterString() + explicit DBusSigFilter(const std::string &filter) + : caller_thread_(Thread::Current()), filter_(filter) { + } + + // Builds a DBus monitor filter string from given DBus path, interface, and + // member. + // See http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html + static std::string BuildFilterString(const std::string &path, + const std::string &interface, + const std::string &member); + + // Handles callback on DBus messages by DBus system. + static DBusHandlerResult DBusCallback(DBusConnection *dbus_conn, + DBusMessage *message, + void *instance); + + // Handles callback on DBus messages to each DBusSigFilter instance. + DBusHandlerResult Callback(DBusMessage *message); + + // From MessageHandler. + virtual void OnMessage(Message *message); + + // Returns the DBus monitor filter string. + const std::string &filter() const { return filter_; } + + private: + // On caller thread. + virtual void ProcessSignal(DBusMessage *message) = 0; + + Thread *caller_thread_; + const std::string filter_; +}; + +// DBusMonitor is a class for DBus signal monitoring. +// +// The caller-thread calls AddFilter() first to add the signals that it wants to +// monitor and then calls StartMonitoring() to start the monitoring. +// This will create a worker-thread which listens on DBus connection and sends +// DBus signals back through the callback. +// The worker-thread will be running forever until either StopMonitoring() is +// called from the caller-thread or the worker-thread hit some error. +// +// Programming model: +// 1. Caller-thread: Creates an object of DBusMonitor. +// 2. Caller-thread: Calls DBusMonitor::AddFilter() one or several times. +// 3. Caller-thread: StartMonitoring(). +// ... +// 4. Worker-thread: DBus signal recieved. Post a message to caller-thread. +// 5. Caller-thread: DBusFilterBase::ProcessSignal() is invoked. +// ... +// 6. Caller-thread: StopMonitoring(). +// +// Assumption: +// AddFilter(), StartMonitoring(), and StopMonitoring() methods are called by +// a single thread. Hence, there is no need to make them thread safe. +class DBusMonitor { + public: + // Status of DBus monitoring. + enum DBusMonitorStatus { + DMS_NOT_INITIALIZED, // Not initialized. + DMS_INITIALIZING, // Initializing the monitoring thread. + DMS_RUNNING, // Monitoring. + DMS_STOPPED, // Not monitoring. Stopped normally. + DMS_FAILED, // Not monitoring. Failed. + }; + + // Returns the DBus-Glib symbol table. + // We should only use this function to access DBus-Glib symbols. + static LibDBusGlibSymbolTable *GetDBusGlibSymbolTable(); + + // Creates an instance of DBusMonitor. + static DBusMonitor *Create(DBusBusType type); + ~DBusMonitor(); + + // Adds a filter to DBusMonitor. + bool AddFilter(DBusSigFilter *filter); + + // Starts DBus message monitoring. + bool StartMonitoring(); + + // Stops DBus message monitoring. + bool StopMonitoring(); + + // Gets the status of DBus monitoring. + DBusMonitorStatus GetStatus(); + + private: + // Forward declaration. Defined in the .cc file. + class DBusMonitoringThread; + + explicit DBusMonitor(DBusBusType type); + + // Updates status_ when monitoring status has changed. + void OnMonitoringStatusChanged(DBusMonitorStatus status); + + DBusBusType type_; + DBusMonitorStatus status_; + DBusMonitoringThread *monitoring_thread_; + std::vector filter_list_; +}; + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB + +#endif // WEBRTC_BASE_DBUS_H_ diff --git a/webrtc/base/dbus_unittest.cc b/webrtc/base/dbus_unittest.cc new file mode 100644 index 000000000..505ddbbc8 --- /dev/null +++ b/webrtc/base/dbus_unittest.cc @@ -0,0 +1,232 @@ +/* + * Copyright 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. + */ + +#ifdef HAVE_DBUS_GLIB + +#include "webrtc/base/dbus.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#define SIG_NAME "NameAcquired" + +static const uint32 kTimeoutMs = 5000U; + +class DBusSigFilterTest : public DBusSigFilter { + public: + // DBusSigFilterTest listens on DBus service itself for "NameAcquired" signal. + // This signal should be received when the application connects to DBus + // service and gains ownership of a name. + // http://dbus.freedesktop.org/doc/dbus-specification.html + DBusSigFilterTest() + : DBusSigFilter(GetFilter()), + message_received_(false) { + } + + bool MessageReceived() { + return message_received_; + } + + private: + static std::string GetFilter() { + return rtc::DBusSigFilter::BuildFilterString("", "", SIG_NAME); + } + + // Implement virtual method of DBusSigFilter. On caller thread. + virtual void ProcessSignal(DBusMessage *message) { + EXPECT_TRUE(message != NULL); + message_received_ = true; + } + + bool message_received_; +}; + +TEST(DBusMonitorTest, StartStopStartStop) { + DBusSigFilterTest filter; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter)); + + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_NOT_INITIALIZED); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_RUNNING); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +// DBusMonitorTest listens on DBus service itself for "NameAcquired" signal. +// This signal should be received when the application connects to DBus +// service and gains ownership of a name. +// This test is to make sure that we capture the "NameAcquired" signal. +TEST(DBusMonitorTest, ReceivedNameAcquiredSignal) { + DBusSigFilterTest filter; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, ConcurrentMonitors) { + DBusSigFilterTest filter1; + rtc::scoped_ptr monitor1; + monitor1.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor1) { + EXPECT_TRUE(monitor1->AddFilter(&filter1)); + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor2; + monitor2.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + EXPECT_TRUE(monitor2->AddFilter(&filter2)); + + EXPECT_TRUE(monitor1->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor1->GetStatus(), kTimeoutMs); + EXPECT_TRUE(monitor2->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor2->GetStatus(), kTimeoutMs); + + EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor2->StopMonitoring()); + EXPECT_EQ(monitor2->GetStatus(), DBusMonitor::DMS_STOPPED); + + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor1->StopMonitoring()); + EXPECT_EQ(monitor1->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, ConcurrentFilters) { + DBusSigFilterTest filter1; + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter1)); + EXPECT_TRUE(monitor->AddFilter(&filter2)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs); + + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, NoAddFilterIfRunning) { + DBusSigFilterTest filter1; + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter1)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_FALSE(monitor->AddFilter(&filter2)); + + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, AddFilterAfterStop) { + DBusSigFilterTest filter1; + DBusSigFilterTest filter2; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter1)); + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + + EXPECT_TRUE(monitor->AddFilter(&filter2)); + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs); + EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs); + EXPECT_TRUE(monitor->StopMonitoring()); + EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED); + } else { + LOG(LS_WARNING) << "DBus Monitor not started. Skipping test."; + } +} + +TEST(DBusMonitorTest, StopRightAfterStart) { + DBusSigFilterTest filter; + rtc::scoped_ptr monitor; + monitor.reset(rtc::DBusMonitor::Create(DBUS_BUS_SYSTEM)); + if (monitor) { + EXPECT_TRUE(monitor->AddFilter(&filter)); + + EXPECT_TRUE(monitor->StartMonitoring()); + EXPECT_TRUE(monitor->StopMonitoring()); + + // Stop the monitoring thread right after it had been started. + // If the monitoring thread got a chance to receive a DBus signal, it would + // post a message to the main thread and signal the main thread wakeup. + // This message will be cleaned out automatically when the filter get + // destructed. Here we also consume the wakeup signal (if there is one) so + // that the testing (main) thread is reset to a clean state. + rtc::Thread::Current()->ProcessMessages(1); + } else { + LOG(LS_WARNING) << "DBus Monitor not started."; + } +} + +TEST(DBusSigFilter, BuildFilterString) { + EXPECT_EQ(DBusSigFilter::BuildFilterString("", "", ""), + (DBUS_TYPE "='" DBUS_SIGNAL "'")); + EXPECT_EQ(DBusSigFilter::BuildFilterString("p", "", ""), + (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'")); + EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i", ""), + (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'," + DBUS_INTERFACE "='i'")); + EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i","m"), + (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'," + DBUS_INTERFACE "='i'," DBUS_MEMBER "='m'")); +} + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB diff --git a/webrtc/base/diskcache.cc b/webrtc/base/diskcache.cc new file mode 100644 index 000000000..f893ce73d --- /dev/null +++ b/webrtc/base/diskcache.cc @@ -0,0 +1,347 @@ +/* + * Copyright 2004 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 + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/diskcache.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +#ifdef _DEBUG +#define TRANSPARENT_CACHE_NAMES 1 +#else // !_DEBUG +#define TRANSPARENT_CACHE_NAMES 0 +#endif // !_DEBUG + +namespace rtc { + +class DiskCache; + +/////////////////////////////////////////////////////////////////////////////// +// DiskCacheAdapter +/////////////////////////////////////////////////////////////////////////////// + +class DiskCacheAdapter : public StreamAdapterInterface { +public: + DiskCacheAdapter(const DiskCache* cache, const std::string& id, size_t index, + StreamInterface* stream) + : StreamAdapterInterface(stream), cache_(cache), id_(id), index_(index) + { } + virtual ~DiskCacheAdapter() { + Close(); + cache_->ReleaseResource(id_, index_); + } + +private: + const DiskCache* cache_; + std::string id_; + size_t index_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// DiskCache +/////////////////////////////////////////////////////////////////////////////// + +DiskCache::DiskCache() : max_cache_(0), total_size_(0), total_accessors_(0) { +} + +DiskCache::~DiskCache() { + ASSERT(0 == total_accessors_); +} + +bool DiskCache::Initialize(const std::string& folder, size_t size) { + if (!folder_.empty() || !Filesystem::CreateFolder(folder)) + return false; + + folder_ = folder; + max_cache_ = size; + ASSERT(0 == total_size_); + + if (!InitializeEntries()) + return false; + + return CheckLimit(); +} + +bool DiskCache::Purge() { + if (folder_.empty()) + return false; + + if (total_accessors_ > 0) { + LOG_F(LS_WARNING) << "Cache files open"; + return false; + } + + if (!PurgeFiles()) + return false; + + map_.clear(); + return true; +} + +bool DiskCache::LockResource(const std::string& id) { + Entry* entry = GetOrCreateEntry(id, true); + if (LS_LOCKED == entry->lock_state) + return false; + if ((LS_UNLOCKED == entry->lock_state) && (entry->accessors > 0)) + return false; + if ((total_size_ > max_cache_) && !CheckLimit()) { + LOG_F(LS_WARNING) << "Cache overfull"; + return false; + } + entry->lock_state = LS_LOCKED; + return true; +} + +StreamInterface* DiskCache::WriteResource(const std::string& id, size_t index) { + Entry* entry = GetOrCreateEntry(id, false); + if (LS_LOCKED != entry->lock_state) + return NULL; + + size_t previous_size = 0; + std::string filename(IdToFilename(id, index)); + FileStream::GetSize(filename, &previous_size); + ASSERT(previous_size <= entry->size); + if (previous_size > entry->size) { + previous_size = entry->size; + } + + scoped_ptr file(new FileStream); + if (!file->Open(filename, "wb", NULL)) { + LOG_F(LS_ERROR) << "Couldn't create cache file"; + return NULL; + } + + entry->streams = stdmax(entry->streams, index + 1); + entry->size -= previous_size; + total_size_ -= previous_size; + + entry->accessors += 1; + total_accessors_ += 1; + return new DiskCacheAdapter(this, id, index, file.release()); +} + +bool DiskCache::UnlockResource(const std::string& id) { + Entry* entry = GetOrCreateEntry(id, false); + if (LS_LOCKED != entry->lock_state) + return false; + + if (entry->accessors > 0) { + entry->lock_state = LS_UNLOCKING; + } else { + entry->lock_state = LS_UNLOCKED; + entry->last_modified = time(0); + CheckLimit(); + } + return true; +} + +StreamInterface* DiskCache::ReadResource(const std::string& id, + size_t index) const { + const Entry* entry = GetEntry(id); + if (LS_UNLOCKED != entry->lock_state) + return NULL; + if (index >= entry->streams) + return NULL; + + scoped_ptr file(new FileStream); + if (!file->Open(IdToFilename(id, index), "rb", NULL)) + return NULL; + + entry->accessors += 1; + total_accessors_ += 1; + return new DiskCacheAdapter(this, id, index, file.release()); +} + +bool DiskCache::HasResource(const std::string& id) const { + const Entry* entry = GetEntry(id); + return (NULL != entry) && (entry->streams > 0); +} + +bool DiskCache::HasResourceStream(const std::string& id, size_t index) const { + const Entry* entry = GetEntry(id); + if ((NULL == entry) || (index >= entry->streams)) + return false; + + std::string filename = IdToFilename(id, index); + + return FileExists(filename); +} + +bool DiskCache::DeleteResource(const std::string& id) { + Entry* entry = GetOrCreateEntry(id, false); + if (!entry) + return true; + + if ((LS_UNLOCKED != entry->lock_state) || (entry->accessors > 0)) + return false; + + bool success = true; + for (size_t index = 0; index < entry->streams; ++index) { + std::string filename = IdToFilename(id, index); + + if (!FileExists(filename)) + continue; + + if (!DeleteFile(filename)) { + LOG_F(LS_ERROR) << "Couldn't remove cache file: " << filename; + success = false; + } + } + + total_size_ -= entry->size; + map_.erase(id); + return success; +} + +bool DiskCache::CheckLimit() { +#ifdef _DEBUG + // Temporary check to make sure everything is working correctly. + size_t cache_size = 0; + for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) { + cache_size += it->second.size; + } + ASSERT(cache_size == total_size_); +#endif // _DEBUG + + // TODO: Replace this with a non-brain-dead algorithm for clearing out the + // oldest resources... something that isn't O(n^2) + while (total_size_ > max_cache_) { + EntryMap::iterator oldest = map_.end(); + for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) { + if ((LS_UNLOCKED != it->second.lock_state) || (it->second.accessors > 0)) + continue; + oldest = it; + break; + } + if (oldest == map_.end()) { + LOG_F(LS_WARNING) << "All resources are locked!"; + return false; + } + for (EntryMap::iterator it = oldest++; it != map_.end(); ++it) { + if (it->second.last_modified < oldest->second.last_modified) { + oldest = it; + } + } + if (!DeleteResource(oldest->first)) { + LOG_F(LS_ERROR) << "Couldn't delete from cache!"; + return false; + } + } + return true; +} + +std::string DiskCache::IdToFilename(const std::string& id, size_t index) const { +#ifdef TRANSPARENT_CACHE_NAMES + // This escapes colons and other filesystem characters, so the user can't open + // special devices (like "COM1:"), or access other directories. + size_t buffer_size = id.length()*3 + 1; + char* buffer = new char[buffer_size]; + encode(buffer, buffer_size, id.data(), id.length(), + unsafe_filename_characters(), '%'); + // TODO: ASSERT(strlen(buffer) < FileSystem::MaxBasenameLength()); +#else // !TRANSPARENT_CACHE_NAMES + // We might want to just use a hash of the filename at some point, both for + // obfuscation, and to avoid both filename length and escaping issues. + ASSERT(false); +#endif // !TRANSPARENT_CACHE_NAMES + + char extension[32]; + sprintfn(extension, ARRAY_SIZE(extension), ".%u", index); + + Pathname pathname; + pathname.SetFolder(folder_); + pathname.SetBasename(buffer); + pathname.SetExtension(extension); + +#ifdef TRANSPARENT_CACHE_NAMES + delete [] buffer; +#endif // TRANSPARENT_CACHE_NAMES + + return pathname.pathname(); +} + +bool DiskCache::FilenameToId(const std::string& filename, std::string* id, + size_t* index) const { + Pathname pathname(filename); + unsigned tempdex; + if (1 != sscanf(pathname.extension().c_str(), ".%u", &tempdex)) + return false; + + *index = static_cast(tempdex); + + size_t buffer_size = pathname.basename().length() + 1; + char* buffer = new char[buffer_size]; + decode(buffer, buffer_size, pathname.basename().data(), + pathname.basename().length(), '%'); + id->assign(buffer); + delete [] buffer; + return true; +} + +DiskCache::Entry* DiskCache::GetOrCreateEntry(const std::string& id, + bool create) { + EntryMap::iterator it = map_.find(id); + if (it != map_.end()) + return &it->second; + if (!create) + return NULL; + Entry e; + e.lock_state = LS_UNLOCKED; + e.accessors = 0; + e.size = 0; + e.streams = 0; + e.last_modified = time(0); + it = map_.insert(EntryMap::value_type(id, e)).first; + return &it->second; +} + +void DiskCache::ReleaseResource(const std::string& id, size_t index) const { + const Entry* entry = GetEntry(id); + if (!entry) { + LOG_F(LS_WARNING) << "Missing cache entry"; + ASSERT(false); + return; + } + + entry->accessors -= 1; + total_accessors_ -= 1; + + if (LS_UNLOCKED != entry->lock_state) { + // This is safe, because locked resources only issue WriteResource, which + // is non-const. Think about a better way to handle it. + DiskCache* this2 = const_cast(this); + Entry* entry2 = this2->GetOrCreateEntry(id, false); + + size_t new_size = 0; + std::string filename(IdToFilename(id, index)); + FileStream::GetSize(filename, &new_size); + entry2->size += new_size; + this2->total_size_ += new_size; + + if ((LS_UNLOCKING == entry->lock_state) && (0 == entry->accessors)) { + entry2->last_modified = time(0); + entry2->lock_state = LS_UNLOCKED; + this2->CheckLimit(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/diskcache.h b/webrtc/base/diskcache.h new file mode 100644 index 000000000..4ac1be15d --- /dev/null +++ b/webrtc/base/diskcache.h @@ -0,0 +1,125 @@ +/* + * Copyright 2004 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_BASE_DISKCACHE_H__ +#define WEBRTC_BASE_DISKCACHE_H__ + +#include +#include + +#if defined(WEBRTC_WIN) +#undef UnlockResource +#endif // WEBRTC_WIN + +namespace rtc { + +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// DiskCache - An LRU cache of streams, stored on disk. +// +// Streams are identified by a unique resource id. Multiple streams can be +// associated with each resource id, distinguished by an index. When old +// resources are flushed from the cache, all streams associated with those +// resources are removed together. +// DiskCache is designed to persist across executions of the program. It is +// safe for use from an arbitrary number of users on a single thread, but not +// from multiple threads or other processes. +/////////////////////////////////////////////////////////////////////////////// + +class DiskCache { +public: + DiskCache(); + virtual ~DiskCache(); + + bool Initialize(const std::string& folder, size_t size); + bool Purge(); + + bool LockResource(const std::string& id); + StreamInterface* WriteResource(const std::string& id, size_t index); + bool UnlockResource(const std::string& id); + + StreamInterface* ReadResource(const std::string& id, size_t index) const; + + bool HasResource(const std::string& id) const; + bool HasResourceStream(const std::string& id, size_t index) const; + bool DeleteResource(const std::string& id); + + protected: + virtual bool InitializeEntries() = 0; + virtual bool PurgeFiles() = 0; + + virtual bool FileExists(const std::string& filename) const = 0; + virtual bool DeleteFile(const std::string& filename) const = 0; + + enum LockState { LS_UNLOCKED, LS_LOCKED, LS_UNLOCKING }; + struct Entry { + LockState lock_state; + mutable size_t accessors; + size_t size; + size_t streams; + time_t last_modified; + }; + typedef std::map EntryMap; + friend class DiskCacheAdapter; + + bool CheckLimit(); + + std::string IdToFilename(const std::string& id, size_t index) const; + bool FilenameToId(const std::string& filename, std::string* id, + size_t* index) const; + + const Entry* GetEntry(const std::string& id) const { + return const_cast(this)->GetOrCreateEntry(id, false); + } + Entry* GetOrCreateEntry(const std::string& id, bool create); + + void ReleaseResource(const std::string& id, size_t index) const; + + std::string folder_; + size_t max_cache_, total_size_; + EntryMap map_; + mutable size_t total_accessors_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// CacheLock - Automatically manage locking and unlocking, with optional +// rollback semantics +/////////////////////////////////////////////////////////////////////////////// + +class CacheLock { +public: + CacheLock(DiskCache* cache, const std::string& id, bool rollback = false) + : cache_(cache), id_(id), rollback_(rollback) + { + locked_ = cache_->LockResource(id_); + } + ~CacheLock() { + if (locked_) { + cache_->UnlockResource(id_); + if (rollback_) { + cache_->DeleteResource(id_); + } + } + } + bool IsLocked() const { return locked_; } + void Commit() { rollback_ = false; } + +private: + DiskCache* cache_; + std::string id_; + bool rollback_, locked_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_DISKCACHE_H__ diff --git a/webrtc/base/diskcache_win32.cc b/webrtc/base/diskcache_win32.cc new file mode 100644 index 000000000..22012ccc1 --- /dev/null +++ b/webrtc/base/diskcache_win32.cc @@ -0,0 +1,86 @@ +/* + * Copyright 2006 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 "webrtc/base/win32.h" +#include +#include +#include + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/diskcache.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +#include "webrtc/base/diskcache_win32.h" + +namespace rtc { + +bool DiskCacheWin32::InitializeEntries() { + // Note: We could store the cache information in a separate file, for faster + // initialization. Figuring it out empirically works, too. + + std::wstring path16 = ToUtf16(folder_); + path16.append(1, '*'); + + WIN32_FIND_DATA find_data; + HANDLE find_handle = FindFirstFile(path16.c_str(), &find_data); + if (find_handle != INVALID_HANDLE_VALUE) { + do { + size_t index; + std::string id; + if (!FilenameToId(ToUtf8(find_data.cFileName), &id, &index)) + continue; + + Entry* entry = GetOrCreateEntry(id, true); + entry->size += find_data.nFileSizeLow; + total_size_ += find_data.nFileSizeLow; + entry->streams = _max(entry->streams, index + 1); + FileTimeToUnixTime(find_data.ftLastWriteTime, &entry->last_modified); + + } while (FindNextFile(find_handle, &find_data)); + + FindClose(find_handle); + } + + return true; +} + +bool DiskCacheWin32::PurgeFiles() { + std::wstring path16 = ToUtf16(folder_); + path16.append(1, '*'); + path16.append(1, '\0'); + + SHFILEOPSTRUCT file_op = { 0 }; + file_op.wFunc = FO_DELETE; + file_op.pFrom = path16.c_str(); + file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT + | FOF_NORECURSION | FOF_FILESONLY; + if (0 != SHFileOperation(&file_op)) { + LOG_F(LS_ERROR) << "Couldn't delete cache files"; + return false; + } + + return true; +} + +bool DiskCacheWin32::FileExists(const std::string& filename) const { + DWORD result = ::GetFileAttributes(ToUtf16(filename).c_str()); + return (INVALID_FILE_ATTRIBUTES != result); +} + +bool DiskCacheWin32::DeleteFile(const std::string& filename) const { + return ::DeleteFile(ToUtf16(filename).c_str()) != 0; +} + +} diff --git a/webrtc/base/diskcache_win32.h b/webrtc/base/diskcache_win32.h new file mode 100644 index 000000000..42cb9b02c --- /dev/null +++ b/webrtc/base/diskcache_win32.h @@ -0,0 +1,29 @@ +/* + * Copyright 2006 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_BASE_DISKCACHEWIN32_H__ +#define WEBRTC_BASE_DISKCACHEWIN32_H__ + +#include "webrtc/base/diskcache.h" + +namespace rtc { + +class DiskCacheWin32 : public DiskCache { + protected: + virtual bool InitializeEntries(); + virtual bool PurgeFiles(); + + virtual bool FileExists(const std::string& filename) const; + virtual bool DeleteFile(const std::string& filename) const; +}; + +} + +#endif // WEBRTC_BASE_DISKCACHEWIN32_H__ diff --git a/webrtc/base/dscp.h b/webrtc/base/dscp.h new file mode 100644 index 000000000..970ff93b9 --- /dev/null +++ b/webrtc/base/dscp.h @@ -0,0 +1,45 @@ +/* + * Copyright 2013 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_BASE_DSCP_H_ +#define WEBRTC_BASE_DSCP_H_ + +namespace rtc { +// Differentiated Services Code Point. +// See http://tools.ietf.org/html/rfc2474 for details. +enum DiffServCodePoint { + DSCP_NO_CHANGE = -1, + DSCP_DEFAULT = 0, // Same as DSCP_CS0 + DSCP_CS0 = 0, // The default + DSCP_CS1 = 8, // Bulk/background traffic + DSCP_AF11 = 10, + DSCP_AF12 = 12, + DSCP_AF13 = 14, + DSCP_CS2 = 16, + DSCP_AF21 = 18, + DSCP_AF22 = 20, + DSCP_AF23 = 22, + DSCP_CS3 = 24, + DSCP_AF31 = 26, + DSCP_AF32 = 28, + DSCP_AF33 = 30, + DSCP_CS4 = 32, + DSCP_AF41 = 34, // Video + DSCP_AF42 = 36, // Video + DSCP_AF43 = 38, // Video + DSCP_CS5 = 40, // Video + DSCP_EF = 46, // Voice + DSCP_CS6 = 48, // Voice + DSCP_CS7 = 56, // Control messages +}; + +} // namespace rtc + + #endif // WEBRTC_BASE_DSCP_H_ diff --git a/webrtc/base/event.cc b/webrtc/base/event.cc new file mode 100644 index 000000000..393142ea2 --- /dev/null +++ b/webrtc/base/event.cc @@ -0,0 +1,135 @@ +/* + * Copyright 2004 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 "webrtc/base/event.h" + +#if defined(WEBRTC_WIN) +#include +#elif defined(WEBRTC_POSIX) +#include +#include +#include +#else +#error "Must define either WEBRTC_WIN or WEBRTC_POSIX." +#endif + +namespace rtc { + +#if defined(WEBRTC_WIN) + +Event::Event(bool manual_reset, bool initially_signaled) + : is_manual_reset_(manual_reset), + is_initially_signaled_(initially_signaled) { + event_handle_ = ::CreateEvent(NULL, // Security attributes. + is_manual_reset_, + is_initially_signaled_, + NULL); // Name. + ASSERT(event_handle_ != NULL); +} + +Event::~Event() { + CloseHandle(event_handle_); +} + +void Event::Set() { + SetEvent(event_handle_); +} + +void Event::Reset() { + ResetEvent(event_handle_); +} + +bool Event::Wait(int cms) { + DWORD ms = (cms == kForever)? INFINITE : cms; + return (WaitForSingleObject(event_handle_, ms) == WAIT_OBJECT_0); +} + +#elif defined(WEBRTC_POSIX) + +Event::Event(bool manual_reset, bool initially_signaled) + : is_manual_reset_(manual_reset), + event_status_(initially_signaled) { + VERIFY(pthread_mutex_init(&event_mutex_, NULL) == 0); + VERIFY(pthread_cond_init(&event_cond_, NULL) == 0); +} + +Event::~Event() { + pthread_mutex_destroy(&event_mutex_); + pthread_cond_destroy(&event_cond_); +} + +void Event::Set() { + pthread_mutex_lock(&event_mutex_); + event_status_ = true; + pthread_cond_broadcast(&event_cond_); + pthread_mutex_unlock(&event_mutex_); +} + +void Event::Reset() { + pthread_mutex_lock(&event_mutex_); + event_status_ = false; + pthread_mutex_unlock(&event_mutex_); +} + +bool Event::Wait(int cms) { + pthread_mutex_lock(&event_mutex_); + int error = 0; + + if (cms != kForever) { + // Converting from seconds and microseconds (1e-6) plus + // milliseconds (1e-3) to seconds and nanoseconds (1e-9). + + struct timespec ts; +#if HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE + // Use relative time version, which tends to be more efficient for + // pthread implementations where provided (like on Android). + ts.tv_sec = cms / 1000; + ts.tv_nsec = (cms % 1000) * 1000000; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + + ts.tv_sec = tv.tv_sec + (cms / 1000); + ts.tv_nsec = tv.tv_usec * 1000 + (cms % 1000) * 1000000; + + // Handle overflow. + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } +#endif + + while (!event_status_ && error == 0) { +#if HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE + error = pthread_cond_timedwait_relative_np( + &event_cond_, &event_mutex_, &ts); +#else + error = pthread_cond_timedwait(&event_cond_, &event_mutex_, &ts); +#endif + } + } else { + while (!event_status_ && error == 0) + error = pthread_cond_wait(&event_cond_, &event_mutex_); + } + + // NOTE(liulk): Exactly one thread will auto-reset this event. All + // the other threads will think it's unsignaled. This seems to be + // consistent with auto-reset events in WEBRTC_WIN + if (error == 0 && !is_manual_reset_) + event_status_ = false; + + pthread_mutex_unlock(&event_mutex_); + + return (error == 0); +} + +#endif + +} // namespace rtc diff --git a/webrtc/base/event.h b/webrtc/base/event.h new file mode 100644 index 000000000..f2691a2f8 --- /dev/null +++ b/webrtc/base/event.h @@ -0,0 +1,51 @@ +/* + * Copyright 2004 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_BASE_EVENT_H__ +#define WEBRTC_BASE_EVENT_H__ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" // NOLINT: consider this a system header. +#elif defined(WEBRTC_POSIX) +#include +#else +#error "Must define either WEBRTC_WIN or WEBRTC_POSIX." +#endif + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" + +namespace rtc { + +class Event { + public: + Event(bool manual_reset, bool initially_signaled); + ~Event(); + + void Set(); + void Reset(); + bool Wait(int cms); + + private: + bool is_manual_reset_; + +#if defined(WEBRTC_WIN) + bool is_initially_signaled_; + HANDLE event_handle_; +#elif defined(WEBRTC_POSIX) + bool event_status_; + pthread_mutex_t event_mutex_; + pthread_cond_t event_cond_; +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_EVENT_H__ diff --git a/webrtc/base/event_unittest.cc b/webrtc/base/event_unittest.cc new file mode 100644 index 000000000..996ad2f95 --- /dev/null +++ b/webrtc/base/event_unittest.cc @@ -0,0 +1,42 @@ +/* + * Copyright 2004 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 "webrtc/base/event.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +TEST(EventTest, InitiallySignaled) { + Event event(false, true); + ASSERT_TRUE(event.Wait(0)); +} + +TEST(EventTest, ManualReset) { + Event event(true, false); + ASSERT_FALSE(event.Wait(0)); + + event.Set(); + ASSERT_TRUE(event.Wait(0)); + ASSERT_TRUE(event.Wait(0)); + + event.Reset(); + ASSERT_FALSE(event.Wait(0)); +} + +TEST(EventTest, AutoReset) { + Event event(false, false); + ASSERT_FALSE(event.Wait(0)); + + event.Set(); + ASSERT_TRUE(event.Wait(0)); + ASSERT_FALSE(event.Wait(0)); +} + +} // namespace rtc diff --git a/webrtc/base/fakecpumonitor.h b/webrtc/base/fakecpumonitor.h new file mode 100644 index 000000000..c6ea0f293 --- /dev/null +++ b/webrtc/base/fakecpumonitor.h @@ -0,0 +1,32 @@ +/* + * Copyright 2013 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_BASE_FAKECPUMONITOR_H_ +#define WEBRTC_BASE_FAKECPUMONITOR_H_ + +#include "webrtc/base/cpumonitor.h" + +namespace rtc { + +class FakeCpuMonitor : public rtc::CpuMonitor { + public: + explicit FakeCpuMonitor(Thread* thread) + : CpuMonitor(thread) { + } + ~FakeCpuMonitor() { + } + + virtual void OnMessage(rtc::Message* msg) { + } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKECPUMONITOR_H_ diff --git a/webrtc/base/fakenetwork.h b/webrtc/base/fakenetwork.h new file mode 100644 index 000000000..60773b409 --- /dev/null +++ b/webrtc/base/fakenetwork.h @@ -0,0 +1,119 @@ +/* + * Copyright 2009 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_BASE_FAKENETWORK_H_ +#define WEBRTC_BASE_FAKENETWORK_H_ + +#include +#include + +#include "webrtc/base/network.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +const int kFakeIPv4NetworkPrefixLength = 24; +const int kFakeIPv6NetworkPrefixLength = 64; + +// Fake network manager that allows us to manually specify the IPs to use. +class FakeNetworkManager : public NetworkManagerBase, + public MessageHandler { + public: + FakeNetworkManager() + : thread_(Thread::Current()), + next_index_(0), + started_(false), + sent_first_update_(false) { + } + + typedef std::vector IfaceList; + + void AddInterface(const SocketAddress& iface) { + // ensure a unique name for the interface + SocketAddress address("test" + rtc::ToString(next_index_++), 0); + address.SetResolvedIP(iface.ipaddr()); + ifaces_.push_back(address); + DoUpdateNetworks(); + } + + void RemoveInterface(const SocketAddress& iface) { + for (IfaceList::iterator it = ifaces_.begin(); + it != ifaces_.end(); ++it) { + if (it->EqualIPs(iface)) { + ifaces_.erase(it); + break; + } + } + DoUpdateNetworks(); + } + + virtual void StartUpdating() { + if (started_) { + if (sent_first_update_) + SignalNetworksChanged(); + return; + } + + started_ = true; + sent_first_update_ = false; + thread_->Post(this); + } + + virtual void StopUpdating() { + started_ = false; + } + + // MessageHandler interface. + virtual void OnMessage(Message* msg) { + DoUpdateNetworks(); + } + + private: + void DoUpdateNetworks() { + if (!started_) + return; + std::vector networks; + for (IfaceList::iterator it = ifaces_.begin(); + it != ifaces_.end(); ++it) { + int prefix_length = 0; + if (it->ipaddr().family() == AF_INET) { + prefix_length = kFakeIPv4NetworkPrefixLength; + } else if (it->ipaddr().family() == AF_INET6) { + prefix_length = kFakeIPv6NetworkPrefixLength; + } + IPAddress prefix = TruncateIP(it->ipaddr(), prefix_length); + scoped_ptr net(new Network(it->hostname(), + it->hostname(), + prefix, + prefix_length)); + net->AddIP(it->ipaddr()); + networks.push_back(net.release()); + } + bool changed; + MergeNetworkList(networks, &changed); + if (changed || !sent_first_update_) { + SignalNetworksChanged(); + sent_first_update_ = true; + } + } + + Thread* thread_; + IfaceList ifaces_; + int next_index_; + bool started_; + bool sent_first_update_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKENETWORK_H_ diff --git a/webrtc/base/fakesslidentity.h b/webrtc/base/fakesslidentity.h new file mode 100644 index 000000000..717cb6c3b --- /dev/null +++ b/webrtc/base/fakesslidentity.h @@ -0,0 +1,94 @@ +/* + * Copyright 2012 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_BASE_FAKESSLIDENTITY_H_ +#define WEBRTC_BASE_FAKESSLIDENTITY_H_ + +#include +#include + +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +class FakeSSLCertificate : public rtc::SSLCertificate { + public: + // SHA-1 is the default digest algorithm because it is available in all build + // configurations used for unit testing. + explicit FakeSSLCertificate(const std::string& data) + : data_(data), digest_algorithm_(DIGEST_SHA_1) {} + explicit FakeSSLCertificate(const std::vector& certs) + : data_(certs.front()), digest_algorithm_(DIGEST_SHA_1) { + std::vector::const_iterator it; + // Skip certs[0]. + for (it = certs.begin() + 1; it != certs.end(); ++it) { + certs_.push_back(FakeSSLCertificate(*it)); + } + } + virtual FakeSSLCertificate* GetReference() const { + return new FakeSSLCertificate(*this); + } + virtual std::string ToPEMString() const { + return data_; + } + virtual void ToDER(Buffer* der_buffer) const { + std::string der_string; + VERIFY(SSLIdentity::PemToDer(kPemTypeCertificate, data_, &der_string)); + der_buffer->SetData(der_string.c_str(), der_string.size()); + } + void set_digest_algorithm(const std::string& algorithm) { + digest_algorithm_ = algorithm; + } + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const { + *algorithm = digest_algorithm_; + return true; + } + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + *length = rtc::ComputeDigest(algorithm, data_.c_str(), data_.size(), + digest, size); + return (*length != 0); + } + virtual bool GetChain(SSLCertChain** chain) const { + if (certs_.empty()) + return false; + std::vector new_certs(certs_.size()); + std::transform(certs_.begin(), certs_.end(), new_certs.begin(), DupCert); + *chain = new SSLCertChain(new_certs); + return true; + } + + private: + static FakeSSLCertificate* DupCert(FakeSSLCertificate cert) { + return cert.GetReference(); + } + std::string data_; + std::vector certs_; + std::string digest_algorithm_; +}; + +class FakeSSLIdentity : public rtc::SSLIdentity { + public: + explicit FakeSSLIdentity(const std::string& data) : cert_(data) {} + explicit FakeSSLIdentity(const FakeSSLCertificate& cert) : cert_(cert) {} + virtual FakeSSLIdentity* GetReference() const { + return new FakeSSLIdentity(*this); + } + virtual const FakeSSLCertificate& certificate() const { return cert_; } + private: + FakeSSLCertificate cert_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKESSLIDENTITY_H_ diff --git a/webrtc/base/faketaskrunner.h b/webrtc/base/faketaskrunner.h new file mode 100644 index 000000000..5408ab8b2 --- /dev/null +++ b/webrtc/base/faketaskrunner.h @@ -0,0 +1,38 @@ +/* + * Copyright 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. + */ + +// A fake TaskRunner for use in unit tests. + +#ifndef WEBRTC_BASE_FAKETASKRUNNER_H_ +#define WEBRTC_BASE_FAKETASKRUNNER_H_ + +#include "webrtc/base/taskparent.h" +#include "webrtc/base/taskrunner.h" + +namespace rtc { + +class FakeTaskRunner : public TaskRunner { + public: + FakeTaskRunner() : current_time_(0) {} + virtual ~FakeTaskRunner() {} + + virtual void WakeTasks() { RunTasks(); } + + virtual int64 CurrentTime() { + // Implement if needed. + return current_time_++; + } + + int64 current_time_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKETASKRUNNER_H_ diff --git a/webrtc/base/filelock.cc b/webrtc/base/filelock.cc new file mode 100644 index 000000000..fc921febc --- /dev/null +++ b/webrtc/base/filelock.cc @@ -0,0 +1,62 @@ +/* + * Copyright 2009 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 "webrtc/base/filelock.h" + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +FileLock::FileLock(const std::string& path, FileStream* file) + : path_(path), file_(file) { +} + +FileLock::~FileLock() { + MaybeUnlock(); +} + +void FileLock::Unlock() { + LOG_F(LS_INFO); + MaybeUnlock(); +} + +void FileLock::MaybeUnlock() { + if (file_) { + LOG(LS_INFO) << "Unlocking:" << path_; + file_->Close(); + Filesystem::DeleteFile(path_); + file_.reset(); + } +} + +FileLock* FileLock::TryLock(const std::string& path) { + FileStream* stream = new FileStream(); + bool ok = false; +#if defined(WEBRTC_WIN) + // Open and lock in a single operation. + ok = stream->OpenShare(path, "a", _SH_DENYRW, NULL); +#else // WEBRTC_LINUX && !WEBRTC_ANDROID and WEBRTC_MAC && !defined(WEBRTC_IOS) + ok = stream->Open(path, "a", NULL) && stream->TryLock(); +#endif + if (ok) { + return new FileLock(path, stream); + } else { + // Something failed, either we didn't succeed to open the + // file or we failed to lock it. Anyway remove the heap + // allocated object and then return NULL to indicate failure. + delete stream; + return NULL; + } +} + +} // namespace rtc diff --git a/webrtc/base/filelock.h b/webrtc/base/filelock.h new file mode 100644 index 000000000..46c58ea4a --- /dev/null +++ b/webrtc/base/filelock.h @@ -0,0 +1,53 @@ +/* + * Copyright 2009 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_BASE_FILELOCK_H_ +#define WEBRTC_BASE_FILELOCK_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class FileStream; + +// Implements a very simple cross process lock based on a file. +// When Lock(...) is called we try to open/create the file in read/write +// mode without any sharing. (Or locking it with flock(...) on Unix) +// If the process crash the OS will make sure that the file descriptor +// is released and another process can accuire the lock. +// This doesn't work on ancient OSX/Linux versions if used on NFS. +// (Nfs-client before: ~2.6 and Linux Kernel < 2.6.) +class FileLock { + public: + virtual ~FileLock(); + + // Attempts to lock the file. The caller owns the returned + // lock object. Returns NULL if the file already was locked. + static FileLock* TryLock(const std::string& path); + void Unlock(); + + protected: + FileLock(const std::string& path, FileStream* file); + + private: + void MaybeUnlock(); + + std::string path_; + scoped_ptr file_; + + DISALLOW_EVIL_CONSTRUCTORS(FileLock); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FILELOCK_H_ diff --git a/webrtc/base/filelock_unittest.cc b/webrtc/base/filelock_unittest.cc new file mode 100644 index 000000000..eecbf07da --- /dev/null +++ b/webrtc/base/filelock_unittest.cc @@ -0,0 +1,87 @@ +/* + * Copyright 2009 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 "webrtc/base/event.h" +#include "webrtc/base/filelock.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +const static std::string kLockFile = "TestLockFile"; +const static int kTimeoutMS = 5000; + +class FileLockTest : public testing::Test, public Runnable { + public: + FileLockTest() : done_(false, false), thread_lock_failed_(false) { + } + + virtual void Run(Thread* t) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + // The lock is already owned by the main thread of + // this test, therefore the TryLock(...) call should fail. + thread_lock_failed_ = lock.get() == NULL; + done_.Set(); + } + + protected: + virtual void SetUp() { + thread_lock_failed_ = false; + Filesystem::GetAppTempFolder(&temp_dir_); + temp_file_ = Pathname(temp_dir_.pathname(), kLockFile); + } + + void LockOnThread() { + locker_.Start(this); + done_.Wait(kTimeoutMS); + } + + Event done_; + Thread locker_; + bool thread_lock_failed_; + Pathname temp_dir_; + Pathname temp_file_; +}; + +TEST_F(FileLockTest, TestLockFileDeleted) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock.get() != NULL); + EXPECT_FALSE(Filesystem::IsAbsent(temp_file_.pathname())); + lock->Unlock(); + EXPECT_TRUE(Filesystem::IsAbsent(temp_file_.pathname())); +} + +TEST_F(FileLockTest, TestLock) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock.get() != NULL); +} + +TEST_F(FileLockTest, TestLockX2) { + scoped_ptr lock1(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock1.get() != NULL); + + scoped_ptr lock2(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock2.get() == NULL); +} + +TEST_F(FileLockTest, TestThreadedLock) { + scoped_ptr lock(FileLock::TryLock(temp_file_.pathname())); + EXPECT_TRUE(lock.get() != NULL); + + LockOnThread(); + EXPECT_TRUE(thread_lock_failed_); +} + +} // namespace rtc diff --git a/webrtc/base/fileutils.cc b/webrtc/base/fileutils.cc new file mode 100644 index 000000000..60bd0f8f4 --- /dev/null +++ b/webrtc/base/fileutils.cc @@ -0,0 +1,307 @@ +/* + * Copyright 2004 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 + +#if defined(WEBRTC_WIN) +// TODO(grunell): Remove io.h includes when Chromium has started +// to use AEC in each source. http://crbug.com/264611. +#include +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/pathutils.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/stream.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32filesystem.h" +#else +#include "webrtc/base/unixfilesystem.h" +#endif + +#if !defined(WEBRTC_WIN) +#define MAX_PATH 260 +#endif + +namespace rtc { + +////////////////////////// +// Directory Iterator // +////////////////////////// + +// A DirectoryIterator is created with a given directory. It originally points +// to the first file in the directory, and can be advanecd with Next(). This +// allows you to get information about each file. + + // Constructor +DirectoryIterator::DirectoryIterator() +#ifdef WEBRTC_WIN + : handle_(INVALID_HANDLE_VALUE) { +#else + : dir_(NULL), dirent_(NULL) { +#endif +} + + // Destructor +DirectoryIterator::~DirectoryIterator() { +#if defined(WEBRTC_WIN) + if (handle_ != INVALID_HANDLE_VALUE) + ::FindClose(handle_); +#else + if (dir_) + closedir(dir_); +#endif +} + + // Starts traversing a directory. + // dir is the directory to traverse + // returns true if the directory exists and is valid +bool DirectoryIterator::Iterate(const Pathname &dir) { + directory_ = dir.pathname(); +#if defined(WEBRTC_WIN) + if (handle_ != INVALID_HANDLE_VALUE) + ::FindClose(handle_); + std::string d = dir.pathname() + '*'; + handle_ = ::FindFirstFile(ToUtf16(d).c_str(), &data_); + if (handle_ == INVALID_HANDLE_VALUE) + return false; +#else + if (dir_ != NULL) + closedir(dir_); + dir_ = ::opendir(directory_.c_str()); + if (dir_ == NULL) + return false; + dirent_ = readdir(dir_); + if (dirent_ == NULL) + return false; + + if (::stat(std::string(directory_ + Name()).c_str(), &stat_) != 0) + return false; +#endif + return true; +} + + // Advances to the next file + // returns true if there were more files in the directory. +bool DirectoryIterator::Next() { +#if defined(WEBRTC_WIN) + return ::FindNextFile(handle_, &data_) == TRUE; +#else + dirent_ = ::readdir(dir_); + if (dirent_ == NULL) + return false; + + return ::stat(std::string(directory_ + Name()).c_str(), &stat_) == 0; +#endif +} + + // returns true if the file currently pointed to is a directory +bool DirectoryIterator::IsDirectory() const { +#if defined(WEBRTC_WIN) + return (data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FALSE; +#else + return S_ISDIR(stat_.st_mode); +#endif +} + + // returns the name of the file currently pointed to +std::string DirectoryIterator::Name() const { +#if defined(WEBRTC_WIN) + return ToUtf8(data_.cFileName); +#else + assert(dirent_ != NULL); + return dirent_->d_name; +#endif +} + + // returns the size of the file currently pointed to +size_t DirectoryIterator::FileSize() const { +#if !defined(WEBRTC_WIN) + return stat_.st_size; +#else + return data_.nFileSizeLow; +#endif +} + + // returns the last modified time of this file +time_t DirectoryIterator::FileModifyTime() const { +#if defined(WEBRTC_WIN) + time_t val; + FileTimeToUnixTime(data_.ftLastWriteTime, &val); + return val; +#else + return stat_.st_mtime; +#endif +} + +FilesystemInterface* Filesystem::default_filesystem_ = NULL; + +FilesystemInterface *Filesystem::EnsureDefaultFilesystem() { + if (!default_filesystem_) { +#if defined(WEBRTC_WIN) + default_filesystem_ = new Win32Filesystem(); +#else + default_filesystem_ = new UnixFilesystem(); +#endif + } + return default_filesystem_; +} + +bool FilesystemInterface::CopyFolder(const Pathname &old_path, + const Pathname &new_path) { + bool success = true; + VERIFY(IsFolder(old_path)); + Pathname new_dir; + new_dir.SetFolder(new_path.pathname()); + Pathname old_dir; + old_dir.SetFolder(old_path.pathname()); + if (!CreateFolder(new_dir)) + return false; + DirectoryIterator *di = IterateDirectory(); + if (!di) + return false; + if (di->Iterate(old_dir.pathname())) { + do { + if (di->Name() == "." || di->Name() == "..") + continue; + Pathname source; + Pathname dest; + source.SetFolder(old_dir.pathname()); + dest.SetFolder(new_path.pathname()); + source.SetFilename(di->Name()); + dest.SetFilename(di->Name()); + if (!CopyFileOrFolder(source, dest)) + success = false; + } while (di->Next()); + } + delete di; + return success; +} + +bool FilesystemInterface::DeleteFolderContents(const Pathname &folder) { + bool success = true; + VERIFY(IsFolder(folder)); + DirectoryIterator *di = IterateDirectory(); + if (!di) + return false; + if (di->Iterate(folder)) { + do { + if (di->Name() == "." || di->Name() == "..") + continue; + Pathname subdir; + subdir.SetFolder(folder.pathname()); + if (di->IsDirectory()) { + subdir.AppendFolder(di->Name()); + if (!DeleteFolderAndContents(subdir)) { + success = false; + } + } else { + subdir.SetFilename(di->Name()); + if (!DeleteFile(subdir)) { + success = false; + } + } + } while (di->Next()); + } + delete di; + return success; +} + +bool FilesystemInterface::CleanAppTempFolder() { + Pathname path; + if (!GetAppTempFolder(&path)) + return false; + if (IsAbsent(path)) + return true; + if (!IsTemporaryPath(path)) { + ASSERT(false); + return false; + } + return DeleteFolderContents(path); +} + +Pathname Filesystem::GetCurrentDirectory() { + return EnsureDefaultFilesystem()->GetCurrentDirectory(); +} + +bool CreateUniqueFile(Pathname& path, bool create_empty) { + LOG(LS_INFO) << "Path " << path.pathname() << std::endl; + // If no folder is supplied, use the temporary folder + if (path.folder().empty()) { + Pathname temporary_path; + if (!Filesystem::GetTemporaryFolder(temporary_path, true, NULL)) { + printf("Get temp failed\n"); + return false; + } + path.SetFolder(temporary_path.pathname()); + } + + // If no filename is supplied, use a temporary name + if (path.filename().empty()) { + std::string folder(path.folder()); + std::string filename = Filesystem::TempFilename(folder, "gt"); + path.SetPathname(filename); + if (!create_empty) { + Filesystem::DeleteFile(path.pathname()); + } + return true; + } + + // Otherwise, create a unique name based on the given filename + // foo.txt -> foo-N.txt + const std::string basename = path.basename(); + const size_t MAX_VERSION = 100; + size_t version = 0; + while (version < MAX_VERSION) { + std::string pathname = path.pathname(); + + if (!Filesystem::IsFile(pathname)) { + if (create_empty) { + FileStream* fs = Filesystem::OpenFile(pathname, "w"); + delete fs; + } + return true; + } + version += 1; + char version_base[MAX_PATH]; + sprintfn(version_base, ARRAY_SIZE(version_base), "%s-%u", + basename.c_str(), version); + path.SetBasename(version_base); + } + return true; +} + +// Taken from Chromium's base/platform_file_*.cc. +// TODO(grunell): Remove when Chromium has started to use AEC in each source. +// http://crbug.com/264611. +FILE* FdopenPlatformFileForWriting(PlatformFile file) { +#if defined(WEBRTC_WIN) + if (file == kInvalidPlatformFileValue) + return NULL; + int fd = _open_osfhandle(reinterpret_cast(file), 0); + if (fd < 0) + return NULL; + return _fdopen(fd, "w"); +#else + return fdopen(file, "w"); +#endif +} + +bool ClosePlatformFile(PlatformFile file) { +#if defined(WEBRTC_WIN) + return CloseHandle(file) != 0; +#else + return close(file); +#endif +} + +} // namespace rtc diff --git a/webrtc/base/fileutils.h b/webrtc/base/fileutils.h new file mode 100644 index 000000000..c0a3f88c6 --- /dev/null +++ b/webrtc/base/fileutils.h @@ -0,0 +1,459 @@ +/* + * Copyright 2004 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_BASE_FILEUTILS_H_ +#define WEBRTC_BASE_FILEUTILS_H_ + +#include + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#else +#include +#include +#include +#include +#include +#endif + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class FileStream; +class Pathname; + +////////////////////////// +// Directory Iterator // +////////////////////////// + +// A DirectoryIterator is created with a given directory. It originally points +// to the first file in the directory, and can be advanecd with Next(). This +// allows you to get information about each file. + +class DirectoryIterator { + friend class Filesystem; + public: + // Constructor + DirectoryIterator(); + // Destructor + virtual ~DirectoryIterator(); + + // Starts traversing a directory + // dir is the directory to traverse + // returns true if the directory exists and is valid + // The iterator will point to the first entry in the directory + virtual bool Iterate(const Pathname &path); + + // Advances to the next file + // returns true if there were more files in the directory. + virtual bool Next(); + + // returns true if the file currently pointed to is a directory + virtual bool IsDirectory() const; + + // returns the name of the file currently pointed to + virtual std::string Name() const; + + // returns the size of the file currently pointed to + virtual size_t FileSize() const; + + // returns the last modified time of the file currently pointed to + virtual time_t FileModifyTime() const; + + // checks whether current file is a special directory file "." or ".." + bool IsDots() const { + std::string filename(Name()); + return (filename.compare(".") == 0) || (filename.compare("..") == 0); + } + + private: + std::string directory_; +#if defined(WEBRTC_WIN) + WIN32_FIND_DATA data_; + HANDLE handle_; +#else + DIR *dir_; + struct dirent *dirent_; + struct stat stat_; +#endif +}; + +enum FileTimeType { FTT_CREATED, FTT_MODIFIED, FTT_ACCESSED }; + +class FilesystemInterface { + public: + virtual ~FilesystemInterface() {} + + // Returns a DirectoryIterator for a given pathname. + // TODO: Do fancy abstracted stuff + virtual DirectoryIterator *IterateDirectory() { + return new DirectoryIterator(); + } + + // Opens a file. Returns an open StreamInterface if function succeeds. + // Otherwise, returns NULL. + // TODO: Add an error param to indicate failure reason, similar to + // FileStream::Open + virtual FileStream *OpenFile(const Pathname &filename, + const std::string &mode) = 0; + + // Atomically creates an empty file accessible only to the current user if one + // does not already exist at the given path, otherwise fails. This is the only + // secure way to create a file in a shared temp directory (e.g., C:\Temp on + // Windows or /tmp on Linux). + // Note that if it is essential that a file be successfully created then the + // app must generate random names and retry on failure, or else it will be + // vulnerable to a trivial DoS. + virtual bool CreatePrivateFile(const Pathname &filename) = 0; + + // This will attempt to delete the path located at filename. + // It ASSERTS and returns false if the path points to a folder or a + // non-existent file. + virtual bool DeleteFile(const Pathname &filename) = 0; + + // This will attempt to delete the empty folder located at 'folder' + // It ASSERTS and returns false if the path points to a file or a non-existent + // folder. It fails normally if the folder is not empty or can otherwise + // not be deleted. + virtual bool DeleteEmptyFolder(const Pathname &folder) = 0; + + // This will call IterateDirectory, to get a directory iterator, and then + // call DeleteFolderAndContents and DeleteFile on every path contained in this + // folder. If the folder is empty, this returns true. + virtual bool DeleteFolderContents(const Pathname &folder); + + // This deletes the contents of a folder, recursively, and then deletes + // the folder itself. + virtual bool DeleteFolderAndContents(const Pathname &folder) { + return DeleteFolderContents(folder) && DeleteEmptyFolder(folder); + } + + // This will delete whatever is located at path, be it a file or a folder. + // If it is a folder, it will delete it recursively by calling + // DeleteFolderAndContents + bool DeleteFileOrFolder(const Pathname &path) { + if (IsFolder(path)) + return DeleteFolderAndContents(path); + else + return DeleteFile(path); + } + + // Creates a directory. This will call itself recursively to create /foo/bar + // even if /foo does not exist. Returns true if the function succeeds. + virtual bool CreateFolder(const Pathname &pathname) = 0; + + // This moves a file from old_path to new_path, where "old_path" is a + // plain file. This ASSERTs and returns false if old_path points to a + // directory, and returns true if the function succeeds. + // If the new path is on a different volume than the old path, this function + // will attempt to copy and, if that succeeds, delete the old path. + virtual bool MoveFolder(const Pathname &old_path, + const Pathname &new_path) = 0; + + // This moves a directory from old_path to new_path, where "old_path" is a + // directory. This ASSERTs and returns false if old_path points to a plain + // file, and returns true if the function succeeds. + // If the new path is on a different volume, this function will attempt to + // copy and if that succeeds, delete the old path. + virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path) = 0; + + // This attempts to move whatever is located at old_path to new_path, + // be it a file or folder. + bool MoveFileOrFolder(const Pathname &old_path, const Pathname &new_path) { + if (IsFile(old_path)) { + return MoveFile(old_path, new_path); + } else { + return MoveFolder(old_path, new_path); + } + } + + // This copies a file from old_path to new_path. This method ASSERTs and + // returns false if old_path is a folder, and returns true if the copy + // succeeds. + virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path) = 0; + + // This copies a folder from old_path to new_path. + bool CopyFolder(const Pathname &old_path, const Pathname &new_path); + + bool CopyFileOrFolder(const Pathname &old_path, const Pathname &new_path) { + if (IsFile(old_path)) + return CopyFile(old_path, new_path); + else + return CopyFolder(old_path, new_path); + } + + // Returns true if pathname refers to a directory + virtual bool IsFolder(const Pathname& pathname) = 0; + + // Returns true if pathname refers to a file + virtual bool IsFile(const Pathname& pathname) = 0; + + // Returns true if pathname refers to no filesystem object, every parent + // directory either exists, or is also absent. + virtual bool IsAbsent(const Pathname& pathname) = 0; + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPath(const Pathname& pathname) = 0; + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exits) + virtual bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append) = 0; + + virtual std::string TempFilename(const Pathname &dir, + const std::string &prefix) = 0; + + // Determines the size of the file indicated by path. + virtual bool GetFileSize(const Pathname& path, size_t* size) = 0; + + // Determines a timestamp associated with the file indicated by path. + virtual bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) = 0; + + // Returns the path to the running application. + // Note: This is not guaranteed to work on all platforms. Be aware of the + // limitations before using it, and robustly handle failure. + virtual bool GetAppPathname(Pathname* path) = 0; + + // Get a folder that is unique to the current application, which is suitable + // for sharing data between executions of the app. If the per_user arg is + // true, the folder is also specific to the current user. + virtual bool GetAppDataFolder(Pathname* path, bool per_user) = 0; + + // Get a temporary folder that is unique to the current user and application. + // TODO: Re-evaluate the goals of this function. We probably just need any + // directory that won't collide with another existing directory, and which + // will be cleaned up when the program exits. + virtual bool GetAppTempFolder(Pathname* path) = 0; + + // Delete the contents of the folder returned by GetAppTempFolder + bool CleanAppTempFolder(); + + virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) = 0; + + // Returns the absolute path of the current directory. + virtual Pathname GetCurrentDirectory() = 0; + + // Note: These might go into some shared config section later, but they're + // used by some methods in this interface, so we're leaving them here for now. + void SetOrganizationName(const std::string& organization) { + organization_name_ = organization; + } + void GetOrganizationName(std::string* organization) { + ASSERT(NULL != organization); + *organization = organization_name_; + } + void SetApplicationName(const std::string& application) { + application_name_ = application; + } + void GetApplicationName(std::string* application) { + ASSERT(NULL != application); + *application = application_name_; + } + + protected: + std::string organization_name_; + std::string application_name_; +}; + +class Filesystem { + public: + static FilesystemInterface *default_filesystem() { + ASSERT(default_filesystem_ != NULL); + return default_filesystem_; + } + + static void set_default_filesystem(FilesystemInterface *filesystem) { + default_filesystem_ = filesystem; + } + + static FilesystemInterface *swap_default_filesystem( + FilesystemInterface *filesystem) { + FilesystemInterface *cur = default_filesystem_; + default_filesystem_ = filesystem; + return cur; + } + + static DirectoryIterator *IterateDirectory() { + return EnsureDefaultFilesystem()->IterateDirectory(); + } + + static bool CreateFolder(const Pathname &pathname) { + return EnsureDefaultFilesystem()->CreateFolder(pathname); + } + + static FileStream *OpenFile(const Pathname &filename, + const std::string &mode) { + return EnsureDefaultFilesystem()->OpenFile(filename, mode); + } + + static bool CreatePrivateFile(const Pathname &filename) { + return EnsureDefaultFilesystem()->CreatePrivateFile(filename); + } + + static bool DeleteFile(const Pathname &filename) { + return EnsureDefaultFilesystem()->DeleteFile(filename); + } + + static bool DeleteEmptyFolder(const Pathname &folder) { + return EnsureDefaultFilesystem()->DeleteEmptyFolder(folder); + } + + static bool DeleteFolderContents(const Pathname &folder) { + return EnsureDefaultFilesystem()->DeleteFolderContents(folder); + } + + static bool DeleteFolderAndContents(const Pathname &folder) { + return EnsureDefaultFilesystem()->DeleteFolderAndContents(folder); + } + + static bool MoveFolder(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->MoveFolder(old_path, new_path); + } + + static bool MoveFile(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->MoveFile(old_path, new_path); + } + + static bool CopyFolder(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->CopyFolder(old_path, new_path); + } + + static bool CopyFile(const Pathname &old_path, const Pathname &new_path) { + return EnsureDefaultFilesystem()->CopyFile(old_path, new_path); + } + + static bool IsFolder(const Pathname& pathname) { + return EnsureDefaultFilesystem()->IsFolder(pathname); + } + + static bool IsFile(const Pathname &pathname) { + return EnsureDefaultFilesystem()->IsFile(pathname); + } + + static bool IsAbsent(const Pathname &pathname) { + return EnsureDefaultFilesystem()->IsAbsent(pathname); + } + + static bool IsTemporaryPath(const Pathname& pathname) { + return EnsureDefaultFilesystem()->IsTemporaryPath(pathname); + } + + static bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append) { + return EnsureDefaultFilesystem()->GetTemporaryFolder(path, create, append); + } + + static std::string TempFilename(const Pathname &dir, + const std::string &prefix) { + return EnsureDefaultFilesystem()->TempFilename(dir, prefix); + } + + static bool GetFileSize(const Pathname& path, size_t* size) { + return EnsureDefaultFilesystem()->GetFileSize(path, size); + } + + static bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) { + return EnsureDefaultFilesystem()->GetFileTime(path, which, time); + } + + static bool GetAppPathname(Pathname* path) { + return EnsureDefaultFilesystem()->GetAppPathname(path); + } + + static bool GetAppDataFolder(Pathname* path, bool per_user) { + return EnsureDefaultFilesystem()->GetAppDataFolder(path, per_user); + } + + static bool GetAppTempFolder(Pathname* path) { + return EnsureDefaultFilesystem()->GetAppTempFolder(path); + } + + static bool CleanAppTempFolder() { + return EnsureDefaultFilesystem()->CleanAppTempFolder(); + } + + static bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { + return EnsureDefaultFilesystem()->GetDiskFreeSpace(path, freebytes); + } + + // Definition has to be in the .cc file due to returning forward-declared + // Pathname by value. + static Pathname GetCurrentDirectory(); + + static void SetOrganizationName(const std::string& organization) { + EnsureDefaultFilesystem()->SetOrganizationName(organization); + } + + static void GetOrganizationName(std::string* organization) { + EnsureDefaultFilesystem()->GetOrganizationName(organization); + } + + static void SetApplicationName(const std::string& application) { + EnsureDefaultFilesystem()->SetApplicationName(application); + } + + static void GetApplicationName(std::string* application) { + EnsureDefaultFilesystem()->GetApplicationName(application); + } + + private: + static FilesystemInterface* default_filesystem_; + + static FilesystemInterface *EnsureDefaultFilesystem(); + DISALLOW_IMPLICIT_CONSTRUCTORS(Filesystem); +}; + +class FilesystemScope{ + public: + explicit FilesystemScope(FilesystemInterface *new_fs) { + old_fs_ = Filesystem::swap_default_filesystem(new_fs); + } + ~FilesystemScope() { + Filesystem::set_default_filesystem(old_fs_); + } + private: + FilesystemInterface* old_fs_; + DISALLOW_IMPLICIT_CONSTRUCTORS(FilesystemScope); +}; + +// Generates a unique filename based on the input path. If no path component +// is specified, it uses the temporary directory. If a filename is provided, +// up to 100 variations of form basename-N.extension are tried. When +// create_empty is true, an empty file of this name is created (which +// decreases the chance of a temporary filename collision with another +// process). +bool CreateUniqueFile(Pathname& path, bool create_empty); + +// Taken from Chromium's base/platform_file.h. +// Don't use ClosePlatformFile to close a file opened with FdopenPlatformFile. +// Use fclose instead. +// TODO(grunell): Remove when Chromium has started to use AEC in each source. +// http://crbug.com/264611. +#if defined(WEBRTC_WIN) +typedef HANDLE PlatformFile; +const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE; +#elif defined(WEBRTC_POSIX) +typedef int PlatformFile; +const PlatformFile kInvalidPlatformFileValue = -1; +#else +#error Unsupported platform +#endif + +FILE* FdopenPlatformFileForWriting(PlatformFile file); +bool ClosePlatformFile(PlatformFile file); + +} // namespace rtc + +#endif // WEBRTC_BASE_FILEUTILS_H_ diff --git a/webrtc/base/fileutils_mock.h b/webrtc/base/fileutils_mock.h new file mode 100644 index 000000000..e9d20a75f --- /dev/null +++ b/webrtc/base/fileutils_mock.h @@ -0,0 +1,253 @@ +/* + * Copyright 2004 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_BASE_FILEUTILS_MOCK_H_ +#define WEBRTC_BASE_FILEUTILS_MOCK_H_ + +#include +#include +#include + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +class FakeFileStream : public FileStream { + public: + explicit FakeFileStream(const std::string & contents) : + string_stream_(contents) + {} + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + return string_stream_.Read(buffer, buffer_len, read, error); + } + + virtual void Close() { + return string_stream_.Close(); + } + virtual bool GetSize(size_t* size) const { + return string_stream_.GetSize(size); + } + + private: + StringStream string_stream_; +}; + +class FakeDirectoryIterator : public DirectoryIterator { + public: + typedef std::pair File; + + /* + * files should be sorted by directory + * put '/' at the end of file if you want it to be a directory + * + * Sample list: + * /var/dir/file1 + * /var/dir/file2 + * /var/dir/subdir1/ + * /var/dir/subdir2/ + * /var/dir2/file2 + * /var/dir3/ + * + * you can call Iterate for any path: /var, /var/dir, /var/dir2 + * unrelated files will be ignored + */ + explicit FakeDirectoryIterator(const std::vector& all_files) : + all_files_(all_files) {} + + virtual bool Iterate(const Pathname& path) { + path_iterator_ = all_files_.begin(); + path_ = path.pathname(); + + // make sure path ends end with '/' + if (path_.rfind(Pathname::DefaultFolderDelimiter()) != path_.size() - 1) + path_ += Pathname::DefaultFolderDelimiter(); + + return FakeDirectoryIterator::Search(std::string("")); + } + + virtual bool Next() { + std::string current_name = Name(); + path_iterator_++; + return FakeDirectoryIterator::Search(current_name); + } + + bool Search(const std::string& current_name) { + for (; path_iterator_ != all_files_.end(); path_iterator_++) { + if (path_iterator_->first.find(path_) == 0 + && Name().compare(current_name) != 0) { + return true; + } + } + + return false; + } + + virtual bool IsDirectory() const { + std::string sub_path = path_iterator_->first; + + return std::string::npos != + sub_path.find(Pathname::DefaultFolderDelimiter(), path_.size()); + } + + virtual std::string Name() const { + std::string sub_path = path_iterator_->first; + + // path - top level path (ex. /var/lib) + // sub_path - subpath under top level path (ex. /var/lib/dir/dir/file ) + // find shortest non-trivial common path. (ex. /var/lib/dir) + size_t start = path_.size(); + size_t end = sub_path.find(Pathname::DefaultFolderDelimiter(), start); + + if (end != std::string::npos) { + return sub_path.substr(start, end - start); + } else { + return sub_path.substr(start); + } + } + + private: + const std::vector all_files_; + + std::string path_; + std::vector::const_iterator path_iterator_; +}; + +class FakeFileSystem : public FilesystemInterface { + public: + typedef std::pair File; + + explicit FakeFileSystem(const std::vector& all_files) : + all_files_(all_files) {} + + virtual DirectoryIterator *IterateDirectory() { + return new FakeDirectoryIterator(all_files_); + } + + virtual FileStream * OpenFile( + const Pathname &filename, + const std::string &mode) { + std::vector::const_iterator i_files = all_files_.begin(); + std::string path = filename.pathname(); + + for (; i_files != all_files_.end(); i_files++) { + if (i_files->first.compare(path) == 0) { + return new FakeFileStream(i_files->second); + } + } + + return NULL; + } + + bool CreatePrivateFile(const Pathname &filename) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteFile(const Pathname &filename) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteEmptyFolder(const Pathname &folder) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteFolderContents(const Pathname &folder) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool DeleteFolderAndContents(const Pathname &folder) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool CreateFolder(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool MoveFolder(const Pathname &old_path, const Pathname &new_path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool MoveFile(const Pathname &old_path, const Pathname &new_path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool CopyFile(const Pathname &old_path, const Pathname &new_path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsFolder(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsFile(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsAbsent(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool IsTemporaryPath(const Pathname &pathname) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + std::string TempFilename(const Pathname &dir, const std::string &prefix) { + EXPECT_TRUE(false) << "Unsupported operation"; + return std::string(); + } + bool GetFileSize(const Pathname &path, size_t *size) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetFileTime(const Pathname &path, FileTimeType which, + time_t* time) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetAppPathname(Pathname *path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetAppDataFolder(Pathname *path, bool per_user) { + EXPECT_TRUE(per_user) << "Unsupported operation"; +#if defined(WEBRTC_WIN) + path->SetPathname("c:\\Users\\test_user", ""); +#else + path->SetPathname("/home/user/test_user", ""); +#endif + return true; + } + bool GetAppTempFolder(Pathname *path) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + bool GetDiskFreeSpace(const Pathname &path, int64 *freebytes) { + EXPECT_TRUE(false) << "Unsupported operation"; + return false; + } + Pathname GetCurrentDirectory() { + return Pathname(); + } + + private: + const std::vector all_files_; +}; +} // namespace rtc + +#endif // WEBRTC_BASE_FILEUTILS_MOCK_H_ diff --git a/webrtc/base/fileutils_unittest.cc b/webrtc/base/fileutils_unittest.cc new file mode 100644 index 000000000..9076bc787 --- /dev/null +++ b/webrtc/base/fileutils_unittest.cc @@ -0,0 +1,131 @@ +/* + * Copyright 2004 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 "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +// Make sure we can get a temp folder for the later tests. +TEST(FilesystemTest, GetTemporaryFolder) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); +} + +// Test creating a temp file, reading it back in, and deleting it. +TEST(FilesystemTest, TestOpenFile) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetPathname(Filesystem::TempFilename(path, "ut")); + + FileStream* fs; + char buf[256]; + size_t bytes; + + fs = Filesystem::OpenFile(path, "wb"); + ASSERT_TRUE(fs != NULL); + EXPECT_EQ(SR_SUCCESS, fs->Write("test", 4, &bytes, NULL)); + EXPECT_EQ(4U, bytes); + delete fs; + + EXPECT_TRUE(Filesystem::IsFile(path)); + + fs = Filesystem::OpenFile(path, "rb"); + ASSERT_TRUE(fs != NULL); + EXPECT_EQ(SR_SUCCESS, fs->Read(buf, sizeof(buf), &bytes, NULL)); + EXPECT_EQ(4U, bytes); + delete fs; + + EXPECT_TRUE(Filesystem::DeleteFile(path)); + EXPECT_FALSE(Filesystem::IsFile(path)); +} + +// Test opening a non-existent file. +TEST(FilesystemTest, TestOpenBadFile) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetFilename("not an actual file"); + + EXPECT_FALSE(Filesystem::IsFile(path)); + + FileStream* fs = Filesystem::OpenFile(path, "rb"); + EXPECT_FALSE(fs != NULL); +} + +// Test that CreatePrivateFile fails for existing files and succeeds for +// non-existent ones. +TEST(FilesystemTest, TestCreatePrivateFile) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetFilename("private_file_test"); + + // First call should succeed because the file doesn't exist yet. + EXPECT_TRUE(Filesystem::CreatePrivateFile(path)); + // Next call should fail, because now it exists. + EXPECT_FALSE(Filesystem::CreatePrivateFile(path)); + + // Verify that we have permission to open the file for reading and writing. + scoped_ptr fs(Filesystem::OpenFile(path, "wb")); + EXPECT_TRUE(fs.get() != NULL); + // Have to close the file on Windows before it will let us delete it. + fs.reset(); + + // Verify that we have permission to delete the file. + EXPECT_TRUE(Filesystem::DeleteFile(path)); +} + +// Test checking for free disk space. +TEST(FilesystemTest, TestGetDiskFreeSpace) { + // Note that we should avoid picking any file/folder which could be located + // at the remotely mounted drive/device. + Pathname path; + ASSERT_TRUE(Filesystem::GetAppDataFolder(&path, true)); + + int64 free1 = 0; + EXPECT_TRUE(Filesystem::IsFolder(path)); + EXPECT_FALSE(Filesystem::IsFile(path)); + EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free1)); + EXPECT_GT(free1, 0); + + int64 free2 = 0; + path.AppendFolder("this_folder_doesnt_exist"); + EXPECT_FALSE(Filesystem::IsFolder(path)); + EXPECT_TRUE(Filesystem::IsAbsent(path)); + EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free2)); + // These should be the same disk, and disk free space should not have changed + // by more than 1% between the two calls. + EXPECT_LT(static_cast(free1 * .9), free2); + EXPECT_LT(free2, static_cast(free1 * 1.1)); + + int64 free3 = 0; + path.clear(); + EXPECT_TRUE(path.empty()); + EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free3)); + // Current working directory may not be where exe is. + // EXPECT_LT(static_cast(free1 * .9), free3); + // EXPECT_LT(free3, static_cast(free1 * 1.1)); + EXPECT_GT(free3, 0); +} + +// Tests that GetCurrentDirectory() returns something. +TEST(FilesystemTest, TestGetCurrentDirectory) { + EXPECT_FALSE(Filesystem::GetCurrentDirectory().empty()); +} + +// Tests that GetAppPathname returns something. +TEST(FilesystemTest, TestGetAppPathname) { + Pathname path; + EXPECT_TRUE(Filesystem::GetAppPathname(&path)); + EXPECT_FALSE(path.empty()); +} + +} // namespace rtc diff --git a/webrtc/base/firewallsocketserver.cc b/webrtc/base/firewallsocketserver.cc new file mode 100644 index 000000000..31c18d981 --- /dev/null +++ b/webrtc/base/firewallsocketserver.cc @@ -0,0 +1,239 @@ +/* + * Copyright 2004 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 "webrtc/base/firewallsocketserver.h" + +#include + +#include + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +class FirewallSocket : public AsyncSocketAdapter { + public: + FirewallSocket(FirewallSocketServer* server, AsyncSocket* socket, int type) + : AsyncSocketAdapter(socket), server_(server), type_(type) { + } + + virtual int Connect(const SocketAddress& addr) { + if (type_ == SOCK_STREAM) { + if (!server_->Check(FP_TCP, GetLocalAddress(), addr)) { + LOG(LS_VERBOSE) << "FirewallSocket outbound TCP connection from " + << GetLocalAddress().ToSensitiveString() << " to " + << addr.ToSensitiveString() << " denied"; + // TODO: Handle this asynchronously. + SetError(EHOSTUNREACH); + return SOCKET_ERROR; + } + } + return AsyncSocketAdapter::Connect(addr); + } + virtual int Send(const void* pv, size_t cb) { + return SendTo(pv, cb, GetRemoteAddress()); + } + virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) { + if (type_ == SOCK_DGRAM) { + if (!server_->Check(FP_UDP, GetLocalAddress(), addr)) { + LOG(LS_VERBOSE) << "FirewallSocket outbound UDP packet from " + << GetLocalAddress().ToSensitiveString() << " to " + << addr.ToSensitiveString() << " dropped"; + return static_cast(cb); + } + } + return AsyncSocketAdapter::SendTo(pv, cb, addr); + } + virtual int Recv(void* pv, size_t cb) { + SocketAddress addr; + return RecvFrom(pv, cb, &addr); + } + virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) { + if (type_ == SOCK_DGRAM) { + while (true) { + int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + if (res <= 0) + return res; + if (server_->Check(FP_UDP, *paddr, GetLocalAddress())) + return res; + LOG(LS_VERBOSE) << "FirewallSocket inbound UDP packet from " + << paddr->ToSensitiveString() << " to " + << GetLocalAddress().ToSensitiveString() << " dropped"; + } + } + return AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + } + + virtual int Listen(int backlog) { + if (!server_->tcp_listen_enabled()) { + LOG(LS_VERBOSE) << "FirewallSocket listen attempt denied"; + return -1; + } + + return AsyncSocketAdapter::Listen(backlog); + } + virtual AsyncSocket* Accept(SocketAddress* paddr) { + SocketAddress addr; + while (AsyncSocket* sock = AsyncSocketAdapter::Accept(&addr)) { + if (server_->Check(FP_TCP, addr, GetLocalAddress())) { + if (paddr) + *paddr = addr; + return sock; + } + sock->Close(); + delete sock; + LOG(LS_VERBOSE) << "FirewallSocket inbound TCP connection from " + << addr.ToSensitiveString() << " to " + << GetLocalAddress().ToSensitiveString() << " denied"; + } + return 0; + } + + private: + FirewallSocketServer* server_; + int type_; +}; + +FirewallSocketServer::FirewallSocketServer(SocketServer* server, + FirewallManager* manager, + bool should_delete_server) + : server_(server), manager_(manager), + should_delete_server_(should_delete_server), + udp_sockets_enabled_(true), tcp_sockets_enabled_(true), + tcp_listen_enabled_(true) { + if (manager_) + manager_->AddServer(this); +} + +FirewallSocketServer::~FirewallSocketServer() { + if (manager_) + manager_->RemoveServer(this); + + if (server_ && should_delete_server_) { + delete server_; + server_ = NULL; + } +} + +void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p, + FirewallDirection d, + const SocketAddress& addr) { + SocketAddress src, dst; + if (d == FD_IN) { + dst = addr; + } else { + src = addr; + } + AddRule(allow, p, src, dst); +} + + +void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p, + const SocketAddress& src, + const SocketAddress& dst) { + Rule r; + r.allow = allow; + r.p = p; + r.src = src; + r.dst = dst; + CritScope scope(&crit_); + rules_.push_back(r); +} + +void FirewallSocketServer::ClearRules() { + CritScope scope(&crit_); + rules_.clear(); +} + +bool FirewallSocketServer::Check(FirewallProtocol p, + const SocketAddress& src, + const SocketAddress& dst) { + CritScope scope(&crit_); + for (size_t i = 0; i < rules_.size(); ++i) { + const Rule& r = rules_[i]; + if ((r.p != p) && (r.p != FP_ANY)) + continue; + if ((r.src.ipaddr() != src.ipaddr()) && !r.src.IsNil()) + continue; + if ((r.src.port() != src.port()) && (r.src.port() != 0)) + continue; + if ((r.dst.ipaddr() != dst.ipaddr()) && !r.dst.IsNil()) + continue; + if ((r.dst.port() != dst.port()) && (r.dst.port() != 0)) + continue; + return r.allow; + } + return true; +} + +Socket* FirewallSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* FirewallSocketServer::CreateSocket(int family, int type) { + return WrapSocket(server_->CreateAsyncSocket(family, type), type); +} + +AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int family, int type) { + return WrapSocket(server_->CreateAsyncSocket(family, type), type); +} + +AsyncSocket* FirewallSocketServer::WrapSocket(AsyncSocket* sock, int type) { + if (!sock || + (type == SOCK_STREAM && !tcp_sockets_enabled_) || + (type == SOCK_DGRAM && !udp_sockets_enabled_)) { + LOG(LS_VERBOSE) << "FirewallSocketServer socket creation denied"; + delete sock; + return NULL; + } + return new FirewallSocket(this, sock, type); +} + +FirewallManager::FirewallManager() { +} + +FirewallManager::~FirewallManager() { + assert(servers_.empty()); +} + +void FirewallManager::AddServer(FirewallSocketServer* server) { + CritScope scope(&crit_); + servers_.push_back(server); +} + +void FirewallManager::RemoveServer(FirewallSocketServer* server) { + CritScope scope(&crit_); + servers_.erase(std::remove(servers_.begin(), servers_.end(), server), + servers_.end()); +} + +void FirewallManager::AddRule(bool allow, FirewallProtocol p, + FirewallDirection d, const SocketAddress& addr) { + CritScope scope(&crit_); + for (std::vector::const_iterator it = + servers_.begin(); it != servers_.end(); ++it) { + (*it)->AddRule(allow, p, d, addr); + } +} + +void FirewallManager::ClearRules() { + CritScope scope(&crit_); + for (std::vector::const_iterator it = + servers_.begin(); it != servers_.end(); ++it) { + (*it)->ClearRules(); + } +} + +} // namespace rtc diff --git a/webrtc/base/firewallsocketserver.h b/webrtc/base/firewallsocketserver.h new file mode 100644 index 000000000..500b7397d --- /dev/null +++ b/webrtc/base/firewallsocketserver.h @@ -0,0 +1,120 @@ +/* + * Copyright 2004 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_BASE_FIREWALLSOCKETSERVER_H_ +#define WEBRTC_BASE_FIREWALLSOCKETSERVER_H_ + +#include +#include "webrtc/base/socketserver.h" +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +class FirewallManager; + +// This SocketServer shim simulates a rule-based firewall server. + +enum FirewallProtocol { FP_UDP, FP_TCP, FP_ANY }; +enum FirewallDirection { FD_IN, FD_OUT, FD_ANY }; + +class FirewallSocketServer : public SocketServer { + public: + FirewallSocketServer(SocketServer * server, + FirewallManager * manager = NULL, + bool should_delete_server = false); + virtual ~FirewallSocketServer(); + + SocketServer* socketserver() const { return server_; } + void set_socketserver(SocketServer* server) { + if (server_ && should_delete_server_) { + delete server_; + server_ = NULL; + should_delete_server_ = false; + } + server_ = server; + } + + // Settings to control whether CreateSocket or Socket::Listen succeed. + void set_udp_sockets_enabled(bool enabled) { udp_sockets_enabled_ = enabled; } + void set_tcp_sockets_enabled(bool enabled) { tcp_sockets_enabled_ = enabled; } + bool tcp_listen_enabled() const { return tcp_listen_enabled_; } + void set_tcp_listen_enabled(bool enabled) { tcp_listen_enabled_ = enabled; } + + // Rules govern the behavior of Connect/Accept/Send/Recv attempts. + void AddRule(bool allow, FirewallProtocol p = FP_ANY, + FirewallDirection d = FD_ANY, + const SocketAddress& addr = SocketAddress()); + void AddRule(bool allow, FirewallProtocol p, + const SocketAddress& src, const SocketAddress& dst); + void ClearRules(); + + bool Check(FirewallProtocol p, + const SocketAddress& src, const SocketAddress& dst); + + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual void SetMessageQueue(MessageQueue* queue) { + server_->SetMessageQueue(queue); + } + virtual bool Wait(int cms, bool process_io) { + return server_->Wait(cms, process_io); + } + virtual void WakeUp() { + return server_->WakeUp(); + } + + Socket * WrapSocket(Socket * sock, int type); + AsyncSocket * WrapSocket(AsyncSocket * sock, int type); + + private: + SocketServer * server_; + FirewallManager * manager_; + CriticalSection crit_; + struct Rule { + bool allow; + FirewallProtocol p; + FirewallDirection d; + SocketAddress src; + SocketAddress dst; + }; + std::vector rules_; + bool should_delete_server_; + bool udp_sockets_enabled_; + bool tcp_sockets_enabled_; + bool tcp_listen_enabled_; +}; + +// FirewallManager allows you to manage firewalls in multiple threads together + +class FirewallManager { + public: + FirewallManager(); + ~FirewallManager(); + + void AddServer(FirewallSocketServer * server); + void RemoveServer(FirewallSocketServer * server); + + void AddRule(bool allow, FirewallProtocol p = FP_ANY, + FirewallDirection d = FD_ANY, + const SocketAddress& addr = SocketAddress()); + void ClearRules(); + + private: + CriticalSection crit_; + std::vector servers_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FIREWALLSOCKETSERVER_H_ diff --git a/webrtc/base/flags.cc b/webrtc/base/flags.cc new file mode 100644 index 000000000..fe7a334a9 --- /dev/null +++ b/webrtc/base/flags.cc @@ -0,0 +1,299 @@ +/* + * Copyright 2006 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 + + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif + +#include "webrtc/base/flags.h" + +namespace rtc { +// ----------------------------------------------------------------------------- +// Implementation of Flag + +Flag::Flag(const char* file, const char* name, const char* comment, + Type type, void* variable, FlagValue default__) + : file_(file), + name_(name), + comment_(comment), + type_(type), + variable_(reinterpret_cast(variable)), + default_(default__) { + FlagList::Register(this); +} + + +void Flag::SetToDefault() { + // Note that we cannot simply do '*variable_ = default_;' since + // flag variables are not really of type FlagValue and thus may + // be smaller! The FlagValue union is simply 'overlayed' on top + // of a flag variable for convenient access. Since union members + // are guarantee to be aligned at the beginning, this works. + switch (type_) { + case Flag::BOOL: + variable_->b = default_.b; + return; + case Flag::INT: + variable_->i = default_.i; + return; + case Flag::FLOAT: + variable_->f = default_.f; + return; + case Flag::STRING: + variable_->s = default_.s; + return; + } + UNREACHABLE(); +} + + +static const char* Type2String(Flag::Type type) { + switch (type) { + case Flag::BOOL: return "bool"; + case Flag::INT: return "int"; + case Flag::FLOAT: return "float"; + case Flag::STRING: return "string"; + } + UNREACHABLE(); + return NULL; +} + + +static void PrintFlagValue(Flag::Type type, FlagValue* p) { + switch (type) { + case Flag::BOOL: + printf("%s", (p->b ? "true" : "false")); + return; + case Flag::INT: + printf("%d", p->i); + return; + case Flag::FLOAT: + printf("%f", p->f); + return; + case Flag::STRING: + printf("%s", p->s); + return; + } + UNREACHABLE(); +} + + +void Flag::Print(bool print_current_value) { + printf(" --%s (%s) type: %s default: ", name_, comment_, + Type2String(type_)); + PrintFlagValue(type_, &default_); + if (print_current_value) { + printf(" current value: "); + PrintFlagValue(type_, variable_); + } + printf("\n"); +} + + +// ----------------------------------------------------------------------------- +// Implementation of FlagList + +Flag* FlagList::list_ = NULL; + + +FlagList::FlagList() { + list_ = NULL; +} + +void FlagList::Print(const char* file, bool print_current_value) { + // Since flag registration is likely by file (= C++ file), + // we don't need to sort by file and still get grouped output. + const char* current = NULL; + for (Flag* f = list_; f != NULL; f = f->next()) { + if (file == NULL || file == f->file()) { + if (current != f->file()) { + printf("Flags from %s:\n", f->file()); + current = f->file(); + } + f->Print(print_current_value); + } + } +} + + +Flag* FlagList::Lookup(const char* name) { + Flag* f = list_; + while (f != NULL && strcmp(name, f->name()) != 0) + f = f->next(); + return f; +} + + +void FlagList::SplitArgument(const char* arg, + char* buffer, int buffer_size, + const char** name, const char** value, + bool* is_bool) { + *name = NULL; + *value = NULL; + *is_bool = false; + + if (*arg == '-') { + // find the begin of the flag name + arg++; // remove 1st '-' + if (*arg == '-') + arg++; // remove 2nd '-' + if (arg[0] == 'n' && arg[1] == 'o') { + arg += 2; // remove "no" + *is_bool = true; + } + *name = arg; + + // find the end of the flag name + while (*arg != '\0' && *arg != '=') + arg++; + + // get the value if any + if (*arg == '=') { + // make a copy so we can NUL-terminate flag name + int n = static_cast(arg - *name); + if (n >= buffer_size) + Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size"); + memcpy(buffer, *name, n * sizeof(char)); + buffer[n] = '\0'; + *name = buffer; + // get the value + *value = arg + 1; + } + } +} + + +int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv, + bool remove_flags) { + // parse arguments + for (int i = 1; i < *argc; /* see below */) { + int j = i; // j > 0 + const char* arg = argv[i++]; + + // split arg into flag components + char buffer[1024]; + const char* name; + const char* value; + bool is_bool; + SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool); + + if (name != NULL) { + // lookup the flag + Flag* flag = Lookup(name); + if (flag == NULL) { + fprintf(stderr, "Error: unrecognized flag %s\n", arg); + return j; + } + + // if we still need a flag value, use the next argument if available + if (flag->type() != Flag::BOOL && value == NULL) { + if (i < *argc) { + value = argv[i++]; + } else { + fprintf(stderr, "Error: missing value for flag %s of type %s\n", + arg, Type2String(flag->type())); + return j; + } + } + + // set the flag + char empty[] = { '\0' }; + char* endp = empty; + switch (flag->type()) { + case Flag::BOOL: + *flag->bool_variable() = !is_bool; + break; + case Flag::INT: + *flag->int_variable() = strtol(value, &endp, 10); + break; + case Flag::FLOAT: + *flag->float_variable() = strtod(value, &endp); + break; + case Flag::STRING: + *flag->string_variable() = value; + break; + } + + // handle errors + if ((flag->type() == Flag::BOOL && value != NULL) || + (flag->type() != Flag::BOOL && is_bool) || + *endp != '\0') { + fprintf(stderr, "Error: illegal value for flag %s of type %s\n", + arg, Type2String(flag->type())); + return j; + } + + // remove the flag & value from the command + if (remove_flags) + while (j < i) + argv[j++] = NULL; + } + } + + // shrink the argument list + if (remove_flags) { + int j = 1; + for (int i = 1; i < *argc; i++) { + if (argv[i] != NULL) + argv[j++] = argv[i]; + } + *argc = j; + } + + // parsed all flags successfully + return 0; +} + +void FlagList::Register(Flag* flag) { + assert(flag != NULL && strlen(flag->name()) > 0); + if (Lookup(flag->name()) != NULL) + Fatal(flag->file(), 0, "flag %s declared twice", flag->name()); + flag->next_ = list_; + list_ = flag; +} + +#if defined(WEBRTC_WIN) +WindowsCommandLineArguments::WindowsCommandLineArguments() { + // start by getting the command line. + LPTSTR command_line = ::GetCommandLine(); + // now, convert it to a list of wide char strings. + LPWSTR *wide_argv = ::CommandLineToArgvW(command_line, &argc_); + // now allocate an array big enough to hold that many string pointers. + argv_ = new char*[argc_]; + + // iterate over the returned wide strings; + for(int i = 0; i < argc_; ++i) { + std::string s = rtc::ToUtf8(wide_argv[i], wcslen(wide_argv[i])); + char *buffer = new char[s.length() + 1]; + rtc::strcpyn(buffer, s.length() + 1, s.c_str()); + + // make sure the argv array has the right string at this point. + argv_[i] = buffer; + } + LocalFree(wide_argv); +} + +WindowsCommandLineArguments::~WindowsCommandLineArguments() { + // need to free each string in the array, and then the array. + for(int i = 0; i < argc_; i++) { + delete[] argv_[i]; + } + + delete[] argv_; +} +#endif // WEBRTC_WIN + +} // namespace rtc diff --git a/webrtc/base/flags.h b/webrtc/base/flags.h new file mode 100644 index 000000000..5cff1cc36 --- /dev/null +++ b/webrtc/base/flags.h @@ -0,0 +1,270 @@ +/* + * Copyright 2006 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. + */ + + +// Originally comes from shared/commandlineflags/flags.h + +// Flags are defined and declared using DEFINE_xxx and DECLARE_xxx macros, +// where xxx is the flag type. Flags are referred to via FLAG_yyy, +// where yyy is the flag name. For intialization and iteration of flags, +// see the FlagList class. For full programmatic access to any +// flag, see the Flag class. +// +// The implementation only relies and basic C++ functionality +// and needs no special library or STL support. + +#ifndef WEBRTC_BASE_FLAGS_H__ +#define WEBRTC_BASE_FLAGS_H__ + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/common.h" + +namespace rtc { + +// Internal use only. +union FlagValue { + // Note: Because in C++ non-bool values are silently converted into + // bool values ('bool b = "false";' results in b == true!), we pass + // and int argument to New_BOOL as this appears to be safer - sigh. + // In particular, it prevents the (not uncommon!) bug where a bool + // flag is defined via: DEFINE_bool(flag, "false", "some comment");. + static FlagValue New_BOOL(int b) { + FlagValue v; + v.b = (b != 0); + return v; + } + + static FlagValue New_INT(int i) { + FlagValue v; + v.i = i; + return v; + } + + static FlagValue New_FLOAT(float f) { + FlagValue v; + v.f = f; + return v; + } + + static FlagValue New_STRING(const char* s) { + FlagValue v; + v.s = s; + return v; + } + + bool b; + int i; + double f; + const char* s; +}; + + +// Each flag can be accessed programmatically via a Flag object. +class Flag { + public: + enum Type { BOOL, INT, FLOAT, STRING }; + + // Internal use only. + Flag(const char* file, const char* name, const char* comment, + Type type, void* variable, FlagValue default_); + + // General flag information + const char* file() const { return file_; } + const char* name() const { return name_; } + const char* comment() const { return comment_; } + + // Flag type + Type type() const { return type_; } + + // Flag variables + bool* bool_variable() const { + assert(type_ == BOOL); + return &variable_->b; + } + + int* int_variable() const { + assert(type_ == INT); + return &variable_->i; + } + + double* float_variable() const { + assert(type_ == FLOAT); + return &variable_->f; + } + + const char** string_variable() const { + assert(type_ == STRING); + return &variable_->s; + } + + // Default values + bool bool_default() const { + assert(type_ == BOOL); + return default_.b; + } + + int int_default() const { + assert(type_ == INT); + return default_.i; + } + + double float_default() const { + assert(type_ == FLOAT); + return default_.f; + } + + const char* string_default() const { + assert(type_ == STRING); + return default_.s; + } + + // Resets a flag to its default value + void SetToDefault(); + + // Iteration support + Flag* next() const { return next_; } + + // Prints flag information. The current flag value is only printed + // if print_current_value is set. + void Print(bool print_current_value); + + private: + const char* file_; + const char* name_; + const char* comment_; + + Type type_; + FlagValue* variable_; + FlagValue default_; + + Flag* next_; + + friend class FlagList; // accesses next_ +}; + + +// Internal use only. +#define DEFINE_FLAG(type, c_type, name, default, comment) \ + /* define and initialize the flag */ \ + c_type FLAG_##name = (default); \ + /* register the flag */ \ + static rtc::Flag Flag_##name(__FILE__, #name, (comment), \ + rtc::Flag::type, &FLAG_##name, \ + rtc::FlagValue::New_##type(default)) + + +// Internal use only. +#define DECLARE_FLAG(c_type, name) \ + /* declare the external flag */ \ + extern c_type FLAG_##name + + +// Use the following macros to define a new flag: +#define DEFINE_bool(name, default, comment) \ + DEFINE_FLAG(BOOL, bool, name, default, comment) +#define DEFINE_int(name, default, comment) \ + DEFINE_FLAG(INT, int, name, default, comment) +#define DEFINE_float(name, default, comment) \ + DEFINE_FLAG(FLOAT, double, name, default, comment) +#define DEFINE_string(name, default, comment) \ + DEFINE_FLAG(STRING, const char*, name, default, comment) + + +// Use the following macros to declare a flag defined elsewhere: +#define DECLARE_bool(name) DECLARE_FLAG(bool, name) +#define DECLARE_int(name) DECLARE_FLAG(int, name) +#define DECLARE_float(name) DECLARE_FLAG(double, name) +#define DECLARE_string(name) DECLARE_FLAG(const char*, name) + + +// The global list of all flags. +class FlagList { + public: + FlagList(); + + // The NULL-terminated list of all flags. Traverse with Flag::next(). + static Flag* list() { return list_; } + + // If file != NULL, prints information for all flags defined in file; + // otherwise prints information for all flags in all files. The current + // flag value is only printed if print_current_value is set. + static void Print(const char* file, bool print_current_value); + + // Lookup a flag by name. Returns the matching flag or NULL. + static Flag* Lookup(const char* name); + + // Helper function to parse flags: Takes an argument arg and splits it into + // a flag name and flag value (or NULL if they are missing). is_bool is set + // if the arg started with "-no" or "--no". The buffer may be used to NUL- + // terminate the name, it must be large enough to hold any possible name. + static void SplitArgument(const char* arg, + char* buffer, int buffer_size, + const char** name, const char** value, + bool* is_bool); + + // Set the flag values by parsing the command line. If remove_flags + // is set, the flags and associated values are removed from (argc, + // argv). Returns 0 if no error occurred. Otherwise, returns the + // argv index > 0 for the argument where an error occurred. In that + // case, (argc, argv) will remain unchanged indepdendent of the + // remove_flags value, and no assumptions about flag settings should + // be made. + // + // The following syntax for flags is accepted (both '-' and '--' are ok): + // + // --flag (bool flags only) + // --noflag (bool flags only) + // --flag=value (non-bool flags only, no spaces around '=') + // --flag value (non-bool flags only) + static int SetFlagsFromCommandLine(int* argc, + const char** argv, + bool remove_flags); + static inline int SetFlagsFromCommandLine(int* argc, + char** argv, + bool remove_flags) { + return SetFlagsFromCommandLine(argc, const_cast(argv), + remove_flags); + } + + // Registers a new flag. Called during program initialization. Not + // thread-safe. + static void Register(Flag* flag); + + private: + static Flag* list_; +}; + +#if defined(WEBRTC_WIN) +// A helper class to translate Windows command line arguments into UTF8, +// which then allows us to just pass them to the flags system. +// This encapsulates all the work of getting the command line and translating +// it to an array of 8-bit strings; all you have to do is create one of these, +// and then call argc() and argv(). +class WindowsCommandLineArguments { + public: + WindowsCommandLineArguments(); + ~WindowsCommandLineArguments(); + + int argc() { return argc_; } + char **argv() { return argv_; } + private: + int argc_; + char **argv_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(WindowsCommandLineArguments); +}; +#endif // WEBRTC_WIN + +} // namespace rtc + +#endif // SHARED_COMMANDLINEFLAGS_FLAGS_H__ diff --git a/webrtc/base/gunit.h b/webrtc/base/gunit.h new file mode 100644 index 000000000..6d9c06fef --- /dev/null +++ b/webrtc/base/gunit.h @@ -0,0 +1,88 @@ +/* + * Copyright 2004 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_BASE_GUNIT_H_ +#define WEBRTC_BASE_GUNIT_H_ + +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" +#if defined(WEBRTC_ANDROID) || defined(GTEST_RELATIVE_PATH) +#include "gtest/gtest.h" +#else +#include "testing/base/public/gunit.h" +#endif + +// Wait until "ex" is true, or "timeout" expires. +#define WAIT(ex, timeout) \ + for (uint32 start = rtc::Time(); \ + !(ex) && rtc::Time() < start + timeout;) \ + rtc::Thread::Current()->ProcessMessages(1); + +// This returns the result of the test in res, so that we don't re-evaluate +// the expression in the XXXX_WAIT macros below, since that causes problems +// when the expression is only true the first time you check it. +#define WAIT_(ex, timeout, res) \ + do { \ + uint32 start = rtc::Time(); \ + res = (ex); \ + while (!res && rtc::Time() < start + timeout) { \ + rtc::Thread::Current()->ProcessMessages(1); \ + res = (ex); \ + } \ + } while (0); + +// The typical EXPECT_XXXX and ASSERT_XXXXs, but done until true or a timeout. +#define EXPECT_TRUE_WAIT(ex, timeout) \ + do { \ + bool res; \ + WAIT_(ex, timeout, res); \ + if (!res) EXPECT_TRUE(ex); \ + } while (0); + +#define EXPECT_EQ_WAIT(v1, v2, timeout) \ + do { \ + bool res; \ + WAIT_(v1 == v2, timeout, res); \ + if (!res) EXPECT_EQ(v1, v2); \ + } while (0); + +#define ASSERT_TRUE_WAIT(ex, timeout) \ + do { \ + bool res; \ + WAIT_(ex, timeout, res); \ + if (!res) ASSERT_TRUE(ex); \ + } while (0); + +#define ASSERT_EQ_WAIT(v1, v2, timeout) \ + do { \ + bool res; \ + WAIT_(v1 == v2, timeout, res); \ + if (!res) ASSERT_EQ(v1, v2); \ + } while (0); + +// Version with a "soft" timeout and a margin. This logs if the timeout is +// exceeded, but it only fails if the expression still isn't true after the +// margin time passes. +#define EXPECT_TRUE_WAIT_MARGIN(ex, timeout, margin) \ + do { \ + bool res; \ + WAIT_(ex, timeout, res); \ + if (res) { \ + break; \ + } \ + LOG(LS_WARNING) << "Expression " << #ex << " still not true after " << \ + timeout << "ms; waiting an additional " << margin << "ms"; \ + WAIT_(ex, margin, res); \ + if (!res) { \ + EXPECT_TRUE(ex); \ + } \ + } while (0); + +#endif // WEBRTC_BASE_GUNIT_H_ diff --git a/webrtc/base/gunit_prod.h b/webrtc/base/gunit_prod.h new file mode 100644 index 000000000..dc39bbd0e --- /dev/null +++ b/webrtc/base/gunit_prod.h @@ -0,0 +1,24 @@ +/* + * Copyright 2012 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_BASE_GUNIT_PROD_H_ +#define WEBRTC_BASE_GUNIT_PROD_H_ + +#if defined(WEBRTC_ANDROID) +// Android doesn't use gtest at all, so anything that relies on gtest should +// check this define first. +#define NO_GTEST +#elif defined (GTEST_RELATIVE_PATH) +#include "gtest/gtest_prod.h" +#else +#include "testing/base/gunit_prod.h" +#endif + +#endif // WEBRTC_BASE_GUNIT_PROD_H_ diff --git a/webrtc/base/helpers.cc b/webrtc/base/helpers.cc new file mode 100644 index 000000000..8b14cdfd6 --- /dev/null +++ b/webrtc/base/helpers.cc @@ -0,0 +1,296 @@ +/* + * Copyright 2004 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 "webrtc/base/helpers.h" + +#include + +#if defined(FEATURE_ENABLE_SSL) +#include "webrtc/base/sslconfig.h" +#if defined(SSL_USE_OPENSSL) +#include +#elif defined(SSL_USE_NSS_RNG) +#include "pk11func.h" +#else +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif // WEBRTC_WIN +#endif // else +#endif // FEATURE_ENABLED_SSL + +#include "webrtc/base/base64.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/timeutils.h" + +// Protect against max macro inclusion. +#undef max + +namespace rtc { + +// Base class for RNG implementations. +class RandomGenerator { + public: + virtual ~RandomGenerator() {} + virtual bool Init(const void* seed, size_t len) = 0; + virtual bool Generate(void* buf, size_t len) = 0; +}; + +#if defined(SSL_USE_OPENSSL) +// The OpenSSL RNG. Need to make sure it doesn't run out of entropy. +class SecureRandomGenerator : public RandomGenerator { + public: + SecureRandomGenerator() : inited_(false) { + } + ~SecureRandomGenerator() { + } + virtual bool Init(const void* seed, size_t len) { + // By default, seed from the system state. + if (!inited_) { + if (RAND_poll() <= 0) { + return false; + } + inited_ = true; + } + // Allow app data to be mixed in, if provided. + if (seed) { + RAND_seed(seed, len); + } + return true; + } + virtual bool Generate(void* buf, size_t len) { + if (!inited_ && !Init(NULL, 0)) { + return false; + } + return (RAND_bytes(reinterpret_cast(buf), len) > 0); + } + + private: + bool inited_; +}; + +#elif defined(SSL_USE_NSS_RNG) +// The NSS RNG. +class SecureRandomGenerator : public RandomGenerator { + public: + SecureRandomGenerator() {} + ~SecureRandomGenerator() {} + virtual bool Init(const void* seed, size_t len) { + return true; + } + virtual bool Generate(void* buf, size_t len) { + return (PK11_GenerateRandom(reinterpret_cast(buf), + static_cast(len)) == SECSuccess); + } +}; + +#else +#if defined(WEBRTC_WIN) +class SecureRandomGenerator : public RandomGenerator { + public: + SecureRandomGenerator() : advapi32_(NULL), rtl_gen_random_(NULL) {} + ~SecureRandomGenerator() { + FreeLibrary(advapi32_); + } + + virtual bool Init(const void* seed, size_t seed_len) { + // We don't do any additional seeding on Win32, we just use the CryptoAPI + // RNG (which is exposed as a hidden function off of ADVAPI32 so that we + // don't need to drag in all of CryptoAPI) + if (rtl_gen_random_) { + return true; + } + + advapi32_ = LoadLibrary(L"advapi32.dll"); + if (!advapi32_) { + return false; + } + + rtl_gen_random_ = reinterpret_cast( + GetProcAddress(advapi32_, "SystemFunction036")); + if (!rtl_gen_random_) { + FreeLibrary(advapi32_); + return false; + } + + return true; + } + virtual bool Generate(void* buf, size_t len) { + if (!rtl_gen_random_ && !Init(NULL, 0)) { + return false; + } + return (rtl_gen_random_(buf, static_cast(len)) != FALSE); + } + + private: + typedef BOOL (WINAPI *RtlGenRandomProc)(PVOID, ULONG); + HINSTANCE advapi32_; + RtlGenRandomProc rtl_gen_random_; +}; + +#elif !defined(FEATURE_ENABLE_SSL) + +// No SSL implementation -- use rand() +class SecureRandomGenerator : public RandomGenerator { + public: + virtual bool Init(const void* seed, size_t len) { + if (len >= 4) { + srand(*reinterpret_cast(seed)); + } else { + srand(*reinterpret_cast(seed)); + } + return true; + } + virtual bool Generate(void* buf, size_t len) { + char* bytes = reinterpret_cast(buf); + for (size_t i = 0; i < len; ++i) { + bytes[i] = static_cast(rand()); + } + return true; + } +}; + +#else + +#error No SSL implementation has been selected! + +#endif // WEBRTC_WIN +#endif + +// A test random generator, for predictable output. +class TestRandomGenerator : public RandomGenerator { + public: + TestRandomGenerator() : seed_(7) { + } + ~TestRandomGenerator() { + } + virtual bool Init(const void* seed, size_t len) { + return true; + } + virtual bool Generate(void* buf, size_t len) { + for (size_t i = 0; i < len; ++i) { + static_cast(buf)[i] = static_cast(GetRandom()); + } + return true; + } + + private: + int GetRandom() { + return ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff; + } + int seed_; +}; + +// TODO: Use Base64::Base64Table instead. +static const char BASE64[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' +}; + +namespace { + +// This round about way of creating a global RNG is to safe-guard against +// indeterminant static initialization order. +scoped_ptr& GetGlobalRng() { + LIBJINGLE_DEFINE_STATIC_LOCAL(scoped_ptr, global_rng, + (new SecureRandomGenerator())); + return global_rng; +} + +RandomGenerator& Rng() { + return *GetGlobalRng(); +} + +} // namespace + +void SetRandomTestMode(bool test) { + if (!test) { + GetGlobalRng().reset(new SecureRandomGenerator()); + } else { + GetGlobalRng().reset(new TestRandomGenerator()); + } +} + +bool InitRandom(int seed) { + return InitRandom(reinterpret_cast(&seed), sizeof(seed)); +} + +bool InitRandom(const char* seed, size_t len) { + if (!Rng().Init(seed, len)) { + LOG(LS_ERROR) << "Failed to init random generator!"; + return false; + } + return true; +} + +std::string CreateRandomString(size_t len) { + std::string str; + CreateRandomString(len, &str); + return str; +} + +bool CreateRandomString(size_t len, + const char* table, int table_size, + std::string* str) { + str->clear(); + scoped_ptr bytes(new uint8[len]); + if (!Rng().Generate(bytes.get(), len)) { + LOG(LS_ERROR) << "Failed to generate random string!"; + return false; + } + str->reserve(len); + for (size_t i = 0; i < len; ++i) { + str->push_back(table[bytes[i] % table_size]); + } + return true; +} + +bool CreateRandomString(size_t len, std::string* str) { + return CreateRandomString(len, BASE64, 64, str); +} + +bool CreateRandomString(size_t len, const std::string& table, + std::string* str) { + return CreateRandomString(len, table.c_str(), + static_cast(table.size()), str); +} + +uint32 CreateRandomId() { + uint32 id; + if (!Rng().Generate(&id, sizeof(id))) { + LOG(LS_ERROR) << "Failed to generate random id!"; + } + return id; +} + +uint64 CreateRandomId64() { + return static_cast(CreateRandomId()) << 32 | CreateRandomId(); +} + +uint32 CreateRandomNonZeroId() { + uint32 id; + do { + id = CreateRandomId(); + } while (id == 0); + return id; +} + +double CreateRandomDouble() { + return CreateRandomId() / (std::numeric_limits::max() + + std::numeric_limits::epsilon()); +} + +} // namespace rtc diff --git a/webrtc/base/helpers.h b/webrtc/base/helpers.h new file mode 100644 index 000000000..e46d12a33 --- /dev/null +++ b/webrtc/base/helpers.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 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_BASE_HELPERS_H_ +#define WEBRTC_BASE_HELPERS_H_ + +#include +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// For testing, we can return predictable data. +void SetRandomTestMode(bool test); + +// Initializes the RNG, and seeds it with the specified entropy. +bool InitRandom(int seed); +bool InitRandom(const char* seed, size_t len); + +// Generates a (cryptographically) random string of the given length. +// We generate base64 values so that they will be printable. +// WARNING: could silently fail. Use the version below instead. +std::string CreateRandomString(size_t length); + +// Generates a (cryptographically) random string of the given length. +// We generate base64 values so that they will be printable. +// Return false if the random number generator failed. +bool CreateRandomString(size_t length, std::string* str); + +// Generates a (cryptographically) random string of the given length, +// with characters from the given table. Return false if the random +// number generator failed. +bool CreateRandomString(size_t length, const std::string& table, + std::string* str); + +// Generates a random id. +uint32 CreateRandomId(); + +// Generates a 64 bit random id. +uint64 CreateRandomId64(); + +// Generates a random id > 0. +uint32 CreateRandomNonZeroId(); + +// Generates a random double between 0.0 (inclusive) and 1.0 (exclusive). +double CreateRandomDouble(); + +} // namespace rtc + +#endif // WEBRTC_BASE_HELPERS_H_ diff --git a/webrtc/base/helpers_unittest.cc b/webrtc/base/helpers_unittest.cc new file mode 100644 index 000000000..7c20540c5 --- /dev/null +++ b/webrtc/base/helpers_unittest.cc @@ -0,0 +1,78 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/ssladapter.h" + +namespace rtc { + +class RandomTest : public testing::Test { + public: + static void SetUpTestCase() { + rtc::InitializeSSL(); + } + + static void TearDownTestCase() { + rtc::CleanupSSL(); + } +}; + +TEST_F(RandomTest, TestCreateRandomId) { + CreateRandomId(); +} + +TEST_F(RandomTest, TestCreateRandomDouble) { + for (int i = 0; i < 100; ++i) { + double r = CreateRandomDouble(); + EXPECT_GE(r, 0.0); + EXPECT_LT(r, 1.0); + } +} + +TEST_F(RandomTest, TestCreateNonZeroRandomId) { + EXPECT_NE(0U, CreateRandomNonZeroId()); +} + +TEST_F(RandomTest, TestCreateRandomString) { + std::string random = CreateRandomString(256); + EXPECT_EQ(256U, random.size()); + std::string random2; + EXPECT_TRUE(CreateRandomString(256, &random2)); + EXPECT_NE(random, random2); + EXPECT_EQ(256U, random2.size()); +} + +TEST_F(RandomTest, TestCreateRandomForTest) { + // Make sure we get the output we expect. + SetRandomTestMode(true); + EXPECT_EQ(2154761789U, CreateRandomId()); + EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16)); + + // Reset and make sure we get the same output. + SetRandomTestMode(true); + EXPECT_EQ(2154761789U, CreateRandomId()); + EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16)); + + // Test different character sets. + SetRandomTestMode(true); + std::string str; + EXPECT_TRUE(CreateRandomString(16, "a", &str)); + EXPECT_EQ("aaaaaaaaaaaaaaaa", str); + EXPECT_TRUE(CreateRandomString(16, "abc", &str)); + EXPECT_EQ("acbccaaaabbaacbb", str); + + // Turn off test mode for other tests. + SetRandomTestMode(false); +} + +} // namespace rtc diff --git a/webrtc/base/httpbase.cc b/webrtc/base/httpbase.cc new file mode 100644 index 000000000..5de2b79d7 --- /dev/null +++ b/webrtc/base/httpbase.cc @@ -0,0 +1,877 @@ +/* + * Copyright 2004 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. + */ + + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#else // !WEBRTC_WIN +#define SEC_E_CERT_EXPIRED (-2146893016) +#endif // !WEBRTC_WIN + +#include "webrtc/base/common.h" +#include "webrtc/base/httpbase.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Helpers +////////////////////////////////////////////////////////////////////// + +bool MatchHeader(const char* str, size_t len, HttpHeader header) { + const char* const header_str = ToString(header); + const size_t header_len = strlen(header_str); + return (len == header_len) && (_strnicmp(str, header_str, header_len) == 0); +} + +enum { + MSG_READ +}; + +////////////////////////////////////////////////////////////////////// +// HttpParser +////////////////////////////////////////////////////////////////////// + +HttpParser::HttpParser() { + reset(); +} + +HttpParser::~HttpParser() { +} + +void +HttpParser::reset() { + state_ = ST_LEADER; + chunked_ = false; + data_size_ = SIZE_UNKNOWN; +} + +HttpParser::ProcessResult +HttpParser::Process(const char* buffer, size_t len, size_t* processed, + HttpError* error) { + *processed = 0; + *error = HE_NONE; + + if (state_ >= ST_COMPLETE) { + ASSERT(false); + return PR_COMPLETE; + } + + while (true) { + if (state_ < ST_DATA) { + size_t pos = *processed; + while ((pos < len) && (buffer[pos] != '\n')) { + pos += 1; + } + if (pos >= len) { + break; // don't have a full header + } + const char* line = buffer + *processed; + size_t len = (pos - *processed); + *processed = pos + 1; + while ((len > 0) && isspace(static_cast(line[len-1]))) { + len -= 1; + } + ProcessResult result = ProcessLine(line, len, error); + LOG(LS_VERBOSE) << "Processed line, result=" << result; + + if (PR_CONTINUE != result) { + return result; + } + } else if (data_size_ == 0) { + if (chunked_) { + state_ = ST_CHUNKTERM; + } else { + return PR_COMPLETE; + } + } else { + size_t available = len - *processed; + if (available <= 0) { + break; // no more data + } + if ((data_size_ != SIZE_UNKNOWN) && (available > data_size_)) { + available = data_size_; + } + size_t read = 0; + ProcessResult result = ProcessData(buffer + *processed, available, read, + error); + LOG(LS_VERBOSE) << "Processed data, result: " << result << " read: " + << read << " err: " << error; + + if (PR_CONTINUE != result) { + return result; + } + *processed += read; + if (data_size_ != SIZE_UNKNOWN) { + data_size_ -= read; + } + } + } + + return PR_CONTINUE; +} + +HttpParser::ProcessResult +HttpParser::ProcessLine(const char* line, size_t len, HttpError* error) { + LOG_F(LS_VERBOSE) << " state: " << state_ << " line: " + << std::string(line, len) << " len: " << len << " err: " + << error; + + switch (state_) { + case ST_LEADER: + state_ = ST_HEADERS; + return ProcessLeader(line, len, error); + + case ST_HEADERS: + if (len > 0) { + const char* value = strchrn(line, len, ':'); + if (!value) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + size_t nlen = (value - line); + const char* eol = line + len; + do { + value += 1; + } while ((value < eol) && isspace(static_cast(*value))); + size_t vlen = eol - value; + if (MatchHeader(line, nlen, HH_CONTENT_LENGTH)) { + // sscanf isn't safe with strings that aren't null-terminated, and there + // is no guarantee that |value| is. + // Create a local copy that is null-terminated. + std::string value_str(value, vlen); + unsigned int temp_size; + if (sscanf(value_str.c_str(), "%u", &temp_size) != 1) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + data_size_ = static_cast(temp_size); + } else if (MatchHeader(line, nlen, HH_TRANSFER_ENCODING)) { + if ((vlen == 7) && (_strnicmp(value, "chunked", 7) == 0)) { + chunked_ = true; + } else if ((vlen == 8) && (_strnicmp(value, "identity", 8) == 0)) { + chunked_ = false; + } else { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + } + return ProcessHeader(line, nlen, value, vlen, error); + } else { + state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA; + return ProcessHeaderComplete(chunked_, data_size_, error); + } + break; + + case ST_CHUNKSIZE: + if (len > 0) { + char* ptr = NULL; + data_size_ = strtoul(line, &ptr, 16); + if (ptr != line + len) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA; + } else { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } + break; + + case ST_CHUNKTERM: + if (len > 0) { + *error = HE_PROTOCOL; + return PR_COMPLETE; + } else { + state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA; + } + break; + + case ST_TRAILERS: + if (len == 0) { + return PR_COMPLETE; + } + // *error = onHttpRecvTrailer(); + break; + + default: + ASSERT(false); + break; + } + + return PR_CONTINUE; +} + +bool +HttpParser::is_valid_end_of_input() const { + return (state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN); +} + +void +HttpParser::complete(HttpError error) { + if (state_ < ST_COMPLETE) { + state_ = ST_COMPLETE; + OnComplete(error); + } +} + +////////////////////////////////////////////////////////////////////// +// HttpBase::DocumentStream +////////////////////////////////////////////////////////////////////// + +class BlockingMemoryStream : public ExternalMemoryStream { +public: + BlockingMemoryStream(char* buffer, size_t size) + : ExternalMemoryStream(buffer, size) { } + + virtual StreamResult DoReserve(size_t size, int* error) { + return (buffer_length_ >= size) ? SR_SUCCESS : SR_BLOCK; + } +}; + +class HttpBase::DocumentStream : public StreamInterface { +public: + DocumentStream(HttpBase* base) : base_(base), error_(HE_DEFAULT) { } + + virtual StreamState GetState() const { + if (NULL == base_) + return SS_CLOSED; + if (HM_RECV == base_->mode_) + return SS_OPEN; + return SS_OPENING; + } + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (!base_) { + if (error) *error = error_; + return (HE_NONE == error_) ? SR_EOS : SR_ERROR; + } + + if (HM_RECV != base_->mode_) { + return SR_BLOCK; + } + + // DoReceiveLoop writes http document data to the StreamInterface* document + // member of HttpData. In this case, we want this data to be written + // directly to our buffer. To accomplish this, we wrap our buffer with a + // StreamInterface, and replace the existing document with our wrapper. + // When the method returns, we restore the old document. Ideally, we would + // pass our StreamInterface* to DoReceiveLoop, but due to the callbacks + // of HttpParser, we would still need to store the pointer temporarily. + scoped_ptr + stream(new BlockingMemoryStream(reinterpret_cast(buffer), + buffer_len)); + + // Replace the existing document with our wrapped buffer. + base_->data_->document.swap(stream); + + // Pump the I/O loop. DoReceiveLoop is guaranteed not to attempt to + // complete the I/O process, which means that our wrapper is not in danger + // of being deleted. To ensure this, DoReceiveLoop returns true when it + // wants complete to be called. We make sure to uninstall our wrapper + // before calling complete(). + HttpError http_error; + bool complete = base_->DoReceiveLoop(&http_error); + + // Reinstall the original output document. + base_->data_->document.swap(stream); + + // If we reach the end of the receive stream, we disconnect our stream + // adapter from the HttpBase, and further calls to read will either return + // EOS or ERROR, appropriately. Finally, we call complete(). + StreamResult result = SR_BLOCK; + if (complete) { + HttpBase* base = Disconnect(http_error); + if (error) *error = error_; + result = (HE_NONE == error_) ? SR_EOS : SR_ERROR; + base->complete(http_error); + } + + // Even if we are complete, if some data was read we must return SUCCESS. + // Future Reads will return EOS or ERROR based on the error_ variable. + size_t position; + stream->GetPosition(&position); + if (position > 0) { + if (read) *read = position; + result = SR_SUCCESS; + } + return result; + } + + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (error) *error = -1; + return SR_ERROR; + } + + virtual void Close() { + if (base_) { + HttpBase* base = Disconnect(HE_NONE); + if (HM_RECV == base->mode_ && base->http_stream_) { + // Read I/O could have been stalled on the user of this DocumentStream, + // so restart the I/O process now that we've removed ourselves. + base->http_stream_->PostEvent(SE_READ, 0); + } + } + } + + virtual bool GetAvailable(size_t* size) const { + if (!base_ || HM_RECV != base_->mode_) + return false; + size_t data_size = base_->GetDataRemaining(); + if (SIZE_UNKNOWN == data_size) + return false; + if (size) + *size = data_size; + return true; + } + + HttpBase* Disconnect(HttpError error) { + ASSERT(NULL != base_); + ASSERT(NULL != base_->doc_stream_); + HttpBase* base = base_; + base_->doc_stream_ = NULL; + base_ = NULL; + error_ = error; + return base; + } + +private: + HttpBase* base_; + HttpError error_; +}; + +////////////////////////////////////////////////////////////////////// +// HttpBase +////////////////////////////////////////////////////////////////////// + +HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL), + http_stream_(NULL), doc_stream_(NULL) { +} + +HttpBase::~HttpBase() { + ASSERT(HM_NONE == mode_); +} + +bool +HttpBase::isConnected() const { + return (http_stream_ != NULL) && (http_stream_->GetState() == SS_OPEN); +} + +bool +HttpBase::attach(StreamInterface* stream) { + if ((mode_ != HM_NONE) || (http_stream_ != NULL) || (stream == NULL)) { + ASSERT(false); + return false; + } + http_stream_ = stream; + http_stream_->SignalEvent.connect(this, &HttpBase::OnHttpStreamEvent); + mode_ = (http_stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE; + return true; +} + +StreamInterface* +HttpBase::detach() { + ASSERT(HM_NONE == mode_); + if (mode_ != HM_NONE) { + return NULL; + } + StreamInterface* stream = http_stream_; + http_stream_ = NULL; + if (stream) { + stream->SignalEvent.disconnect(this); + } + return stream; +} + +void +HttpBase::send(HttpData* data) { + ASSERT(HM_NONE == mode_); + if (mode_ != HM_NONE) { + return; + } else if (!isConnected()) { + OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED); + return; + } + + mode_ = HM_SEND; + data_ = data; + len_ = 0; + ignore_data_ = chunk_data_ = false; + + if (data_->document) { + data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent); + } + + std::string encoding; + if (data_->hasHeader(HH_TRANSFER_ENCODING, &encoding) + && (encoding == "chunked")) { + chunk_data_ = true; + } + + len_ = data_->formatLeader(buffer_, sizeof(buffer_)); + len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n"); + + header_ = data_->begin(); + if (header_ == data_->end()) { + // We must call this at least once, in the case where there are no headers. + queue_headers(); + } + + flush_data(); +} + +void +HttpBase::recv(HttpData* data) { + ASSERT(HM_NONE == mode_); + if (mode_ != HM_NONE) { + return; + } else if (!isConnected()) { + OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED); + return; + } + + mode_ = HM_RECV; + data_ = data; + len_ = 0; + ignore_data_ = chunk_data_ = false; + + reset(); + if (doc_stream_) { + doc_stream_->SignalEvent(doc_stream_, SE_OPEN | SE_READ, 0); + } else { + read_and_process_data(); + } +} + +void +HttpBase::abort(HttpError err) { + if (mode_ != HM_NONE) { + if (http_stream_ != NULL) { + http_stream_->Close(); + } + do_complete(err); + } +} + +StreamInterface* HttpBase::GetDocumentStream() { + if (doc_stream_) + return NULL; + doc_stream_ = new DocumentStream(this); + return doc_stream_; +} + +HttpError HttpBase::HandleStreamClose(int error) { + if (http_stream_ != NULL) { + http_stream_->Close(); + } + if (error == 0) { + if ((mode_ == HM_RECV) && is_valid_end_of_input()) { + return HE_NONE; + } else { + return HE_DISCONNECTED; + } + } else if (error == SOCKET_EACCES) { + return HE_AUTH; + } else if (error == SEC_E_CERT_EXPIRED) { + return HE_CERTIFICATE_EXPIRED; + } + LOG_F(LS_ERROR) << "(" << error << ")"; + return (HM_CONNECT == mode_) ? HE_CONNECT_FAILED : HE_SOCKET_ERROR; +} + +bool HttpBase::DoReceiveLoop(HttpError* error) { + ASSERT(HM_RECV == mode_); + ASSERT(NULL != error); + + // Do to the latency between receiving read notifications from + // pseudotcpchannel, we rely on repeated calls to read in order to acheive + // ideal throughput. The number of reads is limited to prevent starving + // the caller. + + size_t loop_count = 0; + const size_t kMaxReadCount = 20; + bool process_requires_more_data = false; + do { + // The most frequent use of this function is response to new data available + // on http_stream_. Therefore, we optimize by attempting to read from the + // network first (as opposed to processing existing data first). + + if (len_ < sizeof(buffer_)) { + // Attempt to buffer more data. + size_t read; + int read_error; + StreamResult read_result = http_stream_->Read(buffer_ + len_, + sizeof(buffer_) - len_, + &read, &read_error); + switch (read_result) { + case SR_SUCCESS: + ASSERT(len_ + read <= sizeof(buffer_)); + len_ += read; + break; + case SR_BLOCK: + if (process_requires_more_data) { + // We're can't make progress until more data is available. + return false; + } + // Attempt to process the data already in our buffer. + break; + case SR_EOS: + // Clean close, with no error. Fall through to HandleStreamClose. + read_error = 0; + case SR_ERROR: + *error = HandleStreamClose(read_error); + return true; + } + } else if (process_requires_more_data) { + // We have too much unprocessed data in our buffer. This should only + // occur when a single HTTP header is longer than the buffer size (32K). + // Anything longer than that is almost certainly an error. + *error = HE_OVERFLOW; + return true; + } + + // Process data in our buffer. Process is not guaranteed to process all + // the buffered data. In particular, it will wait until a complete + // protocol element (such as http header, or chunk size) is available, + // before processing it in its entirety. Also, it is valid and sometimes + // necessary to call Process with an empty buffer, since the state machine + // may have interrupted state transitions to complete. + size_t processed; + ProcessResult process_result = Process(buffer_, len_, &processed, + error); + ASSERT(processed <= len_); + len_ -= processed; + memmove(buffer_, buffer_ + processed, len_); + switch (process_result) { + case PR_CONTINUE: + // We need more data to make progress. + process_requires_more_data = true; + break; + case PR_BLOCK: + // We're stalled on writing the processed data. + return false; + case PR_COMPLETE: + // *error already contains the correct code. + return true; + } + } while (++loop_count <= kMaxReadCount); + + LOG_F(LS_WARNING) << "danger of starvation"; + return false; +} + +void +HttpBase::read_and_process_data() { + HttpError error; + if (DoReceiveLoop(&error)) { + complete(error); + } +} + +void +HttpBase::flush_data() { + ASSERT(HM_SEND == mode_); + + // When send_required is true, no more buffering can occur without a network + // write. + bool send_required = (len_ >= sizeof(buffer_)); + + while (true) { + ASSERT(len_ <= sizeof(buffer_)); + + // HTTP is inherently sensitive to round trip latency, since a frequent use + // case is for small requests and responses to be sent back and forth, and + // the lack of pipelining forces a single request to take a minimum of the + // round trip time. As a result, it is to our benefit to pack as much data + // into each packet as possible. Thus, we defer network writes until we've + // buffered as much data as possible. + + if (!send_required && (header_ != data_->end())) { + // First, attempt to queue more header data. + send_required = queue_headers(); + } + + if (!send_required && data_->document) { + // Next, attempt to queue document data. + + const size_t kChunkDigits = 8; + size_t offset, reserve; + if (chunk_data_) { + // Reserve characters at the start for X-byte hex value and \r\n + offset = len_ + kChunkDigits + 2; + // ... and 2 characters at the end for \r\n + reserve = offset + 2; + } else { + offset = len_; + reserve = offset; + } + + if (reserve >= sizeof(buffer_)) { + send_required = true; + } else { + size_t read; + int error; + StreamResult result = data_->document->Read(buffer_ + offset, + sizeof(buffer_) - reserve, + &read, &error); + if (result == SR_SUCCESS) { + ASSERT(reserve + read <= sizeof(buffer_)); + if (chunk_data_) { + // Prepend the chunk length in hex. + // Note: sprintfn appends a null terminator, which is why we can't + // combine it with the line terminator. + sprintfn(buffer_ + len_, kChunkDigits + 1, "%.*x", + kChunkDigits, read); + // Add line terminator to the chunk length. + memcpy(buffer_ + len_ + kChunkDigits, "\r\n", 2); + // Add line terminator to the end of the chunk. + memcpy(buffer_ + offset + read, "\r\n", 2); + } + len_ = reserve + read; + } else if (result == SR_BLOCK) { + // Nothing to do but flush data to the network. + send_required = true; + } else if (result == SR_EOS) { + if (chunk_data_) { + // Append the empty chunk and empty trailers, then turn off + // chunking. + ASSERT(len_ + 5 <= sizeof(buffer_)); + memcpy(buffer_ + len_, "0\r\n\r\n", 5); + len_ += 5; + chunk_data_ = false; + } else if (0 == len_) { + // No more data to read, and no more data to write. + do_complete(); + return; + } + // Although we are done reading data, there is still data which needs + // to be flushed to the network. + send_required = true; + } else { + LOG_F(LS_ERROR) << "Read error: " << error; + do_complete(HE_STREAM); + return; + } + } + } + + if (0 == len_) { + // No data currently available to send. + if (!data_->document) { + // If there is no source document, that means we're done. + do_complete(); + } + return; + } + + size_t written; + int error; + StreamResult result = http_stream_->Write(buffer_, len_, &written, &error); + if (result == SR_SUCCESS) { + ASSERT(written <= len_); + len_ -= written; + memmove(buffer_, buffer_ + written, len_); + send_required = false; + } else if (result == SR_BLOCK) { + if (send_required) { + // Nothing more we can do until network is writeable. + return; + } + } else { + ASSERT(result == SR_ERROR); + LOG_F(LS_ERROR) << "error"; + OnHttpStreamEvent(http_stream_, SE_CLOSE, error); + return; + } + } + + ASSERT(false); +} + +bool +HttpBase::queue_headers() { + ASSERT(HM_SEND == mode_); + while (header_ != data_->end()) { + size_t len = sprintfn(buffer_ + len_, sizeof(buffer_) - len_, + "%.*s: %.*s\r\n", + header_->first.size(), header_->first.data(), + header_->second.size(), header_->second.data()); + if (len_ + len < sizeof(buffer_) - 3) { + len_ += len; + ++header_; + } else if (len_ == 0) { + LOG(WARNING) << "discarding header that is too long: " << header_->first; + ++header_; + } else { + // Not enough room for the next header, write to network first. + return true; + } + } + // End of headers + len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n"); + return false; +} + +void +HttpBase::do_complete(HttpError err) { + ASSERT(mode_ != HM_NONE); + HttpMode mode = mode_; + mode_ = HM_NONE; + if (data_ && data_->document) { + data_->document->SignalEvent.disconnect(this); + } + data_ = NULL; + if ((HM_RECV == mode) && doc_stream_) { + ASSERT(HE_NONE != err); // We should have Disconnected doc_stream_ already. + DocumentStream* ds = doc_stream_; + ds->Disconnect(err); + ds->SignalEvent(ds, SE_CLOSE, err); + } + if (notify_) { + notify_->onHttpComplete(mode, err); + } +} + +// +// Stream Signals +// + +void +HttpBase::OnHttpStreamEvent(StreamInterface* stream, int events, int error) { + ASSERT(stream == http_stream_); + if ((events & SE_OPEN) && (mode_ == HM_CONNECT)) { + do_complete(); + return; + } + + if ((events & SE_WRITE) && (mode_ == HM_SEND)) { + flush_data(); + return; + } + + if ((events & SE_READ) && (mode_ == HM_RECV)) { + if (doc_stream_) { + doc_stream_->SignalEvent(doc_stream_, SE_READ, 0); + } else { + read_and_process_data(); + } + return; + } + + if ((events & SE_CLOSE) == 0) + return; + + HttpError http_error = HandleStreamClose(error); + if (mode_ == HM_RECV) { + complete(http_error); + } else if (mode_ != HM_NONE) { + do_complete(http_error); + } else if (notify_) { + notify_->onHttpClosed(http_error); + } +} + +void +HttpBase::OnDocumentEvent(StreamInterface* stream, int events, int error) { + ASSERT(stream == data_->document.get()); + if ((events & SE_WRITE) && (mode_ == HM_RECV)) { + read_and_process_data(); + return; + } + + if ((events & SE_READ) && (mode_ == HM_SEND)) { + flush_data(); + return; + } + + if (events & SE_CLOSE) { + LOG_F(LS_ERROR) << "Read error: " << error; + do_complete(HE_STREAM); + return; + } +} + +// +// HttpParser Implementation +// + +HttpParser::ProcessResult +HttpBase::ProcessLeader(const char* line, size_t len, HttpError* error) { + *error = data_->parseLeader(line, len); + return (HE_NONE == *error) ? PR_CONTINUE : PR_COMPLETE; +} + +HttpParser::ProcessResult +HttpBase::ProcessHeader(const char* name, size_t nlen, const char* value, + size_t vlen, HttpError* error) { + std::string sname(name, nlen), svalue(value, vlen); + data_->addHeader(sname, svalue); + return PR_CONTINUE; +} + +HttpParser::ProcessResult +HttpBase::ProcessHeaderComplete(bool chunked, size_t& data_size, + HttpError* error) { + StreamInterface* old_docstream = doc_stream_; + if (notify_) { + *error = notify_->onHttpHeaderComplete(chunked, data_size); + // The request must not be aborted as a result of this callback. + ASSERT(NULL != data_); + } + if ((HE_NONE == *error) && data_->document) { + data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent); + } + if (HE_NONE != *error) { + return PR_COMPLETE; + } + if (old_docstream != doc_stream_) { + // Break out of Process loop, since our I/O model just changed. + return PR_BLOCK; + } + return PR_CONTINUE; +} + +HttpParser::ProcessResult +HttpBase::ProcessData(const char* data, size_t len, size_t& read, + HttpError* error) { + if (ignore_data_ || !data_->document) { + read = len; + return PR_CONTINUE; + } + int write_error = 0; + switch (data_->document->Write(data, len, &read, &write_error)) { + case SR_SUCCESS: + return PR_CONTINUE; + case SR_BLOCK: + return PR_BLOCK; + case SR_EOS: + LOG_F(LS_ERROR) << "Unexpected EOS"; + *error = HE_STREAM; + return PR_COMPLETE; + case SR_ERROR: + default: + LOG_F(LS_ERROR) << "Write error: " << write_error; + *error = HE_STREAM; + return PR_COMPLETE; + } +} + +void +HttpBase::OnComplete(HttpError err) { + LOG_F(LS_VERBOSE); + do_complete(err); +} + +} // namespace rtc diff --git a/webrtc/base/httpbase.h b/webrtc/base/httpbase.h new file mode 100644 index 000000000..424a61f9f --- /dev/null +++ b/webrtc/base/httpbase.h @@ -0,0 +1,181 @@ +/* + * Copyright 2004 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_BASE_HTTPBASE_H__ +#define WEBRTC_BASE_HTTPBASE_H__ + +#include "webrtc/base/httpcommon.h" + +namespace rtc { + +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// HttpParser - Parses an HTTP stream provided via Process and end_of_input, and +// generates events for: +// Structural Elements: Leader, Headers, Document Data +// Events: End of Headers, End of Document, Errors +/////////////////////////////////////////////////////////////////////////////// + +class HttpParser { +public: + enum ProcessResult { PR_CONTINUE, PR_BLOCK, PR_COMPLETE }; + HttpParser(); + virtual ~HttpParser(); + + void reset(); + ProcessResult Process(const char* buffer, size_t len, size_t* processed, + HttpError* error); + bool is_valid_end_of_input() const; + void complete(HttpError err); + + size_t GetDataRemaining() const { return data_size_; } + +protected: + ProcessResult ProcessLine(const char* line, size_t len, HttpError* error); + + // HttpParser Interface + virtual ProcessResult ProcessLeader(const char* line, size_t len, + HttpError* error) = 0; + virtual ProcessResult ProcessHeader(const char* name, size_t nlen, + const char* value, size_t vlen, + HttpError* error) = 0; + virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size, + HttpError* error) = 0; + virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read, + HttpError* error) = 0; + virtual void OnComplete(HttpError err) = 0; + +private: + enum State { + ST_LEADER, ST_HEADERS, + ST_CHUNKSIZE, ST_CHUNKTERM, ST_TRAILERS, + ST_DATA, ST_COMPLETE + } state_; + bool chunked_; + size_t data_size_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// IHttpNotify +/////////////////////////////////////////////////////////////////////////////// + +enum HttpMode { HM_NONE, HM_CONNECT, HM_RECV, HM_SEND }; + +class IHttpNotify { +public: + virtual ~IHttpNotify() {} + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) = 0; + virtual void onHttpComplete(HttpMode mode, HttpError err) = 0; + virtual void onHttpClosed(HttpError err) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// HttpBase - Provides a state machine for implementing HTTP-based components. +// Attach HttpBase to a StreamInterface which represents a bidirectional HTTP +// stream, and then call send() or recv() to initiate sending or receiving one +// side of an HTTP transaction. By default, HttpBase operates as an I/O pump, +// moving data from the HTTP stream to the HttpData object and vice versa. +// However, it can also operate in stream mode, in which case the user of the +// stream interface drives I/O via calls to Read(). +/////////////////////////////////////////////////////////////////////////////// + +class HttpBase +: private HttpParser, + public sigslot::has_slots<> +{ +public: + HttpBase(); + virtual ~HttpBase(); + + void notify(IHttpNotify* notify) { notify_ = notify; } + bool attach(StreamInterface* stream); + StreamInterface* stream() { return http_stream_; } + StreamInterface* detach(); + bool isConnected() const; + + void send(HttpData* data); + void recv(HttpData* data); + void abort(HttpError err); + + HttpMode mode() const { return mode_; } + + void set_ignore_data(bool ignore) { ignore_data_ = ignore; } + bool ignore_data() const { return ignore_data_; } + + // Obtaining this stream puts HttpBase into stream mode until the stream + // is closed. HttpBase can only expose one open stream interface at a time. + // Further calls will return NULL. + StreamInterface* GetDocumentStream(); + +protected: + // Do cleanup when the http stream closes (error may be 0 for a clean + // shutdown), and return the error code to signal. + HttpError HandleStreamClose(int error); + + // DoReceiveLoop acts as a data pump, pulling data from the http stream, + // pushing it through the HttpParser, and then populating the HttpData object + // based on the callbacks from the parser. One of the most interesting + // callbacks is ProcessData, which provides the actual http document body. + // This data is then written to the HttpData::document. As a result, data + // flows from the network to the document, with some incidental protocol + // parsing in between. + // Ideally, we would pass in the document* to DoReceiveLoop, to more easily + // support GetDocumentStream(). However, since the HttpParser is callback + // driven, we are forced to store the pointer somewhere until the callback + // is triggered. + // Returns true if the received document has finished, and + // HttpParser::complete should be called. + bool DoReceiveLoop(HttpError* err); + + void read_and_process_data(); + void flush_data(); + bool queue_headers(); + void do_complete(HttpError err = HE_NONE); + + void OnHttpStreamEvent(StreamInterface* stream, int events, int error); + void OnDocumentEvent(StreamInterface* stream, int events, int error); + + // HttpParser Interface + virtual ProcessResult ProcessLeader(const char* line, size_t len, + HttpError* error); + virtual ProcessResult ProcessHeader(const char* name, size_t nlen, + const char* value, size_t vlen, + HttpError* error); + virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size, + HttpError* error); + virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read, + HttpError* error); + virtual void OnComplete(HttpError err); + +private: + class DocumentStream; + friend class DocumentStream; + + enum { kBufferSize = 32 * 1024 }; + + HttpMode mode_; + HttpData* data_; + IHttpNotify* notify_; + StreamInterface* http_stream_; + DocumentStream* doc_stream_; + char buffer_[kBufferSize]; + size_t len_; + + bool ignore_data_, chunk_data_; + HttpData::const_iterator header_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPBASE_H__ diff --git a/webrtc/base/httpbase_unittest.cc b/webrtc/base/httpbase_unittest.cc new file mode 100644 index 000000000..6dab0c9ac --- /dev/null +++ b/webrtc/base/httpbase_unittest.cc @@ -0,0 +1,520 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/httpbase.h" +#include "webrtc/base/testutils.h" + +namespace rtc { + +const char* const kHttpResponse = + "HTTP/1.1 200\r\n" + "Connection: Keep-Alive\r\n" + "Content-Type: text/plain\r\n" + "Proxy-Authorization: 42\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "00000008\r\n" + "Goodbye!\r\n" + "0\r\n\r\n"; + +const char* const kHttpEmptyResponse = + "HTTP/1.1 200\r\n" + "Connection: Keep-Alive\r\n" + "Content-Length: 0\r\n" + "Proxy-Authorization: 42\r\n" + "\r\n"; + +const char* const kHttpResponsePrefix = + "HTTP/1.1 200\r\n" + "Connection: Keep-Alive\r\n" + "Content-Type: text/plain\r\n" + "Proxy-Authorization: 42\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "8\r\n" + "Goodbye!\r\n"; + +class HttpBaseTest : public testing::Test, public IHttpNotify { +public: + enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED }; + struct Event { + EventType event; + bool chunked; + size_t data_size; + HttpMode mode; + HttpError err; + }; + HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { } + + virtual void SetUp() { } + virtual void TearDown() { + delete http_stream; + // Avoid an ASSERT, in case a test doesn't clean up properly + base.abort(HE_NONE); + } + + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) { + LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size; + Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE}; + events.push_back(e); + if (obtain_stream) { + ObtainDocumentStream(); + } + return HE_NONE; + } + virtual void onHttpComplete(HttpMode mode, HttpError err) { + LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err; + Event e = { E_COMPLETE, false, 0, mode, err }; + events.push_back(e); + } + virtual void onHttpClosed(HttpError err) { + LOG_F(LS_VERBOSE) << "err: " << err; + Event e = { E_CLOSED, false, 0, HM_NONE, err }; + events.push_back(e); + } + + void SetupSource(const char* response); + + void VerifyHeaderComplete(size_t event_count, bool empty_doc); + void VerifyDocumentContents(const char* expected_data, + size_t expected_length = SIZE_UNKNOWN); + + void ObtainDocumentStream(); + void VerifyDocumentStreamIsOpening(); + void VerifyDocumentStreamOpenEvent(); + void ReadDocumentStreamData(const char* expected_data); + void VerifyDocumentStreamIsEOS(); + + void SetupDocument(const char* response); + void VerifySourceContents(const char* expected_data, + size_t expected_length = SIZE_UNKNOWN); + + void VerifyTransferComplete(HttpMode mode, HttpError error); + + HttpBase base; + MemoryStream* mem; + HttpResponseData data; + + // The source of http data, and source events + testing::StreamSource src; + std::vector events; + + // Document stream, and stream events + bool obtain_stream; + StreamInterface* http_stream; + testing::StreamSink sink; +}; + +void HttpBaseTest::SetupSource(const char* http_data) { + LOG_F(LS_VERBOSE) << "Enter"; + + src.SetState(SS_OPENING); + src.QueueString(http_data); + + base.notify(this); + base.attach(&src); + EXPECT_TRUE(events.empty()); + + src.SetState(SS_OPEN); + ASSERT_EQ(1U, events.size()); + EXPECT_EQ(E_COMPLETE, events[0].event); + EXPECT_EQ(HM_CONNECT, events[0].mode); + EXPECT_EQ(HE_NONE, events[0].err); + events.clear(); + + mem = new MemoryStream; + data.document.reset(mem); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_EQ(event_count, events.size()); + EXPECT_EQ(E_HEADER_COMPLETE, events[0].event); + + std::string header; + EXPECT_EQ(HVER_1_1, data.version); + EXPECT_EQ(static_cast(HC_OK), data.scode); + EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header)); + EXPECT_EQ("42", header); + EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header)); + EXPECT_EQ("Keep-Alive", header); + + if (empty_doc) { + EXPECT_FALSE(events[0].chunked); + EXPECT_EQ(0U, events[0].data_size); + + EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header)); + EXPECT_EQ("0", header); + } else { + EXPECT_TRUE(events[0].chunked); + EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size); + + EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header)); + EXPECT_EQ("text/plain", header); + EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header)); + EXPECT_EQ("chunked", header); + } + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentContents(const char* expected_data, + size_t expected_length) { + LOG_F(LS_VERBOSE) << "Enter"; + + if (SIZE_UNKNOWN == expected_length) { + expected_length = strlen(expected_data); + } + EXPECT_EQ(mem, data.document.get()); + + size_t length; + mem->GetSize(&length); + EXPECT_EQ(expected_length, length); + EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::ObtainDocumentStream() { + LOG_F(LS_VERBOSE) << "Enter"; + EXPECT_FALSE(http_stream); + http_stream = base.GetDocumentStream(); + ASSERT_TRUE(NULL != http_stream); + sink.Monitor(http_stream); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentStreamIsOpening() { + LOG_F(LS_VERBOSE) << "Enter"; + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(0, sink.Events(http_stream)); + EXPECT_EQ(SS_OPENING, http_stream->GetState()); + + size_t read = 0; + char buffer[5] = { 0 }; + EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentStreamOpenEvent() { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream)); + EXPECT_EQ(SS_OPEN, http_stream->GetState()); + + // HTTP headers haven't arrived yet + EXPECT_EQ(0U, events.size()); + EXPECT_EQ(static_cast(HC_INTERNAL_SERVER_ERROR), data.scode); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(SS_OPEN, http_stream->GetState()); + + // Pump the HTTP I/O using Read, and verify the results. + size_t verified_length = 0; + const size_t expected_length = strlen(expected_data); + while (verified_length < expected_length) { + size_t read = 0; + char buffer[5] = { 0 }; + size_t amt_to_read = _min(expected_length - verified_length, sizeof(buffer)); + EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL)); + EXPECT_EQ(amt_to_read, read); + EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read)); + verified_length += read; + } + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyDocumentStreamIsEOS() { + LOG_F(LS_VERBOSE) << "Enter"; + + ASSERT_TRUE(NULL != http_stream); + size_t read = 0; + char buffer[5] = { 0 }; + EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL)); + EXPECT_EQ(SS_CLOSED, http_stream->GetState()); + + // When EOS is caused by Read, we don't expect SE_CLOSE + EXPECT_EQ(0, sink.Events(http_stream)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::SetupDocument(const char* document_data) { + LOG_F(LS_VERBOSE) << "Enter"; + src.SetState(SS_OPEN); + + base.notify(this); + base.attach(&src); + EXPECT_TRUE(events.empty()); + + if (document_data) { + // Note: we could just call data.set_success("text/plain", mem), but that + // won't allow us to use the chunked transfer encoding. + mem = new MemoryStream(document_data); + data.document.reset(mem); + data.setHeader(HH_CONTENT_TYPE, "text/plain"); + data.setHeader(HH_TRANSFER_ENCODING, "chunked"); + } else { + data.setHeader(HH_CONTENT_LENGTH, "0"); + } + data.scode = HC_OK; + data.setHeader(HH_PROXY_AUTHORIZATION, "42"); + data.setHeader(HH_CONNECTION, "Keep-Alive"); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifySourceContents(const char* expected_data, + size_t expected_length) { + LOG_F(LS_VERBOSE) << "Enter"; + if (SIZE_UNKNOWN == expected_length) { + expected_length = strlen(expected_data); + } + std::string contents = src.ReadData(); + EXPECT_EQ(expected_length, contents.length()); + EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length)); + LOG_F(LS_VERBOSE) << "Exit"; +} + +void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) { + LOG_F(LS_VERBOSE) << "Enter"; + // Verify that http operation has completed + ASSERT_TRUE(events.size() > 0); + size_t last_event = events.size() - 1; + EXPECT_EQ(E_COMPLETE, events[last_event].event); + EXPECT_EQ(mode, events[last_event].mode); + EXPECT_EQ(error, events[last_event].err); + LOG_F(LS_VERBOSE) << "Exit"; +} + +// +// Tests +// + +TEST_F(HttpBaseTest, SupportsSend) { + // Queue response document + SetupDocument("Goodbye!"); + + // Begin send + base.send(&data); + + // Send completed successfully + VerifyTransferComplete(HM_SEND, HE_NONE); + VerifySourceContents(kHttpResponse); +} + +TEST_F(HttpBaseTest, SupportsSendNoDocument) { + // Queue response document + SetupDocument(NULL); + + // Begin send + base.send(&data); + + // Send completed successfully + VerifyTransferComplete(HM_SEND, HE_NONE); + VerifySourceContents(kHttpEmptyResponse); +} + +TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) { + // This test is attempting to expose a bug that occurs when a particular + // base objects is used for receiving, and then used for sending. In + // particular, the HttpParser state is different after receiving. Simulate + // that here. + SetupSource(kHttpResponse); + base.recv(&data); + VerifyTransferComplete(HM_RECV, HE_NONE); + + src.Clear(); + data.clear(true); + events.clear(); + base.detach(); + + // Queue response document + SetupDocument("Goodbye!"); + + // Prevent entire response from being sent + const size_t kInterruptedLength = strlen(kHttpResponse) - 1; + src.SetWriteBlock(kInterruptedLength); + + // Begin send + base.send(&data); + + // Document is mostly complete, but no completion signal yet. + EXPECT_TRUE(events.empty()); + VerifySourceContents(kHttpResponse, kInterruptedLength); + + src.SetState(SS_CLOSED); + + // Send completed with disconnect error, and no additional data. + VerifyTransferComplete(HM_SEND, HE_DISCONNECTED); + EXPECT_TRUE(src.ReadData().empty()); +} + +TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) { + // Queue response document + SetupSource(kHttpResponse); + + // Begin receive + base.recv(&data); + + // Document completed successfully + VerifyHeaderComplete(2, false); + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents("Goodbye!"); +} + +TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) { + // Switch to pull mode + ObtainDocumentStream(); + VerifyDocumentStreamIsOpening(); + + // Queue response document + SetupSource(kHttpResponse); + VerifyDocumentStreamIsOpening(); + + // Begin receive + base.recv(&data); + + // Pull document data + VerifyDocumentStreamOpenEvent(); + ReadDocumentStreamData("Goodbye!"); + VerifyDocumentStreamIsEOS(); + + // Document completed successfully + VerifyHeaderComplete(2, false); + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents(""); +} + +TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) { + + // TODO: Remove extra logging once test failure is understood + int old_sev = rtc::LogMessage::GetLogToDebug(); + rtc::LogMessage::LogToDebug(LS_VERBOSE); + + + // Switch to pull mode + ObtainDocumentStream(); + VerifyDocumentStreamIsOpening(); + + // Queue response document + SetupSource(kHttpResponse); + VerifyDocumentStreamIsOpening(); + + // Begin receive + base.recv(&data); + + // Pull some of the data + VerifyDocumentStreamOpenEvent(); + ReadDocumentStreamData("Goodb"); + + // We've seen the header by now + VerifyHeaderComplete(1, false); + + // Close the pull stream, this will transition back to push I/O. + http_stream->Close(); + Thread::Current()->ProcessMessages(0); + + // Remainder of document completed successfully + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents("ye!"); + + rtc::LogMessage::LogToDebug(old_sev); +} + +TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) { + // Queue response document + SetupSource(kHttpResponse); + + // Switch to pull mode in response to header arrival + obtain_stream = true; + + // Begin receive + base.recv(&data); + + // We've already seen the header, but not data has arrived + VerifyHeaderComplete(1, false); + VerifyDocumentContents(""); + + // Pull the document data + ReadDocumentStreamData("Goodbye!"); + VerifyDocumentStreamIsEOS(); + + // Document completed successfully + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents(""); +} + +TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) { + // Queue empty response document + SetupSource(kHttpEmptyResponse); + + // Switch to pull mode in response to header arrival + obtain_stream = true; + + // Begin receive + base.recv(&data); + + // We've already seen the header, but not data has arrived + VerifyHeaderComplete(1, true); + VerifyDocumentContents(""); + + // The document is still open, until we attempt to read + ASSERT_TRUE(NULL != http_stream); + EXPECT_EQ(SS_OPEN, http_stream->GetState()); + + // Attempt to read data, and discover EOS + VerifyDocumentStreamIsEOS(); + + // Document completed successfully + VerifyTransferComplete(HM_RECV, HE_NONE); + VerifyDocumentContents(""); +} + +TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) { + // Switch to pull mode + ObtainDocumentStream(); + VerifyDocumentStreamIsOpening(); + + // Queue response document + SetupSource(kHttpResponsePrefix); + VerifyDocumentStreamIsOpening(); + + // Begin receive + base.recv(&data); + + // Pull document data + VerifyDocumentStreamOpenEvent(); + ReadDocumentStreamData("Goodbye!"); + + // Simulate unexpected close + src.SetState(SS_CLOSED); + + // Observe error event on document stream + EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream)); + + // Future reads give an error + int error = 0; + char buffer[5] = { 0 }; + EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error)); + EXPECT_EQ(HE_DISCONNECTED, error); + + // Document completed with error + VerifyHeaderComplete(2, false); + VerifyTransferComplete(HM_RECV, HE_DISCONNECTED); + VerifyDocumentContents(""); +} + +} // namespace rtc diff --git a/webrtc/base/httpclient.cc b/webrtc/base/httpclient.cc new file mode 100644 index 000000000..625677210 --- /dev/null +++ b/webrtc/base/httpclient.cc @@ -0,0 +1,829 @@ +/* + * Copyright 2004 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 "webrtc/base/httpcommon-inl.h" + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/diskcache.h" +#include "webrtc/base/httpclient.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Helpers +////////////////////////////////////////////////////////////////////// + +namespace { + +const size_t kCacheHeader = 0; +const size_t kCacheBody = 1; + +// Convert decimal string to integer +bool HttpStringToUInt(const std::string& str, size_t* val) { + ASSERT(NULL != val); + char* eos = NULL; + *val = strtoul(str.c_str(), &eos, 10); + return (*eos == '\0'); +} + +bool HttpShouldCache(const HttpTransaction& t) { + bool verb_allows_cache = (t.request.verb == HV_GET) + || (t.request.verb == HV_HEAD); + bool is_range_response = t.response.hasHeader(HH_CONTENT_RANGE, NULL); + bool has_expires = t.response.hasHeader(HH_EXPIRES, NULL); + bool request_allows_cache = + has_expires || (std::string::npos != t.request.path.find('?')); + bool response_allows_cache = + has_expires || HttpCodeIsCacheable(t.response.scode); + + bool may_cache = verb_allows_cache + && request_allows_cache + && response_allows_cache + && !is_range_response; + + std::string value; + if (t.response.hasHeader(HH_CACHE_CONTROL, &value)) { + HttpAttributeList directives; + HttpParseAttributes(value.data(), value.size(), directives); + // Response Directives Summary: + // public - always cacheable + // private - do not cache in a shared cache + // no-cache - may cache, but must revalidate whether fresh or stale + // no-store - sensitive information, do not cache or store in any way + // max-age - supplants Expires for staleness + // s-maxage - use as max-age for shared caches, ignore otherwise + // must-revalidate - may cache, but must revalidate after stale + // proxy-revalidate - shared cache must revalidate + if (HttpHasAttribute(directives, "no-store", NULL)) { + may_cache = false; + } else if (HttpHasAttribute(directives, "public", NULL)) { + may_cache = true; + } + } + return may_cache; +} + +enum HttpCacheState { + HCS_FRESH, // In cache, may use + HCS_STALE, // In cache, must revalidate + HCS_NONE // Not in cache +}; + +HttpCacheState HttpGetCacheState(const HttpTransaction& t) { + // Temporaries + std::string s_temp; + time_t u_temp; + + // Current time + size_t now = time(0); + + HttpAttributeList cache_control; + if (t.response.hasHeader(HH_CACHE_CONTROL, &s_temp)) { + HttpParseAttributes(s_temp.data(), s_temp.size(), cache_control); + } + + // Compute age of cache document + time_t date; + if (!t.response.hasHeader(HH_DATE, &s_temp) + || !HttpDateToSeconds(s_temp, &date)) + return HCS_NONE; + + // TODO: Timestamp when cache request sent and response received? + time_t request_time = date; + time_t response_time = date; + + time_t apparent_age = 0; + if (response_time > date) { + apparent_age = response_time - date; + } + + size_t corrected_received_age = apparent_age; + size_t i_temp; + if (t.response.hasHeader(HH_AGE, &s_temp) + && HttpStringToUInt(s_temp, (&i_temp))) { + u_temp = static_cast(i_temp); + corrected_received_age = stdmax(apparent_age, u_temp); + } + + size_t response_delay = response_time - request_time; + size_t corrected_initial_age = corrected_received_age + response_delay; + size_t resident_time = now - response_time; + size_t current_age = corrected_initial_age + resident_time; + + // Compute lifetime of document + size_t lifetime; + if (HttpHasAttribute(cache_control, "max-age", &s_temp)) { + lifetime = atoi(s_temp.c_str()); + } else if (t.response.hasHeader(HH_EXPIRES, &s_temp) + && HttpDateToSeconds(s_temp, &u_temp)) { + lifetime = u_temp - date; + } else if (t.response.hasHeader(HH_LAST_MODIFIED, &s_temp) + && HttpDateToSeconds(s_temp, &u_temp)) { + // TODO: Issue warning 113 if age > 24 hours + lifetime = static_cast(now - u_temp) / 10; + } else { + return HCS_STALE; + } + + return (lifetime > current_age) ? HCS_FRESH : HCS_STALE; +} + +enum HttpValidatorStrength { + HVS_NONE, + HVS_WEAK, + HVS_STRONG +}; + +HttpValidatorStrength +HttpRequestValidatorLevel(const HttpRequestData& request) { + if (HV_GET != request.verb) + return HVS_STRONG; + return request.hasHeader(HH_RANGE, NULL) ? HVS_STRONG : HVS_WEAK; +} + +HttpValidatorStrength +HttpResponseValidatorLevel(const HttpResponseData& response) { + std::string value; + if (response.hasHeader(HH_ETAG, &value)) { + bool is_weak = (strnicmp(value.c_str(), "W/", 2) == 0); + return is_weak ? HVS_WEAK : HVS_STRONG; + } + if (response.hasHeader(HH_LAST_MODIFIED, &value)) { + time_t last_modified, date; + if (HttpDateToSeconds(value, &last_modified) + && response.hasHeader(HH_DATE, &value) + && HttpDateToSeconds(value, &date) + && (last_modified + 60 < date)) { + return HVS_STRONG; + } + return HVS_WEAK; + } + return HVS_NONE; +} + +std::string GetCacheID(const HttpRequestData& request) { + std::string id, url; + id.append(ToString(request.verb)); + id.append("_"); + request.getAbsoluteUri(&url); + id.append(url); + return id; +} + +} // anonymous namespace + +////////////////////////////////////////////////////////////////////// +// Public Helpers +////////////////////////////////////////////////////////////////////// + +bool HttpWriteCacheHeaders(const HttpResponseData* response, + StreamInterface* output, size_t* size) { + size_t length = 0; + // Write all unknown and end-to-end headers to a cache file + for (HttpData::const_iterator it = response->begin(); + it != response->end(); ++it) { + HttpHeader header; + if (FromString(header, it->first) && !HttpHeaderIsEndToEnd(header)) + continue; + length += it->first.length() + 2 + it->second.length() + 2; + if (!output) + continue; + std::string formatted_header(it->first); + formatted_header.append(": "); + formatted_header.append(it->second); + formatted_header.append("\r\n"); + StreamResult result = output->WriteAll(formatted_header.data(), + formatted_header.length(), + NULL, NULL); + if (SR_SUCCESS != result) { + return false; + } + } + if (output && (SR_SUCCESS != output->WriteAll("\r\n", 2, NULL, NULL))) { + return false; + } + length += 2; + if (size) + *size = length; + return true; +} + +bool HttpReadCacheHeaders(StreamInterface* input, HttpResponseData* response, + HttpData::HeaderCombine combine) { + while (true) { + std::string formatted_header; + StreamResult result = input->ReadLine(&formatted_header); + if ((SR_EOS == result) || (1 == formatted_header.size())) { + break; + } + if (SR_SUCCESS != result) { + return false; + } + size_t end_of_name = formatted_header.find(':'); + if (std::string::npos == end_of_name) { + LOG_F(LS_WARNING) << "Malformed cache header"; + continue; + } + size_t start_of_value = end_of_name + 1; + size_t end_of_value = formatted_header.length(); + while ((start_of_value < end_of_value) + && isspace(formatted_header[start_of_value])) + ++start_of_value; + while ((start_of_value < end_of_value) + && isspace(formatted_header[end_of_value-1])) + --end_of_value; + size_t value_length = end_of_value - start_of_value; + + std::string name(formatted_header.substr(0, end_of_name)); + std::string value(formatted_header.substr(start_of_value, value_length)); + response->changeHeader(name, value, combine); + } + return true; +} + +////////////////////////////////////////////////////////////////////// +// HttpClient +////////////////////////////////////////////////////////////////////// + +const size_t kDefaultRetries = 1; +const size_t kMaxRedirects = 5; + +HttpClient::HttpClient(const std::string& agent, StreamPool* pool, + HttpTransaction* transaction) + : agent_(agent), pool_(pool), + transaction_(transaction), free_transaction_(false), + retries_(kDefaultRetries), attempt_(0), redirects_(0), + redirect_action_(REDIRECT_DEFAULT), + uri_form_(URI_DEFAULT), cache_(NULL), cache_state_(CS_READY), + resolver_(NULL) { + base_.notify(this); + if (NULL == transaction_) { + free_transaction_ = true; + transaction_ = new HttpTransaction; + } +} + +HttpClient::~HttpClient() { + base_.notify(NULL); + base_.abort(HE_SHUTDOWN); + if (resolver_) { + resolver_->Destroy(false); + } + release(); + if (free_transaction_) + delete transaction_; +} + +void HttpClient::reset() { + server_.Clear(); + request().clear(true); + response().clear(true); + context_.reset(); + redirects_ = 0; + base_.abort(HE_OPERATION_CANCELLED); +} + +void HttpClient::OnResolveResult(AsyncResolverInterface* resolver) { + if (resolver != resolver_) { + return; + } + int error = resolver_->GetError(); + server_ = resolver_->address(); + resolver_->Destroy(false); + resolver_ = NULL; + if (error != 0) { + LOG(LS_ERROR) << "Error " << error << " resolving name: " + << server_; + onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED); + } else { + connect(); + } +} + +void HttpClient::StartDNSLookup() { + resolver_ = new AsyncResolver(); + resolver_->SignalDone.connect(this, &HttpClient::OnResolveResult); + resolver_->Start(server_); +} + +void HttpClient::set_server(const SocketAddress& address) { + server_ = address; + // Setting 'Host' here allows it to be overridden before starting the request, + // if necessary. + request().setHeader(HH_HOST, HttpAddress(server_, false), true); +} + +StreamInterface* HttpClient::GetDocumentStream() { + return base_.GetDocumentStream(); +} + +void HttpClient::start() { + if (base_.mode() != HM_NONE) { + // call reset() to abort an in-progress request + ASSERT(false); + return; + } + + ASSERT(!IsCacheActive()); + + if (request().hasHeader(HH_TRANSFER_ENCODING, NULL)) { + // Exact size must be known on the client. Instead of using chunked + // encoding, wrap data with auto-caching file or memory stream. + ASSERT(false); + return; + } + + attempt_ = 0; + + // If no content has been specified, using length of 0. + request().setHeader(HH_CONTENT_LENGTH, "0", false); + + if (!agent_.empty()) { + request().setHeader(HH_USER_AGENT, agent_, false); + } + + UriForm uri_form = uri_form_; + if (PROXY_HTTPS == proxy_.type) { + // Proxies require absolute form + uri_form = URI_ABSOLUTE; + request().version = HVER_1_0; + request().setHeader(HH_PROXY_CONNECTION, "Keep-Alive", false); + } else { + request().setHeader(HH_CONNECTION, "Keep-Alive", false); + } + + if (URI_ABSOLUTE == uri_form) { + // Convert to absolute uri form + std::string url; + if (request().getAbsoluteUri(&url)) { + request().path = url; + } else { + LOG(LS_WARNING) << "Couldn't obtain absolute uri"; + } + } else if (URI_RELATIVE == uri_form) { + // Convert to relative uri form + std::string host, path; + if (request().getRelativeUri(&host, &path)) { + request().setHeader(HH_HOST, host); + request().path = path; + } else { + LOG(LS_WARNING) << "Couldn't obtain relative uri"; + } + } + + if ((NULL != cache_) && CheckCache()) { + return; + } + + connect(); +} + +void HttpClient::connect() { + int stream_err; + if (server_.IsUnresolvedIP()) { + StartDNSLookup(); + return; + } + StreamInterface* stream = pool_->RequestConnectedStream(server_, &stream_err); + if (stream == NULL) { + ASSERT(0 != stream_err); + LOG(LS_ERROR) << "RequestConnectedStream error: " << stream_err; + onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED); + } else { + base_.attach(stream); + if (stream->GetState() == SS_OPEN) { + base_.send(&transaction_->request); + } + } +} + +void HttpClient::prepare_get(const std::string& url) { + reset(); + Url purl(url); + set_server(SocketAddress(purl.host(), purl.port())); + request().verb = HV_GET; + request().path = purl.full_path(); +} + +void HttpClient::prepare_post(const std::string& url, + const std::string& content_type, + StreamInterface* request_doc) { + reset(); + Url purl(url); + set_server(SocketAddress(purl.host(), purl.port())); + request().verb = HV_POST; + request().path = purl.full_path(); + request().setContent(content_type, request_doc); +} + +void HttpClient::release() { + if (StreamInterface* stream = base_.detach()) { + pool_->ReturnConnectedStream(stream); + } +} + +bool HttpClient::ShouldRedirect(std::string* location) const { + // TODO: Unittest redirection. + if ((REDIRECT_NEVER == redirect_action_) + || !HttpCodeIsRedirection(response().scode) + || !response().hasHeader(HH_LOCATION, location) + || (redirects_ >= kMaxRedirects)) + return false; + return (REDIRECT_ALWAYS == redirect_action_) + || (HC_SEE_OTHER == response().scode) + || (HV_HEAD == request().verb) + || (HV_GET == request().verb); +} + +bool HttpClient::BeginCacheFile() { + ASSERT(NULL != cache_); + ASSERT(CS_READY == cache_state_); + + std::string id = GetCacheID(request()); + CacheLock lock(cache_, id, true); + if (!lock.IsLocked()) { + LOG_F(LS_WARNING) << "Couldn't lock cache"; + return false; + } + + if (HE_NONE != WriteCacheHeaders(id)) { + return false; + } + + scoped_ptr stream(cache_->WriteResource(id, kCacheBody)); + if (!stream) { + LOG_F(LS_ERROR) << "Couldn't open body cache"; + return false; + } + lock.Commit(); + + // Let's secretly replace the response document with Folgers Crystals, + // er, StreamTap, so that we can mirror the data to our cache. + StreamInterface* output = response().document.release(); + if (!output) { + output = new NullStream; + } + StreamTap* tap = new StreamTap(output, stream.release()); + response().document.reset(tap); + return true; +} + +HttpError HttpClient::WriteCacheHeaders(const std::string& id) { + scoped_ptr stream(cache_->WriteResource(id, kCacheHeader)); + if (!stream) { + LOG_F(LS_ERROR) << "Couldn't open header cache"; + return HE_CACHE; + } + + if (!HttpWriteCacheHeaders(&transaction_->response, stream.get(), NULL)) { + LOG_F(LS_ERROR) << "Couldn't write header cache"; + return HE_CACHE; + } + + return HE_NONE; +} + +void HttpClient::CompleteCacheFile() { + // Restore previous response document + StreamTap* tap = static_cast(response().document.release()); + response().document.reset(tap->Detach()); + + int error; + StreamResult result = tap->GetTapResult(&error); + + // Delete the tap and cache stream (which completes cache unlock) + delete tap; + + if (SR_SUCCESS != result) { + LOG(LS_ERROR) << "Cache file error: " << error; + cache_->DeleteResource(GetCacheID(request())); + } +} + +bool HttpClient::CheckCache() { + ASSERT(NULL != cache_); + ASSERT(CS_READY == cache_state_); + + std::string id = GetCacheID(request()); + if (!cache_->HasResource(id)) { + // No cache file available + return false; + } + + HttpError error = ReadCacheHeaders(id, true); + + if (HE_NONE == error) { + switch (HttpGetCacheState(*transaction_)) { + case HCS_FRESH: + // Cache content is good, read from cache + break; + case HCS_STALE: + // Cache content may be acceptable. Issue a validation request. + if (PrepareValidate()) { + return false; + } + // Couldn't validate, fall through. + case HCS_NONE: + // Cache content is not useable. Issue a regular request. + response().clear(false); + return false; + } + } + + if (HE_NONE == error) { + error = ReadCacheBody(id); + cache_state_ = CS_READY; + } + + if (HE_CACHE == error) { + LOG_F(LS_WARNING) << "Cache failure, continuing with normal request"; + response().clear(false); + return false; + } + + SignalHttpClientComplete(this, error); + return true; +} + +HttpError HttpClient::ReadCacheHeaders(const std::string& id, bool override) { + scoped_ptr stream(cache_->ReadResource(id, kCacheHeader)); + if (!stream) { + return HE_CACHE; + } + + HttpData::HeaderCombine combine = + override ? HttpData::HC_REPLACE : HttpData::HC_AUTO; + + if (!HttpReadCacheHeaders(stream.get(), &transaction_->response, combine)) { + LOG_F(LS_ERROR) << "Error reading cache headers"; + return HE_CACHE; + } + + response().scode = HC_OK; + return HE_NONE; +} + +HttpError HttpClient::ReadCacheBody(const std::string& id) { + cache_state_ = CS_READING; + + HttpError error = HE_NONE; + + size_t data_size; + scoped_ptr stream(cache_->ReadResource(id, kCacheBody)); + if (!stream || !stream->GetAvailable(&data_size)) { + LOG_F(LS_ERROR) << "Unavailable cache body"; + error = HE_CACHE; + } else { + error = OnHeaderAvailable(false, false, data_size); + } + + if ((HE_NONE == error) + && (HV_HEAD != request().verb) + && response().document) { + char buffer[1024 * 64]; + StreamResult result = Flow(stream.get(), buffer, ARRAY_SIZE(buffer), + response().document.get()); + if (SR_SUCCESS != result) { + error = HE_STREAM; + } + } + + return error; +} + +bool HttpClient::PrepareValidate() { + ASSERT(CS_READY == cache_state_); + // At this point, request() contains the pending request, and response() + // contains the cached response headers. Reformat the request to validate + // the cached content. + HttpValidatorStrength vs_required = HttpRequestValidatorLevel(request()); + HttpValidatorStrength vs_available = HttpResponseValidatorLevel(response()); + if (vs_available < vs_required) { + return false; + } + std::string value; + if (response().hasHeader(HH_ETAG, &value)) { + request().addHeader(HH_IF_NONE_MATCH, value); + } + if (response().hasHeader(HH_LAST_MODIFIED, &value)) { + request().addHeader(HH_IF_MODIFIED_SINCE, value); + } + response().clear(false); + cache_state_ = CS_VALIDATING; + return true; +} + +HttpError HttpClient::CompleteValidate() { + ASSERT(CS_VALIDATING == cache_state_); + + std::string id = GetCacheID(request()); + + // Merge cached headers with new headers + HttpError error = ReadCacheHeaders(id, false); + if (HE_NONE != error) { + // Rewrite merged headers to cache + CacheLock lock(cache_, id); + error = WriteCacheHeaders(id); + } + if (HE_NONE != error) { + error = ReadCacheBody(id); + } + return error; +} + +HttpError HttpClient::OnHeaderAvailable(bool ignore_data, bool chunked, + size_t data_size) { + // If we are ignoring the data, this is an intermediate header. + // TODO: don't signal intermediate headers. Instead, do all header-dependent + // processing now, and either set up the next request, or fail outright. + // TODO: by default, only write response documents with a success code. + SignalHeaderAvailable(this, !ignore_data, ignore_data ? 0 : data_size); + if (!ignore_data && !chunked && (data_size != SIZE_UNKNOWN) + && response().document) { + // Attempt to pre-allocate space for the downloaded data. + if (!response().document->ReserveSize(data_size)) { + return HE_OVERFLOW; + } + } + return HE_NONE; +} + +// +// HttpBase Implementation +// + +HttpError HttpClient::onHttpHeaderComplete(bool chunked, size_t& data_size) { + if (CS_VALIDATING == cache_state_) { + if (HC_NOT_MODIFIED == response().scode) { + return CompleteValidate(); + } + // Should we remove conditional headers from request? + cache_state_ = CS_READY; + cache_->DeleteResource(GetCacheID(request())); + // Continue processing response as normal + } + + ASSERT(!IsCacheActive()); + if ((request().verb == HV_HEAD) || !HttpCodeHasBody(response().scode)) { + // HEAD requests and certain response codes contain no body + data_size = 0; + } + if (ShouldRedirect(NULL) + || ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode) + && (PROXY_HTTPS == proxy_.type))) { + // We're going to issue another request, so ignore the incoming data. + base_.set_ignore_data(true); + } + + HttpError error = OnHeaderAvailable(base_.ignore_data(), chunked, data_size); + if (HE_NONE != error) { + return error; + } + + if ((NULL != cache_) + && !base_.ignore_data() + && HttpShouldCache(*transaction_)) { + if (BeginCacheFile()) { + cache_state_ = CS_WRITING; + } + } + return HE_NONE; +} + +void HttpClient::onHttpComplete(HttpMode mode, HttpError err) { + if (((HE_DISCONNECTED == err) || (HE_CONNECT_FAILED == err) + || (HE_SOCKET_ERROR == err)) + && (HC_INTERNAL_SERVER_ERROR == response().scode) + && (attempt_ < retries_)) { + // If the response code has not changed from the default, then we haven't + // received anything meaningful from the server, so we are eligible for a + // retry. + ++attempt_; + if (request().document && !request().document->Rewind()) { + // Unable to replay the request document. + err = HE_STREAM; + } else { + release(); + connect(); + return; + } + } else if (err != HE_NONE) { + // fall through + } else if (mode == HM_CONNECT) { + base_.send(&transaction_->request); + return; + } else if ((mode == HM_SEND) || HttpCodeIsInformational(response().scode)) { + // If you're interested in informational headers, catch + // SignalHeaderAvailable. + base_.recv(&transaction_->response); + return; + } else { + if (!HttpShouldKeepAlive(response())) { + LOG(LS_VERBOSE) << "HttpClient: closing socket"; + base_.stream()->Close(); + } + std::string location; + if (ShouldRedirect(&location)) { + Url purl(location); + set_server(SocketAddress(purl.host(), purl.port())); + request().path = purl.full_path(); + if (response().scode == HC_SEE_OTHER) { + request().verb = HV_GET; + request().clearHeader(HH_CONTENT_TYPE); + request().clearHeader(HH_CONTENT_LENGTH); + request().document.reset(); + } else if (request().document && !request().document->Rewind()) { + // Unable to replay the request document. + ASSERT(REDIRECT_ALWAYS == redirect_action_); + err = HE_STREAM; + } + if (err == HE_NONE) { + ++redirects_; + context_.reset(); + response().clear(false); + release(); + start(); + return; + } + } else if ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode) + && (PROXY_HTTPS == proxy_.type)) { + std::string authorization, auth_method; + HttpData::const_iterator begin = response().begin(HH_PROXY_AUTHENTICATE); + HttpData::const_iterator end = response().end(HH_PROXY_AUTHENTICATE); + for (HttpData::const_iterator it = begin; it != end; ++it) { + HttpAuthContext *context = context_.get(); + HttpAuthResult res = HttpAuthenticate( + it->second.data(), it->second.size(), + proxy_.address, + ToString(request().verb), request().path, + proxy_.username, proxy_.password, + context, authorization, auth_method); + context_.reset(context); + if (res == HAR_RESPONSE) { + request().setHeader(HH_PROXY_AUTHORIZATION, authorization); + if (request().document && !request().document->Rewind()) { + err = HE_STREAM; + } else { + // Explicitly do not reset the HttpAuthContext + response().clear(false); + // TODO: Reuse socket when authenticating? + release(); + start(); + return; + } + } else if (res == HAR_IGNORE) { + LOG(INFO) << "Ignoring Proxy-Authenticate: " << auth_method; + continue; + } else { + break; + } + } + } + } + if (CS_WRITING == cache_state_) { + CompleteCacheFile(); + cache_state_ = CS_READY; + } else if (CS_READING == cache_state_) { + cache_state_ = CS_READY; + } + release(); + SignalHttpClientComplete(this, err); +} + +void HttpClient::onHttpClosed(HttpError err) { + // This shouldn't occur, since we return the stream to the pool upon command + // completion. + ASSERT(false); +} + +////////////////////////////////////////////////////////////////////// +// HttpClientDefault +////////////////////////////////////////////////////////////////////// + +HttpClientDefault::HttpClientDefault(SocketFactory* factory, + const std::string& agent, + HttpTransaction* transaction) + : ReuseSocketPool(factory ? factory : Thread::Current()->socketserver()), + HttpClient(agent, NULL, transaction) { + set_pool(this); +} + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/httpclient.h b/webrtc/base/httpclient.h new file mode 100644 index 000000000..b634b9345 --- /dev/null +++ b/webrtc/base/httpclient.h @@ -0,0 +1,202 @@ +/* + * Copyright 2004 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_BASE_HTTPCLIENT_H__ +#define WEBRTC_BASE_HTTPCLIENT_H__ + +#include "webrtc/base/common.h" +#include "webrtc/base/httpbase.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/socketpool.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Client-specific http utilities +////////////////////////////////////////////////////////////////////// + +// Write cache-relevant response headers to output stream. If size is non-null, +// it contains the length of the output in bytes. output may be null if only +// the length is desired. +bool HttpWriteCacheHeaders(const HttpResponseData* response, + StreamInterface* output, size_t* size); +// Read cached headers from a stream, and them merge them into the response +// object using the specified combine operation. +bool HttpReadCacheHeaders(StreamInterface* input, + HttpResponseData* response, + HttpData::HeaderCombine combine); + +////////////////////////////////////////////////////////////////////// +// HttpClient +// Implements an HTTP 1.1 client. +////////////////////////////////////////////////////////////////////// + +class DiskCache; +class HttpClient; +class IPNetPool; + +class SignalThread; +// What to do: Define STRICT_HTTP_ERROR=1 in your makefile. Use HttpError in +// your code (HttpErrorType should only be used for code that is shared +// with groups which have not yet migrated). +#if STRICT_HTTP_ERROR +typedef HttpError HttpErrorType; +#else // !STRICT_HTTP_ERROR +typedef int HttpErrorType; +#endif // !STRICT_HTTP_ERROR + +class HttpClient : private IHttpNotify, public sigslot::has_slots<> { +public: + // If HttpRequestData and HttpResponseData objects are provided, they must + // be freed by the caller. Otherwise, an internal object is allocated. + HttpClient(const std::string& agent, StreamPool* pool, + HttpTransaction* transaction = NULL); + virtual ~HttpClient(); + + void set_pool(StreamPool* pool) { pool_ = pool; } + + void set_agent(const std::string& agent) { agent_ = agent; } + const std::string& agent() const { return agent_; } + + void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; } + const ProxyInfo& proxy() const { return proxy_; } + + // Request retries occur when the connection closes before the beginning of + // an http response is received. In these cases, the http server may have + // timed out the keepalive connection before it received our request. Note + // that if a request document cannot be rewound, no retry is made. The + // default is 1. + void set_request_retries(size_t retries) { retries_ = retries; } + size_t request_retries() const { return retries_; } + + enum RedirectAction { REDIRECT_DEFAULT, REDIRECT_ALWAYS, REDIRECT_NEVER }; + void set_redirect_action(RedirectAction action) { redirect_action_ = action; } + RedirectAction redirect_action() const { return redirect_action_; } + // Deprecated + void set_fail_redirect(bool fail_redirect) { + redirect_action_ = REDIRECT_NEVER; + } + bool fail_redirect() const { return (REDIRECT_NEVER == redirect_action_); } + + enum UriForm { URI_DEFAULT, URI_ABSOLUTE, URI_RELATIVE }; + void set_uri_form(UriForm form) { uri_form_ = form; } + UriForm uri_form() const { return uri_form_; } + + void set_cache(DiskCache* cache) { ASSERT(!IsCacheActive()); cache_ = cache; } + bool cache_enabled() const { return (NULL != cache_); } + + // reset clears the server, request, and response structures. It will also + // abort an active request. + void reset(); + + void set_server(const SocketAddress& address); + const SocketAddress& server() const { return server_; } + + // Note: in order for HttpClient to retry a POST in response to + // an authentication challenge, a redirect response, or socket disconnection, + // the request document must support 'replaying' by calling Rewind() on it. + // In the case where just a subset of a stream should be used as the request + // document, the stream may be wrapped with the StreamSegment adapter. + HttpTransaction* transaction() { return transaction_; } + const HttpTransaction* transaction() const { return transaction_; } + HttpRequestData& request() { return transaction_->request; } + const HttpRequestData& request() const { return transaction_->request; } + HttpResponseData& response() { return transaction_->response; } + const HttpResponseData& response() const { return transaction_->response; } + + // convenience methods + void prepare_get(const std::string& url); + void prepare_post(const std::string& url, const std::string& content_type, + StreamInterface* request_doc); + + // Convert HttpClient to a pull-based I/O model. + StreamInterface* GetDocumentStream(); + + // After you finish setting up your request, call start. + void start(); + + // Signalled when the header has finished downloading, before the document + // content is processed. You may change the response document in response + // to this signal. The second parameter indicates whether this is an + // intermediate (false) or final (true) header. An intermediate header is + // one that generates another request, such as a redirect or authentication + // challenge. The third parameter indicates the length of the response + // document, or else SIZE_UNKNOWN. Note: Do NOT abort the request in response + // to this signal. + sigslot::signal3 SignalHeaderAvailable; + // Signalled when the current request finishes. On success, err is 0. + sigslot::signal2 SignalHttpClientComplete; + +protected: + void connect(); + void release(); + + bool ShouldRedirect(std::string* location) const; + + bool BeginCacheFile(); + HttpError WriteCacheHeaders(const std::string& id); + void CompleteCacheFile(); + + bool CheckCache(); + HttpError ReadCacheHeaders(const std::string& id, bool override); + HttpError ReadCacheBody(const std::string& id); + + bool PrepareValidate(); + HttpError CompleteValidate(); + + HttpError OnHeaderAvailable(bool ignore_data, bool chunked, size_t data_size); + + void StartDNSLookup(); + void OnResolveResult(AsyncResolverInterface* resolver); + + // IHttpNotify Interface + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size); + virtual void onHttpComplete(HttpMode mode, HttpError err); + virtual void onHttpClosed(HttpError err); + +private: + enum CacheState { CS_READY, CS_WRITING, CS_READING, CS_VALIDATING }; + bool IsCacheActive() const { return (cache_state_ > CS_READY); } + + std::string agent_; + StreamPool* pool_; + HttpBase base_; + SocketAddress server_; + ProxyInfo proxy_; + HttpTransaction* transaction_; + bool free_transaction_; + size_t retries_, attempt_, redirects_; + RedirectAction redirect_action_; + UriForm uri_form_; + scoped_ptr context_; + DiskCache* cache_; + CacheState cache_state_; + AsyncResolverInterface* resolver_; +}; + +////////////////////////////////////////////////////////////////////// +// HttpClientDefault - Default implementation of HttpClient +////////////////////////////////////////////////////////////////////// + +class HttpClientDefault : public ReuseSocketPool, public HttpClient { +public: + HttpClientDefault(SocketFactory* factory, const std::string& agent, + HttpTransaction* transaction = NULL); +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPCLIENT_H__ diff --git a/webrtc/base/httpcommon-inl.h b/webrtc/base/httpcommon-inl.h new file mode 100644 index 000000000..2f525ce79 --- /dev/null +++ b/webrtc/base/httpcommon-inl.h @@ -0,0 +1,131 @@ +/* + * Copyright 2004 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_BASE_HTTPCOMMON_INL_H__ +#define WEBRTC_BASE_HTTPCOMMON_INL_H__ + +#include "webrtc/base/common.h" +#include "webrtc/base/httpcommon.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Url +/////////////////////////////////////////////////////////////////////////////// + +template +void Url::do_set_url(const CTYPE* val, size_t len) { + if (ascnicmp(val, "http://", 7) == 0) { + val += 7; len -= 7; + secure_ = false; + } else if (ascnicmp(val, "https://", 8) == 0) { + val += 8; len -= 8; + secure_ = true; + } else { + clear(); + return; + } + const CTYPE* path = strchrn(val, len, static_cast('/')); + if (!path) { + path = val + len; + } + size_t address_length = (path - val); + do_set_address(val, address_length); + do_set_full_path(path, len - address_length); +} + +template +void Url::do_set_address(const CTYPE* val, size_t len) { + if (const CTYPE* at = strchrn(val, len, static_cast('@'))) { + // Everything before the @ is a user:password combo, so skip it. + len -= at - val + 1; + val = at + 1; + } + if (const CTYPE* colon = strchrn(val, len, static_cast(':'))) { + host_.assign(val, colon - val); + // Note: In every case, we're guaranteed that colon is followed by a null, + // or non-numeric character. + port_ = static_cast(::strtoul(colon + 1, NULL, 10)); + // TODO: Consider checking for invalid data following port number. + } else { + host_.assign(val, len); + port_ = HttpDefaultPort(secure_); + } +} + +template +void Url::do_set_full_path(const CTYPE* val, size_t len) { + const CTYPE* query = strchrn(val, len, static_cast('?')); + if (!query) { + query = val + len; + } + size_t path_length = (query - val); + if (0 == path_length) { + // TODO: consider failing in this case. + path_.assign(1, static_cast('/')); + } else { + ASSERT(val[0] == static_cast('/')); + path_.assign(val, path_length); + } + query_.assign(query, len - path_length); +} + +template +void Url::do_get_url(string* val) const { + CTYPE protocol[9]; + asccpyn(protocol, ARRAY_SIZE(protocol), secure_ ? "https://" : "http://"); + val->append(protocol); + do_get_address(val); + do_get_full_path(val); +} + +template +void Url::do_get_address(string* val) const { + val->append(host_); + if (port_ != HttpDefaultPort(secure_)) { + CTYPE format[5], port[32]; + asccpyn(format, ARRAY_SIZE(format), ":%hu"); + sprintfn(port, ARRAY_SIZE(port), format, port_); + val->append(port); + } +} + +template +void Url::do_get_full_path(string* val) const { + val->append(path_); + val->append(query_); +} + +template +bool Url::get_attribute(const string& name, string* value) const { + if (query_.empty()) + return false; + + std::string::size_type pos = query_.find(name, 1); + if (std::string::npos == pos) + return false; + + pos += name.length() + 1; + if ((pos > query_.length()) || (static_cast('=') != query_[pos-1])) + return false; + + std::string::size_type end = query_.find(static_cast('&'), pos); + if (std::string::npos == end) { + end = query_.length(); + } + value->assign(query_.substr(pos, end - pos)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPCOMMON_INL_H__ diff --git a/webrtc/base/httpcommon.cc b/webrtc/base/httpcommon.cc new file mode 100644 index 000000000..095cdafef --- /dev/null +++ b/webrtc/base/httpcommon.cc @@ -0,0 +1,1045 @@ +/* + * Copyright 2004 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 + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#define SECURITY_WIN32 +#include +#endif + +#include "webrtc/base/httpcommon-inl.h" + +#include "webrtc/base/base64.h" +#include "webrtc/base/common.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/stringdigest.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +#if defined(WEBRTC_WIN) +extern const ConstantLabel SECURITY_ERRORS[]; +#endif + +////////////////////////////////////////////////////////////////////// +// Enum - TODO: expose globally later? +////////////////////////////////////////////////////////////////////// + +bool find_string(size_t& index, const std::string& needle, + const char* const haystack[], size_t max_index) { + for (index=0; index +struct Enum { + static const char** Names; + static size_t Size; + + static inline const char* Name(E val) { return Names[val]; } + static inline bool Parse(E& val, const std::string& name) { + size_t index; + if (!find_string(index, name, Names, Size)) + return false; + val = static_cast(index); + return true; + } + + E val; + + inline operator E&() { return val; } + inline Enum& operator=(E rhs) { val = rhs; return *this; } + + inline const char* name() const { return Name(val); } + inline bool assign(const std::string& name) { return Parse(val, name); } + inline Enum& operator=(const std::string& rhs) { assign(rhs); return *this; } +}; + +#define ENUM(e,n) \ + template<> const char** Enum::Names = n; \ + template<> size_t Enum::Size = sizeof(n)/sizeof(n[0]) + +////////////////////////////////////////////////////////////////////// +// HttpCommon +////////////////////////////////////////////////////////////////////// + +static const char* kHttpVersions[HVER_LAST+1] = { + "1.0", "1.1", "Unknown" +}; +ENUM(HttpVersion, kHttpVersions); + +static const char* kHttpVerbs[HV_LAST+1] = { + "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD" +}; +ENUM(HttpVerb, kHttpVerbs); + +static const char* kHttpHeaders[HH_LAST+1] = { + "Age", + "Cache-Control", + "Connection", + "Content-Disposition", + "Content-Length", + "Content-Range", + "Content-Type", + "Cookie", + "Date", + "ETag", + "Expires", + "Host", + "If-Modified-Since", + "If-None-Match", + "Keep-Alive", + "Last-Modified", + "Location", + "Proxy-Authenticate", + "Proxy-Authorization", + "Proxy-Connection", + "Range", + "Set-Cookie", + "TE", + "Trailers", + "Transfer-Encoding", + "Upgrade", + "User-Agent", + "WWW-Authenticate", +}; +ENUM(HttpHeader, kHttpHeaders); + +const char* ToString(HttpVersion version) { + return Enum::Name(version); +} + +bool FromString(HttpVersion& version, const std::string& str) { + return Enum::Parse(version, str); +} + +const char* ToString(HttpVerb verb) { + return Enum::Name(verb); +} + +bool FromString(HttpVerb& verb, const std::string& str) { + return Enum::Parse(verb, str); +} + +const char* ToString(HttpHeader header) { + return Enum::Name(header); +} + +bool FromString(HttpHeader& header, const std::string& str) { + return Enum::Parse(header, str); +} + +bool HttpCodeHasBody(uint32 code) { + return !HttpCodeIsInformational(code) + && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED); +} + +bool HttpCodeIsCacheable(uint32 code) { + switch (code) { + case HC_OK: + case HC_NON_AUTHORITATIVE: + case HC_PARTIAL_CONTENT: + case HC_MULTIPLE_CHOICES: + case HC_MOVED_PERMANENTLY: + case HC_GONE: + return true; + default: + return false; + } +} + +bool HttpHeaderIsEndToEnd(HttpHeader header) { + switch (header) { + case HH_CONNECTION: + case HH_KEEP_ALIVE: + case HH_PROXY_AUTHENTICATE: + case HH_PROXY_AUTHORIZATION: + case HH_PROXY_CONNECTION: // Note part of RFC... this is non-standard header + case HH_TE: + case HH_TRAILERS: + case HH_TRANSFER_ENCODING: + case HH_UPGRADE: + return false; + default: + return true; + } +} + +bool HttpHeaderIsCollapsible(HttpHeader header) { + switch (header) { + case HH_SET_COOKIE: + case HH_PROXY_AUTHENTICATE: + case HH_WWW_AUTHENTICATE: + return false; + default: + return true; + } +} + +bool HttpShouldKeepAlive(const HttpData& data) { + std::string connection; + if ((data.hasHeader(HH_PROXY_CONNECTION, &connection) + || data.hasHeader(HH_CONNECTION, &connection))) { + return (_stricmp(connection.c_str(), "Keep-Alive") == 0); + } + return (data.version >= HVER_1_1); +} + +namespace { + +inline bool IsEndOfAttributeName(size_t pos, size_t len, const char * data) { + if (pos >= len) + return true; + if (isspace(static_cast(data[pos]))) + return true; + // The reason for this complexity is that some attributes may contain trailing + // equal signs (like base64 tokens in Negotiate auth headers) + if ((pos+1 < len) && (data[pos] == '=') && + !isspace(static_cast(data[pos+1])) && + (data[pos+1] != '=')) { + return true; + } + return false; +} + +// TODO: unittest for EscapeAttribute and HttpComposeAttributes. + +std::string EscapeAttribute(const std::string& attribute) { + const size_t kMaxLength = attribute.length() * 2 + 1; + char* buffer = STACK_ARRAY(char, kMaxLength); + size_t len = escape(buffer, kMaxLength, attribute.data(), attribute.length(), + "\"", '\\'); + return std::string(buffer, len); +} + +} // anonymous namespace + +void HttpComposeAttributes(const HttpAttributeList& attributes, char separator, + std::string* composed) { + std::stringstream ss; + for (size_t i=0; i 0) { + ss << separator << " "; + } + ss << attributes[i].first; + if (!attributes[i].second.empty()) { + ss << "=\"" << EscapeAttribute(attributes[i].second) << "\""; + } + } + *composed = ss.str(); +} + +void HttpParseAttributes(const char * data, size_t len, + HttpAttributeList& attributes) { + size_t pos = 0; + while (true) { + // Skip leading whitespace + while ((pos < len) && isspace(static_cast(data[pos]))) { + ++pos; + } + + // End of attributes? + if (pos >= len) + return; + + // Find end of attribute name + size_t start = pos; + while (!IsEndOfAttributeName(pos, len, data)) { + ++pos; + } + + HttpAttribute attribute; + attribute.first.assign(data + start, data + pos); + + // Attribute has value? + if ((pos < len) && (data[pos] == '=')) { + ++pos; // Skip '=' + // Check if quoted value + if ((pos < len) && (data[pos] == '"')) { + while (++pos < len) { + if (data[pos] == '"') { + ++pos; + break; + } + if ((data[pos] == '\\') && (pos + 1 < len)) + ++pos; + attribute.second.append(1, data[pos]); + } + } else { + while ((pos < len) && + !isspace(static_cast(data[pos])) && + (data[pos] != ',')) { + attribute.second.append(1, data[pos++]); + } + } + } + + attributes.push_back(attribute); + if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ',' + } +} + +bool HttpHasAttribute(const HttpAttributeList& attributes, + const std::string& name, + std::string* value) { + for (HttpAttributeList::const_iterator it = attributes.begin(); + it != attributes.end(); ++it) { + if (it->first == name) { + if (value) { + *value = it->second; + } + return true; + } + } + return false; +} + +bool HttpHasNthAttribute(HttpAttributeList& attributes, + size_t index, + std::string* name, + std::string* value) { + if (index >= attributes.size()) + return false; + + if (name) + *name = attributes[index].first; + if (value) + *value = attributes[index].second; + return true; +} + +bool HttpDateToSeconds(const std::string& date, time_t* seconds) { + const char* const kTimeZones[] = { + "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y" + }; + const int kTimeZoneOffsets[] = { + 0, 0, -5, -4, -6, -5, -7, -6, -8, -7, + -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 + }; + + ASSERT(NULL != seconds); + struct tm tval; + memset(&tval, 0, sizeof(tval)); + char month[4], zone[6]; + memset(month, 0, sizeof(month)); + memset(zone, 0, sizeof(zone)); + + if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c", + &tval.tm_mday, month, &tval.tm_year, + &tval.tm_hour, &tval.tm_min, &tval.tm_sec, zone)) { + return false; + } + switch (toupper(month[2])) { + case 'N': tval.tm_mon = (month[1] == 'A') ? 0 : 5; break; + case 'B': tval.tm_mon = 1; break; + case 'R': tval.tm_mon = (month[0] == 'M') ? 2 : 3; break; + case 'Y': tval.tm_mon = 4; break; + case 'L': tval.tm_mon = 6; break; + case 'G': tval.tm_mon = 7; break; + case 'P': tval.tm_mon = 8; break; + case 'T': tval.tm_mon = 9; break; + case 'V': tval.tm_mon = 10; break; + case 'C': tval.tm_mon = 11; break; + } + tval.tm_year -= 1900; + size_t gmt, non_gmt = mktime(&tval); + if ((zone[0] == '+') || (zone[0] == '-')) { + if (!isdigit(zone[1]) || !isdigit(zone[2]) + || !isdigit(zone[3]) || !isdigit(zone[4])) { + return false; + } + int hours = (zone[1] - '0') * 10 + (zone[2] - '0'); + int minutes = (zone[3] - '0') * 10 + (zone[4] - '0'); + int offset = (hours * 60 + minutes) * 60; + gmt = non_gmt + ((zone[0] == '+') ? offset : -offset); + } else { + size_t zindex; + if (!find_string(zindex, zone, kTimeZones, ARRAY_SIZE(kTimeZones))) { + return false; + } + gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60; + } + // TODO: Android should support timezone, see b/2441195 +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(WEBRTC_ANDROID) || defined(BSD) + tm *tm_for_timezone = localtime((time_t *)&gmt); + *seconds = gmt + tm_for_timezone->tm_gmtoff; +#else + *seconds = gmt - timezone; +#endif + return true; +} + +std::string HttpAddress(const SocketAddress& address, bool secure) { + return (address.port() == HttpDefaultPort(secure)) + ? address.hostname() : address.ToString(); +} + +////////////////////////////////////////////////////////////////////// +// HttpData +////////////////////////////////////////////////////////////////////// + +void +HttpData::clear(bool release_document) { + // Clear headers first, since releasing a document may have far-reaching + // effects. + headers_.clear(); + if (release_document) { + document.reset(); + } +} + +void +HttpData::copy(const HttpData& src) { + headers_ = src.headers_; +} + +void +HttpData::changeHeader(const std::string& name, const std::string& value, + HeaderCombine combine) { + if (combine == HC_AUTO) { + HttpHeader header; + // Unrecognized headers are collapsible + combine = !FromString(header, name) || HttpHeaderIsCollapsible(header) + ? HC_YES : HC_NO; + } else if (combine == HC_REPLACE) { + headers_.erase(name); + combine = HC_NO; + } + // At this point, combine is one of (YES, NO, NEW) + if (combine != HC_NO) { + HeaderMap::iterator it = headers_.find(name); + if (it != headers_.end()) { + if (combine == HC_YES) { + it->second.append(","); + it->second.append(value); + } + return; + } + } + headers_.insert(HeaderMap::value_type(name, value)); +} + +size_t HttpData::clearHeader(const std::string& name) { + return headers_.erase(name); +} + +HttpData::iterator HttpData::clearHeader(iterator header) { + iterator deprecated = header++; + headers_.erase(deprecated); + return header; +} + +bool +HttpData::hasHeader(const std::string& name, std::string* value) const { + HeaderMap::const_iterator it = headers_.find(name); + if (it == headers_.end()) { + return false; + } else if (value) { + *value = it->second; + } + return true; +} + +void HttpData::setContent(const std::string& content_type, + StreamInterface* document) { + setHeader(HH_CONTENT_TYPE, content_type); + setDocumentAndLength(document); +} + +void HttpData::setDocumentAndLength(StreamInterface* document) { + // TODO: Consider calling Rewind() here? + ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL)); + ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL)); + ASSERT(document != NULL); + this->document.reset(document); + size_t content_length = 0; + if (this->document->GetAvailable(&content_length)) { + char buffer[32]; + sprintfn(buffer, sizeof(buffer), "%d", content_length); + setHeader(HH_CONTENT_LENGTH, buffer); + } else { + setHeader(HH_TRANSFER_ENCODING, "chunked"); + } +} + +// +// HttpRequestData +// + +void +HttpRequestData::clear(bool release_document) { + verb = HV_GET; + path.clear(); + HttpData::clear(release_document); +} + +void +HttpRequestData::copy(const HttpRequestData& src) { + verb = src.verb; + path = src.path; + HttpData::copy(src); +} + +size_t +HttpRequestData::formatLeader(char* buffer, size_t size) const { + ASSERT(path.find(' ') == std::string::npos); + return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(), + path.data(), ToString(version)); +} + +HttpError +HttpRequestData::parseLeader(const char* line, size_t len) { + unsigned int vmajor, vminor; + int vend, dstart, dend; + // sscanf isn't safe with strings that aren't null-terminated, and there is + // no guarantee that |line| is. Create a local copy that is null-terminated. + std::string line_str(line, len); + line = line_str.c_str(); + if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", + &vend, &dstart, &dend, &vmajor, &vminor) != 2) + || (vmajor != 1)) { + return HE_PROTOCOL; + } + if (vminor == 0) { + version = HVER_1_0; + } else if (vminor == 1) { + version = HVER_1_1; + } else { + return HE_PROTOCOL; + } + std::string sverb(line, vend); + if (!FromString(verb, sverb.c_str())) { + return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED? + } + path.assign(line + dstart, line + dend); + return HE_NONE; +} + +bool HttpRequestData::getAbsoluteUri(std::string* uri) const { + if (HV_CONNECT == verb) + return false; + Url url(path); + if (url.valid()) { + uri->assign(path); + return true; + } + std::string host; + if (!hasHeader(HH_HOST, &host)) + return false; + url.set_address(host); + url.set_full_path(path); + uri->assign(url.url()); + return url.valid(); +} + +bool HttpRequestData::getRelativeUri(std::string* host, + std::string* path) const +{ + if (HV_CONNECT == verb) + return false; + Url url(this->path); + if (url.valid()) { + host->assign(url.address()); + path->assign(url.full_path()); + return true; + } + if (!hasHeader(HH_HOST, host)) + return false; + path->assign(this->path); + return true; +} + +// +// HttpResponseData +// + +void +HttpResponseData::clear(bool release_document) { + scode = HC_INTERNAL_SERVER_ERROR; + message.clear(); + HttpData::clear(release_document); +} + +void +HttpResponseData::copy(const HttpResponseData& src) { + scode = src.scode; + message = src.message; + HttpData::copy(src); +} + +void +HttpResponseData::set_success(uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_CONTENT_LENGTH, "0", false); +} + +void +HttpResponseData::set_success(const std::string& content_type, + StreamInterface* document, + uint32 scode) { + this->scode = scode; + message.erase(message.begin(), message.end()); + setContent(content_type, document); +} + +void +HttpResponseData::set_redirect(const std::string& location, uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_LOCATION, location); + setHeader(HH_CONTENT_LENGTH, "0", false); +} + +void +HttpResponseData::set_error(uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_CONTENT_LENGTH, "0", false); +} + +size_t +HttpResponseData::formatLeader(char* buffer, size_t size) const { + size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode); + if (!message.empty()) { + len += sprintfn(buffer + len, size - len, " %.*s", + message.size(), message.data()); + } + return len; +} + +HttpError +HttpResponseData::parseLeader(const char* line, size_t len) { + size_t pos = 0; + unsigned int vmajor, vminor, temp_scode; + int temp_pos; + // sscanf isn't safe with strings that aren't null-terminated, and there is + // no guarantee that |line| is. Create a local copy that is null-terminated. + std::string line_str(line, len); + line = line_str.c_str(); + if (sscanf(line, "HTTP %u%n", + &temp_scode, &temp_pos) == 1) { + // This server's response has no version. :( NOTE: This happens for every + // response to requests made from Chrome plugins, regardless of the server's + // behaviour. + LOG(LS_VERBOSE) << "HTTP version missing from response"; + version = HVER_UNKNOWN; + } else if ((sscanf(line, "HTTP/%u.%u %u%n", + &vmajor, &vminor, &temp_scode, &temp_pos) == 3) + && (vmajor == 1)) { + // This server's response does have a version. + if (vminor == 0) { + version = HVER_1_0; + } else if (vminor == 1) { + version = HVER_1_1; + } else { + return HE_PROTOCOL; + } + } else { + return HE_PROTOCOL; + } + scode = temp_scode; + pos = static_cast(temp_pos); + while ((pos < len) && isspace(static_cast(line[pos]))) ++pos; + message.assign(line + pos, len - pos); + return HE_NONE; +} + +////////////////////////////////////////////////////////////////////// +// Http Authentication +////////////////////////////////////////////////////////////////////// + +#define TEST_DIGEST 0 +#if TEST_DIGEST +/* +const char * const DIGEST_CHALLENGE = + "Digest realm=\"testrealm@host.com\"," + " qop=\"auth,auth-int\"," + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"," + " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""; +const char * const DIGEST_METHOD = "GET"; +const char * const DIGEST_URI = + "/dir/index.html";; +const char * const DIGEST_CNONCE = + "0a4f113b"; +const char * const DIGEST_RESPONSE = + "6629fae49393a05397450978507c4ef1"; +//user_ = "Mufasa"; +//pass_ = "Circle Of Life"; +*/ +const char * const DIGEST_CHALLENGE = + "Digest realm=\"Squid proxy-caching web server\"," + " nonce=\"Nny4QuC5PwiSDixJ\"," + " qop=\"auth\"," + " stale=false"; +const char * const DIGEST_URI = + "/"; +const char * const DIGEST_CNONCE = + "6501d58e9a21cee1e7b5fec894ded024"; +const char * const DIGEST_RESPONSE = + "edffcb0829e755838b073a4a42de06bc"; +#endif + +std::string quote(const std::string& str) { + std::string result; + result.push_back('"'); + for (size_t i=0; iauth_method != auth_method)) + return HAR_IGNORE; + + // BASIC + if (_stricmp(auth_method.c_str(), "basic") == 0) { + if (context) + return HAR_CREDENTIALS; // Bad credentials + if (username.empty()) + return HAR_CREDENTIALS; // Missing credentials + + context = new HttpAuthContext(auth_method); + + // TODO: convert sensitive to a secure buffer that gets securely deleted + //std::string decoded = username + ":" + password; + size_t len = username.size() + password.GetLength() + 2; + char * sensitive = new char[len]; + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + response = auth_method; + response.append(" "); + // TODO: create a sensitive-source version of Base64::encode + response.append(Base64::Encode(sensitive)); + memset(sensitive, 0, len); + delete [] sensitive; + return HAR_RESPONSE; + } + + // DIGEST + if (_stricmp(auth_method.c_str(), "digest") == 0) { + if (context) + return HAR_CREDENTIALS; // Bad credentials + if (username.empty()) + return HAR_CREDENTIALS; // Missing credentials + + context = new HttpAuthContext(auth_method); + + std::string cnonce, ncount; +#if TEST_DIGEST + method = DIGEST_METHOD; + uri = DIGEST_URI; + cnonce = DIGEST_CNONCE; +#else + char buffer[256]; + sprintf(buffer, "%d", static_cast(time(0))); + cnonce = MD5(buffer); +#endif + ncount = "00000001"; + + std::string realm, nonce, qop, opaque; + HttpHasAttribute(args, "realm", &realm); + HttpHasAttribute(args, "nonce", &nonce); + bool has_qop = HttpHasAttribute(args, "qop", &qop); + bool has_opaque = HttpHasAttribute(args, "opaque", &opaque); + + // TODO: convert sensitive to be secure buffer + //std::string A1 = username + ":" + realm + ":" + password; + size_t len = username.size() + realm.size() + password.GetLength() + 3; + char * sensitive = new char[len]; // A1 + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + pos += strcpyn(sensitive + pos, len - pos, realm.c_str()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + std::string A2 = method + ":" + uri; + std::string middle; + if (has_qop) { + qop = "auth"; + middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop; + } else { + middle = nonce; + } + std::string HA1 = MD5(sensitive); + memset(sensitive, 0, len); + delete [] sensitive; + std::string HA2 = MD5(A2); + std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2); + +#if TEST_DIGEST + ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0); +#endif + + std::stringstream ss; + ss << auth_method; + ss << " username=" << quote(username); + ss << ", realm=" << quote(realm); + ss << ", nonce=" << quote(nonce); + ss << ", uri=" << quote(uri); + if (has_qop) { + ss << ", qop=" << qop; + ss << ", nc=" << ncount; + ss << ", cnonce=" << quote(cnonce); + } + ss << ", response=\"" << dig_response << "\""; + if (has_opaque) { + ss << ", opaque=" << quote(opaque); + } + response = ss.str(); + return HAR_RESPONSE; + } + +#if defined(WEBRTC_WIN) +#if 1 + bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0); + bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0); + // SPNEGO & NTLM + if (want_negotiate || want_ntlm) { + const size_t MAX_MESSAGE = 12000, MAX_SPN = 256; + char out_buf[MAX_MESSAGE], spn[MAX_SPN]; + +#if 0 // Requires funky windows versions + DWORD len = MAX_SPN; + if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), NULL, + server.port(), + 0, &len, spn) != ERROR_SUCCESS) { + LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed"; + return HAR_IGNORE; + } +#else + sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str()); +#endif + + SecBuffer out_sec; + out_sec.pvBuffer = out_buf; + out_sec.cbBuffer = sizeof(out_buf); + out_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc out_buf_desc; + out_buf_desc.ulVersion = 0; + out_buf_desc.cBuffers = 1; + out_buf_desc.pBuffers = &out_sec; + + const ULONG NEG_FLAGS_DEFAULT = + //ISC_REQ_ALLOCATE_MEMORY + ISC_REQ_CONFIDENTIALITY + //| ISC_REQ_EXTENDED_ERROR + //| ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + //| ISC_REQ_STREAM + //| ISC_REQ_USE_SUPPLIED_CREDS + ; + + ::TimeStamp lifetime; + SECURITY_STATUS ret = S_OK; + ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT; + + bool specify_credentials = !username.empty(); + size_t steps = 0; + + //uint32 now = Time(); + + NegotiateAuthContext * neg = static_cast(context); + if (neg) { + const size_t max_steps = 10; + if (++neg->steps >= max_steps) { + LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries"; + return HAR_ERROR; + } + steps = neg->steps; + + std::string challenge, decoded_challenge; + if (HttpHasNthAttribute(args, 1, &challenge, NULL) + && Base64::Decode(challenge, Base64::DO_STRICT, + &decoded_challenge, NULL)) { + SecBuffer in_sec; + in_sec.pvBuffer = const_cast(decoded_challenge.data()); + in_sec.cbBuffer = static_cast(decoded_challenge.size()); + in_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc in_buf_desc; + in_buf_desc.ulVersion = 0; + in_buf_desc.cBuffers = 1; + in_buf_desc.pBuffers = &in_sec; + + ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + return HAR_ERROR; + } + } else if (neg->specified_credentials) { + // Try again with default credentials + specify_credentials = false; + delete context; + context = neg = 0; + } else { + return HAR_CREDENTIALS; + } + } + + if (!neg) { + unsigned char userbuf[256], passbuf[256], domainbuf[16]; + SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0; + if (specify_credentials) { + memset(&auth_id, 0, sizeof(auth_id)); + size_t len = password.GetLength()+1; + char * sensitive = new char[len]; + password.CopyTo(sensitive, true); + std::string::size_type pos = username.find('\\'); + if (pos == std::string::npos) { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size())); + memcpy(userbuf, username.c_str(), auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = 0; + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } else { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size() - pos - 1)); + memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = static_cast( + _min(sizeof(domainbuf) - 1, pos)); + memcpy(domainbuf, username.c_str(), auth_id.DomainLength); + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } + memset(sensitive, 0, len); + delete [] sensitive; + auth_id.User = userbuf; + auth_id.Domain = domainbuf; + auth_id.Password = passbuf; + auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + pauth_id = &auth_id; + LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials"; + } else { + LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials"; + } + + CredHandle cred; + ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime); + //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now); + if (ret != SEC_E_OK) { + LOG(LS_ERROR) << "AcquireCredentialsHandle error: " + << ErrorName(ret, SECURITY_ERRORS); + return HAR_IGNORE; + } + + //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out; + + CtxtHandle ctx; + ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + FreeCredentialsHandle(&cred); + return HAR_IGNORE; + } + + ASSERT(!context); + context = neg = new NegotiateAuthContext(auth_method, cred, ctx); + neg->specified_credentials = specify_credentials; + neg->steps = steps; + } + + if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) { + ret = CompleteAuthToken(&neg->ctx, &out_buf_desc); + //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now); + LOG(LS_VERBOSE) << "CompleteAuthToken returned: " + << ErrorName(ret, SECURITY_ERRORS); + if (FAILED(ret)) { + return HAR_ERROR; + } + } + + //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms"; + + std::string decoded(out_buf, out_buf + out_sec.cbBuffer); + response = auth_method; + response.append(" "); + response.append(Base64::Encode(decoded)); + return HAR_RESPONSE; + } +#endif +#endif // WEBRTC_WIN + + return HAR_IGNORE; +} + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/httpcommon.h b/webrtc/base/httpcommon.h new file mode 100644 index 000000000..c43a9e276 --- /dev/null +++ b/webrtc/base/httpcommon.h @@ -0,0 +1,446 @@ +/* + * Copyright 2004 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_BASE_HTTPCOMMON_H__ +#define WEBRTC_BASE_HTTPCOMMON_H__ + +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +class CryptString; +class SocketAddress; + +////////////////////////////////////////////////////////////////////// +// Constants +////////////////////////////////////////////////////////////////////// + +enum HttpCode { + HC_OK = 200, + HC_NON_AUTHORITATIVE = 203, + HC_NO_CONTENT = 204, + HC_PARTIAL_CONTENT = 206, + + HC_MULTIPLE_CHOICES = 300, + HC_MOVED_PERMANENTLY = 301, + HC_FOUND = 302, + HC_SEE_OTHER = 303, + HC_NOT_MODIFIED = 304, + HC_MOVED_TEMPORARILY = 307, + + HC_BAD_REQUEST = 400, + HC_UNAUTHORIZED = 401, + HC_FORBIDDEN = 403, + HC_NOT_FOUND = 404, + HC_PROXY_AUTHENTICATION_REQUIRED = 407, + HC_GONE = 410, + + HC_INTERNAL_SERVER_ERROR = 500, + HC_NOT_IMPLEMENTED = 501, + HC_SERVICE_UNAVAILABLE = 503, +}; + +enum HttpVersion { + HVER_1_0, HVER_1_1, HVER_UNKNOWN, + HVER_LAST = HVER_UNKNOWN +}; + +enum HttpVerb { + HV_GET, HV_POST, HV_PUT, HV_DELETE, HV_CONNECT, HV_HEAD, + HV_LAST = HV_HEAD +}; + +enum HttpError { + HE_NONE, + HE_PROTOCOL, // Received non-valid HTTP data + HE_DISCONNECTED, // Connection closed unexpectedly + HE_OVERFLOW, // Received too much data for internal buffers + HE_CONNECT_FAILED, // The socket failed to connect. + HE_SOCKET_ERROR, // An error occurred on a connected socket + HE_SHUTDOWN, // Http object is being destroyed + HE_OPERATION_CANCELLED, // Connection aborted locally + HE_AUTH, // Proxy Authentication Required + HE_CERTIFICATE_EXPIRED, // During SSL negotiation + HE_STREAM, // Problem reading or writing to the document + HE_CACHE, // Problem reading from cache + HE_DEFAULT +}; + +enum HttpHeader { + HH_AGE, + HH_CACHE_CONTROL, + HH_CONNECTION, + HH_CONTENT_DISPOSITION, + HH_CONTENT_LENGTH, + HH_CONTENT_RANGE, + HH_CONTENT_TYPE, + HH_COOKIE, + HH_DATE, + HH_ETAG, + HH_EXPIRES, + HH_HOST, + HH_IF_MODIFIED_SINCE, + HH_IF_NONE_MATCH, + HH_KEEP_ALIVE, + HH_LAST_MODIFIED, + HH_LOCATION, + HH_PROXY_AUTHENTICATE, + HH_PROXY_AUTHORIZATION, + HH_PROXY_CONNECTION, + HH_RANGE, + HH_SET_COOKIE, + HH_TE, + HH_TRAILERS, + HH_TRANSFER_ENCODING, + HH_UPGRADE, + HH_USER_AGENT, + HH_WWW_AUTHENTICATE, + HH_LAST = HH_WWW_AUTHENTICATE +}; + +const uint16 HTTP_DEFAULT_PORT = 80; +const uint16 HTTP_SECURE_PORT = 443; + +////////////////////////////////////////////////////////////////////// +// Utility Functions +////////////////////////////////////////////////////////////////////// + +inline HttpError mkerr(HttpError err, HttpError def_err = HE_DEFAULT) { + return (err != HE_NONE) ? err : def_err; +} + +const char* ToString(HttpVersion version); +bool FromString(HttpVersion& version, const std::string& str); + +const char* ToString(HttpVerb verb); +bool FromString(HttpVerb& verb, const std::string& str); + +const char* ToString(HttpHeader header); +bool FromString(HttpHeader& header, const std::string& str); + +inline bool HttpCodeIsInformational(uint32 code) { return ((code / 100) == 1); } +inline bool HttpCodeIsSuccessful(uint32 code) { return ((code / 100) == 2); } +inline bool HttpCodeIsRedirection(uint32 code) { return ((code / 100) == 3); } +inline bool HttpCodeIsClientError(uint32 code) { return ((code / 100) == 4); } +inline bool HttpCodeIsServerError(uint32 code) { return ((code / 100) == 5); } + +bool HttpCodeHasBody(uint32 code); +bool HttpCodeIsCacheable(uint32 code); +bool HttpHeaderIsEndToEnd(HttpHeader header); +bool HttpHeaderIsCollapsible(HttpHeader header); + +struct HttpData; +bool HttpShouldKeepAlive(const HttpData& data); + +typedef std::pair HttpAttribute; +typedef std::vector HttpAttributeList; +void HttpComposeAttributes(const HttpAttributeList& attributes, char separator, + std::string* composed); +void HttpParseAttributes(const char * data, size_t len, + HttpAttributeList& attributes); +bool HttpHasAttribute(const HttpAttributeList& attributes, + const std::string& name, + std::string* value); +bool HttpHasNthAttribute(HttpAttributeList& attributes, + size_t index, + std::string* name, + std::string* value); + +// Convert RFC1123 date (DoW, DD Mon YYYY HH:MM:SS TZ) to unix timestamp +bool HttpDateToSeconds(const std::string& date, time_t* seconds); + +inline uint16 HttpDefaultPort(bool secure) { + return secure ? HTTP_SECURE_PORT : HTTP_DEFAULT_PORT; +} + +// Returns the http server notation for a given address +std::string HttpAddress(const SocketAddress& address, bool secure); + +// functional for insensitive std::string compare +struct iless { + bool operator()(const std::string& lhs, const std::string& rhs) const { + return (::_stricmp(lhs.c_str(), rhs.c_str()) < 0); + } +}; + +// put quotes around a string and escape any quotes inside it +std::string quote(const std::string& str); + +////////////////////////////////////////////////////////////////////// +// Url +////////////////////////////////////////////////////////////////////// + +template +class Url { +public: + typedef typename Traits::string string; + + // TODO: Implement Encode/Decode + static int Encode(const CTYPE* source, CTYPE* destination, size_t len); + static int Encode(const string& source, string& destination); + static int Decode(const CTYPE* source, CTYPE* destination, size_t len); + static int Decode(const string& source, string& destination); + + Url(const string& url) { do_set_url(url.c_str(), url.size()); } + Url(const string& path, const string& host, uint16 port = HTTP_DEFAULT_PORT) + : host_(host), port_(port), secure_(HTTP_SECURE_PORT == port) + { set_full_path(path); } + + bool valid() const { return !host_.empty(); } + void clear() { + host_.clear(); + port_ = HTTP_DEFAULT_PORT; + secure_ = false; + path_.assign(1, static_cast('/')); + query_.clear(); + } + + void set_url(const string& val) { + do_set_url(val.c_str(), val.size()); + } + string url() const { + string val; do_get_url(&val); return val; + } + + void set_address(const string& val) { + do_set_address(val.c_str(), val.size()); + } + string address() const { + string val; do_get_address(&val); return val; + } + + void set_full_path(const string& val) { + do_set_full_path(val.c_str(), val.size()); + } + string full_path() const { + string val; do_get_full_path(&val); return val; + } + + void set_host(const string& val) { host_ = val; } + const string& host() const { return host_; } + + void set_port(uint16 val) { port_ = val; } + uint16 port() const { return port_; } + + void set_secure(bool val) { secure_ = val; } + bool secure() const { return secure_; } + + void set_path(const string& val) { + if (val.empty()) { + path_.assign(1, static_cast('/')); + } else { + ASSERT(val[0] == static_cast('/')); + path_ = val; + } + } + const string& path() const { return path_; } + + void set_query(const string& val) { + ASSERT(val.empty() || (val[0] == static_cast('?'))); + query_ = val; + } + const string& query() const { return query_; } + + bool get_attribute(const string& name, string* value) const; + +private: + void do_set_url(const CTYPE* val, size_t len); + void do_set_address(const CTYPE* val, size_t len); + void do_set_full_path(const CTYPE* val, size_t len); + + void do_get_url(string* val) const; + void do_get_address(string* val) const; + void do_get_full_path(string* val) const; + + string host_, path_, query_; + uint16 port_; + bool secure_; +}; + +////////////////////////////////////////////////////////////////////// +// HttpData +////////////////////////////////////////////////////////////////////// + +struct HttpData { + typedef std::multimap HeaderMap; + typedef HeaderMap::const_iterator const_iterator; + typedef HeaderMap::iterator iterator; + + HttpVersion version; + scoped_ptr document; + + HttpData() : version(HVER_1_1) { } + + enum HeaderCombine { HC_YES, HC_NO, HC_AUTO, HC_REPLACE, HC_NEW }; + void changeHeader(const std::string& name, const std::string& value, + HeaderCombine combine); + inline void addHeader(const std::string& name, const std::string& value, + bool append = true) { + changeHeader(name, value, append ? HC_AUTO : HC_NO); + } + inline void setHeader(const std::string& name, const std::string& value, + bool overwrite = true) { + changeHeader(name, value, overwrite ? HC_REPLACE : HC_NEW); + } + // Returns count of erased headers + size_t clearHeader(const std::string& name); + // Returns iterator to next header + iterator clearHeader(iterator header); + + // keep in mind, this may not do what you want in the face of multiple headers + bool hasHeader(const std::string& name, std::string* value) const; + + inline const_iterator begin() const { + return headers_.begin(); + } + inline const_iterator end() const { + return headers_.end(); + } + inline iterator begin() { + return headers_.begin(); + } + inline iterator end() { + return headers_.end(); + } + inline const_iterator begin(const std::string& name) const { + return headers_.lower_bound(name); + } + inline const_iterator end(const std::string& name) const { + return headers_.upper_bound(name); + } + inline iterator begin(const std::string& name) { + return headers_.lower_bound(name); + } + inline iterator end(const std::string& name) { + return headers_.upper_bound(name); + } + + // Convenience methods using HttpHeader + inline void changeHeader(HttpHeader header, const std::string& value, + HeaderCombine combine) { + changeHeader(ToString(header), value, combine); + } + inline void addHeader(HttpHeader header, const std::string& value, + bool append = true) { + addHeader(ToString(header), value, append); + } + inline void setHeader(HttpHeader header, const std::string& value, + bool overwrite = true) { + setHeader(ToString(header), value, overwrite); + } + inline void clearHeader(HttpHeader header) { + clearHeader(ToString(header)); + } + inline bool hasHeader(HttpHeader header, std::string* value) const { + return hasHeader(ToString(header), value); + } + inline const_iterator begin(HttpHeader header) const { + return headers_.lower_bound(ToString(header)); + } + inline const_iterator end(HttpHeader header) const { + return headers_.upper_bound(ToString(header)); + } + inline iterator begin(HttpHeader header) { + return headers_.lower_bound(ToString(header)); + } + inline iterator end(HttpHeader header) { + return headers_.upper_bound(ToString(header)); + } + + void setContent(const std::string& content_type, StreamInterface* document); + void setDocumentAndLength(StreamInterface* document); + + virtual size_t formatLeader(char* buffer, size_t size) const = 0; + virtual HttpError parseLeader(const char* line, size_t len) = 0; + +protected: + virtual ~HttpData() { } + void clear(bool release_document); + void copy(const HttpData& src); + +private: + HeaderMap headers_; +}; + +struct HttpRequestData : public HttpData { + HttpVerb verb; + std::string path; + + HttpRequestData() : verb(HV_GET) { } + + void clear(bool release_document); + void copy(const HttpRequestData& src); + + virtual size_t formatLeader(char* buffer, size_t size) const; + virtual HttpError parseLeader(const char* line, size_t len); + + bool getAbsoluteUri(std::string* uri) const; + bool getRelativeUri(std::string* host, std::string* path) const; +}; + +struct HttpResponseData : public HttpData { + uint32 scode; + std::string message; + + HttpResponseData() : scode(HC_INTERNAL_SERVER_ERROR) { } + void clear(bool release_document); + void copy(const HttpResponseData& src); + + // Convenience methods + void set_success(uint32 scode = HC_OK); + void set_success(const std::string& content_type, StreamInterface* document, + uint32 scode = HC_OK); + void set_redirect(const std::string& location, + uint32 scode = HC_MOVED_TEMPORARILY); + void set_error(uint32 scode); + + virtual size_t formatLeader(char* buffer, size_t size) const; + virtual HttpError parseLeader(const char* line, size_t len); +}; + +struct HttpTransaction { + HttpRequestData request; + HttpResponseData response; +}; + +////////////////////////////////////////////////////////////////////// +// Http Authentication +////////////////////////////////////////////////////////////////////// + +struct HttpAuthContext { + std::string auth_method; + HttpAuthContext(const std::string& auth) : auth_method(auth) { } + virtual ~HttpAuthContext() { } +}; + +enum HttpAuthResult { HAR_RESPONSE, HAR_IGNORE, HAR_CREDENTIALS, HAR_ERROR }; + +// 'context' is used by this function to record information between calls. +// Start by passing a null pointer, then pass the same pointer each additional +// call. When the authentication attempt is finished, delete the context. +HttpAuthResult HttpAuthenticate( + const char * challenge, size_t len, + const SocketAddress& server, + const std::string& method, const std::string& uri, + const std::string& username, const CryptString& password, + HttpAuthContext *& context, std::string& response, std::string& auth_method); + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPCOMMON_H__ diff --git a/webrtc/base/httpcommon_unittest.cc b/webrtc/base/httpcommon_unittest.cc new file mode 100644 index 000000000..10e378987 --- /dev/null +++ b/webrtc/base/httpcommon_unittest.cc @@ -0,0 +1,165 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/httpcommon.h" + +namespace rtc { + +#define TEST_PROTOCOL "http://" +#define TEST_HOST "www.google.com" +#define TEST_PATH "/folder/file.html" +#define TEST_QUERY "?query=x&attr=y" +#define TEST_URL TEST_PROTOCOL TEST_HOST TEST_PATH TEST_QUERY + +TEST(Url, DecomposesUrls) { + Url url(TEST_URL); + EXPECT_TRUE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ(TEST_HOST, url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ(TEST_PATH, url.path().c_str()); + EXPECT_STREQ(TEST_QUERY, url.query().c_str()); + EXPECT_STREQ(TEST_HOST, url.address().c_str()); + EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str()); + EXPECT_STREQ(TEST_URL, url.url().c_str()); +} + +TEST(Url, ComposesUrls) { + // Set in constructor + Url url(TEST_PATH TEST_QUERY, TEST_HOST, 80); + EXPECT_TRUE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ(TEST_HOST, url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ(TEST_PATH, url.path().c_str()); + EXPECT_STREQ(TEST_QUERY, url.query().c_str()); + EXPECT_STREQ(TEST_HOST, url.address().c_str()); + EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str()); + EXPECT_STREQ(TEST_URL, url.url().c_str()); + + url.clear(); + EXPECT_FALSE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ("", url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ("/", url.path().c_str()); + EXPECT_STREQ("", url.query().c_str()); + + // Set component-wise + url.set_host(TEST_HOST); + url.set_port(80); + url.set_path(TEST_PATH); + url.set_query(TEST_QUERY); + EXPECT_TRUE(url.valid()); + EXPECT_FALSE(url.secure()); + EXPECT_STREQ(TEST_HOST, url.host().c_str()); + EXPECT_EQ(80, url.port()); + EXPECT_STREQ(TEST_PATH, url.path().c_str()); + EXPECT_STREQ(TEST_QUERY, url.query().c_str()); + EXPECT_STREQ(TEST_HOST, url.address().c_str()); + EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str()); + EXPECT_STREQ(TEST_URL, url.url().c_str()); +} + +TEST(Url, EnsuresNonEmptyPath) { + Url url(TEST_PROTOCOL TEST_HOST); + EXPECT_TRUE(url.valid()); + EXPECT_STREQ("/", url.path().c_str()); + + url.clear(); + EXPECT_STREQ("/", url.path().c_str()); + url.set_path(""); + EXPECT_STREQ("/", url.path().c_str()); + + url.clear(); + EXPECT_STREQ("/", url.path().c_str()); + url.set_full_path(""); + EXPECT_STREQ("/", url.path().c_str()); +} + +TEST(Url, GetQueryAttributes) { + Url url(TEST_URL); + std::string value; + EXPECT_TRUE(url.get_attribute("query", &value)); + EXPECT_STREQ("x", value.c_str()); + value.clear(); + EXPECT_TRUE(url.get_attribute("attr", &value)); + EXPECT_STREQ("y", value.c_str()); + value.clear(); + EXPECT_FALSE(url.get_attribute("Query", &value)); + EXPECT_TRUE(value.empty()); +} + +TEST(Url, SkipsUserAndPassword) { + Url url("https://mail.google.com:pwd@badsite.com:12345/asdf"); + EXPECT_TRUE(url.valid()); + EXPECT_TRUE(url.secure()); + EXPECT_STREQ("badsite.com", url.host().c_str()); + EXPECT_EQ(12345, url.port()); + EXPECT_STREQ("/asdf", url.path().c_str()); + EXPECT_STREQ("badsite.com:12345", url.address().c_str()); +} + +TEST(Url, SkipsUser) { + Url url("https://mail.google.com@badsite.com:12345/asdf"); + EXPECT_TRUE(url.valid()); + EXPECT_TRUE(url.secure()); + EXPECT_STREQ("badsite.com", url.host().c_str()); + EXPECT_EQ(12345, url.port()); + EXPECT_STREQ("/asdf", url.path().c_str()); + EXPECT_STREQ("badsite.com:12345", url.address().c_str()); +} + +TEST(HttpResponseData, parseLeaderHttp1_0) { + static const char kResponseString[] = "HTTP/1.0 200 OK"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_1_0, response.version); + EXPECT_EQ(200U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttp1_1) { + static const char kResponseString[] = "HTTP/1.1 200 OK"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_1_1, response.version); + EXPECT_EQ(200U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttpUnknown) { + static const char kResponseString[] = "HTTP 200 OK"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_UNKNOWN, response.version); + EXPECT_EQ(200U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttpFailure) { + static const char kResponseString[] = "HTTP/1.1 503 Service Unavailable"; + HttpResponseData response; + EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); + EXPECT_EQ(HVER_1_1, response.version); + EXPECT_EQ(503U, response.scode); +} + +TEST(HttpResponseData, parseLeaderHttpInvalid) { + static const char kResponseString[] = "Durrrrr, what's HTTP?"; + HttpResponseData response; + EXPECT_EQ(HE_PROTOCOL, response.parseLeader(kResponseString, + sizeof(kResponseString) - 1)); +} + +} // namespace rtc diff --git a/webrtc/base/httprequest.cc b/webrtc/base/httprequest.cc new file mode 100644 index 000000000..9ce2377e8 --- /dev/null +++ b/webrtc/base/httprequest.cc @@ -0,0 +1,110 @@ +/* + * Copyright 2006 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 "webrtc/base/httprequest.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/firewallsocketserver.h" +#include "webrtc/base/httpclient.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/socketpool.h" +#include "webrtc/base/ssladapter.h" + +using namespace rtc; + +/////////////////////////////////////////////////////////////////////////////// +// HttpMonitor +/////////////////////////////////////////////////////////////////////////////// + +HttpMonitor::HttpMonitor(SocketServer *ss) { + ASSERT(Thread::Current() != NULL); + ss_ = ss; + reset(); +} + +void HttpMonitor::Connect(HttpClient *http) { + http->SignalHttpClientComplete.connect(this, + &HttpMonitor::OnHttpClientComplete); +} + +void HttpMonitor::OnHttpClientComplete(HttpClient * http, HttpErrorType error) { + complete_ = true; + error_ = error; + ss_->WakeUp(); +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpRequest +/////////////////////////////////////////////////////////////////////////////// + +const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec + +HttpRequest::HttpRequest(const std::string &user_agent) + : firewall_(0), port_(80), secure_(false), + timeout_(kDefaultHTTPTimeout), fail_redirect_(false), + client_(user_agent.c_str(), NULL), error_(HE_NONE) { +} + +void HttpRequest::Send() { + // TODO: Rewrite this to use the thread's native socket server, and a more + // natural flow? + + PhysicalSocketServer physical; + SocketServer * ss = &physical; + if (firewall_) { + ss = new FirewallSocketServer(ss, firewall_); + } + + SslSocketFactory factory(ss, client_.agent()); + factory.SetProxy(proxy_); + if (secure_) + factory.UseSSL(host_.c_str()); + + //factory.SetLogging("HttpRequest"); + + ReuseSocketPool pool(&factory); + client_.set_pool(&pool); + + bool transparent_proxy = (port_ == 80) && ((proxy_.type == PROXY_HTTPS) || + (proxy_.type == PROXY_UNKNOWN)); + + if (transparent_proxy) { + client_.set_proxy(proxy_); + } + client_.set_fail_redirect(fail_redirect_); + + SocketAddress server(host_, port_); + client_.set_server(server); + + LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path; + + HttpMonitor monitor(ss); + monitor.Connect(&client_); + client_.start(); + ss->Wait(timeout_, true); + if (!monitor.done()) { + LOG(LS_INFO) << "HttpRequest request timed out"; + client_.reset(); + return; + } + + set_error(monitor.error()); + if (error_) { + LOG(LS_INFO) << "HttpRequest request error: " << error_; + return; + } + + std::string value; + if (client_.response().hasHeader(HH_LOCATION, &value)) { + response_redirect_ = value.c_str(); + } +} diff --git a/webrtc/base/httprequest.h b/webrtc/base/httprequest.h new file mode 100644 index 000000000..37983324c --- /dev/null +++ b/webrtc/base/httprequest.h @@ -0,0 +1,115 @@ +/* + * Copyright 2006 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 _HTTPREQUEST_H_ +#define _HTTPREQUEST_H_ + +#include "webrtc/base/httpclient.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/sslsocketfactory.h" // Deprecated include + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// HttpRequest +/////////////////////////////////////////////////////////////////////////////// + +class FirewallManager; +class MemoryStream; + +class HttpRequest { +public: + HttpRequest(const std::string &user_agent); + + void Send(); + + void set_proxy(const ProxyInfo& proxy) { + proxy_ = proxy; + } + void set_firewall(FirewallManager * firewall) { + firewall_ = firewall; + } + + // The DNS name of the host to connect to. + const std::string& host() { return host_; } + void set_host(const std::string& host) { host_ = host; } + + // The port to connect to on the target host. + int port() { return port_; } + void set_port(int port) { port_ = port; } + + // Whether the request should use SSL. + bool secure() { return secure_; } + void set_secure(bool secure) { secure_ = secure; } + + // Returns the redirect when redirection occurs + const std::string& response_redirect() { return response_redirect_; } + + // Time to wait on the download, in ms. Default is 5000 (5s) + int timeout() { return timeout_; } + void set_timeout(int timeout) { timeout_ = timeout; } + + // Fail redirects to allow analysis of redirect urls, etc. + bool fail_redirect() const { return fail_redirect_; } + void set_fail_redirect(bool fail_redirect) { fail_redirect_ = fail_redirect; } + + HttpRequestData& request() { return client_.request(); } + HttpResponseData& response() { return client_.response(); } + HttpErrorType error() { return error_; } + +protected: + void set_error(HttpErrorType error) { error_ = error; } + +private: + ProxyInfo proxy_; + FirewallManager * firewall_; + std::string host_; + int port_; + bool secure_; + int timeout_; + bool fail_redirect_; + HttpClient client_; + HttpErrorType error_; + std::string response_redirect_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// HttpMonitor +/////////////////////////////////////////////////////////////////////////////// + +class HttpMonitor : public sigslot::has_slots<> { +public: + HttpMonitor(SocketServer *ss); + + void reset() { + complete_ = false; + error_ = HE_DEFAULT; + } + + bool done() const { return complete_; } + HttpErrorType error() const { return error_; } + + void Connect(HttpClient* http); + void OnHttpClientComplete(HttpClient * http, HttpErrorType error); + +private: + bool complete_; + HttpErrorType error_; + SocketServer *ss_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc_ + +#endif // _HTTPREQUEST_H_ diff --git a/webrtc/base/httpserver.cc b/webrtc/base/httpserver.cc new file mode 100644 index 000000000..0d2258422 --- /dev/null +++ b/webrtc/base/httpserver.cc @@ -0,0 +1,288 @@ +/* + * Copyright 2004 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 "webrtc/base/httpcommon-inl.h" + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// HttpServer +/////////////////////////////////////////////////////////////////////////////// + +HttpServer::HttpServer() : next_connection_id_(1), closing_(false) { +} + +HttpServer::~HttpServer() { + if (closing_) { + LOG(LS_WARNING) << "HttpServer::CloseAll has not completed"; + } + for (ConnectionMap::iterator it = connections_.begin(); + it != connections_.end(); + ++it) { + StreamInterface* stream = it->second->EndProcess(); + delete stream; + delete it->second; + } +} + +int +HttpServer::HandleConnection(StreamInterface* stream) { + int connection_id = next_connection_id_++; + ASSERT(connection_id != HTTP_INVALID_CONNECTION_ID); + Connection* connection = new Connection(connection_id, this); + connections_.insert(ConnectionMap::value_type(connection_id, connection)); + connection->BeginProcess(stream); + return connection_id; +} + +void +HttpServer::Respond(HttpServerTransaction* transaction) { + int connection_id = transaction->connection_id(); + if (Connection* connection = Find(connection_id)) { + connection->Respond(transaction); + } else { + delete transaction; + // We may be tempted to SignalHttpComplete, but that implies that a + // connection still exists. + } +} + +void +HttpServer::Close(int connection_id, bool force) { + if (Connection* connection = Find(connection_id)) { + connection->InitiateClose(force); + } +} + +void +HttpServer::CloseAll(bool force) { + if (connections_.empty()) { + SignalCloseAllComplete(this); + return; + } + closing_ = true; + std::list connections; + for (ConnectionMap::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + connections.push_back(it->second); + } + for (std::list::const_iterator it = connections.begin(); + it != connections.end(); ++it) { + (*it)->InitiateClose(force); + } +} + +HttpServer::Connection* +HttpServer::Find(int connection_id) { + ConnectionMap::iterator it = connections_.find(connection_id); + if (it == connections_.end()) + return NULL; + return it->second; +} + +void +HttpServer::Remove(int connection_id) { + ConnectionMap::iterator it = connections_.find(connection_id); + if (it == connections_.end()) { + ASSERT(false); + return; + } + Connection* connection = it->second; + connections_.erase(it); + SignalConnectionClosed(this, connection_id, connection->EndProcess()); + delete connection; + if (closing_ && connections_.empty()) { + closing_ = false; + SignalCloseAllComplete(this); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpServer::Connection +/////////////////////////////////////////////////////////////////////////////// + +HttpServer::Connection::Connection(int connection_id, HttpServer* server) + : connection_id_(connection_id), server_(server), + current_(NULL), signalling_(false), close_(false) { +} + +HttpServer::Connection::~Connection() { + // It's possible that an object hosted inside this transaction signalled + // an event which caused the connection to close. + Thread::Current()->Dispose(current_); +} + +void +HttpServer::Connection::BeginProcess(StreamInterface* stream) { + base_.notify(this); + base_.attach(stream); + current_ = new HttpServerTransaction(connection_id_); + if (base_.mode() != HM_CONNECT) + base_.recv(¤t_->request); +} + +StreamInterface* +HttpServer::Connection::EndProcess() { + base_.notify(NULL); + base_.abort(HE_DISCONNECTED); + return base_.detach(); +} + +void +HttpServer::Connection::Respond(HttpServerTransaction* transaction) { + ASSERT(current_ == NULL); + current_ = transaction; + if (current_->response.begin() == current_->response.end()) { + current_->response.set_error(HC_INTERNAL_SERVER_ERROR); + } + bool keep_alive = HttpShouldKeepAlive(current_->request); + current_->response.setHeader(HH_CONNECTION, + keep_alive ? "Keep-Alive" : "Close", + false); + close_ = !HttpShouldKeepAlive(current_->response); + base_.send(¤t_->response); +} + +void +HttpServer::Connection::InitiateClose(bool force) { + bool request_in_progress = (HM_SEND == base_.mode()) || (NULL == current_); + if (!signalling_ && (force || !request_in_progress)) { + server_->Remove(connection_id_); + } else { + close_ = true; + } +} + +// +// IHttpNotify Implementation +// + +HttpError +HttpServer::Connection::onHttpHeaderComplete(bool chunked, size_t& data_size) { + if (data_size == SIZE_UNKNOWN) { + data_size = 0; + } + ASSERT(current_ != NULL); + bool custom_document = false; + server_->SignalHttpRequestHeader(server_, current_, &custom_document); + if (!custom_document) { + current_->request.document.reset(new MemoryStream); + } + return HE_NONE; +} + +void +HttpServer::Connection::onHttpComplete(HttpMode mode, HttpError err) { + if (mode == HM_SEND) { + ASSERT(current_ != NULL); + signalling_ = true; + server_->SignalHttpRequestComplete(server_, current_, err); + signalling_ = false; + if (close_) { + // Force a close + err = HE_DISCONNECTED; + } + } + if (err != HE_NONE) { + server_->Remove(connection_id_); + } else if (mode == HM_CONNECT) { + base_.recv(¤t_->request); + } else if (mode == HM_RECV) { + ASSERT(current_ != NULL); + // TODO: do we need this? + //request_.document_->rewind(); + HttpServerTransaction* transaction = current_; + current_ = NULL; + server_->SignalHttpRequest(server_, transaction); + } else if (mode == HM_SEND) { + Thread::Current()->Dispose(current_->response.document.release()); + current_->request.clear(true); + current_->response.clear(true); + base_.recv(¤t_->request); + } else { + ASSERT(false); + } +} + +void +HttpServer::Connection::onHttpClosed(HttpError err) { + RTC_UNUSED(err); + server_->Remove(connection_id_); +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpListenServer +/////////////////////////////////////////////////////////////////////////////// + +HttpListenServer::HttpListenServer() { + SignalConnectionClosed.connect(this, &HttpListenServer::OnConnectionClosed); +} + +HttpListenServer::~HttpListenServer() { +} + +int HttpListenServer::Listen(const SocketAddress& address) { + AsyncSocket* sock = + Thread::Current()->socketserver()->CreateAsyncSocket(address.family(), + SOCK_STREAM); + if (!sock) { + return SOCKET_ERROR; + } + listener_.reset(sock); + listener_->SignalReadEvent.connect(this, &HttpListenServer::OnReadEvent); + if ((listener_->Bind(address) != SOCKET_ERROR) && + (listener_->Listen(5) != SOCKET_ERROR)) + return 0; + return listener_->GetError(); +} + +bool HttpListenServer::GetAddress(SocketAddress* address) const { + if (!listener_) { + return false; + } + *address = listener_->GetLocalAddress(); + return !address->IsNil(); +} + +void HttpListenServer::StopListening() { + if (listener_) { + listener_->Close(); + } +} + +void HttpListenServer::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == listener_.get()); + ASSERT(listener_); + AsyncSocket* incoming = listener_->Accept(NULL); + if (incoming) { + StreamInterface* stream = new SocketStream(incoming); + //stream = new LoggingAdapter(stream, LS_VERBOSE, "HttpServer", false); + HandleConnection(stream); + } +} + +void HttpListenServer::OnConnectionClosed(HttpServer* server, + int connection_id, + StreamInterface* stream) { + Thread::Current()->Dispose(stream); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/httpserver.h b/webrtc/base/httpserver.h new file mode 100644 index 000000000..77de615fe --- /dev/null +++ b/webrtc/base/httpserver.h @@ -0,0 +1,137 @@ +/* + * Copyright 2004 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_BASE_HTTPSERVER_H__ +#define WEBRTC_BASE_HTTPSERVER_H__ + +#include +#include "webrtc/base/httpbase.h" + +namespace rtc { + +class AsyncSocket; +class HttpServer; +class SocketAddress; + +////////////////////////////////////////////////////////////////////// +// HttpServer +////////////////////////////////////////////////////////////////////// + +const int HTTP_INVALID_CONNECTION_ID = 0; + +struct HttpServerTransaction : public HttpTransaction { +public: + HttpServerTransaction(int id) : connection_id_(id) { } + int connection_id() const { return connection_id_; } + +private: + int connection_id_; +}; + +class HttpServer { +public: + HttpServer(); + virtual ~HttpServer(); + + int HandleConnection(StreamInterface* stream); + // Due to sigslot issues, we can't destroy some streams at an arbitrary time. + sigslot::signal3 SignalConnectionClosed; + + // This signal occurs when the HTTP request headers have been received, but + // before the request body is written to the request document. By default, + // the request document is a MemoryStream. By handling this signal, the + // document can be overridden, in which case the third signal argument should + // be set to true. In the case where the request body should be ignored, + // the document can be set to NULL. Note that the transaction object is still + // owened by the HttpServer at this point. + sigslot::signal3 + SignalHttpRequestHeader; + + // An HTTP request has been made, and is available in the transaction object. + // Populate the transaction's response, and then return the object via the + // Respond method. Note that during this time, ownership of the transaction + // object is transferred, so it may be passed between threads, although + // respond must be called on the server's active thread. + sigslot::signal2 SignalHttpRequest; + void Respond(HttpServerTransaction* transaction); + + // If you want to know when a request completes, listen to this event. + sigslot::signal3 + SignalHttpRequestComplete; + + // Stop processing the connection indicated by connection_id. + // Unless force is true, the server will complete sending a response that is + // in progress. + void Close(int connection_id, bool force); + void CloseAll(bool force); + + // After calling CloseAll, this event is signalled to indicate that all + // outstanding connections have closed. + sigslot::signal1 SignalCloseAllComplete; + +private: + class Connection : private IHttpNotify { + public: + Connection(int connection_id, HttpServer* server); + virtual ~Connection(); + + void BeginProcess(StreamInterface* stream); + StreamInterface* EndProcess(); + + void Respond(HttpServerTransaction* transaction); + void InitiateClose(bool force); + + // IHttpNotify Interface + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size); + virtual void onHttpComplete(HttpMode mode, HttpError err); + virtual void onHttpClosed(HttpError err); + + int connection_id_; + HttpServer* server_; + HttpBase base_; + HttpServerTransaction* current_; + bool signalling_, close_; + }; + + Connection* Find(int connection_id); + void Remove(int connection_id); + + friend class Connection; + typedef std::map ConnectionMap; + + ConnectionMap connections_; + int next_connection_id_; + bool closing_; +}; + +////////////////////////////////////////////////////////////////////// + +class HttpListenServer : public HttpServer, public sigslot::has_slots<> { +public: + HttpListenServer(); + virtual ~HttpListenServer(); + + int Listen(const SocketAddress& address); + bool GetAddress(SocketAddress* address) const; + void StopListening(); + +private: + void OnReadEvent(AsyncSocket* socket); + void OnConnectionClosed(HttpServer* server, int connection_id, + StreamInterface* stream); + + scoped_ptr listener_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_HTTPSERVER_H__ diff --git a/webrtc/base/httpserver_unittest.cc b/webrtc/base/httpserver_unittest.cc new file mode 100644 index 000000000..0c653cbb9 --- /dev/null +++ b/webrtc/base/httpserver_unittest.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2007 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 "webrtc/base/gunit.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/testutils.h" + +using namespace testing; + +namespace rtc { + +namespace { + const char* const kRequest = + "GET /index.html HTTP/1.1\r\n" + "Host: localhost\r\n" + "\r\n"; + + struct HttpServerMonitor : public sigslot::has_slots<> { + HttpServerTransaction* transaction; + bool server_closed, connection_closed; + + HttpServerMonitor(HttpServer* server) + : transaction(NULL), server_closed(false), connection_closed(false) { + server->SignalCloseAllComplete.connect(this, + &HttpServerMonitor::OnClosed); + server->SignalHttpRequest.connect(this, &HttpServerMonitor::OnRequest); + server->SignalHttpRequestComplete.connect(this, + &HttpServerMonitor::OnRequestComplete); + server->SignalConnectionClosed.connect(this, + &HttpServerMonitor::OnConnectionClosed); + } + void OnRequest(HttpServer*, HttpServerTransaction* t) { + ASSERT_FALSE(transaction); + transaction = t; + transaction->response.set_success(); + transaction->response.setHeader(HH_CONNECTION, "Close"); + } + void OnRequestComplete(HttpServer*, HttpServerTransaction* t, int) { + ASSERT_EQ(transaction, t); + transaction = NULL; + } + void OnClosed(HttpServer*) { + server_closed = true; + } + void OnConnectionClosed(HttpServer*, int, StreamInterface* stream) { + connection_closed = true; + delete stream; + } + }; + + void CreateClientConnection(HttpServer& server, + HttpServerMonitor& monitor, + bool send_request) { + StreamSource* client = new StreamSource; + client->SetState(SS_OPEN); + server.HandleConnection(client); + EXPECT_FALSE(monitor.server_closed); + EXPECT_FALSE(monitor.transaction); + + if (send_request) { + // Simulate a request + client->QueueString(kRequest); + EXPECT_FALSE(monitor.server_closed); + } + } +} // anonymous namespace + +TEST(HttpServer, DoesNotSignalCloseUnlessCloseAllIsCalled) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an active client connection + CreateClientConnection(server, monitor, true); + // Simulate a response + ASSERT_TRUE(NULL != monitor.transaction); + server.Respond(monitor.transaction); + EXPECT_FALSE(monitor.transaction); + // Connection has closed, but no server close signal + EXPECT_FALSE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +TEST(HttpServer, SignalsCloseWhenNoConnectionsAreActive) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an idle client connection + CreateClientConnection(server, monitor, false); + // Perform graceful close + server.CloseAll(false); + // Connections have all closed + EXPECT_TRUE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +TEST(HttpServer, SignalsCloseAfterGracefulCloseAll) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an active client connection + CreateClientConnection(server, monitor, true); + // Initiate a graceful close + server.CloseAll(false); + EXPECT_FALSE(monitor.server_closed); + // Simulate a response + ASSERT_TRUE(NULL != monitor.transaction); + server.Respond(monitor.transaction); + EXPECT_FALSE(monitor.transaction); + // Connections have all closed + EXPECT_TRUE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +TEST(HttpServer, SignalsCloseAfterForcedCloseAll) { + HttpServer server; + HttpServerMonitor monitor(&server); + // Add an active client connection + CreateClientConnection(server, monitor, true); + // Initiate a forceful close + server.CloseAll(true); + // Connections have all closed + EXPECT_TRUE(monitor.server_closed); + EXPECT_TRUE(monitor.connection_closed); +} + +} // namespace rtc diff --git a/webrtc/base/ifaddrs-android.cc b/webrtc/base/ifaddrs-android.cc new file mode 100644 index 000000000..c8363d567 --- /dev/null +++ b/webrtc/base/ifaddrs-android.cc @@ -0,0 +1,223 @@ +/* + * Copyright 2012 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. + */ + +#if defined(WEBRTC_ANDROID) +#include "webrtc/base/ifaddrs-android.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct netlinkrequest { + nlmsghdr header; + ifaddrmsg msg; +}; + +const int kMaxReadSize = 4096; + +} // namespace + +namespace rtc { + +int set_ifname(struct ifaddrs* ifaddr, int interface) { + char buf[IFNAMSIZ] = {0}; + char* name = if_indextoname(interface, buf); + if (name == NULL) { + return -1; + } + ifaddr->ifa_name = new char[strlen(name) + 1]; + strncpy(ifaddr->ifa_name, name, strlen(name) + 1); + return 0; +} + +int set_flags(struct ifaddrs* ifaddr) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + return -1; + } + ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifaddr->ifa_name, IFNAMSIZ - 1); + int rc = ioctl(fd, SIOCGIFFLAGS, &ifr); + close(fd); + if (rc == -1) { + return -1; + } + ifaddr->ifa_flags = ifr.ifr_flags; + return 0; +} + +int set_addresses(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* data, + size_t len) { + if (msg->ifa_family == AF_INET) { + sockaddr_in* sa = new sockaddr_in; + sa->sin_family = AF_INET; + memcpy(&sa->sin_addr, data, len); + ifaddr->ifa_addr = reinterpret_cast(sa); + } else if (msg->ifa_family == AF_INET6) { + sockaddr_in6* sa = new sockaddr_in6; + sa->sin6_family = AF_INET6; + sa->sin6_scope_id = msg->ifa_index; + memcpy(&sa->sin6_addr, data, len); + ifaddr->ifa_addr = reinterpret_cast(sa); + } else { + return -1; + } + return 0; +} + +int make_prefixes(struct ifaddrs* ifaddr, int family, int prefixlen) { + char* prefix = NULL; + if (family == AF_INET) { + sockaddr_in* mask = new sockaddr_in; + mask->sin_family = AF_INET; + memset(&mask->sin_addr, 0, sizeof(in_addr)); + ifaddr->ifa_netmask = reinterpret_cast(mask); + if (prefixlen > 32) { + prefixlen = 32; + } + prefix = reinterpret_cast(&mask->sin_addr); + } else if (family == AF_INET6) { + sockaddr_in6* mask = new sockaddr_in6; + mask->sin6_family = AF_INET6; + memset(&mask->sin6_addr, 0, sizeof(in6_addr)); + ifaddr->ifa_netmask = reinterpret_cast(mask); + if (prefixlen > 128) { + prefixlen = 128; + } + prefix = reinterpret_cast(&mask->sin6_addr); + } else { + return -1; + } + for (int i = 0; i < (prefixlen / 8); i++) { + *prefix++ = 0xFF; + } + char remainder = 0xff; + remainder <<= (8 - prefixlen % 8); + *prefix = remainder; + return 0; +} + +int populate_ifaddrs(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* bytes, + size_t len) { + if (set_ifname(ifaddr, msg->ifa_index) != 0) { + return -1; + } + if (set_flags(ifaddr) != 0) { + return -1; + } + if (set_addresses(ifaddr, msg, bytes, len) != 0) { + return -1; + } + if (make_prefixes(ifaddr, msg->ifa_family, msg->ifa_prefixlen) != 0) { + return -1; + } + return 0; +} + +int getifaddrs(struct ifaddrs** result) { + int fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + return -1; + } + + netlinkrequest ifaddr_request; + memset(&ifaddr_request, 0, sizeof(ifaddr_request)); + ifaddr_request.header.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST; + ifaddr_request.header.nlmsg_type = RTM_GETADDR; + ifaddr_request.header.nlmsg_len = NLMSG_LENGTH(sizeof(ifaddrmsg)); + + ssize_t count = send(fd, &ifaddr_request, ifaddr_request.header.nlmsg_len, 0); + if (static_cast(count) != ifaddr_request.header.nlmsg_len) { + close(fd); + return -1; + } + struct ifaddrs* start = NULL; + struct ifaddrs* current = NULL; + char buf[kMaxReadSize]; + ssize_t amount_read = recv(fd, &buf, kMaxReadSize, 0); + while (amount_read > 0) { + nlmsghdr* header = reinterpret_cast(&buf[0]); + size_t header_size = static_cast(amount_read); + for ( ; NLMSG_OK(header, header_size); + header = NLMSG_NEXT(header, header_size)) { + switch (header->nlmsg_type) { + case NLMSG_DONE: + // Success. Return. + *result = start; + close(fd); + return 0; + case NLMSG_ERROR: + close(fd); + freeifaddrs(start); + return -1; + case RTM_NEWADDR: { + ifaddrmsg* address_msg = + reinterpret_cast(NLMSG_DATA(header)); + rtattr* rta = IFA_RTA(address_msg); + ssize_t payload_len = IFA_PAYLOAD(header); + while (RTA_OK(rta, payload_len)) { + if (rta->rta_type == IFA_ADDRESS) { + int family = address_msg->ifa_family; + if (family == AF_INET || family == AF_INET6) { + ifaddrs* newest = new ifaddrs; + memset(newest, 0, sizeof(ifaddrs)); + if (current) { + current->ifa_next = newest; + } else { + start = newest; + } + if (populate_ifaddrs(newest, address_msg, RTA_DATA(rta), + RTA_PAYLOAD(rta)) != 0) { + freeifaddrs(start); + *result = NULL; + return -1; + } + current = newest; + } + } + rta = RTA_NEXT(rta, payload_len); + } + break; + } + } + } + amount_read = recv(fd, &buf, kMaxReadSize, 0); + } + close(fd); + freeifaddrs(start); + return -1; +} + +void freeifaddrs(struct ifaddrs* addrs) { + struct ifaddrs* last = NULL; + struct ifaddrs* cursor = addrs; + while (cursor) { + delete[] cursor->ifa_name; + delete cursor->ifa_addr; + delete cursor->ifa_netmask; + last = cursor; + cursor = cursor->ifa_next; + delete last; + } +} +#endif // defined(WEBRTC_ANDROID) + +} // namespace rtc diff --git a/webrtc/base/ifaddrs-android.h b/webrtc/base/ifaddrs-android.h new file mode 100644 index 000000000..10890af65 --- /dev/null +++ b/webrtc/base/ifaddrs-android.h @@ -0,0 +1,39 @@ +/* + * Copyright 2013 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_BASE_IFADDRS_ANDROID_H_ +#define WEBRTC_BASE_IFADDRS_ANDROID_H_ + +#include +#include + + +// Implementation of getifaddrs for Android. +// Fills out a list of ifaddr structs (see below) which contain information +// about every network interface available on the host. +// See 'man getifaddrs' on Linux or OS X (nb: it is not a POSIX function). +struct ifaddrs { + struct ifaddrs* ifa_next; + char* ifa_name; + unsigned int ifa_flags; + struct sockaddr* ifa_addr; + struct sockaddr* ifa_netmask; + // Real ifaddrs has broadcast, point to point and data members. + // We don't need them (yet?). +}; + +namespace rtc { + +int getifaddrs(struct ifaddrs** result); +void freeifaddrs(struct ifaddrs* addrs); + +} // namespace rtc + +#endif // WEBRTC_BASE_IFADDRS_ANDROID_H_ diff --git a/webrtc/base/iosfilesystem.mm b/webrtc/base/iosfilesystem.mm new file mode 100644 index 000000000..eb4bbecd5 --- /dev/null +++ b/webrtc/base/iosfilesystem.mm @@ -0,0 +1,53 @@ +/* + * Copyright 2014 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 file only exists because various iOS system APIs are only +// available from Objective-C. See unixfilesystem.cc for the only use +// (enforced by a lack of a header file). + +#import +#import +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/pathutils.h" + +// Return a new[]'d |char*| copy of the UTF8 representation of |s|. +// Caller owns the returned memory and must use delete[] on it. +static char* copyString(NSString* s) { + const char* utf8 = [s UTF8String]; + size_t len = strlen(utf8) + 1; + char* copy = new char[len]; + // This uses a new[] + strcpy (instead of strdup) because the + // receiver expects to be able to delete[] the returned pointer + // (instead of free()ing it). + strcpy(copy, utf8); + return copy; +} + +// Return a (leaked) copy of a directory name suitable for application data. +char* IOSDataDirectory() { + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, NSUserDomainMask, YES); + ASSERT([paths count] == 1); + return copyString([paths objectAtIndex:0]); +} + +// Return a (leaked) copy of a directory name suitable for use as a $TEMP. +char* IOSTempDirectory() { + return copyString(NSTemporaryDirectory()); +} + +// Return the binary's path. +void IOSAppName(rtc::Pathname* path) { + NSProcessInfo *pInfo = [NSProcessInfo processInfo]; + NSString* argv0 = [[pInfo arguments] objectAtIndex:0]; + path->SetPathname([argv0 UTF8String]); +} diff --git a/webrtc/base/ipaddress.cc b/webrtc/base/ipaddress.cc new file mode 100644 index 000000000..4441e16f6 --- /dev/null +++ b/webrtc/base/ipaddress.cc @@ -0,0 +1,449 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#ifdef OPENBSD +#include +#endif +#ifndef __native_client__ +#include +#endif +#include +#include +#include +#endif + +#include + +#include "webrtc/base/ipaddress.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/win32.h" + +namespace rtc { + +// Prefixes used for categorizing IPv6 addresses. +static const in6_addr kV4MappedPrefix = {{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0xFF, 0xFF, 0}}}; +static const in6_addr k6To4Prefix = {{{0x20, 0x02, 0}}}; +static const in6_addr kTeredoPrefix = {{{0x20, 0x01, 0x00, 0x00}}}; +static const in6_addr kV4CompatibilityPrefix = {{{0}}}; +static const in6_addr k6BonePrefix = {{{0x3f, 0xfe, 0}}}; + +bool IPAddress::strip_sensitive_ = false; + +static bool IsPrivateV4(uint32 ip); +static in_addr ExtractMappedAddress(const in6_addr& addr); + +uint32 IPAddress::v4AddressAsHostOrderInteger() const { + if (family_ == AF_INET) { + return NetworkToHost32(u_.ip4.s_addr); + } else { + return 0; + } +} + +size_t IPAddress::Size() const { + switch (family_) { + case AF_INET: + return sizeof(in_addr); + case AF_INET6: + return sizeof(in6_addr); + } + return 0; +} + + +bool IPAddress::operator==(const IPAddress &other) const { + if (family_ != other.family_) { + return false; + } + if (family_ == AF_INET) { + return memcmp(&u_.ip4, &other.u_.ip4, sizeof(u_.ip4)) == 0; + } + if (family_ == AF_INET6) { + return memcmp(&u_.ip6, &other.u_.ip6, sizeof(u_.ip6)) == 0; + } + return family_ == AF_UNSPEC; +} + +bool IPAddress::operator!=(const IPAddress &other) const { + return !((*this) == other); +} + +bool IPAddress::operator >(const IPAddress &other) const { + return (*this) != other && !((*this) < other); +} + +bool IPAddress::operator <(const IPAddress &other) const { + // IPv4 is 'less than' IPv6 + if (family_ != other.family_) { + if (family_ == AF_UNSPEC) { + return true; + } + if (family_ == AF_INET && other.family_ == AF_INET6) { + return true; + } + return false; + } + // Comparing addresses of the same family. + switch (family_) { + case AF_INET: { + return NetworkToHost32(u_.ip4.s_addr) < + NetworkToHost32(other.u_.ip4.s_addr); + } + case AF_INET6: { + return memcmp(&u_.ip6.s6_addr, &other.u_.ip6.s6_addr, 16) < 0; + } + } + // Catches AF_UNSPEC and invalid addresses. + return false; +} + +std::ostream& operator<<(std::ostream& os, const IPAddress& ip) { + os << ip.ToString(); + return os; +} + +in6_addr IPAddress::ipv6_address() const { + return u_.ip6; +} + +in_addr IPAddress::ipv4_address() const { + return u_.ip4; +} + +std::string IPAddress::ToString() const { + if (family_ != AF_INET && family_ != AF_INET6) { + return std::string(); + } + char buf[INET6_ADDRSTRLEN] = {0}; + const void* src = &u_.ip4; + if (family_ == AF_INET6) { + src = &u_.ip6; + } + if (!rtc::inet_ntop(family_, src, buf, sizeof(buf))) { + return std::string(); + } + return std::string(buf); +} + +std::string IPAddress::ToSensitiveString() const { + if (!strip_sensitive_) + return ToString(); + + switch (family_) { + case AF_INET: { + std::string address = ToString(); + size_t find_pos = address.rfind('.'); + if (find_pos == std::string::npos) + return std::string(); + address.resize(find_pos); + address += ".x"; + return address; + } + case AF_INET6: { + // TODO(grunell): Return a string of format 1:2:3:x:x:x:x:x or such + // instead of zeroing out. + return TruncateIP(*this, 128 - 80).ToString(); + } + } + return std::string(); +} + +IPAddress IPAddress::Normalized() const { + if (family_ != AF_INET6) { + return *this; + } + if (!IPIsV4Mapped(*this)) { + return *this; + } + in_addr addr = ExtractMappedAddress(u_.ip6); + return IPAddress(addr); +} + +IPAddress IPAddress::AsIPv6Address() const { + if (family_ != AF_INET) { + return *this; + } + in6_addr v6addr = kV4MappedPrefix; + ::memcpy(&v6addr.s6_addr[12], &u_.ip4.s_addr, sizeof(u_.ip4.s_addr)); + return IPAddress(v6addr); +} + +void IPAddress::set_strip_sensitive(bool enable) { + strip_sensitive_ = enable; +} + + +bool IsPrivateV4(uint32 ip_in_host_order) { + return ((ip_in_host_order >> 24) == 127) || + ((ip_in_host_order >> 24) == 10) || + ((ip_in_host_order >> 20) == ((172 << 4) | 1)) || + ((ip_in_host_order >> 16) == ((192 << 8) | 168)) || + ((ip_in_host_order >> 16) == ((169 << 8) | 254)); +} + +in_addr ExtractMappedAddress(const in6_addr& in6) { + in_addr ipv4; + ::memcpy(&ipv4.s_addr, &in6.s6_addr[12], sizeof(ipv4.s_addr)); + return ipv4; +} + +bool IPFromAddrInfo(struct addrinfo* info, IPAddress* out) { + if (!info || !info->ai_addr) { + return false; + } + if (info->ai_addr->sa_family == AF_INET) { + sockaddr_in* addr = reinterpret_cast(info->ai_addr); + *out = IPAddress(addr->sin_addr); + return true; + } else if (info->ai_addr->sa_family == AF_INET6) { + sockaddr_in6* addr = reinterpret_cast(info->ai_addr); + *out = IPAddress(addr->sin6_addr); + return true; + } + return false; +} + +bool IPFromString(const std::string& str, IPAddress* out) { + if (!out) { + return false; + } + in_addr addr; + if (rtc::inet_pton(AF_INET, str.c_str(), &addr) == 0) { + in6_addr addr6; + if (rtc::inet_pton(AF_INET6, str.c_str(), &addr6) == 0) { + *out = IPAddress(); + return false; + } + *out = IPAddress(addr6); + } else { + *out = IPAddress(addr); + } + return true; +} + +bool IPIsAny(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: + return ip == IPAddress(INADDR_ANY); + case AF_INET6: + return ip == IPAddress(in6addr_any); + case AF_UNSPEC: + return false; + } + return false; +} + +bool IPIsLoopback(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: { + return ip == IPAddress(INADDR_LOOPBACK); + } + case AF_INET6: { + return ip == IPAddress(in6addr_loopback); + } + } + return false; +} + +bool IPIsPrivate(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: { + return IsPrivateV4(ip.v4AddressAsHostOrderInteger()); + } + case AF_INET6: { + in6_addr v6 = ip.ipv6_address(); + return (v6.s6_addr[0] == 0xFE && v6.s6_addr[1] == 0x80) || + IPIsLoopback(ip); + } + } + return false; +} + +bool IPIsUnspec(const IPAddress& ip) { + return ip.family() == AF_UNSPEC; +} + +size_t HashIP(const IPAddress& ip) { + switch (ip.family()) { + case AF_INET: { + return ip.ipv4_address().s_addr; + } + case AF_INET6: { + in6_addr v6addr = ip.ipv6_address(); + const uint32* v6_as_ints = + reinterpret_cast(&v6addr.s6_addr); + return v6_as_ints[0] ^ v6_as_ints[1] ^ v6_as_ints[2] ^ v6_as_ints[3]; + } + } + return 0; +} + +IPAddress TruncateIP(const IPAddress& ip, int length) { + if (length < 0) { + return IPAddress(); + } + if (ip.family() == AF_INET) { + if (length > 31) { + return ip; + } + if (length == 0) { + return IPAddress(INADDR_ANY); + } + int mask = (0xFFFFFFFF << (32 - length)); + uint32 host_order_ip = NetworkToHost32(ip.ipv4_address().s_addr); + in_addr masked; + masked.s_addr = HostToNetwork32(host_order_ip & mask); + return IPAddress(masked); + } else if (ip.family() == AF_INET6) { + if (length > 127) { + return ip; + } + if (length == 0) { + return IPAddress(in6addr_any); + } + in6_addr v6addr = ip.ipv6_address(); + int position = length / 32; + int inner_length = 32 - (length - (position * 32)); + // Note: 64bit mask constant needed to allow possible 32-bit left shift. + uint32 inner_mask = 0xFFFFFFFFLL << inner_length; + uint32* v6_as_ints = + reinterpret_cast(&v6addr.s6_addr); + for (int i = 0; i < 4; ++i) { + if (i == position) { + uint32 host_order_inner = NetworkToHost32(v6_as_ints[i]); + v6_as_ints[i] = HostToNetwork32(host_order_inner & inner_mask); + } else if (i > position) { + v6_as_ints[i] = 0; + } + } + return IPAddress(v6addr); + } + return IPAddress(); +} + +int CountIPMaskBits(IPAddress mask) { + uint32 word_to_count = 0; + int bits = 0; + switch (mask.family()) { + case AF_INET: { + word_to_count = NetworkToHost32(mask.ipv4_address().s_addr); + break; + } + case AF_INET6: { + in6_addr v6addr = mask.ipv6_address(); + const uint32* v6_as_ints = + reinterpret_cast(&v6addr.s6_addr); + int i = 0; + for (; i < 4; ++i) { + if (v6_as_ints[i] != 0xFFFFFFFF) { + break; + } + } + if (i < 4) { + word_to_count = NetworkToHost32(v6_as_ints[i]); + } + bits = (i * 32); + break; + } + default: { + return 0; + } + } + if (word_to_count == 0) { + return bits; + } + + // Public domain bit-twiddling hack from: + // http://graphics.stanford.edu/~seander/bithacks.html + // Counts the trailing 0s in the word. + unsigned int zeroes = 32; + word_to_count &= -static_cast(word_to_count); + if (word_to_count) zeroes--; + if (word_to_count & 0x0000FFFF) zeroes -= 16; + if (word_to_count & 0x00FF00FF) zeroes -= 8; + if (word_to_count & 0x0F0F0F0F) zeroes -= 4; + if (word_to_count & 0x33333333) zeroes -= 2; + if (word_to_count & 0x55555555) zeroes -= 1; + + return bits + (32 - zeroes); +} + +bool IPIsHelper(const IPAddress& ip, const in6_addr& tomatch, int length) { + // Helper method for checking IP prefix matches (but only on whole byte + // lengths). Length is in bits. + in6_addr addr = ip.ipv6_address(); + return ::memcmp(&addr, &tomatch, (length >> 3)) == 0; +} + +bool IPIs6Bone(const IPAddress& ip) { + return IPIsHelper(ip, k6BonePrefix, 16); +} + +bool IPIs6To4(const IPAddress& ip) { + return IPIsHelper(ip, k6To4Prefix, 16); +} + +bool IPIsSiteLocal(const IPAddress& ip) { + // Can't use the helper because the prefix is 10 bits. + in6_addr addr = ip.ipv6_address(); + return addr.s6_addr[0] == 0xFE && (addr.s6_addr[1] & 0xC0) == 0xC0; +} + +bool IPIsULA(const IPAddress& ip) { + // Can't use the helper because the prefix is 7 bits. + in6_addr addr = ip.ipv6_address(); + return (addr.s6_addr[0] & 0xFE) == 0xFC; +} + +bool IPIsTeredo(const IPAddress& ip) { + return IPIsHelper(ip, kTeredoPrefix, 32); +} + +bool IPIsV4Compatibility(const IPAddress& ip) { + return IPIsHelper(ip, kV4CompatibilityPrefix, 96); +} + +bool IPIsV4Mapped(const IPAddress& ip) { + return IPIsHelper(ip, kV4MappedPrefix, 96); +} + +int IPAddressPrecedence(const IPAddress& ip) { + // Precedence values from RFC 3484-bis. Prefers native v4 over 6to4/Teredo. + if (ip.family() == AF_INET) { + return 30; + } else if (ip.family() == AF_INET6) { + if (IPIsLoopback(ip)) { + return 60; + } else if (IPIsULA(ip)) { + return 50; + } else if (IPIsV4Mapped(ip)) { + return 30; + } else if (IPIs6To4(ip)) { + return 20; + } else if (IPIsTeredo(ip)) { + return 10; + } else if (IPIsV4Compatibility(ip) || IPIsSiteLocal(ip) || IPIs6Bone(ip)) { + return 1; + } else { + // A 'normal' IPv6 address. + return 40; + } + } + return 0; +} + +} // Namespace talk base diff --git a/webrtc/base/ipaddress.h b/webrtc/base/ipaddress.h new file mode 100644 index 000000000..e7d649acb --- /dev/null +++ b/webrtc/base/ipaddress.h @@ -0,0 +1,141 @@ +/* + * Copyright 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_BASE_IPADDRESS_H_ +#define WEBRTC_BASE_IPADDRESS_H_ + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#include +#endif +#if defined(WEBRTC_WIN) +#include +#include +#endif +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +// Version-agnostic IP address class, wraps a union of in_addr and in6_addr. +class IPAddress { + public: + IPAddress() : family_(AF_UNSPEC) { + ::memset(&u_, 0, sizeof(u_)); + } + + explicit IPAddress(const in_addr &ip4) : family_(AF_INET) { + memset(&u_, 0, sizeof(u_)); + u_.ip4 = ip4; + } + + explicit IPAddress(const in6_addr &ip6) : family_(AF_INET6) { + u_.ip6 = ip6; + } + + explicit IPAddress(uint32 ip_in_host_byte_order) : family_(AF_INET) { + memset(&u_, 0, sizeof(u_)); + u_.ip4.s_addr = HostToNetwork32(ip_in_host_byte_order); + } + + IPAddress(const IPAddress &other) : family_(other.family_) { + ::memcpy(&u_, &other.u_, sizeof(u_)); + } + + ~IPAddress() {} + + const IPAddress & operator=(const IPAddress &other) { + family_ = other.family_; + ::memcpy(&u_, &other.u_, sizeof(u_)); + return *this; + } + + bool operator==(const IPAddress &other) const; + bool operator!=(const IPAddress &other) const; + bool operator <(const IPAddress &other) const; + bool operator >(const IPAddress &other) const; + friend std::ostream& operator<<(std::ostream& os, const IPAddress& addr); + + int family() const { return family_; } + in_addr ipv4_address() const; + in6_addr ipv6_address() const; + + // Returns the number of bytes needed to store the raw address. + size_t Size() const; + + // Wraps inet_ntop. + std::string ToString() const; + + // Same as ToString but anonymizes it by hiding the last part. + std::string ToSensitiveString() const; + + // Returns an unmapped address from a possibly-mapped address. + // Returns the same address if this isn't a mapped address. + IPAddress Normalized() const; + + // Returns this address as an IPv6 address. + // Maps v4 addresses (as ::ffff:a.b.c.d), returns v6 addresses unchanged. + IPAddress AsIPv6Address() const; + + // For socketaddress' benefit. Returns the IP in host byte order. + uint32 v4AddressAsHostOrderInteger() const; + + static void set_strip_sensitive(bool enable); + + private: + int family_; + union { + in_addr ip4; + in6_addr ip6; + } u_; + + static bool strip_sensitive_; +}; + +bool IPFromAddrInfo(struct addrinfo* info, IPAddress* out); +bool IPFromString(const std::string& str, IPAddress* out); +bool IPIsAny(const IPAddress& ip); +bool IPIsLoopback(const IPAddress& ip); +bool IPIsPrivate(const IPAddress& ip); +bool IPIsUnspec(const IPAddress& ip); +size_t HashIP(const IPAddress& ip); + +// These are only really applicable for IPv6 addresses. +bool IPIs6Bone(const IPAddress& ip); +bool IPIs6To4(const IPAddress& ip); +bool IPIsSiteLocal(const IPAddress& ip); +bool IPIsTeredo(const IPAddress& ip); +bool IPIsULA(const IPAddress& ip); +bool IPIsV4Compatibility(const IPAddress& ip); +bool IPIsV4Mapped(const IPAddress& ip); + +// Returns the precedence value for this IP as given in RFC3484. +int IPAddressPrecedence(const IPAddress& ip); + +// Returns 'ip' truncated to be 'length' bits long. +IPAddress TruncateIP(const IPAddress& ip, int length); + +// Returns the number of contiguously set bits, counting from the MSB in network +// byte order, in this IPAddress. Bits after the first 0 encountered are not +// counted. +int CountIPMaskBits(IPAddress mask); + +} // namespace rtc + +#endif // WEBRTC_BASE_IPADDRESS_H_ diff --git a/webrtc/base/ipaddress_unittest.cc b/webrtc/base/ipaddress_unittest.cc new file mode 100644 index 000000000..657595f68 --- /dev/null +++ b/webrtc/base/ipaddress_unittest.cc @@ -0,0 +1,859 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/ipaddress.h" + +namespace rtc { + +static const unsigned int kIPv4AddrSize = 4; +static const unsigned int kIPv6AddrSize = 16; +static const unsigned int kIPv4RFC1918Addr = 0xC0A80701; +static const unsigned int kIPv4PublicAddr = 0x01020304; +static const in6_addr kIPv6LinkLocalAddr = {{{0xfe, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xbe, 0x30, 0x5b, 0xff, + 0xfe, 0xe5, 0x00, 0xc3}}}; +static const in6_addr kIPv6PublicAddr = {{{0x24, 0x01, 0xfa, 0x00, + 0x00, 0x04, 0x10, 0x00, + 0xbe, 0x30, 0x5b, 0xff, + 0xfe, 0xe5, 0x00, 0xc3}}}; +static const in6_addr kIPv4MappedAnyAddr = {{{0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00}}}; +static const in6_addr kIPv4MappedRFC1918Addr = {{{0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0xc0, 0xa8, 0x07, 0x01}}}; +static const in6_addr kIPv4MappedPublicAddr = {{{0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0x01, 0x02, 0x03, 0x04}}}; + +static const std::string kIPv4AnyAddrString = "0.0.0.0"; +static const std::string kIPv4LoopbackAddrString = "127.0.0.1"; +static const std::string kIPv4RFC1918AddrString = "192.168.7.1"; +static const std::string kIPv4PublicAddrString = "1.2.3.4"; +static const std::string kIPv4PublicAddrAnonymizedString = "1.2.3.x"; +static const std::string kIPv6AnyAddrString = "::"; +static const std::string kIPv6LoopbackAddrString = "::1"; +static const std::string kIPv6LinkLocalAddrString = "fe80::be30:5bff:fee5:c3"; +static const std::string kIPv6PublicAddrString = + "2401:fa00:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6PublicAddrAnonymizedString = "2401:fa00:4::"; +static const std::string kIPv4MappedAnyAddrString = "::ffff:0:0"; +static const std::string kIPv4MappedRFC1918AddrString = "::ffff:c0a8:701"; +static const std::string kIPv4MappedLoopbackAddrString = "::ffff:7f00:1"; +static const std::string kIPv4MappedPublicAddrString = "::ffff:102:0304"; +static const std::string kIPv4MappedV4StyleAddrString = "::ffff:192.168.7.1"; + +static const std::string kIPv4BrokenString1 = "192.168.7."; +static const std::string kIPv4BrokenString2 = "192.168.7.1.1"; +static const std::string kIPv4BrokenString3 = "192.168.7.1:80"; +static const std::string kIPv4BrokenString4 = "192.168.7.ONE"; +static const std::string kIPv4BrokenString5 = "-192.168.7.1"; +static const std::string kIPv4BrokenString6 = "256.168.7.1"; +static const std::string kIPv6BrokenString1 = "2401:fa00:4:1000:be30"; +static const std::string kIPv6BrokenString2 = + "2401:fa00:4:1000:be30:5bff:fee5:c3:1"; +static const std::string kIPv6BrokenString3 = + "[2401:fa00:4:1000:be30:5bff:fee5:c3]:1"; +static const std::string kIPv6BrokenString4 = + "2401::4::be30"; +static const std::string kIPv6BrokenString5 = + "2401:::4:fee5:be30"; +static const std::string kIPv6BrokenString6 = + "2401f:fa00:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString7 = + "2401:ga00:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString8 = + "2401:fa000:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString9 = + "2401:fal0:4:1000:be30:5bff:fee5:c3"; +static const std::string kIPv6BrokenString10 = + "::ffff:192.168.7."; +static const std::string kIPv6BrokenString11 = + "::ffff:192.168.7.1.1.1"; +static const std::string kIPv6BrokenString12 = + "::fffe:192.168.7.1"; +static const std::string kIPv6BrokenString13 = + "::ffff:192.168.7.ff"; +static const std::string kIPv6BrokenString14 = + "0x2401:fa00:4:1000:be30:5bff:fee5:c3"; + +bool AreEqual(const IPAddress& addr, + const IPAddress& addr2) { + if ((IPIsAny(addr) != IPIsAny(addr2)) || + (IPIsLoopback(addr) != IPIsLoopback(addr2)) || + (IPIsPrivate(addr) != IPIsPrivate(addr2)) || + (HashIP(addr) != HashIP(addr2)) || + (addr.Size() != addr2.Size()) || + (addr.family() != addr2.family()) || + (addr.ToString() != addr2.ToString())) { + return false; + } + in_addr v4addr, v4addr2; + v4addr = addr.ipv4_address(); + v4addr2 = addr2.ipv4_address(); + if (0 != memcmp(&v4addr, &v4addr2, sizeof(v4addr))) { + return false; + } + in6_addr v6addr, v6addr2; + v6addr = addr.ipv6_address(); + v6addr2 = addr2.ipv6_address(); + if (0 != memcmp(&v6addr, &v6addr2, sizeof(v6addr))) { + return false; + } + return true; +} + +bool BrokenIPStringFails(const std::string& broken) { + IPAddress addr(0); // Intentionally make it v4. + if (IPFromString(kIPv4BrokenString1, &addr)) { + return false; + } + return addr.family() == AF_UNSPEC; +} + +bool CheckMaskCount(const std::string& mask, int expected_length) { + IPAddress addr; + return IPFromString(mask, &addr) && + (expected_length == CountIPMaskBits(addr)); +} + +bool TryInvalidMaskCount(const std::string& mask) { + // We don't care about the result at all, but we do want to know if + // CountIPMaskBits is going to crash or infinite loop or something. + IPAddress addr; + if (!IPFromString(mask, &addr)) { + return false; + } + CountIPMaskBits(addr); + return true; +} + +bool CheckTruncateIP(const std::string& initial, int truncate_length, + const std::string& expected_result) { + IPAddress addr, expected; + IPFromString(initial, &addr); + IPFromString(expected_result, &expected); + IPAddress truncated = TruncateIP(addr, truncate_length); + return truncated == expected; +} + +TEST(IPAddressTest, TestDefaultCtor) { + IPAddress addr; + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + + EXPECT_EQ(0U, addr.Size()); + EXPECT_EQ(AF_UNSPEC, addr.family()); + EXPECT_EQ("", addr.ToString()); +} + +TEST(IPAddressTest, TestInAddrCtor) { + in_addr v4addr; + + // Test V4 Any address. + v4addr.s_addr = INADDR_ANY; + IPAddress addr(v4addr); + EXPECT_TRUE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4AnyAddrString, addr.ToString()); + + // Test a V4 loopback address. + v4addr.s_addr = htonl(INADDR_LOOPBACK); + addr = IPAddress(v4addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_TRUE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString()); + + // Test an RFC1918 address. + v4addr.s_addr = htonl(kIPv4RFC1918Addr); + addr = IPAddress(v4addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString()); + + // Test a 'normal' v4 address. + v4addr.s_addr = htonl(kIPv4PublicAddr); + addr = IPAddress(v4addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4PublicAddrString, addr.ToString()); +} + +TEST(IPAddressTest, TestInAddr6Ctor) { + // Test v6 empty. + IPAddress addr(in6addr_any); + EXPECT_TRUE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6AnyAddrString, addr.ToString()); + + // Test v6 loopback. + addr = IPAddress(in6addr_loopback); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_TRUE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6LoopbackAddrString, addr.ToString()); + + // Test v6 link-local. + addr = IPAddress(kIPv6LinkLocalAddr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6LinkLocalAddrString, addr.ToString()); + + // Test v6 global address. + addr = IPAddress(kIPv6PublicAddr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv6AddrSize, addr.Size()); + EXPECT_EQ(kIPv6PublicAddrString, addr.ToString()); +} + +TEST(IPAddressTest, TestUint32Ctor) { + // Test V4 Any address. + IPAddress addr(0); + EXPECT_TRUE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4AnyAddrString, addr.ToString()); + + // Test a V4 loopback address. + addr = IPAddress(INADDR_LOOPBACK); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_TRUE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString()); + + // Test an RFC1918 address. + addr = IPAddress(kIPv4RFC1918Addr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_TRUE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString()); + + // Test a 'normal' v4 address. + addr = IPAddress(kIPv4PublicAddr); + EXPECT_FALSE(IPIsAny(addr)); + EXPECT_FALSE(IPIsLoopback(addr)); + EXPECT_FALSE(IPIsPrivate(addr)); + EXPECT_EQ(kIPv4AddrSize, addr.Size()); + EXPECT_EQ(kIPv4PublicAddrString, addr.ToString()); +} + +TEST(IPAddressTest, TestCopyCtor) { + in_addr v4addr; + v4addr.s_addr = htonl(kIPv4PublicAddr); + IPAddress addr(v4addr); + IPAddress addr2(addr); + + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(INADDR_ANY); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(INADDR_LOOPBACK); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv4PublicAddr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv4RFC1918Addr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(in6addr_any); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(in6addr_loopback); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv6LinkLocalAddr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr = IPAddress(kIPv6PublicAddr); + addr2 = IPAddress(addr); + EXPECT_PRED2(AreEqual, addr, addr2); +} + +TEST(IPAddressTest, TestEquality) { + // Check v4 equality + in_addr v4addr, v4addr2; + v4addr.s_addr = htonl(kIPv4PublicAddr); + v4addr2.s_addr = htonl(kIPv4PublicAddr + 1); + IPAddress addr(v4addr); + IPAddress addr2(v4addr2); + IPAddress addr3(v4addr); + + EXPECT_TRUE(addr == addr); + EXPECT_TRUE(addr2 == addr2); + EXPECT_TRUE(addr3 == addr3); + EXPECT_TRUE(addr == addr3); + EXPECT_TRUE(addr3 == addr); + EXPECT_FALSE(addr2 == addr); + EXPECT_FALSE(addr2 == addr3); + EXPECT_FALSE(addr == addr2); + EXPECT_FALSE(addr3 == addr2); + + // Check v6 equality + IPAddress addr4(kIPv6PublicAddr); + IPAddress addr5(kIPv6LinkLocalAddr); + IPAddress addr6(kIPv6PublicAddr); + + EXPECT_TRUE(addr4 == addr4); + EXPECT_TRUE(addr5 == addr5); + EXPECT_TRUE(addr4 == addr6); + EXPECT_TRUE(addr6 == addr4); + EXPECT_FALSE(addr4 == addr5); + EXPECT_FALSE(addr5 == addr4); + EXPECT_FALSE(addr6 == addr5); + EXPECT_FALSE(addr5 == addr6); + + // Check v4/v6 cross-equality + EXPECT_FALSE(addr == addr4); + EXPECT_FALSE(addr == addr5); + EXPECT_FALSE(addr == addr6); + EXPECT_FALSE(addr4 == addr); + EXPECT_FALSE(addr5 == addr); + EXPECT_FALSE(addr6 == addr); + EXPECT_FALSE(addr2 == addr4); + EXPECT_FALSE(addr2 == addr5); + EXPECT_FALSE(addr2 == addr6); + EXPECT_FALSE(addr4 == addr2); + EXPECT_FALSE(addr5 == addr2); + EXPECT_FALSE(addr6 == addr2); + EXPECT_FALSE(addr3 == addr4); + EXPECT_FALSE(addr3 == addr5); + EXPECT_FALSE(addr3 == addr6); + EXPECT_FALSE(addr4 == addr3); + EXPECT_FALSE(addr5 == addr3); + EXPECT_FALSE(addr6 == addr3); + + // Special cases: loopback and any. + // They're special but they're still not equal. + IPAddress v4loopback(htonl(INADDR_LOOPBACK)); + IPAddress v6loopback(in6addr_loopback); + EXPECT_FALSE(v4loopback == v6loopback); + + IPAddress v4any(0); + IPAddress v6any(in6addr_any); + EXPECT_FALSE(v4any == v6any); +} + +TEST(IPAddressTest, TestComparison) { + // Defined in 'ascending' order. + // v6 > v4, and intra-family sorting is purely numerical + IPAddress addr0; // AF_UNSPEC + IPAddress addr1(INADDR_ANY); // 0.0.0.0 + IPAddress addr2(kIPv4PublicAddr); // 1.2.3.4 + IPAddress addr3(INADDR_LOOPBACK); // 127.0.0.1 + IPAddress addr4(kIPv4RFC1918Addr); // 192.168.7.1. + IPAddress addr5(in6addr_any); // :: + IPAddress addr6(in6addr_loopback); // ::1 + IPAddress addr7(kIPv6PublicAddr); // 2401.... + IPAddress addr8(kIPv6LinkLocalAddr); // fe80.... + + EXPECT_TRUE(addr0 < addr1); + EXPECT_TRUE(addr1 < addr2); + EXPECT_TRUE(addr2 < addr3); + EXPECT_TRUE(addr3 < addr4); + EXPECT_TRUE(addr4 < addr5); + EXPECT_TRUE(addr5 < addr6); + EXPECT_TRUE(addr6 < addr7); + EXPECT_TRUE(addr7 < addr8); + + EXPECT_FALSE(addr0 > addr1); + EXPECT_FALSE(addr1 > addr2); + EXPECT_FALSE(addr2 > addr3); + EXPECT_FALSE(addr3 > addr4); + EXPECT_FALSE(addr4 > addr5); + EXPECT_FALSE(addr5 > addr6); + EXPECT_FALSE(addr6 > addr7); + EXPECT_FALSE(addr7 > addr8); + + EXPECT_FALSE(addr0 > addr0); + EXPECT_FALSE(addr1 > addr1); + EXPECT_FALSE(addr2 > addr2); + EXPECT_FALSE(addr3 > addr3); + EXPECT_FALSE(addr4 > addr4); + EXPECT_FALSE(addr5 > addr5); + EXPECT_FALSE(addr6 > addr6); + EXPECT_FALSE(addr7 > addr7); + EXPECT_FALSE(addr8 > addr8); + + EXPECT_FALSE(addr0 < addr0); + EXPECT_FALSE(addr1 < addr1); + EXPECT_FALSE(addr2 < addr2); + EXPECT_FALSE(addr3 < addr3); + EXPECT_FALSE(addr4 < addr4); + EXPECT_FALSE(addr5 < addr5); + EXPECT_FALSE(addr6 < addr6); + EXPECT_FALSE(addr7 < addr7); + EXPECT_FALSE(addr8 < addr8); +} + +TEST(IPAddressTest, TestFromString) { + IPAddress addr; + IPAddress addr2; + addr2 = IPAddress(INADDR_ANY); + + EXPECT_TRUE(IPFromString(kIPv4AnyAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4AnyAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(INADDR_LOOPBACK); + EXPECT_TRUE(IPFromString(kIPv4LoopbackAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4LoopbackAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv4RFC1918Addr); + EXPECT_TRUE(IPFromString(kIPv4RFC1918AddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4RFC1918AddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv4PublicAddr); + EXPECT_TRUE(IPFromString(kIPv4PublicAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv4PublicAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(in6addr_any); + EXPECT_TRUE(IPFromString(kIPv6AnyAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6AnyAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(in6addr_loopback); + EXPECT_TRUE(IPFromString(kIPv6LoopbackAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6LoopbackAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv6LinkLocalAddr); + EXPECT_TRUE(IPFromString(kIPv6LinkLocalAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6LinkLocalAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv6PublicAddr); + EXPECT_TRUE(IPFromString(kIPv6PublicAddrString, &addr)); + EXPECT_EQ(addr.ToString(), kIPv6PublicAddrString); + EXPECT_PRED2(AreEqual, addr, addr2); + + addr2 = IPAddress(kIPv4MappedRFC1918Addr); + EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr)); + EXPECT_PRED2(AreEqual, addr, addr2); + + // Broken cases, should set addr to AF_UNSPEC. + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString1); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString2); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString3); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString4); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString5); + EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString6); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString1); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString2); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString3); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString4); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString5); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString6); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString7); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString8); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString9); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString10); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString11); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString12); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString13); + EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString14); +} + +TEST(IPAddressTest, TestIPFromAddrInfo) { + struct sockaddr_in expected4; + struct sockaddr_in6 expected6; + struct addrinfo test_info; + struct addrinfo next_info; + memset(&next_info, 'A', sizeof(next_info)); + test_info.ai_next = &next_info; + // Check that we can get an IPv4 address out. + test_info.ai_addr = reinterpret_cast(&expected4); + expected4.sin_addr.s_addr = HostToNetwork32(kIPv4PublicAddr); + expected4.sin_family = AF_INET; + IPAddress expected(kIPv4PublicAddr); + IPAddress addr; + EXPECT_TRUE(IPFromAddrInfo(&test_info, &addr)); + EXPECT_EQ(expected, addr); + // Check that we can get an IPv6 address out. + expected6.sin6_addr = kIPv6PublicAddr; + expected6.sin6_family = AF_INET6; + expected = IPAddress(kIPv6PublicAddr); + test_info.ai_addr = reinterpret_cast(&expected6); + EXPECT_TRUE(IPFromAddrInfo(&test_info, &addr)); + EXPECT_EQ(expected, addr); + // Check that unspec fails. + expected6.sin6_family = AF_UNSPEC; + EXPECT_FALSE(IPFromAddrInfo(&test_info, &addr)); + // Check a zeroed out addrinfo doesn't crash us. + memset(&next_info, 0, sizeof(next_info)); + EXPECT_FALSE(IPFromAddrInfo(&next_info, &addr)); +} + +TEST(IPAddressTest, TestIsPrivate) { + EXPECT_FALSE(IPIsPrivate(IPAddress(INADDR_ANY))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4PublicAddr))); + EXPECT_FALSE(IPIsPrivate(IPAddress(in6addr_any))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv6PublicAddr))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedAnyAddr))); + EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedPublicAddr))); + + EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv4RFC1918Addr))); + EXPECT_TRUE(IPIsPrivate(IPAddress(INADDR_LOOPBACK))); + EXPECT_TRUE(IPIsPrivate(IPAddress(in6addr_loopback))); + EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv6LinkLocalAddr))); +} + +TEST(IPAddressTest, TestIsLoopback) { + EXPECT_FALSE(IPIsLoopback(IPAddress(INADDR_ANY))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4PublicAddr))); + EXPECT_FALSE(IPIsLoopback(IPAddress(in6addr_any))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv6PublicAddr))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedAnyAddr))); + EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedPublicAddr))); + + EXPECT_TRUE(IPIsLoopback(IPAddress(INADDR_LOOPBACK))); + EXPECT_TRUE(IPIsLoopback(IPAddress(in6addr_loopback))); +} + +TEST(IPAddressTest, TestNormalized) { + // Check normalizing a ::ffff:a.b.c.d address. + IPAddress addr; + EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr)); + IPAddress addr2(kIPv4RFC1918Addr); + addr = addr.Normalized(); + EXPECT_EQ(addr2, addr); + + // Check normalizing a ::ffff:aabb:ccdd address. + addr = IPAddress(kIPv4MappedPublicAddr); + addr2 = IPAddress(kIPv4PublicAddr); + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + + // Check that a non-mapped v6 addresses isn't altered. + addr = IPAddress(kIPv6PublicAddr); + addr2 = IPAddress(kIPv6PublicAddr); + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + + // Check that addresses that look a bit like mapped addresses aren't altered + EXPECT_TRUE(IPFromString("fe80::ffff:0102:0304", &addr)); + addr2 = addr; + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + EXPECT_TRUE(IPFromString("::0102:0304", &addr)); + addr2 = addr; + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + // This string should 'work' as an IP address but is not a mapped address, + // so it shouldn't change on normalization. + EXPECT_TRUE(IPFromString("::192.168.7.1", &addr)); + addr2 = addr; + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); + + // Check that v4 addresses aren't altered. + addr = IPAddress(htonl(kIPv4PublicAddr)); + addr2 = IPAddress(htonl(kIPv4PublicAddr)); + addr = addr.Normalized(); + EXPECT_EQ(addr, addr2); +} + +TEST(IPAddressTest, TestAsIPv6Address) { + IPAddress addr(kIPv4PublicAddr); + IPAddress addr2(kIPv4MappedPublicAddr); + addr = addr.AsIPv6Address(); + EXPECT_EQ(addr, addr2); + + addr = IPAddress(kIPv4MappedPublicAddr); + addr2 = IPAddress(kIPv4MappedPublicAddr); + addr = addr.AsIPv6Address(); + EXPECT_EQ(addr, addr2); + + addr = IPAddress(kIPv6PublicAddr); + addr2 = IPAddress(kIPv6PublicAddr); + addr = addr.AsIPv6Address(); + EXPECT_EQ(addr, addr2); +} + +TEST(IPAddressTest, TestCountIPMaskBits) { + IPAddress mask; + // IPv4 on byte boundaries + EXPECT_PRED2(CheckMaskCount, "255.255.255.255", 32); + EXPECT_PRED2(CheckMaskCount, "255.255.255.0", 24); + EXPECT_PRED2(CheckMaskCount, "255.255.0.0", 16); + EXPECT_PRED2(CheckMaskCount, "255.0.0.0", 8); + EXPECT_PRED2(CheckMaskCount, "0.0.0.0", 0); + + // IPv4 not on byte boundaries + EXPECT_PRED2(CheckMaskCount, "128.0.0.0", 1); + EXPECT_PRED2(CheckMaskCount, "224.0.0.0", 3); + EXPECT_PRED2(CheckMaskCount, "255.248.0.0", 13); + EXPECT_PRED2(CheckMaskCount, "255.255.224.0", 19); + EXPECT_PRED2(CheckMaskCount, "255.255.255.252", 30); + + // V6 on byte boundaries + EXPECT_PRED2(CheckMaskCount, "::", 0); + EXPECT_PRED2(CheckMaskCount, "ff00::", 8); + EXPECT_PRED2(CheckMaskCount, "ffff::", 16); + EXPECT_PRED2(CheckMaskCount, "ffff:ff00::", 24); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff::", 32); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00::", 40); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::", 48); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00::", 56); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff::", 64); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff00::", 72); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff::", 80); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff00::", 88); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff::", 96); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0000", 104); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0000", 112); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128); + + // V6 not on byte boundaries. + EXPECT_PRED2(CheckMaskCount, "8000::", 1); + EXPECT_PRED2(CheckMaskCount, "ff80::", 9); + EXPECT_PRED2(CheckMaskCount, "ffff:fe00::", 23); + EXPECT_PRED2(CheckMaskCount, "ffff:fffe::", 31); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:e000::", 35); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffe0::", 43); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:f800::", 53); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:fff8::", 61); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fc00::", 70); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fffc::", 78); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:8000::", 81); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff80::", 89); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fe00::", 103); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0000", 111); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00", 118); + EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc", 126); + + // Non-contiguous ranges. These are invalid but lets test them + // to make sure they don't crash anything or infinite loop or something. + EXPECT_PRED1(TryInvalidMaskCount, "217.0.0.0"); + EXPECT_PRED1(TryInvalidMaskCount, "255.185.0.0"); + EXPECT_PRED1(TryInvalidMaskCount, "255.255.251.0"); + EXPECT_PRED1(TryInvalidMaskCount, "255.255.251.255"); + EXPECT_PRED1(TryInvalidMaskCount, "255.255.254.201"); + EXPECT_PRED1(TryInvalidMaskCount, "::1"); + EXPECT_PRED1(TryInvalidMaskCount, "fe80::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ff80::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ff00:1::1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff::ffff:1"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ff00:1::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff::ff00"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ff00:1234::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:0012::ffff"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ff01::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:7f00::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ff7a::"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:7f00:0000"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff70:0000"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0211"); + EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff7f"); +} + +TEST(IPAddressTest, TestTruncateIP) { + EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 24, "255.255.255.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 16, "255.255.0.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 8, "255.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "202.67.7.255", 24, "202.67.7.0"); + EXPECT_PRED3(CheckTruncateIP, "202.129.65.205", 16, "202.129.0.0"); + EXPECT_PRED3(CheckTruncateIP, "55.25.2.77", 8, "55.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "74.128.99.254", 1, "0.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "106.55.99.254", 3, "96.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "172.167.53.222", 13, "172.160.0.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.224.0", 18, "255.255.192.0"); + EXPECT_PRED3(CheckTruncateIP, "255.255.255.252", 28, "255.255.255.240"); + + EXPECT_PRED3(CheckTruncateIP, "fe80:1111:2222:3333:4444:5555:6666:7777", 1, + "8000::"); + EXPECT_PRED3(CheckTruncateIP, "fff0:1111:2222:3333:4444:5555:6666:7777", 9, + "ff80::"); + EXPECT_PRED3(CheckTruncateIP, "ffff:ff80:1111:2222:3333:4444:5555:6666", 23, + "ffff:fe00::"); + EXPECT_PRED3(CheckTruncateIP, "ffff:ff80:1111:2222:3333:4444:5555:6666", 32, + "ffff:ff80::"); + EXPECT_PRED3(CheckTruncateIP, "2400:f9af:e456:1111:2222:3333:4444:5555", 35, + "2400:f9af:e000::"); + EXPECT_PRED3(CheckTruncateIP, "9999:1111:2233:4444:5555:6666:7777:8888", 53, + "9999:1111:2233:4000::"); + EXPECT_PRED3(CheckTruncateIP, "9999:1111:2233:4567:5555:6666:7777:8888", 64, + "9999:1111:2233:4567::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 68, + "1111:2222:3333:4444:5000::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 92, + "1111:2222:3333:4444:5555:6660::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 96, + "1111:2222:3333:4444:5555:6666::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 105, + "1111:2222:3333:4444:5555:6666:7700::"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 124, + "1111:2222:3333:4444:5555:6666:7777:8880"); + + // Slightly degenerate cases + EXPECT_PRED3(CheckTruncateIP, "202.165.33.127", 32, "202.165.33.127"); + EXPECT_PRED3(CheckTruncateIP, "235.105.77.12", 0, "0.0.0.0"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 128, + "1111:2222:3333:4444:5555:6666:7777:8888"); + EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 0, + "::"); +} + +TEST(IPAddressTest, TestCategorizeIPv6) { + // Test determining if an IPAddress is 6Bone/6To4/Teredo/etc. + // IPv4 address, should be none of these (not even v4compat/v4mapped). + IPAddress v4_addr(kIPv4PublicAddr); + EXPECT_FALSE(IPIs6Bone(v4_addr)); + EXPECT_FALSE(IPIs6To4(v4_addr)); + EXPECT_FALSE(IPIsSiteLocal(v4_addr)); + EXPECT_FALSE(IPIsTeredo(v4_addr)); + EXPECT_FALSE(IPIsULA(v4_addr)); + EXPECT_FALSE(IPIsV4Compatibility(v4_addr)); + EXPECT_FALSE(IPIsV4Mapped(v4_addr)); + // Linklocal (fe80::/16) adddress; should be none of these. + IPAddress linklocal_addr(kIPv6LinkLocalAddr); + EXPECT_FALSE(IPIs6Bone(linklocal_addr)); + EXPECT_FALSE(IPIs6To4(linklocal_addr)); + EXPECT_FALSE(IPIsSiteLocal(linklocal_addr)); + EXPECT_FALSE(IPIsTeredo(linklocal_addr)); + EXPECT_FALSE(IPIsULA(linklocal_addr)); + EXPECT_FALSE(IPIsV4Compatibility(linklocal_addr)); + EXPECT_FALSE(IPIsV4Mapped(linklocal_addr)); + // 'Normal' IPv6 address, should also be none of these. + IPAddress normal_addr(kIPv6PublicAddr); + EXPECT_FALSE(IPIs6Bone(normal_addr)); + EXPECT_FALSE(IPIs6To4(normal_addr)); + EXPECT_FALSE(IPIsSiteLocal(normal_addr)); + EXPECT_FALSE(IPIsTeredo(normal_addr)); + EXPECT_FALSE(IPIsULA(normal_addr)); + EXPECT_FALSE(IPIsV4Compatibility(normal_addr)); + EXPECT_FALSE(IPIsV4Mapped(normal_addr)); + // IPv4 mapped address (::ffff:123.123.123.123) + IPAddress v4mapped_addr(kIPv4MappedPublicAddr); + EXPECT_TRUE(IPIsV4Mapped(v4mapped_addr)); + EXPECT_FALSE(IPIsV4Compatibility(v4mapped_addr)); + EXPECT_FALSE(IPIs6Bone(v4mapped_addr)); + EXPECT_FALSE(IPIs6To4(v4mapped_addr)); + EXPECT_FALSE(IPIsSiteLocal(v4mapped_addr)); + EXPECT_FALSE(IPIsTeredo(v4mapped_addr)); + EXPECT_FALSE(IPIsULA(v4mapped_addr)); + // IPv4 compatibility address (::123.123.123.123) + IPAddress v4compat_addr; + IPFromString("::192.168.7.1", &v4compat_addr); + EXPECT_TRUE(IPIsV4Compatibility(v4compat_addr)); + EXPECT_FALSE(IPIs6Bone(v4compat_addr)); + EXPECT_FALSE(IPIs6To4(v4compat_addr)); + EXPECT_FALSE(IPIsSiteLocal(v4compat_addr)); + EXPECT_FALSE(IPIsTeredo(v4compat_addr)); + EXPECT_FALSE(IPIsULA(v4compat_addr)); + EXPECT_FALSE(IPIsV4Mapped(v4compat_addr)); + // 6Bone address (3FFE::/16) + IPAddress sixbone_addr; + IPFromString("3FFE:123:456::789:123", &sixbone_addr); + EXPECT_TRUE(IPIs6Bone(sixbone_addr)); + EXPECT_FALSE(IPIs6To4(sixbone_addr)); + EXPECT_FALSE(IPIsSiteLocal(sixbone_addr)); + EXPECT_FALSE(IPIsTeredo(sixbone_addr)); + EXPECT_FALSE(IPIsULA(sixbone_addr)); + EXPECT_FALSE(IPIsV4Mapped(sixbone_addr)); + EXPECT_FALSE(IPIsV4Compatibility(sixbone_addr)); + // Unique Local Address (FC::/7) + IPAddress ula_addr; + IPFromString("FC00:123:456::789:123", &ula_addr); + EXPECT_TRUE(IPIsULA(ula_addr)); + EXPECT_FALSE(IPIs6Bone(ula_addr)); + EXPECT_FALSE(IPIs6To4(ula_addr)); + EXPECT_FALSE(IPIsSiteLocal(ula_addr)); + EXPECT_FALSE(IPIsTeredo(ula_addr)); + EXPECT_FALSE(IPIsV4Mapped(ula_addr)); + EXPECT_FALSE(IPIsV4Compatibility(ula_addr)); + // 6To4 Address (2002::/16) + IPAddress sixtofour_addr; + IPFromString("2002:123:456::789:123", &sixtofour_addr); + EXPECT_TRUE(IPIs6To4(sixtofour_addr)); + EXPECT_FALSE(IPIs6Bone(sixtofour_addr)); + EXPECT_FALSE(IPIsSiteLocal(sixtofour_addr)); + EXPECT_FALSE(IPIsTeredo(sixtofour_addr)); + EXPECT_FALSE(IPIsULA(sixtofour_addr)); + EXPECT_FALSE(IPIsV4Compatibility(sixtofour_addr)); + EXPECT_FALSE(IPIsV4Mapped(sixtofour_addr)); + // Site Local address (FEC0::/10) + IPAddress sitelocal_addr; + IPFromString("FEC0:123:456::789:123", &sitelocal_addr); + EXPECT_TRUE(IPIsSiteLocal(sitelocal_addr)); + EXPECT_FALSE(IPIs6Bone(sitelocal_addr)); + EXPECT_FALSE(IPIs6To4(sitelocal_addr)); + EXPECT_FALSE(IPIsTeredo(sitelocal_addr)); + EXPECT_FALSE(IPIsULA(sitelocal_addr)); + EXPECT_FALSE(IPIsV4Compatibility(sitelocal_addr)); + EXPECT_FALSE(IPIsV4Mapped(sitelocal_addr)); + // Teredo Address (2001:0000::/32) + IPAddress teredo_addr; + IPFromString("2001:0000:123:456::789:123", &teredo_addr); + EXPECT_TRUE(IPIsTeredo(teredo_addr)); + EXPECT_FALSE(IPIsSiteLocal(teredo_addr)); + EXPECT_FALSE(IPIs6Bone(teredo_addr)); + EXPECT_FALSE(IPIs6To4(teredo_addr)); + EXPECT_FALSE(IPIsULA(teredo_addr)); + EXPECT_FALSE(IPIsV4Compatibility(teredo_addr)); + EXPECT_FALSE(IPIsV4Mapped(teredo_addr)); +} + +TEST(IPAddressTest, TestToSensitiveString) { + IPAddress addr_v4 = IPAddress(kIPv4PublicAddr); + EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToString()); + EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToString()); + EXPECT_EQ(kIPv4PublicAddrAnonymizedString, addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); + + IPAddress addr_v6 = IPAddress(kIPv6PublicAddr); + EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToString()); + EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToString()); + EXPECT_EQ(kIPv6PublicAddrAnonymizedString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); +} + +} // namespace rtc diff --git a/webrtc/base/json.cc b/webrtc/base/json.cc new file mode 100644 index 000000000..49a051c01 --- /dev/null +++ b/webrtc/base/json.cc @@ -0,0 +1,296 @@ +/* + * Copyright 2004 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 "webrtc/base/json.h" + +#include +#include +#include + +#include + +bool GetStringFromJson(const Json::Value& in, std::string* out) { + if (!in.isString()) { + std::ostringstream s; + if (in.isBool()) { + s << std::boolalpha << in.asBool(); + } else if (in.isInt()) { + s << in.asInt(); + } else if (in.isUInt()) { + s << in.asUInt(); + } else if (in.isDouble()) { + s << in.asDouble(); + } else { + return false; + } + *out = s.str(); + } else { + *out = in.asString(); + } + return true; +} + +bool GetIntFromJson(const Json::Value& in, int* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::intValue); + if (ret) { + *out = in.asInt(); + } + } else { + long val; // NOLINT + const char* c_str = in.asCString(); + char* end_ptr; + errno = 0; + val = strtol(c_str, &end_ptr, 10); // NOLINT + ret = (end_ptr != c_str && *end_ptr == '\0' && !errno && + val >= INT_MIN && val <= INT_MAX); + *out = val; + } + return ret; +} + +bool GetUIntFromJson(const Json::Value& in, unsigned int* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::uintValue); + if (ret) { + *out = in.asUInt(); + } + } else { + unsigned long val; // NOLINT + const char* c_str = in.asCString(); + char* end_ptr; + errno = 0; + val = strtoul(c_str, &end_ptr, 10); // NOLINT + ret = (end_ptr != c_str && *end_ptr == '\0' && !errno && + val <= UINT_MAX); + *out = val; + } + return ret; +} + +bool GetBoolFromJson(const Json::Value& in, bool* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::booleanValue); + if (ret) { + *out = in.asBool(); + } + } else { + if (in.asString() == "true") { + *out = true; + ret = true; + } else if (in.asString() == "false") { + *out = false; + ret = true; + } else { + ret = false; + } + } + return ret; +} + +bool GetDoubleFromJson(const Json::Value& in, double* out) { + bool ret; + if (!in.isString()) { + ret = in.isConvertibleTo(Json::realValue); + if (ret) { + *out = in.asDouble(); + } + } else { + double val; + const char* c_str = in.asCString(); + char* end_ptr; + errno = 0; + val = strtod(c_str, &end_ptr); + ret = (end_ptr != c_str && *end_ptr == '\0' && !errno); + *out = val; + } + return ret; +} + +namespace { +template +bool JsonArrayToVector(const Json::Value& value, + bool (*getter)(const Json::Value& in, T* out), + std::vector *vec) { + vec->clear(); + if (!value.isArray()) { + return false; + } + + for (Json::Value::ArrayIndex i = 0; i < value.size(); ++i) { + T val; + if (!getter(value[i], &val)) { + return false; + } + vec->push_back(val); + } + + return true; +} +// Trivial getter helper +bool GetValueFromJson(const Json::Value& in, Json::Value* out) { + *out = in; + return true; +} +} // unnamed namespace + +bool JsonArrayToValueVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetValueFromJson, out); +} + +bool JsonArrayToIntVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetIntFromJson, out); +} + +bool JsonArrayToUIntVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetUIntFromJson, out); +} + +bool JsonArrayToStringVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetStringFromJson, out); +} + +bool JsonArrayToBoolVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetBoolFromJson, out); +} + +bool JsonArrayToDoubleVector(const Json::Value& in, + std::vector* out) { + return JsonArrayToVector(in, GetDoubleFromJson, out); +} + +namespace { +template +Json::Value VectorToJsonArray(const std::vector& vec) { + Json::Value result(Json::arrayValue); + for (size_t i = 0; i < vec.size(); ++i) { + result.append(Json::Value(vec[i])); + } + return result; +} +} // unnamed namespace + +Json::Value ValueVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value IntVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value UIntVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value StringVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value BoolVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +Json::Value DoubleVectorToJsonArray(const std::vector& in) { + return VectorToJsonArray(in); +} + +bool GetValueFromJsonArray(const Json::Value& in, size_t n, + Json::Value* out) { + if (!in.isArray() || !in.isValidIndex(static_cast(n))) { + return false; + } + + *out = in[static_cast(n)]; + return true; +} + +bool GetIntFromJsonArray(const Json::Value& in, size_t n, + int* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetIntFromJson(x, out); +} + +bool GetUIntFromJsonArray(const Json::Value& in, size_t n, + unsigned int* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetUIntFromJson(x, out); +} + +bool GetStringFromJsonArray(const Json::Value& in, size_t n, + std::string* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetStringFromJson(x, out); +} + +bool GetBoolFromJsonArray(const Json::Value& in, size_t n, + bool* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetBoolFromJson(x, out); +} + +bool GetDoubleFromJsonArray(const Json::Value& in, size_t n, + double* out) { + Json::Value x; + return GetValueFromJsonArray(in, n, &x) && GetDoubleFromJson(x, out); +} + +bool GetValueFromJsonObject(const Json::Value& in, const std::string& k, + Json::Value* out) { + if (!in.isObject() || !in.isMember(k)) { + return false; + } + + *out = in[k]; + return true; +} + +bool GetIntFromJsonObject(const Json::Value& in, const std::string& k, + int* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetIntFromJson(x, out); +} + +bool GetUIntFromJsonObject(const Json::Value& in, const std::string& k, + unsigned int* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetUIntFromJson(x, out); +} + +bool GetStringFromJsonObject(const Json::Value& in, const std::string& k, + std::string* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetStringFromJson(x, out); +} + +bool GetBoolFromJsonObject(const Json::Value& in, const std::string& k, + bool* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetBoolFromJson(x, out); +} + +bool GetDoubleFromJsonObject(const Json::Value& in, const std::string& k, + double* out) { + Json::Value x; + return GetValueFromJsonObject(in, k, &x) && GetDoubleFromJson(x, out); +} + +std::string JsonValueToString(const Json::Value& json) { + Json::FastWriter w; + std::string value = w.write(json); + return value.substr(0, value.size() - 1); // trim trailing newline +} diff --git a/webrtc/base/json.h b/webrtc/base/json.h new file mode 100644 index 000000000..9d45ded97 --- /dev/null +++ b/webrtc/base/json.h @@ -0,0 +1,89 @@ +/* + * Copyright 2004 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_BASE_JSON_H_ +#define WEBRTC_BASE_JSON_H_ + +#include +#include + +#if !defined(WEBRTC_EXTERNAL_JSON) +#include "json/json.h" +#else +#include "third_party/jsoncpp/json.h" +#endif + +// TODO: Move to rtc namespace + +/////////////////////////////////////////////////////////////////////////////// +// JSON Helpers +/////////////////////////////////////////////////////////////////////////////// + +// Robust conversion operators, better than the ones in JsonCpp. +bool GetIntFromJson(const Json::Value& in, int* out); +bool GetUIntFromJson(const Json::Value& in, unsigned int* out); +bool GetStringFromJson(const Json::Value& in, std::string* out); +bool GetBoolFromJson(const Json::Value& in, bool* out); +bool GetDoubleFromJson(const Json::Value& in, double* out); + +// Pull values out of a JSON array. +bool GetValueFromJsonArray(const Json::Value& in, size_t n, + Json::Value* out); +bool GetIntFromJsonArray(const Json::Value& in, size_t n, + int* out); +bool GetUIntFromJsonArray(const Json::Value& in, size_t n, + unsigned int* out); +bool GetStringFromJsonArray(const Json::Value& in, size_t n, + std::string* out); +bool GetBoolFromJsonArray(const Json::Value& in, size_t n, + bool* out); +bool GetDoubleFromJsonArray(const Json::Value& in, size_t n, + double* out); + +// Convert json arrays to std::vector +bool JsonArrayToValueVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToIntVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToUIntVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToStringVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToBoolVector(const Json::Value& in, + std::vector* out); +bool JsonArrayToDoubleVector(const Json::Value& in, + std::vector* out); + +// Convert std::vector to json array +Json::Value ValueVectorToJsonArray(const std::vector& in); +Json::Value IntVectorToJsonArray(const std::vector& in); +Json::Value UIntVectorToJsonArray(const std::vector& in); +Json::Value StringVectorToJsonArray(const std::vector& in); +Json::Value BoolVectorToJsonArray(const std::vector& in); +Json::Value DoubleVectorToJsonArray(const std::vector& in); + +// Pull values out of a JSON object. +bool GetValueFromJsonObject(const Json::Value& in, const std::string& k, + Json::Value* out); +bool GetIntFromJsonObject(const Json::Value& in, const std::string& k, + int* out); +bool GetUIntFromJsonObject(const Json::Value& in, const std::string& k, + unsigned int* out); +bool GetStringFromJsonObject(const Json::Value& in, const std::string& k, + std::string* out); +bool GetBoolFromJsonObject(const Json::Value& in, const std::string& k, + bool* out); +bool GetDoubleFromJsonObject(const Json::Value& in, const std::string& k, + double* out); + +// Writes out a Json value as a string. +std::string JsonValueToString(const Json::Value& json); + +#endif // WEBRTC_BASE_JSON_H_ diff --git a/webrtc/base/json_unittest.cc b/webrtc/base/json_unittest.cc new file mode 100644 index 000000000..e7e58227b --- /dev/null +++ b/webrtc/base/json_unittest.cc @@ -0,0 +1,277 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/json.h" + +static Json::Value in_s("foo"); +static Json::Value in_sn("99"); +static Json::Value in_si("-99"); +static Json::Value in_sb("true"); +static Json::Value in_sd("1.2"); +static Json::Value in_n(12); +static Json::Value in_i(-12); +static Json::Value in_u(34U); +static Json::Value in_b(true); +static Json::Value in_d(1.2); +static Json::Value big_sn("12345678901234567890"); +static Json::Value big_si("-12345678901234567890"); +static Json::Value big_u(0xFFFFFFFF); +static Json::Value bad_a(Json::arrayValue); +static Json::Value bad_o(Json::objectValue); + +TEST(JsonTest, GetString) { + std::string out; + EXPECT_TRUE(GetStringFromJson(in_s, &out)); + EXPECT_EQ("foo", out); + EXPECT_TRUE(GetStringFromJson(in_sn, &out)); + EXPECT_EQ("99", out); + EXPECT_TRUE(GetStringFromJson(in_si, &out)); + EXPECT_EQ("-99", out); + EXPECT_TRUE(GetStringFromJson(in_i, &out)); + EXPECT_EQ("-12", out); + EXPECT_TRUE(GetStringFromJson(in_n, &out)); + EXPECT_EQ("12", out); + EXPECT_TRUE(GetStringFromJson(in_u, &out)); + EXPECT_EQ("34", out); + EXPECT_TRUE(GetStringFromJson(in_b, &out)); + EXPECT_EQ("true", out); + // Not supported here yet. + EXPECT_FALSE(GetStringFromJson(bad_a, &out)); + EXPECT_FALSE(GetStringFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetInt) { + int out; + EXPECT_TRUE(GetIntFromJson(in_sn, &out)); + EXPECT_EQ(99, out); + EXPECT_TRUE(GetIntFromJson(in_si, &out)); + EXPECT_EQ(-99, out); + EXPECT_TRUE(GetIntFromJson(in_n, &out)); + EXPECT_EQ(12, out); + EXPECT_TRUE(GetIntFromJson(in_i, &out)); + EXPECT_EQ(-12, out); + EXPECT_TRUE(GetIntFromJson(in_u, &out)); + EXPECT_EQ(34, out); + EXPECT_TRUE(GetIntFromJson(in_b, &out)); + EXPECT_EQ(1, out); + EXPECT_FALSE(GetIntFromJson(in_s, &out)); + EXPECT_FALSE(GetIntFromJson(big_sn, &out)); + EXPECT_FALSE(GetIntFromJson(big_si, &out)); + EXPECT_FALSE(GetIntFromJson(big_u, &out)); + EXPECT_FALSE(GetIntFromJson(bad_a, &out)); + EXPECT_FALSE(GetIntFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetUInt) { + unsigned int out; + EXPECT_TRUE(GetUIntFromJson(in_sn, &out)); + EXPECT_EQ(99U, out); + EXPECT_TRUE(GetUIntFromJson(in_n, &out)); + EXPECT_EQ(12U, out); + EXPECT_TRUE(GetUIntFromJson(in_u, &out)); + EXPECT_EQ(34U, out); + EXPECT_TRUE(GetUIntFromJson(in_b, &out)); + EXPECT_EQ(1U, out); + EXPECT_TRUE(GetUIntFromJson(big_u, &out)); + EXPECT_EQ(0xFFFFFFFFU, out); + EXPECT_FALSE(GetUIntFromJson(in_s, &out)); + // TODO: Fail reading negative strings. + // EXPECT_FALSE(GetUIntFromJson(in_si, &out)); + EXPECT_FALSE(GetUIntFromJson(in_i, &out)); + EXPECT_FALSE(GetUIntFromJson(big_sn, &out)); + EXPECT_FALSE(GetUIntFromJson(big_si, &out)); + EXPECT_FALSE(GetUIntFromJson(bad_a, &out)); + EXPECT_FALSE(GetUIntFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetBool) { + bool out; + EXPECT_TRUE(GetBoolFromJson(in_sb, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_n, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_i, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_u, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(in_b, &out)); + EXPECT_EQ(true, out); + EXPECT_TRUE(GetBoolFromJson(big_u, &out)); + EXPECT_EQ(true, out); + EXPECT_FALSE(GetBoolFromJson(in_s, &out)); + EXPECT_FALSE(GetBoolFromJson(in_sn, &out)); + EXPECT_FALSE(GetBoolFromJson(in_si, &out)); + EXPECT_FALSE(GetBoolFromJson(big_sn, &out)); + EXPECT_FALSE(GetBoolFromJson(big_si, &out)); + EXPECT_FALSE(GetBoolFromJson(bad_a, &out)); + EXPECT_FALSE(GetBoolFromJson(bad_o, &out)); +} + +TEST(JsonTest, GetDouble) { + double out; + EXPECT_TRUE(GetDoubleFromJson(in_sn, &out)); + EXPECT_EQ(99, out); + EXPECT_TRUE(GetDoubleFromJson(in_si, &out)); + EXPECT_EQ(-99, out); + EXPECT_TRUE(GetDoubleFromJson(in_sd, &out)); + EXPECT_EQ(1.2, out); + EXPECT_TRUE(GetDoubleFromJson(in_n, &out)); + EXPECT_EQ(12, out); + EXPECT_TRUE(GetDoubleFromJson(in_i, &out)); + EXPECT_EQ(-12, out); + EXPECT_TRUE(GetDoubleFromJson(in_u, &out)); + EXPECT_EQ(34, out); + EXPECT_TRUE(GetDoubleFromJson(in_b, &out)); + EXPECT_EQ(1, out); + EXPECT_TRUE(GetDoubleFromJson(in_d, &out)); + EXPECT_EQ(1.2, out); + EXPECT_FALSE(GetDoubleFromJson(in_s, &out)); +} + +TEST(JsonTest, GetFromArray) { + Json::Value a, out; + a.append(in_s); + a.append(in_i); + a.append(in_u); + a.append(in_b); + EXPECT_TRUE(GetValueFromJsonArray(a, 0, &out)); + EXPECT_TRUE(GetValueFromJsonArray(a, 3, &out)); + EXPECT_FALSE(GetValueFromJsonArray(a, 99, &out)); + EXPECT_FALSE(GetValueFromJsonArray(a, 0xFFFFFFFF, &out)); +} + +TEST(JsonTest, GetFromObject) { + Json::Value o, out; + o["string"] = in_s; + o["int"] = in_i; + o["uint"] = in_u; + o["bool"] = in_b; + EXPECT_TRUE(GetValueFromJsonObject(o, "int", &out)); + EXPECT_TRUE(GetValueFromJsonObject(o, "bool", &out)); + EXPECT_FALSE(GetValueFromJsonObject(o, "foo", &out)); + EXPECT_FALSE(GetValueFromJsonObject(o, "", &out)); +} + +namespace { +template +std::vector VecOf3(const T& a, const T& b, const T& c) { + std::vector in; + in.push_back(a); + in.push_back(b); + in.push_back(c); + return in; +} +template +Json::Value JsonVecOf3(const T& a, const T& b, const T& c) { + Json::Value in(Json::arrayValue); + in.append(a); + in.append(b); + in.append(c); + return in; +} +} // unnamed namespace + +TEST(JsonTest, ValueVectorToFromArray) { + std::vector in = VecOf3("a", "b", "c"); + Json::Value out = ValueVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i].asString(), out[i].asString()); + } + Json::Value inj = JsonVecOf3("a", "b", "c"); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToValueVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, IntVectorToFromArray) { + std::vector in = VecOf3(1, 2, 3); + Json::Value out = IntVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asInt()); + } + Json::Value inj = JsonVecOf3(1, 2, 3); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToIntVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, UIntVectorToFromArray) { + std::vector in = VecOf3(1, 2, 3); + Json::Value out = UIntVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asUInt()); + } + Json::Value inj = JsonVecOf3(1, 2, 3); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToUIntVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, StringVectorToFromArray) { + std::vector in = VecOf3("a", "b", "c"); + Json::Value out = StringVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asString()); + } + Json::Value inj = JsonVecOf3("a", "b", "c"); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToStringVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, BoolVectorToFromArray) { + std::vector in = VecOf3(false, true, false); + Json::Value out = BoolVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asBool()); + } + Json::Value inj = JsonVecOf3(false, true, false); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToBoolVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} + +TEST(JsonTest, DoubleVectorToFromArray) { + std::vector in = VecOf3(1.0, 2.0, 3.0); + Json::Value out = DoubleVectorToJsonArray(in); + EXPECT_EQ(in.size(), out.size()); + for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) { + EXPECT_EQ(in[i], out[i].asDouble()); + } + Json::Value inj = JsonVecOf3(1.0, 2.0, 3.0); + EXPECT_EQ(inj, out); + std::vector outj; + EXPECT_TRUE(JsonArrayToDoubleVector(inj, &outj)); + for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) { + EXPECT_EQ(in[i], outj[i]); + } +} diff --git a/webrtc/base/latebindingsymboltable.cc b/webrtc/base/latebindingsymboltable.cc new file mode 100644 index 000000000..1896bd0f9 --- /dev/null +++ b/webrtc/base/latebindingsymboltable.cc @@ -0,0 +1,156 @@ +/* + * Copyright 2004 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 "webrtc/base/latebindingsymboltable.h" + +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/logging.h" + +namespace rtc { + +#if defined(WEBRTC_POSIX) +static const DllHandle kInvalidDllHandle = NULL; +#else +#error Not implemented +#endif + +static const char *GetDllError() { +#if defined(WEBRTC_POSIX) + const char *err = dlerror(); + if (err) { + return err; + } else { + return "No error"; + } +#else +#error Not implemented +#endif +} + +static bool LoadSymbol(DllHandle handle, + const char *symbol_name, + void **symbol) { +#if defined(WEBRTC_POSIX) + *symbol = dlsym(handle, symbol_name); + const char *err = dlerror(); + if (err) { + LOG(LS_ERROR) << "Error loading symbol " << symbol_name << ": " << err; + return false; + } else if (!*symbol) { + // ELF allows for symbols to be NULL, but that should never happen for our + // usage. + LOG(LS_ERROR) << "Symbol " << symbol_name << " is NULL"; + return false; + } + return true; +#else +#error Not implemented +#endif +} + +LateBindingSymbolTable::LateBindingSymbolTable(const TableInfo *info, + void **table) + : info_(info), + table_(table), + handle_(kInvalidDllHandle), + undefined_symbols_(false) { + ClearSymbols(); +} + +LateBindingSymbolTable::~LateBindingSymbolTable() { + Unload(); +} + +bool LateBindingSymbolTable::IsLoaded() const { + return handle_ != kInvalidDllHandle; +} + +bool LateBindingSymbolTable::Load() { + ASSERT(info_->dll_name != NULL); + return LoadFromPath(info_->dll_name); +} + +bool LateBindingSymbolTable::LoadFromPath(const char *dll_path) { + if (IsLoaded()) { + return true; + } + if (undefined_symbols_) { + // We do not attempt to load again because repeated attempts are not + // likely to succeed and DLL loading is costly. + LOG(LS_ERROR) << "We know there are undefined symbols"; + return false; + } + +#if defined(WEBRTC_POSIX) + handle_ = dlopen(dll_path, + // RTLD_NOW front-loads symbol resolution so that errors are + // caught early instead of causing a process abort later. + // RTLD_LOCAL prevents other modules from automatically + // seeing symbol definitions in the newly-loaded tree. This + // is necessary for same-named symbols in different ABI + // versions of the same library to not explode. + RTLD_NOW|RTLD_LOCAL +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // RTLD_DEEPBIND makes symbol dependencies in the + // newly-loaded tree prefer to resolve to definitions within + // that tree (the default on OS X). This is necessary for + // same-named symbols in different ABI versions of the same + // library to not explode. + |RTLD_DEEPBIND +#endif + ); // NOLINT +#else +#error Not implemented +#endif + + if (handle_ == kInvalidDllHandle) { + LOG(LS_WARNING) << "Can't load " << dll_path << ": " + << GetDllError(); + return false; + } +#if defined(WEBRTC_POSIX) + // Clear any old errors. + dlerror(); +#endif + for (int i = 0; i < info_->num_symbols; ++i) { + if (!LoadSymbol(handle_, info_->symbol_names[i], &table_[i])) { + undefined_symbols_ = true; + Unload(); + return false; + } + } + return true; +} + +void LateBindingSymbolTable::Unload() { + if (!IsLoaded()) { + return; + } + +#if defined(WEBRTC_POSIX) + if (dlclose(handle_) != 0) { + LOG(LS_ERROR) << GetDllError(); + } +#else +#error Not implemented +#endif + + handle_ = kInvalidDllHandle; + ClearSymbols(); +} + +void LateBindingSymbolTable::ClearSymbols() { + memset(table_, 0, sizeof(void *) * info_->num_symbols); +} + +} // namespace rtc diff --git a/webrtc/base/latebindingsymboltable.cc.def b/webrtc/base/latebindingsymboltable.cc.def new file mode 100644 index 000000000..6ddb2ae62 --- /dev/null +++ b/webrtc/base/latebindingsymboltable.cc.def @@ -0,0 +1,69 @@ +/* + * Copyright 2012 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 file is a supermacro +// (see http://wanderinghorse.net/computing/papers/supermacros_cpp.html) to +// expand a definition of a late-binding symbol table class. +// +// Arguments: +// LATE_BINDING_SYMBOL_TABLE_CLASS_NAME: Name of the class to generate. +// LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST: List of symbols to load from the DLL, +// as an X-Macro list (see http://www.drdobbs.com/blogs/cpp/228700289). +// LATE_BINDING_SYMBOL_TABLE_DLL_NAME: String literal for the DLL file name to +// load. +// +// From a .cc file, include the header file containing your call to the .h.def +// supermacro, and then call this supermacro (optionally from inside the +// namespace for the class to generate, if any). Example: +// +// #include "myclassname.h" +// +// namespace foo { +// +// #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME MY_CLASS_NAME +// #define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST MY_SYMBOLS_LIST +// #define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libdll.so.n" +// #include "webrtc/base/latebindingsymboltable.cc.def" +// +// } + +#ifndef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#error You must define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#error You must define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_DLL_NAME +#error You must define LATE_BINDING_SYMBOL_TABLE_DLL_NAME +#endif + +#define X(sym) #sym, +const char* const LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kSymbolNames[] = { + LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +}; +#undef X + +const ::rtc::LateBindingSymbolTable::TableInfo + LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kTableInfo = { + LATE_BINDING_SYMBOL_TABLE_DLL_NAME, + SYMBOL_TABLE_SIZE, + LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kSymbolNames +}; + +LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::LATE_BINDING_SYMBOL_TABLE_CLASS_NAME() + : ::rtc::LateBindingSymbolTable(&kTableInfo, table_) {} + +LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::~LATE_BINDING_SYMBOL_TABLE_CLASS_NAME() {} + +#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#undef LATE_BINDING_SYMBOL_TABLE_DLL_NAME diff --git a/webrtc/base/latebindingsymboltable.h b/webrtc/base/latebindingsymboltable.h new file mode 100644 index 000000000..c1f535cd2 --- /dev/null +++ b/webrtc/base/latebindingsymboltable.h @@ -0,0 +1,69 @@ +/* + * Copyright 2004 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_BASE_LATEBINDINGSYMBOLTABLE_H_ +#define WEBRTC_BASE_LATEBINDINGSYMBOLTABLE_H_ + +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +#if defined(WEBRTC_POSIX) +typedef void *DllHandle; +#else +#error Not implemented for this platform +#endif + +// This is the base class for "symbol table" classes to simplify the dynamic +// loading of symbols from DLLs. Currently the implementation only supports +// Linux and OS X, and pure C symbols (or extern "C" symbols that wrap C++ +// functions). Sub-classes for specific DLLs are generated via the "supermacro" +// files latebindingsymboltable.h.def and latebindingsymboltable.cc.def. See +// talk/sound/pulseaudiosymboltable.(h|cc) for an example. +class LateBindingSymbolTable { + public: + struct TableInfo { + const char *dll_name; + int num_symbols; + // Array of size num_symbols. + const char *const *symbol_names; + }; + + LateBindingSymbolTable(const TableInfo *info, void **table); + ~LateBindingSymbolTable(); + + bool IsLoaded() const; + // Loads the DLL and the symbol table. Returns true iff the DLL and symbol + // table loaded successfully. + bool Load(); + // Like load, but allows overriding the dll path for when the dll path is + // dynamic. + bool LoadFromPath(const char *dll_path); + void Unload(); + + // Gets the raw OS handle to the DLL. Be careful what you do with it. + DllHandle GetDllHandle() const { return handle_; } + + private: + void ClearSymbols(); + + const TableInfo *info_; + void **table_; + DllHandle handle_; + bool undefined_symbols_; + + DISALLOW_COPY_AND_ASSIGN(LateBindingSymbolTable); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_LATEBINDINGSYMBOLTABLE_H_ diff --git a/webrtc/base/latebindingsymboltable.h.def b/webrtc/base/latebindingsymboltable.h.def new file mode 100644 index 000000000..39b515fbd --- /dev/null +++ b/webrtc/base/latebindingsymboltable.h.def @@ -0,0 +1,83 @@ +/* + * Copyright 2012 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 file is a supermacro +// (see http://wanderinghorse.net/computing/papers/supermacros_cpp.html) to +// expand a declaration of a late-binding symbol table class. +// +// Arguments: +// LATE_BINDING_SYMBOL_TABLE_CLASS_NAME: Name of the class to generate. +// LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST: List of symbols to load from the DLL, +// as an X-Macro list (see http://www.drdobbs.com/blogs/cpp/228700289). +// +// From a .h file, include the header(s) for the DLL to late-bind and the +// latebindingsymboltable.h header, and then call this supermacro (optionally +// from inside the namespace for the class to generate, if any). Example: +// +// #include +// +// #include "webrtc/base/latebindingsymboltable.h" +// +// namespace foo { +// +// #define MY_CLASS_NAME DesiredClassName +// #define MY_SYMBOLS_LIST X(acos) X(sin) X(tan) +// +// #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME MY_CLASS_NAME +// #define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST MY_SYMBOLS_LIST +// #include "webrtc/base/latebindingsymboltable.h.def" +// +// } + +#ifndef WEBRTC_BASE_LATEBINDINGSYMBOLTABLE_H_ +#error You must first include latebindingsymboltable.h +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#error You must define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#endif + +#ifndef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#error You must define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#endif + +class LATE_BINDING_SYMBOL_TABLE_CLASS_NAME : + public ::rtc::LateBindingSymbolTable { + public: + LATE_BINDING_SYMBOL_TABLE_CLASS_NAME(); + ~LATE_BINDING_SYMBOL_TABLE_CLASS_NAME(); + +#define X(sym) \ + typeof(&::sym) sym() const { \ + ASSERT(::rtc::LateBindingSymbolTable::IsLoaded()); \ + return reinterpret_cast(table_[SYMBOL_TABLE_INDEX_##sym]); \ + } +LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#undef X + + private: + enum { +#define X(sym) \ + SYMBOL_TABLE_INDEX_##sym, +LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST +#undef X + SYMBOL_TABLE_SIZE + }; + + static const ::rtc::LateBindingSymbolTable::TableInfo kTableInfo; + static const char *const kSymbolNames[]; + + void *table_[SYMBOL_TABLE_SIZE]; + + DISALLOW_COPY_AND_ASSIGN(LATE_BINDING_SYMBOL_TABLE_CLASS_NAME); +}; + +#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME +#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST diff --git a/webrtc/base/latebindingsymboltable_unittest.cc b/webrtc/base/latebindingsymboltable_unittest.cc new file mode 100644 index 000000000..30ebd17cb --- /dev/null +++ b/webrtc/base/latebindingsymboltable_unittest.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2010 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. + */ + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include +#endif + +#include "webrtc/base/gunit.h" +#include "webrtc/base/latebindingsymboltable.h" + +namespace rtc { + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + +#define LIBM_SYMBOLS_CLASS_NAME LibmTestSymbolTable +#define LIBM_SYMBOLS_LIST \ + X(acos) \ + X(sin) \ + X(tan) + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBM_SYMBOLS_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBM_SYMBOLS_LIST +#include "webrtc/base/latebindingsymboltable.h.def" + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBM_SYMBOLS_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBM_SYMBOLS_LIST +#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libm.so.6" +#include "webrtc/base/latebindingsymboltable.cc.def" + +TEST(LateBindingSymbolTable, libm) { + LibmTestSymbolTable table; + EXPECT_FALSE(table.IsLoaded()); + ASSERT_TRUE(table.Load()); + EXPECT_TRUE(table.IsLoaded()); + EXPECT_EQ(table.acos()(0.5), acos(0.5)); + EXPECT_EQ(table.sin()(0.5), sin(0.5)); + EXPECT_EQ(table.tan()(0.5), tan(0.5)); + // It would be nice to check that the addresses are the same, but the nature + // of dynamic linking and relocation makes them actually be different. + table.Unload(); + EXPECT_FALSE(table.IsLoaded()); +} + +#else +#error Not implemented +#endif + +} // namespace rtc diff --git a/webrtc/base/libdbusglibsymboltable.cc b/webrtc/base/libdbusglibsymboltable.cc new file mode 100644 index 000000000..ad51064bc --- /dev/null +++ b/webrtc/base/libdbusglibsymboltable.cc @@ -0,0 +1,24 @@ +/* + * Copyright 2004 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. + */ + +#ifdef HAVE_DBUS_GLIB + +#include "webrtc/base/libdbusglibsymboltable.h" + +namespace rtc { + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBDBUS_GLIB_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBDBUS_GLIB_SYMBOLS_LIST +#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libdbus-glib-1.so.2" +#include "webrtc/base/latebindingsymboltable.cc.def" + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB diff --git a/webrtc/base/libdbusglibsymboltable.h b/webrtc/base/libdbusglibsymboltable.h new file mode 100644 index 000000000..b87b4c174 --- /dev/null +++ b/webrtc/base/libdbusglibsymboltable.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 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_BASE_LIBDBUSGLIBSYMBOLTABLE_H_ +#define WEBRTC_BASE_LIBDBUSGLIBSYMBOLTABLE_H_ + +#ifdef HAVE_DBUS_GLIB + +#include +#include + +#include "webrtc/base/latebindingsymboltable.h" + +namespace rtc { + +#define LIBDBUS_GLIB_CLASS_NAME LibDBusGlibSymbolTable +// The libdbus-glib symbols we need, as an X-Macro list. +// This list must contain precisely every libdbus-glib function that is used in +// dbus.cc. +#define LIBDBUS_GLIB_SYMBOLS_LIST \ + X(dbus_bus_add_match) \ + X(dbus_connection_add_filter) \ + X(dbus_connection_close) \ + X(dbus_connection_remove_filter) \ + X(dbus_connection_set_exit_on_disconnect) \ + X(dbus_g_bus_get) \ + X(dbus_g_bus_get_private) \ + X(dbus_g_connection_get_connection) \ + X(dbus_g_connection_unref) \ + X(dbus_g_thread_init) \ + X(dbus_message_get_interface) \ + X(dbus_message_get_member) \ + X(dbus_message_get_path) \ + X(dbus_message_get_type) \ + X(dbus_message_iter_get_arg_type) \ + X(dbus_message_iter_get_basic) \ + X(dbus_message_iter_init) \ + X(dbus_message_ref) \ + X(dbus_message_unref) + +#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBDBUS_GLIB_CLASS_NAME +#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBDBUS_GLIB_SYMBOLS_LIST +#include "webrtc/base/latebindingsymboltable.h.def" + +} // namespace rtc + +#endif // HAVE_DBUS_GLIB + +#endif // WEBRTC_BASE_LIBDBUSGLIBSYMBOLTABLE_H_ diff --git a/webrtc/base/linked_ptr.h b/webrtc/base/linked_ptr.h new file mode 100644 index 000000000..65e5a00ec --- /dev/null +++ b/webrtc/base/linked_ptr.h @@ -0,0 +1,125 @@ +/* + * Copyright 2004 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. + */ + +/* + * linked_ptr - simple reference linked pointer + * (like reference counting, just using a linked list of the references + * instead of their count.) + * + * The implementation stores three pointers for every linked_ptr, but + * does not allocate anything on the free store. + */ + +#ifndef WEBRTC_BASE_LINKED_PTR_H__ +#define WEBRTC_BASE_LINKED_PTR_H__ + +namespace rtc { + +/* For ANSI-challenged compilers, you may want to #define + * NO_MEMBER_TEMPLATES, explicit or mutable */ +#define NO_MEMBER_TEMPLATES + +template class linked_ptr +{ +public: + +#ifndef NO_MEMBER_TEMPLATES +# define TEMPLATE_FUNCTION template + TEMPLATE_FUNCTION friend class linked_ptr; +#else +# define TEMPLATE_FUNCTION + typedef X Y; +#endif + + typedef X element_type; + + explicit linked_ptr(X* p = 0) throw() + : itsPtr(p) {itsPrev = itsNext = this;} + ~linked_ptr() + {release();} + linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } + +#ifndef NO_MEMBER_TEMPLATES + template friend class linked_ptr; + template linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + template linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } +#endif // NO_MEMBER_TEMPLATES + + X& operator*() const throw() {return *itsPtr;} + X* operator->() const throw() {return itsPtr;} + X* get() const throw() {return itsPtr;} + bool unique() const throw() {return itsPrev ? itsPrev==this : true;} + +private: + X* itsPtr; + mutable const linked_ptr* itsPrev; + mutable const linked_ptr* itsNext; + + void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } + +#ifndef NO_MEMBER_TEMPLATES + template void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } +#endif // NO_MEMBER_TEMPLATES + + void release() + { // erase this from the list, delete if unique + if (unique()) delete itsPtr; + else { + itsPrev->itsNext = itsNext; + itsNext->itsPrev = itsPrev; + itsPrev = itsNext = 0; + } + itsPtr = 0; + } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_LINKED_PTR_H__ + diff --git a/webrtc/base/linux.cc b/webrtc/base/linux.cc new file mode 100644 index 000000000..b95854353 --- /dev/null +++ b/webrtc/base/linux.cc @@ -0,0 +1,348 @@ +/* + * Copyright 2008 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. + */ + +#if defined(WEBRTC_LINUX) +#include "webrtc/base/linux.h" + +#include + +#include +#include +#include + +#include +#include + +#include "webrtc/base/stringencode.h" + +namespace rtc { + +static const char kCpuInfoFile[] = "/proc/cpuinfo"; +static const char kCpuMaxFreqFile[] = + "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"; + +ProcCpuInfo::ProcCpuInfo() { +} + +ProcCpuInfo::~ProcCpuInfo() { +} + +bool ProcCpuInfo::LoadFromSystem() { + ConfigParser procfs; + if (!procfs.Open(kCpuInfoFile)) { + return false; + } + return procfs.Parse(§ions_); +}; + +bool ProcCpuInfo::GetSectionCount(size_t* count) { + if (sections_.empty()) { + return false; + } + if (count) { + *count = sections_.size(); + } + return true; +} + +bool ProcCpuInfo::GetNumCpus(int* num) { + if (sections_.empty()) { + return false; + } + int total_cpus = 0; +#if defined(__arm__) + // Count the number of blocks that have a "processor" key defined. On ARM, + // there may be extra blocks of information that aren't per-processor. + size_t section_count = sections_.size(); + for (size_t i = 0; i < section_count; ++i) { + int processor_id; + if (GetSectionIntValue(i, "processor", &processor_id)) { + ++total_cpus; + } + } + // Single core ARM systems don't include "processor" keys at all, so return + // that we have a single core if we didn't find any explicitly above. + if (total_cpus == 0) { + total_cpus = 1; + } +#else + // On X86, there is exactly one info section per processor. + total_cpus = static_cast(sections_.size()); +#endif + if (num) { + *num = total_cpus; + } + return true; +} + +bool ProcCpuInfo::GetNumPhysicalCpus(int* num) { + if (sections_.empty()) { + return false; + } + // TODO: /proc/cpuinfo only reports cores that are currently + // _online_, so this may underreport the number of physical cores. +#if defined(__arm__) + // ARM (currently) has no hyperthreading, so just return the same value + // as GetNumCpus. + return GetNumCpus(num); +#else + int total_cores = 0; + std::set physical_ids; + size_t section_count = sections_.size(); + for (size_t i = 0; i < section_count; ++i) { + int physical_id; + int cores; + // Count the cores for the physical id only if we have not counted the id. + if (GetSectionIntValue(i, "physical id", &physical_id) && + GetSectionIntValue(i, "cpu cores", &cores) && + physical_ids.find(physical_id) == physical_ids.end()) { + physical_ids.insert(physical_id); + total_cores += cores; + } + } + + if (num) { + *num = total_cores; + } + return true; +#endif +} + +bool ProcCpuInfo::GetCpuFamily(int* id) { + int cpu_family = 0; + +#if defined(__arm__) + // On some ARM platforms, there is no 'cpu family' in '/proc/cpuinfo'. But + // there is 'CPU Architecture' which can be used as 'cpu family'. + // See http://en.wikipedia.org/wiki/ARM_architecture for a good list of + // ARM cpu families, architectures, and their mappings. + // There may be multiple sessions that aren't per-processor. We need to scan + // through each session until we find the first 'CPU architecture'. + size_t section_count = sections_.size(); + for (size_t i = 0; i < section_count; ++i) { + if (GetSectionIntValue(i, "CPU architecture", &cpu_family)) { + // We returns the first one (if there are multiple entries). + break; + }; + } +#else + GetSectionIntValue(0, "cpu family", &cpu_family); +#endif + if (id) { + *id = cpu_family; + } + return true; +} + +bool ProcCpuInfo::GetSectionStringValue(size_t section_num, + const std::string& key, + std::string* result) { + if (section_num >= sections_.size()) { + return false; + } + ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key); + if (iter == sections_[section_num].end()) { + return false; + } + *result = iter->second; + return true; +} + +bool ProcCpuInfo::GetSectionIntValue(size_t section_num, + const std::string& key, + int* result) { + if (section_num >= sections_.size()) { + return false; + } + ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key); + if (iter == sections_[section_num].end()) { + return false; + } + return FromString(iter->second, result); +} + +ConfigParser::ConfigParser() {} + +ConfigParser::~ConfigParser() {} + +bool ConfigParser::Open(const std::string& filename) { + FileStream* fs = new FileStream(); + if (!fs->Open(filename, "r", NULL)) { + return false; + } + instream_.reset(fs); + return true; +} + +void ConfigParser::Attach(StreamInterface* stream) { + instream_.reset(stream); +} + +bool ConfigParser::Parse(MapVector* key_val_pairs) { + // Parses the file and places the found key-value pairs into key_val_pairs. + SimpleMap section; + while (ParseSection(§ion)) { + key_val_pairs->push_back(section); + section.clear(); + } + return (!key_val_pairs->empty()); +} + +bool ConfigParser::ParseSection(SimpleMap* key_val_pair) { + // Parses the next section in the filestream and places the found key-value + // pairs into key_val_pair. + std::string key, value; + while (ParseLine(&key, &value)) { + (*key_val_pair)[key] = value; + } + return (!key_val_pair->empty()); +} + +bool ConfigParser::ParseLine(std::string* key, std::string* value) { + // Parses the next line in the filestream and places the found key-value + // pair into key and val. + std::string line; + if ((instream_->ReadLine(&line)) == SR_EOS) { + return false; + } + std::vector tokens; + if (2 != split(line, ':', &tokens)) { + return false; + } + // Removes whitespace at the end of Key name + size_t pos = tokens[0].length() - 1; + while ((pos > 0) && isspace(tokens[0][pos])) { + pos--; + } + tokens[0].erase(pos + 1); + // Removes whitespace at the start of value + pos = 0; + while (pos < tokens[1].length() && isspace(tokens[1][pos])) { + pos++; + } + tokens[1].erase(0, pos); + *key = tokens[0]; + *value = tokens[1]; + return true; +} + +#if !defined(WEBRTC_CHROMIUM_BUILDs) +static bool ExpectLineFromStream(FileStream* stream, + std::string* out) { + StreamResult res = stream->ReadLine(out); + if (res != SR_SUCCESS) { + if (res != SR_EOS) { + LOG(LS_ERROR) << "Error when reading from stream"; + } else { + LOG(LS_ERROR) << "Incorrect number of lines in stream"; + } + return false; + } + return true; +} + +static void ExpectEofFromStream(FileStream* stream) { + std::string unused; + StreamResult res = stream->ReadLine(&unused); + if (res == SR_SUCCESS) { + LOG(LS_WARNING) << "Ignoring unexpected extra lines from stream"; + } else if (res != SR_EOS) { + LOG(LS_WARNING) << "Error when checking for extra lines from stream"; + } +} + +// For caching the lsb_release output (reading it invokes a sub-process and +// hence is somewhat expensive). +static std::string lsb_release_string; +static CriticalSection lsb_release_string_critsec; + +std::string ReadLinuxLsbRelease() { + CritScope cs(&lsb_release_string_critsec); + if (!lsb_release_string.empty()) { + // Have cached result from previous call. + return lsb_release_string; + } + // No cached result. Run lsb_release and parse output. + POpenStream lsb_release_output; + if (!lsb_release_output.Open("lsb_release -idrcs", "r", NULL)) { + LOG_ERR(LS_ERROR) << "Can't run lsb_release"; + return lsb_release_string; // empty + } + // Read in the command's output and build the string. + std::ostringstream sstr; + std::string line; + int wait_status; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << "DISTRIB_ID=" << line; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << " DISTRIB_DESCRIPTION=\"" << line << '"'; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << " DISTRIB_RELEASE=" << line; + + if (!ExpectLineFromStream(&lsb_release_output, &line)) { + return lsb_release_string; // empty + } + sstr << " DISTRIB_CODENAME=" << line; + + // Should not be anything left. + ExpectEofFromStream(&lsb_release_output); + + lsb_release_output.Close(); + wait_status = lsb_release_output.GetWaitStatus(); + if (wait_status == -1 || + !WIFEXITED(wait_status) || + WEXITSTATUS(wait_status) != 0) { + LOG(LS_WARNING) << "Unexpected exit status from lsb_release"; + } + + lsb_release_string = sstr.str(); + + return lsb_release_string; +} +#endif + +std::string ReadLinuxUname() { + struct utsname buf; + if (uname(&buf) < 0) { + LOG_ERR(LS_ERROR) << "Can't call uname()"; + return std::string(); + } + std::ostringstream sstr; + sstr << buf.sysname << " " + << buf.release << " " + << buf.version << " " + << buf.machine; + return sstr.str(); +} + +int ReadCpuMaxFreq() { + FileStream fs; + std::string str; + int freq = -1; + if (!fs.Open(kCpuMaxFreqFile, "r", NULL) || + SR_SUCCESS != fs.ReadLine(&str) || + !FromString(str, &freq)) { + return -1; + } + return freq; +} + +} // namespace rtc + +#endif // defined(WEBRTC_LINUX) diff --git a/webrtc/base/linux.h b/webrtc/base/linux.h new file mode 100644 index 000000000..8f601878f --- /dev/null +++ b/webrtc/base/linux.h @@ -0,0 +1,123 @@ +/* + * Copyright 2008 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_BASE_LINUX_H_ +#define WEBRTC_BASE_LINUX_H_ + +#if defined(WEBRTC_LINUX) +#include +#include +#include + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////////////// +// ConfigParser parses a FileStream of an ".ini."-type format into a map. +////////////////////////////////////////////////////////////////////////////// + +// Sample Usage: +// ConfigParser parser; +// ConfigParser::MapVector key_val_pairs; +// if (parser.Open(inifile) && parser.Parse(&key_val_pairs)) { +// for (int section_num=0; i < key_val_pairs.size(); ++section_num) { +// std::string val1 = key_val_pairs[section_num][key1]; +// std::string val2 = key_val_pairs[section_num][key2]; +// // Do something with valn; +// } +// } + +class ConfigParser { + public: + typedef std::map SimpleMap; + typedef std::vector MapVector; + + ConfigParser(); + virtual ~ConfigParser(); + + virtual bool Open(const std::string& filename); + virtual void Attach(StreamInterface* stream); + virtual bool Parse(MapVector* key_val_pairs); + virtual bool ParseSection(SimpleMap* key_val_pair); + virtual bool ParseLine(std::string* key, std::string* value); + + private: + scoped_ptr instream_; +}; + +////////////////////////////////////////////////////////////////////////////// +// ProcCpuInfo reads CPU info from the /proc subsystem on any *NIX platform. +////////////////////////////////////////////////////////////////////////////// + +// Sample Usage: +// ProcCpuInfo proc_info; +// int no_of_cpu; +// if (proc_info.LoadFromSystem()) { +// std::string out_str; +// proc_info.GetNumCpus(&no_of_cpu); +// proc_info.GetCpuStringValue(0, "vendor_id", &out_str); +// } +// } + +class ProcCpuInfo { + public: + ProcCpuInfo(); + virtual ~ProcCpuInfo(); + + // Reads the proc subsystem's cpu info into memory. If this fails, this + // returns false; if it succeeds, it returns true. + virtual bool LoadFromSystem(); + + // Obtains the number of logical CPU threads and places the value num. + virtual bool GetNumCpus(int* num); + + // Obtains the number of physical CPU cores and places the value num. + virtual bool GetNumPhysicalCpus(int* num); + + // Obtains the CPU family id. + virtual bool GetCpuFamily(int* id); + + // Obtains the number of sections in /proc/cpuinfo, which may be greater + // than the number of CPUs (e.g. on ARM) + virtual bool GetSectionCount(size_t* count); + + // Looks for the CPU proc item with the given name for the given section + // number and places the string value in result. + virtual bool GetSectionStringValue(size_t section_num, const std::string& key, + std::string* result); + + // Looks for the CPU proc item with the given name for the given section + // number and places the int value in result. + virtual bool GetSectionIntValue(size_t section_num, const std::string& key, + int* result); + + private: + ConfigParser::MapVector sections_; +}; + +#if !defined(WEBRTC_CHROMIUM_BUILDs) +// Builds a string containing the info from lsb_release on a single line. +std::string ReadLinuxLsbRelease(); +#endif + +// Returns the output of "uname". +std::string ReadLinuxUname(); + +// Returns the content (int) of +// /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq +// Returns -1 on error. +int ReadCpuMaxFreq(); + +} // namespace rtc + +#endif // defined(WEBRTC_LINUX) +#endif // WEBRTC_BASE_LINUX_H_ diff --git a/webrtc/base/linux_unittest.cc b/webrtc/base/linux_unittest.cc new file mode 100644 index 000000000..c65ef071c --- /dev/null +++ b/webrtc/base/linux_unittest.cc @@ -0,0 +1,104 @@ +/* + * Copyright 2008 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 "webrtc/base/linux.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +// These tests running on ARM are fairly specific to the output of the tegra2 +// ARM processor, and so may fail on other ARM-based systems. +TEST(ProcCpuInfo, GetProcInfo) { + ProcCpuInfo proc_info; + EXPECT_TRUE(proc_info.LoadFromSystem()); + + int out_cpus = 0; + EXPECT_TRUE(proc_info.GetNumCpus(&out_cpus)); + LOG(LS_INFO) << "GetNumCpus: " << out_cpus; + EXPECT_GT(out_cpus, 0); + + int out_cpus_phys = 0; + EXPECT_TRUE(proc_info.GetNumPhysicalCpus(&out_cpus_phys)); + LOG(LS_INFO) << "GetNumPhysicalCpus: " << out_cpus_phys; + EXPECT_GT(out_cpus_phys, 0); + EXPECT_LE(out_cpus_phys, out_cpus); + + int out_family = 0; + EXPECT_TRUE(proc_info.GetCpuFamily(&out_family)); + LOG(LS_INFO) << "cpu family: " << out_family; + EXPECT_GE(out_family, 4); + +#if defined(__arm__) + std::string out_processor; + EXPECT_TRUE(proc_info.GetSectionStringValue(0, "Processor", &out_processor)); + LOG(LS_INFO) << "Processor: " << out_processor; + EXPECT_NE(std::string::npos, out_processor.find("ARM")); + + // Most other info, such as model, stepping, vendor, etc. + // is missing on ARM systems. +#else + int out_model = 0; + EXPECT_TRUE(proc_info.GetSectionIntValue(0, "model", &out_model)); + LOG(LS_INFO) << "model: " << out_model; + + int out_stepping = 0; + EXPECT_TRUE(proc_info.GetSectionIntValue(0, "stepping", &out_stepping)); + LOG(LS_INFO) << "stepping: " << out_stepping; + + int out_processor = 0; + EXPECT_TRUE(proc_info.GetSectionIntValue(0, "processor", &out_processor)); + LOG(LS_INFO) << "processor: " << out_processor; + EXPECT_EQ(0, out_processor); + + std::string out_str; + EXPECT_TRUE(proc_info.GetSectionStringValue(0, "vendor_id", &out_str)); + LOG(LS_INFO) << "vendor_id: " << out_str; + EXPECT_FALSE(out_str.empty()); +#endif +} + +TEST(ConfigParser, ParseConfig) { + ConfigParser parser; + MemoryStream *test_stream = new MemoryStream( + "Key1: Value1\n" + "Key2\t: Value2\n" + "Key3:Value3\n" + "\n" + "Key1:Value1\n"); + ConfigParser::MapVector key_val_pairs; + parser.Attach(test_stream); + EXPECT_EQ(true, parser.Parse(&key_val_pairs)); + EXPECT_EQ(2U, key_val_pairs.size()); + EXPECT_EQ("Value1", key_val_pairs[0]["Key1"]); + EXPECT_EQ("Value2", key_val_pairs[0]["Key2"]); + EXPECT_EQ("Value3", key_val_pairs[0]["Key3"]); + EXPECT_EQ("Value1", key_val_pairs[1]["Key1"]); + key_val_pairs.clear(); + EXPECT_EQ(true, parser.Open("/proc/cpuinfo")); + EXPECT_EQ(true, parser.Parse(&key_val_pairs)); +} + +#if !defined(WEBRTC_CHROMIUM_BUILDs) +TEST(ReadLinuxLsbRelease, ReturnsSomething) { + std::string str = ReadLinuxLsbRelease(); + // ChromeOS don't have lsb_release + // EXPECT_FALSE(str.empty()); +} +#endif + +TEST(ReadLinuxUname, ReturnsSomething) { + std::string str = ReadLinuxUname(); + EXPECT_FALSE(str.empty()); +} + +} // namespace rtc diff --git a/webrtc/base/linuxfdwalk.c b/webrtc/base/linuxfdwalk.c new file mode 100644 index 000000000..ae60cc524 --- /dev/null +++ b/webrtc/base/linuxfdwalk.c @@ -0,0 +1,81 @@ +/* + * Copyright 2004 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 +#include + +#include "webrtc/base/linuxfdwalk.h" + +// Parses a file descriptor number in base 10, requiring the strict format used +// in /proc/*/fd. Returns the value, or -1 if not a valid string. +static int parse_fd(const char *s) { + if (!*s) { + // Empty string is invalid. + return -1; + } + int val = 0; + do { + if (*s < '0' || *s > '9') { + // Non-numeric characters anywhere are invalid. + return -1; + } + int digit = *s++ - '0'; + val = val * 10 + digit; + } while (*s); + return val; +} + +int fdwalk(void (*func)(void *, int), void *opaque) { + DIR *dir = opendir("/proc/self/fd"); + if (!dir) { + return -1; + } + int opendirfd = dirfd(dir); + int parse_errors = 0; + struct dirent *ent; + // Have to clear errno to distinguish readdir() completion from failure. + while (errno = 0, (ent = readdir(dir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) { + continue; + } + // We avoid atoi or strtol because those are part of libc and they involve + // locale stuff, which is probably not safe from a post-fork context in a + // multi-threaded app. + int fd = parse_fd(ent->d_name); + if (fd < 0) { + parse_errors = 1; + continue; + } + if (fd != opendirfd) { + (*func)(opaque, fd); + } + } + int saved_errno = errno; + if (closedir(dir) < 0) { + if (!saved_errno) { + // Return the closedir error. + return -1; + } + // Else ignore it because we have a more relevant error to return. + } + if (saved_errno) { + errno = saved_errno; + return -1; + } else if (parse_errors) { + errno = EBADF; + return -1; + } else { + return 0; + } +} diff --git a/webrtc/base/linuxfdwalk.h b/webrtc/base/linuxfdwalk.h new file mode 100644 index 000000000..fe5a6977d --- /dev/null +++ b/webrtc/base/linuxfdwalk.h @@ -0,0 +1,34 @@ +/* + * Copyright 2004 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_BASE_LINUXFDWALK_H_ +#define WEBRTC_BASE_LINUXFDWALK_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Linux port of SunOS's fdwalk(3) call. It loops over all open file descriptors +// and calls func on each one. Additionally, it is safe to use from the child +// of a fork that hasn't exec'ed yet, so you can use it to close all open file +// descriptors prior to exec'ing a daemon. +// The return value is 0 if successful, or else -1 and errno is set. The +// possible errors include any error that can be returned by opendir(), +// readdir(), or closedir(), plus EBADF if there are problems parsing the +// contents of /proc/self/fd. +// The file descriptors that are enumerated will not include the file descriptor +// used for the enumeration itself. +int fdwalk(void (*func)(void *, int), void *opaque); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBRTC_BASE_LINUXFDWALK_H_ diff --git a/webrtc/base/linuxfdwalk_unittest.cc b/webrtc/base/linuxfdwalk_unittest.cc new file mode 100644 index 000000000..bba48e887 --- /dev/null +++ b/webrtc/base/linuxfdwalk_unittest.cc @@ -0,0 +1,75 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/linuxfdwalk.h" + +#include +#include +#include +#include + +static const int kArbitraryLargeFdNumber = 424; + +static void FdCheckVisitor(void *data, int fd) { + std::set *fds = static_cast *>(data); + EXPECT_EQ(1U, fds->erase(fd)); +} + +static void FdEnumVisitor(void *data, int fd) { + std::set *fds = static_cast *>(data); + EXPECT_TRUE(fds->insert(fd).second); +} + +// Checks that the set of open fds is exactly the given list. +static void CheckOpenFdList(std::set fds) { + EXPECT_EQ(0, fdwalk(&FdCheckVisitor, &fds)); + EXPECT_EQ(0U, fds.size()); +} + +static void GetOpenFdList(std::set *fds) { + fds->clear(); + EXPECT_EQ(0, fdwalk(&FdEnumVisitor, fds)); +} + +TEST(LinuxFdWalk, TestFdWalk) { + std::set fds; + GetOpenFdList(&fds); + std::ostringstream str; + // I have observed that the open set when starting a test is [0, 6]. Leaked + // fds would change that, but so can (e.g.) running under a debugger, so we + // can't really do an EXPECT. :( + str << "File descriptors open in test executable:"; + for (std::set::const_iterator i = fds.begin(); i != fds.end(); ++i) { + str << " " << *i; + } + LOG(LS_INFO) << str.str(); + // Open some files. + int fd1 = open("/dev/null", O_RDONLY); + EXPECT_LE(0, fd1); + int fd2 = open("/dev/null", O_WRONLY); + EXPECT_LE(0, fd2); + int fd3 = open("/dev/null", O_RDWR); + EXPECT_LE(0, fd3); + int fd4 = dup2(fd3, kArbitraryLargeFdNumber); + EXPECT_LE(0, fd4); + EXPECT_TRUE(fds.insert(fd1).second); + EXPECT_TRUE(fds.insert(fd2).second); + EXPECT_TRUE(fds.insert(fd3).second); + EXPECT_TRUE(fds.insert(fd4).second); + CheckOpenFdList(fds); + EXPECT_EQ(0, close(fd1)); + EXPECT_EQ(0, close(fd2)); + EXPECT_EQ(0, close(fd3)); + EXPECT_EQ(0, close(fd4)); +} diff --git a/webrtc/base/linuxwindowpicker.cc b/webrtc/base/linuxwindowpicker.cc new file mode 100644 index 000000000..56d565e55 --- /dev/null +++ b/webrtc/base/linuxwindowpicker.cc @@ -0,0 +1,818 @@ +/* + * Copyright 2010 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 "webrtc/base/linuxwindowpicker.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "webrtc/base/logging.h" + +namespace rtc { + +// Convenience wrapper for XGetWindowProperty results. +template +class XWindowProperty { + public: + XWindowProperty(Display* display, Window window, Atom property) + : data_(NULL) { + const int kBitsPerByte = 8; + Atom actual_type; + int actual_format; + unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty + int status = XGetWindowProperty(display, window, property, 0L, ~0L, False, + AnyPropertyType, &actual_type, + &actual_format, &size_, + &bytes_after, &data_); + succeeded_ = (status == Success); + if (!succeeded_) { + data_ = NULL; // Ensure nothing is freed. + } else if (sizeof(PropertyType) * kBitsPerByte != actual_format) { + LOG(LS_WARNING) << "Returned type size differs from " + "requested type size."; + succeeded_ = false; + // We still need to call XFree in this case, so leave data_ alone. + } + if (!succeeded_) { + size_ = 0; + } + } + + ~XWindowProperty() { + if (data_) { + XFree(data_); + } + } + + bool succeeded() const { return succeeded_; } + size_t size() const { return size_; } + const PropertyType* data() const { + return reinterpret_cast(data_); + } + PropertyType* data() { + return reinterpret_cast(data_); + } + + private: + bool succeeded_; + unsigned long size_; // NOLINT: type required by XGetWindowProperty + unsigned char* data_; + + DISALLOW_COPY_AND_ASSIGN(XWindowProperty); +}; + +// Stupid X11. It seems none of the synchronous returns codes from X11 calls +// are meaningful unless an asynchronous error handler is configured. This +// RAII class registers and unregisters an X11 error handler. +class XErrorSuppressor { + public: + explicit XErrorSuppressor(Display* display) + : display_(display), original_error_handler_(NULL) { + SuppressX11Errors(); + } + ~XErrorSuppressor() { + UnsuppressX11Errors(); + } + + private: + static int ErrorHandler(Display* display, XErrorEvent* e) { + char buf[256]; + XGetErrorText(display, e->error_code, buf, sizeof buf); + LOG(LS_WARNING) << "Received X11 error \"" << buf << "\" for request code " + << static_cast(e->request_code); + return 0; + } + + void SuppressX11Errors() { + XFlush(display_); + XSync(display_, False); + original_error_handler_ = XSetErrorHandler(&ErrorHandler); + } + + void UnsuppressX11Errors() { + XFlush(display_); + XSync(display_, False); + XErrorHandler handler = XSetErrorHandler(original_error_handler_); + if (handler != &ErrorHandler) { + LOG(LS_WARNING) << "Unbalanced XSetErrorHandler() calls detected. " + << "Final error handler may not be what you expect!"; + } + original_error_handler_ = NULL; + } + + Display* display_; + XErrorHandler original_error_handler_; + + DISALLOW_COPY_AND_ASSIGN(XErrorSuppressor); +}; + +// Hiding all X11 specifics inside its own class. This to avoid +// conflicts between talk and X11 header declarations. +class XWindowEnumerator { + public: + XWindowEnumerator() + : display_(NULL), + has_composite_extension_(false), + has_render_extension_(false) { + } + + ~XWindowEnumerator() { + if (display_ != NULL) { + XCloseDisplay(display_); + } + } + + bool Init() { + if (display_ != NULL) { + // Already initialized. + return true; + } + display_ = XOpenDisplay(NULL); + if (display_ == NULL) { + LOG(LS_ERROR) << "Failed to open display."; + return false; + } + + XErrorSuppressor error_suppressor(display_); + + wm_state_ = XInternAtom(display_, "WM_STATE", True); + net_wm_icon_ = XInternAtom(display_, "_NET_WM_ICON", False); + + int event_base, error_base, major_version, minor_version; + if (XCompositeQueryExtension(display_, &event_base, &error_base) && + XCompositeQueryVersion(display_, &major_version, &minor_version) && + // XCompositeNameWindowPixmap() requires version 0.2 + (major_version > 0 || minor_version >= 2)) { + has_composite_extension_ = true; + } else { + LOG(LS_INFO) << "Xcomposite extension not available or too old."; + } + + if (XRenderQueryExtension(display_, &event_base, &error_base) && + XRenderQueryVersion(display_, &major_version, &minor_version) && + // XRenderSetPictureTransform() requires version 0.6 + (major_version > 0 || minor_version >= 6)) { + has_render_extension_ = true; + } else { + LOG(LS_INFO) << "Xrender extension not available or too old."; + } + return true; + } + + bool EnumerateWindows(WindowDescriptionList* descriptions) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + int num_screens = XScreenCount(display_); + bool result = false; + for (int i = 0; i < num_screens; ++i) { + if (EnumerateScreenWindows(descriptions, i)) { + // We know we succeded on at least one screen. + result = true; + } + } + return result; + } + + bool EnumerateDesktops(DesktopDescriptionList* descriptions) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + Window default_root_window = XDefaultRootWindow(display_); + int num_screens = XScreenCount(display_); + for (int i = 0; i < num_screens; ++i) { + Window root_window = XRootWindow(display_, i); + DesktopId id(DesktopId(root_window, i)); + // TODO: Figure out an appropriate desktop title. + DesktopDescription desc(id, ""); + desc.set_primary(root_window == default_root_window); + descriptions->push_back(desc); + } + return num_screens > 0; + } + + bool IsVisible(const WindowId& id) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, id.id(), &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + return false; + } + return attr.map_state == IsViewable; + } + + bool MoveToFront(const WindowId& id) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + unsigned int num_children; + Window* children; + Window parent; + Window root; + + // Find root window to pass event to. + int status = XQueryTree(display_, id.id(), &root, &parent, &children, + &num_children); + if (status == 0) { + LOG(LS_WARNING) << "Failed to query for child windows."; + return false; + } + if (children != NULL) { + XFree(children); + } + + // Move the window to front. + XRaiseWindow(display_, id.id()); + + // Some window managers (e.g., metacity in GNOME) consider it illegal to + // raise a window without also giving it input focus with + // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough. + Atom atom = XInternAtom(display_, "_NET_ACTIVE_WINDOW", True); + if (atom != None) { + XEvent xev; + long event_mask; + + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.window = id.id(); + xev.xclient.message_type = atom; + + // The format member is set to 8, 16, or 32 and specifies whether the + // data should be viewed as a list of bytes, shorts, or longs. + xev.xclient.format = 32; + + xev.xclient.data.l[0] = 0; + xev.xclient.data.l[1] = 0; + xev.xclient.data.l[2] = 0; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + event_mask = SubstructureRedirectMask | SubstructureNotifyMask; + + XSendEvent(display_, root, False, event_mask, &xev); + } + XFlush(display_); + return true; + } + + uint8* GetWindowIcon(const WindowId& id, int* width, int* height) { + if (!Init()) { + return NULL; + } + XErrorSuppressor error_suppressor(display_); + Atom ret_type; + int format; + unsigned long length, bytes_after, size; + unsigned char* data = NULL; + + // Find out the size of the icon data. + if (XGetWindowProperty( + display_, id.id(), net_wm_icon_, 0, 0, False, XA_CARDINAL, + &ret_type, &format, &length, &size, &data) == Success && + data) { + XFree(data); + } else { + LOG(LS_ERROR) << "Failed to get size of the icon."; + return NULL; + } + // Get the icon data, the format is one uint32 each for width and height, + // followed by the actual pixel data. + if (size >= 2 && + XGetWindowProperty( + display_, id.id(), net_wm_icon_, 0, size, False, XA_CARDINAL, + &ret_type, &format, &length, &bytes_after, &data) == Success && + data) { + uint32* data_ptr = reinterpret_cast(data); + int w, h; + w = data_ptr[0]; + h = data_ptr[1]; + if (size < static_cast(w * h + 2)) { + XFree(data); + LOG(LS_ERROR) << "Not a vaild icon."; + return NULL; + } + uint8* rgba = + ArgbToRgba(&data_ptr[2], 0, 0, w, h, w, h, true); + XFree(data); + *width = w; + *height = h; + return rgba; + } else { + LOG(LS_ERROR) << "Failed to get window icon data."; + return NULL; + } + } + + uint8* GetWindowThumbnail(const WindowId& id, int width, int height) { + if (!Init()) { + return NULL; + } + + if (!has_composite_extension_) { + // Without the Xcomposite extension we would only get a good thumbnail if + // the whole window is visible on screen and not covered by any + // other window. This is not something we want so instead, just + // bail out. + LOG(LS_INFO) << "No Xcomposite extension detected."; + return NULL; + } + XErrorSuppressor error_suppressor(display_); + + Window root; + int x; + int y; + unsigned int src_width; + unsigned int src_height; + unsigned int border_width; + unsigned int depth; + + // In addition to needing X11 server-side support for Xcomposite, it + // actually needs to be turned on for this window in order to get a good + // thumbnail. If the user has modern hardware/drivers but isn't using a + // compositing window manager, that won't be the case. Here we + // automatically turn it on for shareable windows so that we can get + // thumbnails. We used to avoid it because the transition is visually ugly, + // but recent window managers don't always redirect windows which led to + // no thumbnails at all, which is a worse experience. + + // Redirect drawing to an offscreen buffer (ie, turn on compositing). + // X11 remembers what has requested this and will turn it off for us when + // we exit. + XCompositeRedirectWindow(display_, id.id(), CompositeRedirectAutomatic); + Pixmap src_pixmap = XCompositeNameWindowPixmap(display_, id.id()); + if (!src_pixmap) { + // Even if the backing pixmap doesn't exist, this still should have + // succeeded and returned a valid handle (it just wouldn't be a handle to + // anything). So this is a real error path. + LOG(LS_ERROR) << "XCompositeNameWindowPixmap() failed"; + return NULL; + } + if (!XGetGeometry(display_, src_pixmap, &root, &x, &y, + &src_width, &src_height, &border_width, + &depth)) { + // If the window does not actually have a backing pixmap, this is the path + // that will "fail", so it's a warning rather than an error. + LOG(LS_WARNING) << "XGetGeometry() failed (probably composite is not in " + << "use)"; + XFreePixmap(display_, src_pixmap); + return NULL; + } + + // If we get to here, then composite is in use for this window and it has a + // valid backing pixmap. + + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, id.id(), &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + XFreePixmap(display_, src_pixmap); + return NULL; + } + + uint8* data = GetDrawableThumbnail(src_pixmap, + attr.visual, + src_width, + src_height, + width, + height); + XFreePixmap(display_, src_pixmap); + return data; + } + + int GetNumDesktops() { + if (!Init()) { + return -1; + } + + return XScreenCount(display_); + } + + uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height) { + if (!Init()) { + return NULL; + } + XErrorSuppressor error_suppressor(display_); + + Window root_window = id.id(); + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, root_window, &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + return NULL; + } + + return GetDrawableThumbnail(root_window, + attr.visual, + attr.width, + attr.height, + width, + height); + } + + bool GetDesktopDimensions(const DesktopId& id, int* width, int* height) { + if (!Init()) { + return false; + } + XErrorSuppressor error_suppressor(display_); + XWindowAttributes attr; + if (!XGetWindowAttributes(display_, id.id(), &attr)) { + LOG(LS_ERROR) << "XGetWindowAttributes() failed"; + return false; + } + *width = attr.width; + *height = attr.height; + return true; + } + + private: + uint8* GetDrawableThumbnail(Drawable src_drawable, + Visual* visual, + int src_width, + int src_height, + int dst_width, + int dst_height) { + if (!has_render_extension_) { + // Without the Xrender extension we would have to read the full window and + // scale it down in our process. Xrender is over a decade old so we aren't + // going to expend effort to support that situation. We still need to + // check though because probably some virtual VNC displays are in this + // category. + LOG(LS_INFO) << "No Xrender extension detected."; + return NULL; + } + + XRenderPictFormat* format = XRenderFindVisualFormat(display_, + visual); + if (!format) { + LOG(LS_ERROR) << "XRenderFindVisualFormat() failed"; + return NULL; + } + + // Create a picture to reference the window pixmap. + XRenderPictureAttributes pa; + pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets + Picture src = XRenderCreatePicture(display_, + src_drawable, + format, + CPSubwindowMode, + &pa); + if (!src) { + LOG(LS_ERROR) << "XRenderCreatePicture() failed"; + return NULL; + } + + // Create a picture to reference the destination pixmap. + Pixmap dst_pixmap = XCreatePixmap(display_, + src_drawable, + dst_width, + dst_height, + format->depth); + if (!dst_pixmap) { + LOG(LS_ERROR) << "XCreatePixmap() failed"; + XRenderFreePicture(display_, src); + return NULL; + } + + Picture dst = XRenderCreatePicture(display_, dst_pixmap, format, 0, NULL); + if (!dst) { + LOG(LS_ERROR) << "XRenderCreatePicture() failed"; + XFreePixmap(display_, dst_pixmap); + XRenderFreePicture(display_, src); + return NULL; + } + + // Clear the background. + XRenderColor transparent = {0}; + XRenderFillRectangle(display_, + PictOpSrc, + dst, + &transparent, + 0, + 0, + dst_width, + dst_height); + + // Calculate how much we need to scale the image. + double scale_x = static_cast(dst_width) / + static_cast(src_width); + double scale_y = static_cast(dst_height) / + static_cast(src_height); + double scale = rtc::_min(scale_y, scale_x); + + int scaled_width = round(src_width * scale); + int scaled_height = round(src_height * scale); + + // Render the thumbnail centered on both axis. + int centered_x = (dst_width - scaled_width) / 2; + int centered_y = (dst_height - scaled_height) / 2; + + // Scaling matrix + XTransform xform = { { + { XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) }, + { XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) }, + { XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(scale) } + } }; + XRenderSetPictureTransform(display_, src, &xform); + + // Apply filter to smooth out the image. + XRenderSetPictureFilter(display_, src, FilterBest, NULL, 0); + + // Render the image to the destination picture. + XRenderComposite(display_, + PictOpSrc, + src, + None, + dst, + 0, + 0, + 0, + 0, + centered_x, + centered_y, + scaled_width, + scaled_height); + + // Get the pixel data from the X server. TODO: XGetImage + // might be slow here, compare with ShmGetImage. + XImage* image = XGetImage(display_, + dst_pixmap, + 0, + 0, + dst_width, + dst_height, + AllPlanes, ZPixmap); + uint8* data = ArgbToRgba(reinterpret_cast(image->data), + centered_x, + centered_y, + scaled_width, + scaled_height, + dst_width, + dst_height, + false); + XDestroyImage(image); + XRenderFreePicture(display_, dst); + XFreePixmap(display_, dst_pixmap); + XRenderFreePicture(display_, src); + return data; + } + + uint8* ArgbToRgba(uint32* argb_data, int x, int y, int w, int h, + int stride_x, int stride_y, bool has_alpha) { + uint8* p; + int len = stride_x * stride_y * 4; + uint8* data = new uint8[len]; + memset(data, 0, len); + p = data + 4 * (y * stride_x + x); + for (int i = 0; i < h; ++i) { + for (int j = 0; j < w; ++j) { + uint32 argb; + uint32 rgba; + argb = argb_data[stride_x * (y + i) + x + j]; + rgba = (argb << 8) | (argb >> 24); + *p = rgba >> 24; + ++p; + *p = (rgba >> 16) & 0xff; + ++p; + *p = (rgba >> 8) & 0xff; + ++p; + *p = has_alpha ? rgba & 0xFF : 0xFF; + ++p; + } + p += (stride_x - w) * 4; + } + return data; + } + + bool EnumerateScreenWindows(WindowDescriptionList* descriptions, int screen) { + Window parent; + Window *children; + int status; + unsigned int num_children; + Window root_window = XRootWindow(display_, screen); + status = XQueryTree(display_, root_window, &root_window, &parent, &children, + &num_children); + if (status == 0) { + LOG(LS_ERROR) << "Failed to query for child windows."; + return false; + } + for (unsigned int i = 0; i < num_children; ++i) { + // Iterate in reverse order to display windows from front to back. +#ifdef CHROMEOS + // TODO(jhorwich): Short-term fix for crbug.com/120229: Don't need to + // filter, just return all windows and let the picker scan through them. + Window app_window = children[num_children - 1 - i]; +#else + Window app_window = GetApplicationWindow(children[num_children - 1 - i]); +#endif + if (app_window && + !LinuxWindowPicker::IsDesktopElement(display_, app_window)) { + std::string title; + if (GetWindowTitle(app_window, &title)) { + WindowId id(app_window); + WindowDescription desc(id, title); + descriptions->push_back(desc); + } + } + } + if (children != NULL) { + XFree(children); + } + return true; + } + + bool GetWindowTitle(Window window, std::string* title) { + int status; + bool result = false; + XTextProperty window_name; + window_name.value = NULL; + if (window) { + status = XGetWMName(display_, window, &window_name); + if (status && window_name.value && window_name.nitems) { + int cnt; + char **list = NULL; + status = Xutf8TextPropertyToTextList(display_, &window_name, &list, + &cnt); + if (status >= Success && cnt && *list) { + if (cnt > 1) { + LOG(LS_INFO) << "Window has " << cnt + << " text properties, only using the first one."; + } + *title = *list; + result = true; + } + if (list != NULL) { + XFreeStringList(list); + } + } + if (window_name.value != NULL) { + XFree(window_name.value); + } + } + return result; + } + + Window GetApplicationWindow(Window window) { + Window root, parent; + Window app_window = 0; + Window *children; + unsigned int num_children; + Atom type = None; + int format; + unsigned long nitems, after; + unsigned char *data; + + int ret = XGetWindowProperty(display_, window, + wm_state_, 0L, 2, + False, wm_state_, &type, &format, + &nitems, &after, &data); + if (ret != Success) { + LOG(LS_ERROR) << "XGetWindowProperty failed with return code " << ret + << " for window " << window << "."; + return 0; + } + if (type != None) { + int64 state = static_cast(*data); + XFree(data); + return state == NormalState ? window : 0; + } + XFree(data); + if (!XQueryTree(display_, window, &root, &parent, &children, + &num_children)) { + LOG(LS_ERROR) << "Failed to query for child windows although window" + << "does not have a valid WM_STATE."; + return 0; + } + for (unsigned int i = 0; i < num_children; ++i) { + app_window = GetApplicationWindow(children[i]); + if (app_window) { + break; + } + } + if (children != NULL) { + XFree(children); + } + return app_window; + } + + Atom wm_state_; + Atom net_wm_icon_; + Display* display_; + bool has_composite_extension_; + bool has_render_extension_; +}; + +LinuxWindowPicker::LinuxWindowPicker() : enumerator_(new XWindowEnumerator()) { +} + +LinuxWindowPicker::~LinuxWindowPicker() { +} + +bool LinuxWindowPicker::IsDesktopElement(_XDisplay* display, Window window) { + if (window == 0) { + LOG(LS_WARNING) << "Zero is never a valid window."; + return false; + } + + // First look for _NET_WM_WINDOW_TYPE. The standard + // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306) + // says this hint *should* be present on all windows, and we use the existence + // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not + // a desktop element (that is, only "normal" windows should be shareable). + Atom window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", True); + XWindowProperty window_type(display, window, window_type_atom); + if (window_type.succeeded() && window_type.size() > 0) { + Atom normal_window_type_atom = XInternAtom( + display, "_NET_WM_WINDOW_TYPE_NORMAL", True); + uint32_t* end = window_type.data() + window_type.size(); + bool is_normal = (end != std::find( + window_type.data(), end, normal_window_type_atom)); + return !is_normal; + } + + // Fall back on using the hint. + XClassHint class_hint; + Status s = XGetClassHint(display, window, &class_hint); + bool result = false; + if (s == 0) { + // No hints, assume this is a normal application window. + return result; + } + static const std::string gnome_panel("gnome-panel"); + static const std::string desktop_window("desktop_window"); + + if (gnome_panel.compare(class_hint.res_name) == 0 || + desktop_window.compare(class_hint.res_name) == 0) { + result = true; + } + XFree(class_hint.res_name); + XFree(class_hint.res_class); + return result; +} + +bool LinuxWindowPicker::Init() { + return enumerator_->Init(); +} + +bool LinuxWindowPicker::GetWindowList(WindowDescriptionList* descriptions) { + return enumerator_->EnumerateWindows(descriptions); +} + +bool LinuxWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { + return enumerator_->EnumerateDesktops(descriptions); +} + +bool LinuxWindowPicker::IsVisible(const WindowId& id) { + return enumerator_->IsVisible(id); +} + +bool LinuxWindowPicker::MoveToFront(const WindowId& id) { + return enumerator_->MoveToFront(id); +} + + +uint8* LinuxWindowPicker::GetWindowIcon(const WindowId& id, int* width, + int* height) { + return enumerator_->GetWindowIcon(id, width, height); +} + +uint8* LinuxWindowPicker::GetWindowThumbnail(const WindowId& id, int width, + int height) { + return enumerator_->GetWindowThumbnail(id, width, height); +} + +int LinuxWindowPicker::GetNumDesktops() { + return enumerator_->GetNumDesktops(); +} + +uint8* LinuxWindowPicker::GetDesktopThumbnail(const DesktopId& id, + int width, + int height) { + return enumerator_->GetDesktopThumbnail(id, width, height); +} + +bool LinuxWindowPicker::GetDesktopDimensions(const DesktopId& id, int* width, + int* height) { + return enumerator_->GetDesktopDimensions(id, width, height); +} + +} // namespace rtc diff --git a/webrtc/base/linuxwindowpicker.h b/webrtc/base/linuxwindowpicker.h new file mode 100644 index 000000000..f87b15081 --- /dev/null +++ b/webrtc/base/linuxwindowpicker.h @@ -0,0 +1,51 @@ +/* + * Copyright 2010 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_BASE_LINUXWINDOWPICKER_H_ +#define WEBRTC_BASE_LINUXWINDOWPICKER_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/windowpicker.h" + +// Avoid include . +struct _XDisplay; +typedef unsigned long Window; + +namespace rtc { + +class XWindowEnumerator; + +class LinuxWindowPicker : public WindowPicker { + public: + LinuxWindowPicker(); + ~LinuxWindowPicker(); + + static bool IsDesktopElement(_XDisplay* display, Window window); + + virtual bool Init(); + virtual bool IsVisible(const WindowId& id); + virtual bool MoveToFront(const WindowId& id); + virtual bool GetWindowList(WindowDescriptionList* descriptions); + virtual bool GetDesktopList(DesktopDescriptionList* descriptions); + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height); + uint8* GetWindowIcon(const WindowId& id, int* width, int* height); + uint8* GetWindowThumbnail(const WindowId& id, int width, int height); + int GetNumDesktops(); + uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height); + + private: + scoped_ptr enumerator_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_LINUXWINDOWPICKER_H_ diff --git a/webrtc/base/linuxwindowpicker_unittest.cc b/webrtc/base/linuxwindowpicker_unittest.cc new file mode 100644 index 000000000..c2276ccd6 --- /dev/null +++ b/webrtc/base/linuxwindowpicker_unittest.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/linuxwindowpicker.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/windowpicker.h" + +#if !defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID) +#error Only for Linux +#endif + +namespace rtc { + +TEST(LinuxWindowPickerTest, TestGetWindowList) { + MAYBE_SKIP_SCREENCAST_TEST(); + LinuxWindowPicker window_picker; + WindowDescriptionList descriptions; + window_picker.Init(); + window_picker.GetWindowList(&descriptions); +} + +TEST(LinuxWindowPickerTest, TestGetDesktopList) { + MAYBE_SKIP_SCREENCAST_TEST(); + LinuxWindowPicker window_picker; + DesktopDescriptionList descriptions; + EXPECT_TRUE(window_picker.Init()); + EXPECT_TRUE(window_picker.GetDesktopList(&descriptions)); + EXPECT_TRUE(descriptions.size() > 0); +} + +} // namespace rtc diff --git a/webrtc/base/logging.cc b/webrtc/base/logging.cc new file mode 100644 index 000000000..a417ed6c2 --- /dev/null +++ b/webrtc/base/logging.cc @@ -0,0 +1,618 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#define snprintf _snprintf +#undef ERROR // wingdi.h +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#elif defined(WEBRTC_ANDROID) +#include +static const char kLibjingle[] = "libjingle"; +// Android has a 1024 limit on log inputs. We use 60 chars as an +// approx for the header/tag portion. +// See android/system/core/liblog/logd_write.c +static const int kMaxLogLineSize = 1024 - 60; +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) || WEBRTC_ANDROID + +#include + +#include +#include +#include +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +///////////////////////////////////////////////////////////////////////////// +// Constant Labels +///////////////////////////////////////////////////////////////////////////// + +const char * FindLabel(int value, const ConstantLabel entries[]) { + for (int i = 0; entries[i].label; ++i) { + if (value == entries[i].value) { + return entries[i].label; + } + } + return 0; +} + +std::string ErrorName(int err, const ConstantLabel * err_table) { + if (err == 0) + return "No error"; + + if (err_table != 0) { + if (const char * value = FindLabel(err, err_table)) + return value; + } + + char buffer[16]; + snprintf(buffer, sizeof(buffer), "0x%08x", err); + return buffer; +} + +///////////////////////////////////////////////////////////////////////////// +// LogMessage +///////////////////////////////////////////////////////////////////////////// + +const int LogMessage::NO_LOGGING = LS_ERROR + 1; + +#if _DEBUG +static const int LOG_DEFAULT = LS_INFO; +#else // !_DEBUG +static const int LOG_DEFAULT = LogMessage::NO_LOGGING; +#endif // !_DEBUG + +// Global lock for log subsystem, only needed to serialize access to streams_. +CriticalSection LogMessage::crit_; + +// By default, release builds don't log, debug builds at info level +int LogMessage::min_sev_ = LOG_DEFAULT; +int LogMessage::dbg_sev_ = LOG_DEFAULT; + +// Don't bother printing context for the ubiquitous INFO log messages +int LogMessage::ctx_sev_ = LS_WARNING; + +// The list of logging streams currently configured. +// Note: we explicitly do not clean this up, because of the uncertain ordering +// of destructors at program exit. Let the person who sets the stream trigger +// cleanup by setting to NULL, or let it leak (safe at program exit). +LogMessage::StreamList LogMessage::streams_; + +// Boolean options default to false (0) +bool LogMessage::thread_, LogMessage::timestamp_; + +// If we're in diagnostic mode, we'll be explicitly set that way; default=false. +bool LogMessage::is_diagnostic_mode_ = false; + +LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev, + LogErrorContext err_ctx, int err, const char* module) + : severity_(sev), + warn_slow_logs_delay_(WARN_SLOW_LOGS_DELAY) { + if (timestamp_) { + uint32 time = TimeSince(LogStartTime()); + // Also ensure WallClockStartTime is initialized, so that it matches + // LogStartTime. + WallClockStartTime(); + print_stream_ << "[" << std::setfill('0') << std::setw(3) << (time / 1000) + << ":" << std::setw(3) << (time % 1000) << std::setfill(' ') + << "] "; + } + + if (thread_) { +#if defined(WEBRTC_WIN) + DWORD id = GetCurrentThreadId(); + print_stream_ << "[" << std::hex << id << std::dec << "] "; +#endif // WEBRTC_WIN + } + + if (severity_ >= ctx_sev_) { + print_stream_ << Describe(sev) << "(" << DescribeFile(file) + << ":" << line << "): "; + } + + if (err_ctx != ERRCTX_NONE) { + std::ostringstream tmp; + tmp << "[0x" << std::setfill('0') << std::hex << std::setw(8) << err << "]"; + switch (err_ctx) { + case ERRCTX_ERRNO: + tmp << " " << strerror(err); + break; +#if WEBRTC_WIN + case ERRCTX_HRESULT: { + char msgbuf[256]; + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM; + HMODULE hmod = GetModuleHandleA(module); + if (hmod) + flags |= FORMAT_MESSAGE_FROM_HMODULE; + if (DWORD len = FormatMessageA( + flags, hmod, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), NULL)) { + while ((len > 0) && + isspace(static_cast(msgbuf[len-1]))) { + msgbuf[--len] = 0; + } + tmp << " " << msgbuf; + } + break; + } +#endif // WEBRTC_WIN +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + case ERRCTX_OSSTATUS: { + tmp << " " << nonnull(GetMacOSStatusErrorString(err), "Unknown error"); + if (const char* desc = GetMacOSStatusCommentString(err)) { + tmp << ": " << desc; + } + break; + } +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + default: + break; + } + extra_ = tmp.str(); + } +} + +LogMessage::~LogMessage() { + if (!extra_.empty()) + print_stream_ << " : " << extra_; + print_stream_ << std::endl; + + const std::string& str = print_stream_.str(); + if (severity_ >= dbg_sev_) { + OutputToDebug(str, severity_); + } + + uint32 before = Time(); + // Must lock streams_ before accessing + CritScope cs(&crit_); + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + if (severity_ >= it->second) { + OutputToStream(it->first, str); + } + } + uint32 delay = TimeSince(before); + if (delay >= warn_slow_logs_delay_) { + LogMessage slow_log_warning = + rtc::LogMessage(__FILE__, __LINE__, LS_WARNING); + // If our warning is slow, we don't want to warn about it, because + // that would lead to inifinite recursion. So, give a really big + // number for the delay threshold. + slow_log_warning.warn_slow_logs_delay_ = UINT_MAX; + slow_log_warning.stream() << "Slow log: took " << delay << "ms to write " + << str.size() << " bytes."; + } +} + +uint32 LogMessage::LogStartTime() { + static const uint32 g_start = Time(); + return g_start; +} + +uint32 LogMessage::WallClockStartTime() { + static const uint32 g_start_wallclock = time(NULL); + return g_start_wallclock; +} + +void LogMessage::LogContext(int min_sev) { + ctx_sev_ = min_sev; +} + +void LogMessage::LogThreads(bool on) { + thread_ = on; +} + +void LogMessage::LogTimestamps(bool on) { + timestamp_ = on; +} + +void LogMessage::LogToDebug(int min_sev) { + dbg_sev_ = min_sev; + UpdateMinLogSeverity(); +} + +void LogMessage::LogToStream(StreamInterface* stream, int min_sev) { + CritScope cs(&crit_); + // Discard and delete all previously installed streams + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + delete it->first; + } + streams_.clear(); + // Install the new stream, if specified + if (stream) { + AddLogToStream(stream, min_sev); + } +} + +int LogMessage::GetLogToStream(StreamInterface* stream) { + CritScope cs(&crit_); + int sev = NO_LOGGING; + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + if (!stream || stream == it->first) { + sev = _min(sev, it->second); + } + } + return sev; +} + +void LogMessage::AddLogToStream(StreamInterface* stream, int min_sev) { + CritScope cs(&crit_); + streams_.push_back(std::make_pair(stream, min_sev)); + UpdateMinLogSeverity(); +} + +void LogMessage::RemoveLogToStream(StreamInterface* stream) { + CritScope cs(&crit_); + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + if (stream == it->first) { + streams_.erase(it); + break; + } + } + UpdateMinLogSeverity(); +} + +void LogMessage::ConfigureLogging(const char* params, const char* filename) { + int current_level = LS_VERBOSE; + int debug_level = GetLogToDebug(); + int file_level = GetLogToStream(); + + std::vector tokens; + tokenize(params, ' ', &tokens); + + for (size_t i = 0; i < tokens.size(); ++i) { + if (tokens[i].empty()) + continue; + + // Logging features + if (tokens[i] == "tstamp") { + LogTimestamps(); + } else if (tokens[i] == "thread") { + LogThreads(); + + // Logging levels + } else if (tokens[i] == "sensitive") { + current_level = LS_SENSITIVE; + } else if (tokens[i] == "verbose") { + current_level = LS_VERBOSE; + } else if (tokens[i] == "info") { + current_level = LS_INFO; + } else if (tokens[i] == "warning") { + current_level = LS_WARNING; + } else if (tokens[i] == "error") { + current_level = LS_ERROR; + } else if (tokens[i] == "none") { + current_level = NO_LOGGING; + + // Logging targets + } else if (tokens[i] == "file") { + file_level = current_level; + } else if (tokens[i] == "debug") { + debug_level = current_level; + } + } + +#if defined(WEBRTC_WIN) + if ((NO_LOGGING != debug_level) && !::IsDebuggerPresent()) { + // First, attempt to attach to our parent's console... so if you invoke + // from the command line, we'll see the output there. Otherwise, create + // our own console window. + // Note: These methods fail if a console already exists, which is fine. + bool success = false; + typedef BOOL (WINAPI* PFN_AttachConsole)(DWORD); + if (HINSTANCE kernel32 = ::LoadLibrary(L"kernel32.dll")) { + // AttachConsole is defined on WinXP+. + if (PFN_AttachConsole attach_console = reinterpret_cast + (::GetProcAddress(kernel32, "AttachConsole"))) { + success = (FALSE != attach_console(ATTACH_PARENT_PROCESS)); + } + ::FreeLibrary(kernel32); + } + if (!success) { + ::AllocConsole(); + } + } +#endif // WEBRTC_WIN + + LogToDebug(debug_level); + +#if !defined(__native_client__) // No logging to file in NaCl. + scoped_ptr stream; + if (NO_LOGGING != file_level) { + stream.reset(new FileStream); + if (!stream->Open(filename, "wb", NULL) || !stream->DisableBuffering()) { + stream.reset(); + } + } + + LogToStream(stream.release(), file_level); +#endif +} + +int LogMessage::ParseLogSeverity(const std::string& value) { + int level = NO_LOGGING; + if (value == "LS_SENSITIVE") { + level = LS_SENSITIVE; + } else if (value == "LS_VERBOSE") { + level = LS_VERBOSE; + } else if (value == "LS_INFO") { + level = LS_INFO; + } else if (value == "LS_WARNING") { + level = LS_WARNING; + } else if (value == "LS_ERROR") { + level = LS_ERROR; + } else if (isdigit(value[0])) { + level = atoi(value.c_str()); // NOLINT + } + return level; +} + +void LogMessage::UpdateMinLogSeverity() { + int min_sev = dbg_sev_; + for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) { + min_sev = _min(dbg_sev_, it->second); + } + min_sev_ = min_sev; +} + +const char* LogMessage::Describe(LoggingSeverity sev) { + switch (sev) { + case LS_SENSITIVE: return "Sensitive"; + case LS_VERBOSE: return "Verbose"; + case LS_INFO: return "Info"; + case LS_WARNING: return "Warning"; + case LS_ERROR: return "Error"; + default: return ""; + } +} + +const char* LogMessage::DescribeFile(const char* file) { + const char* end1 = ::strrchr(file, '/'); + const char* end2 = ::strrchr(file, '\\'); + if (!end1 && !end2) + return file; + else + return (end1 > end2) ? end1 + 1 : end2 + 1; +} + +void LogMessage::OutputToDebug(const std::string& str, + LoggingSeverity severity) { + bool log_to_stderr = true; +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) && (!defined(DEBUG) || defined(NDEBUG)) + // On the Mac, all stderr output goes to the Console log and causes clutter. + // So in opt builds, don't log to stderr unless the user specifically sets + // a preference to do so. + CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault, + "logToStdErr", + kCFStringEncodingUTF8); + CFStringRef domain = CFBundleGetIdentifier(CFBundleGetMainBundle()); + if (key != NULL && domain != NULL) { + Boolean exists_and_is_valid; + Boolean should_log = + CFPreferencesGetAppBooleanValue(key, domain, &exists_and_is_valid); + // If the key doesn't exist or is invalid or is false, we will not log to + // stderr. + log_to_stderr = exists_and_is_valid && should_log; + } + if (key != NULL) { + CFRelease(key); + } +#endif +#if defined(WEBRTC_WIN) + // Always log to the debugger. + // Perhaps stderr should be controlled by a preference, as on Mac? + OutputDebugStringA(str.c_str()); + if (log_to_stderr) { + // This handles dynamically allocated consoles, too. + if (HANDLE error_handle = ::GetStdHandle(STD_ERROR_HANDLE)) { + log_to_stderr = false; + DWORD written = 0; + ::WriteFile(error_handle, str.data(), static_cast(str.size()), + &written, 0); + } + } +#endif // WEBRTC_WIN +#if defined(WEBRTC_ANDROID) + // Android's logging facility uses severity to log messages but we + // need to map libjingle's severity levels to Android ones first. + // Also write to stderr which maybe available to executable started + // from the shell. + int prio; + switch (severity) { + case LS_SENSITIVE: + __android_log_write(ANDROID_LOG_INFO, kLibjingle, "SENSITIVE"); + if (log_to_stderr) { + fprintf(stderr, "SENSITIVE"); + fflush(stderr); + } + return; + case LS_VERBOSE: + prio = ANDROID_LOG_VERBOSE; + break; + case LS_INFO: + prio = ANDROID_LOG_INFO; + break; + case LS_WARNING: + prio = ANDROID_LOG_WARN; + break; + case LS_ERROR: + prio = ANDROID_LOG_ERROR; + break; + default: + prio = ANDROID_LOG_UNKNOWN; + } + + int size = str.size(); + int line = 0; + int idx = 0; + const int max_lines = size / kMaxLogLineSize + 1; + if (max_lines == 1) { + __android_log_print(prio, kLibjingle, "%.*s", size, str.c_str()); + } else { + while (size > 0) { + const int len = std::min(size, kMaxLogLineSize); + // Use the size of the string in the format (str may have \0 in the + // middle). + __android_log_print(prio, kLibjingle, "[%d/%d] %.*s", + line + 1, max_lines, + len, str.c_str() + idx); + idx += len; + size -= len; + ++line; + } + } +#endif // WEBRTC_ANDROID + if (log_to_stderr) { + fprintf(stderr, "%s", str.c_str()); + fflush(stderr); + } +} + +void LogMessage::OutputToStream(StreamInterface* stream, + const std::string& str) { + // If write isn't fully successful, what are we going to do, log it? :) + stream->WriteAll(str.data(), str.size(), NULL, NULL); +} + +////////////////////////////////////////////////////////////////////// +// Logging Helpers +////////////////////////////////////////////////////////////////////// + +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state) { + if (!LOG_CHECK_LEVEL_V(level)) + return; + + const char * direction = (input ? " << " : " >> "); + + // NULL data means to flush our count of unprintable characters. + if (!data) { + if (state && state->unprintable_count_[input]) { + LOG_V(level) << label << direction << "## " + << state->unprintable_count_[input] + << " consecutive unprintable ##"; + state->unprintable_count_[input] = 0; + } + return; + } + + // The ctype classification functions want unsigned chars. + const unsigned char* udata = static_cast(data); + + if (hex_mode) { + const size_t LINE_SIZE = 24; + char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1]; + while (len > 0) { + memset(asc_line, ' ', sizeof(asc_line)); + memset(hex_line, ' ', sizeof(hex_line)); + size_t line_len = _min(len, LINE_SIZE); + for (size_t i = 0; i < line_len; ++i) { + unsigned char ch = udata[i]; + asc_line[i] = isprint(ch) ? ch : '.'; + hex_line[i*2 + i/4] = hex_encode(ch >> 4); + hex_line[i*2 + i/4 + 1] = hex_encode(ch & 0xf); + } + asc_line[sizeof(asc_line)-1] = 0; + hex_line[sizeof(hex_line)-1] = 0; + LOG_V(level) << label << direction + << asc_line << " " << hex_line << " "; + udata += line_len; + len -= line_len; + } + return; + } + + size_t consecutive_unprintable = state ? state->unprintable_count_[input] : 0; + + const unsigned char* end = udata + len; + while (udata < end) { + const unsigned char* line = udata; + const unsigned char* end_of_line = strchrn(udata, + end - udata, + '\n'); + if (!end_of_line) { + udata = end_of_line = end; + } else { + udata = end_of_line + 1; + } + + bool is_printable = true; + + // If we are in unprintable mode, we need to see a line of at least + // kMinPrintableLine characters before we'll switch back. + const ptrdiff_t kMinPrintableLine = 4; + if (consecutive_unprintable && ((end_of_line - line) < kMinPrintableLine)) { + is_printable = false; + } else { + // Determine if the line contains only whitespace and printable + // characters. + bool is_entirely_whitespace = true; + for (const unsigned char* pos = line; pos < end_of_line; ++pos) { + if (isspace(*pos)) + continue; + is_entirely_whitespace = false; + if (!isprint(*pos)) { + is_printable = false; + break; + } + } + // Treat an empty line following unprintable data as unprintable. + if (consecutive_unprintable && is_entirely_whitespace) { + is_printable = false; + } + } + if (!is_printable) { + consecutive_unprintable += (udata - line); + continue; + } + // Print out the current line, but prefix with a count of prior unprintable + // characters. + if (consecutive_unprintable) { + LOG_V(level) << label << direction << "## " << consecutive_unprintable + << " consecutive unprintable ##"; + consecutive_unprintable = 0; + } + // Strip off trailing whitespace. + while ((end_of_line > line) && isspace(*(end_of_line-1))) { + --end_of_line; + } + // Filter out any private data + std::string substr(reinterpret_cast(line), end_of_line - line); + std::string::size_type pos_private = substr.find("Email"); + if (pos_private == std::string::npos) { + pos_private = substr.find("Passwd"); + } + if (pos_private == std::string::npos) { + LOG_V(level) << label << direction << substr; + } else { + LOG_V(level) << label << direction << "## omitted for privacy ##"; + } + } + + if (state) { + state->unprintable_count_[input] = consecutive_unprintable; + } +} + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/logging.h b/webrtc/base/logging.h new file mode 100644 index 000000000..91d61b3e9 --- /dev/null +++ b/webrtc/base/logging.h @@ -0,0 +1,387 @@ +/* + * Copyright 2004 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. + */ + +// LOG(...) an ostream target that can be used to send formatted +// output to a variety of logging targets, such as debugger console, stderr, +// file, or any StreamInterface. +// The severity level passed as the first argument to the LOGging +// functions is used as a filter, to limit the verbosity of the logging. +// Static members of LogMessage documented below are used to control the +// verbosity and target of the output. +// There are several variations on the LOG macro which facilitate logging +// of common error conditions, detailed below. + +// LOG(sev) logs the given stream at severity "sev", which must be a +// compile-time constant of the LoggingSeverity type, without the namespace +// prefix. +// LOG_V(sev) Like LOG(), but sev is a run-time variable of the LoggingSeverity +// type (basically, it just doesn't prepend the namespace). +// LOG_F(sev) Like LOG(), but includes the name of the current function. +// LOG_T(sev) Like LOG(), but includes the this pointer. +// LOG_T_F(sev) Like LOG_F(), but includes the this pointer. +// LOG_GLE(M)(sev [, mod]) attempt to add a string description of the +// HRESULT returned by GetLastError. The "M" variant allows searching of a +// DLL's string table for the error description. +// LOG_ERRNO(sev) attempts to add a string description of an errno-derived +// error. errno and associated facilities exist on both Windows and POSIX, +// but on Windows they only apply to the C/C++ runtime. +// LOG_ERR(sev) is an alias for the platform's normal error system, i.e. _GLE on +// Windows and _ERRNO on POSIX. +// (The above three also all have _EX versions that let you specify the error +// code, rather than using the last one.) +// LOG_E(sev, ctx, err, ...) logs a detailed error interpreted using the +// specified context. +// LOG_CHECK_LEVEL(sev) (and LOG_CHECK_LEVEL_V(sev)) can be used as a test +// before performing expensive or sensitive operations whose sole purpose is +// to output logging data at the desired level. +// Lastly, PLOG(sev, err) is an alias for LOG_ERR_EX. + +#ifndef WEBRTC_BASE_LOGGING_H_ +#define WEBRTC_BASE_LOGGING_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" // NOLINT +#endif + +#include +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// ConstantLabel can be used to easily generate string names from constant +// values. This can be useful for logging descriptive names of error messages. +// Usage: +// const ConstantLabel LIBRARY_ERRORS[] = { +// KLABEL(SOME_ERROR), +// KLABEL(SOME_OTHER_ERROR), +// ... +// LASTLABEL +// } +// +// int err = LibraryFunc(); +// LOG(LS_ERROR) << "LibraryFunc returned: " +// << ErrorName(err, LIBRARY_ERRORS); + +struct ConstantLabel { int value; const char * label; }; +#define KLABEL(x) { x, #x } +#define TLABEL(x, y) { x, y } +#define LASTLABEL { 0, 0 } + +const char * FindLabel(int value, const ConstantLabel entries[]); +std::string ErrorName(int err, const ConstantLabel* err_table); + +////////////////////////////////////////////////////////////////////// + +// Note that the non-standard LoggingSeverity aliases exist because they are +// still in broad use. The meanings of the levels are: +// LS_SENSITIVE: Information which should only be logged with the consent +// of the user, due to privacy concerns. +// LS_VERBOSE: This level is for data which we do not want to appear in the +// normal debug log, but should appear in diagnostic logs. +// LS_INFO: Chatty level used in debugging for all sorts of things, the default +// in debug builds. +// LS_WARNING: Something that may warrant investigation. +// LS_ERROR: Something that should not have occurred. +enum LoggingSeverity { LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR, + INFO = LS_INFO, + WARNING = LS_WARNING, + LERROR = LS_ERROR }; + +// LogErrorContext assists in interpreting the meaning of an error value. +enum LogErrorContext { + ERRCTX_NONE, + ERRCTX_ERRNO, // System-local errno + ERRCTX_HRESULT, // Windows HRESULT + ERRCTX_OSSTATUS, // MacOS OSStatus + + // Abbreviations for LOG_E macro + ERRCTX_EN = ERRCTX_ERRNO, // LOG_E(sev, EN, x) + ERRCTX_HR = ERRCTX_HRESULT, // LOG_E(sev, HR, x) + ERRCTX_OS = ERRCTX_OSSTATUS, // LOG_E(sev, OS, x) +}; + +class LogMessage { + public: + static const int NO_LOGGING; + static const uint32 WARN_SLOW_LOGS_DELAY = 50; // ms + + LogMessage(const char* file, int line, LoggingSeverity sev, + LogErrorContext err_ctx = ERRCTX_NONE, int err = 0, + const char* module = NULL); + ~LogMessage(); + + static inline bool Loggable(LoggingSeverity sev) { return (sev >= min_sev_); } + std::ostream& stream() { return print_stream_; } + + // Returns the time at which this function was called for the first time. + // The time will be used as the logging start time. + // If this is not called externally, the LogMessage ctor also calls it, in + // which case the logging start time will be the time of the first LogMessage + // instance is created. + static uint32 LogStartTime(); + + // Returns the wall clock equivalent of |LogStartTime|, in seconds from the + // epoch. + static uint32 WallClockStartTime(); + + // These are attributes which apply to all logging channels + // LogContext: Display the file and line number of the message + static void LogContext(int min_sev); + // LogThreads: Display the thread identifier of the current thread + static void LogThreads(bool on = true); + // LogTimestamps: Display the elapsed time of the program + static void LogTimestamps(bool on = true); + + // These are the available logging channels + // Debug: Debug console on Windows, otherwise stderr + static void LogToDebug(int min_sev); + static int GetLogToDebug() { return dbg_sev_; } + + // Stream: Any non-blocking stream interface. LogMessage takes ownership of + // the stream. Multiple streams may be specified by using AddLogToStream. + // LogToStream is retained for backwards compatibility; when invoked, it + // will discard any previously set streams and install the specified stream. + // GetLogToStream gets the severity for the specified stream, of if none + // is specified, the minimum stream severity. + // RemoveLogToStream removes the specified stream, without destroying it. + static void LogToStream(StreamInterface* stream, int min_sev); + static int GetLogToStream(StreamInterface* stream = NULL); + static void AddLogToStream(StreamInterface* stream, int min_sev); + static void RemoveLogToStream(StreamInterface* stream); + + // Testing against MinLogSeverity allows code to avoid potentially expensive + // logging operations by pre-checking the logging level. + static int GetMinLogSeverity() { return min_sev_; } + + static void SetDiagnosticMode(bool f) { is_diagnostic_mode_ = f; } + static bool IsDiagnosticMode() { return is_diagnostic_mode_; } + + // Parses the provided parameter stream to configure the options above. + // Useful for configuring logging from the command line. If file logging + // is enabled, it is output to the specified filename. + static void ConfigureLogging(const char* params, const char* filename); + + // Convert the string to a LS_ value; also accept numeric values. + static int ParseLogSeverity(const std::string& value); + + private: + typedef std::list > StreamList; + + // Updates min_sev_ appropriately when debug sinks change. + static void UpdateMinLogSeverity(); + + // These assist in formatting some parts of the debug output. + static const char* Describe(LoggingSeverity sev); + static const char* DescribeFile(const char* file); + + // These write out the actual log messages. + static void OutputToDebug(const std::string& msg, LoggingSeverity severity_); + static void OutputToStream(StreamInterface* stream, const std::string& msg); + + // The ostream that buffers the formatted message before output + std::ostringstream print_stream_; + + // The severity level of this message + LoggingSeverity severity_; + + // String data generated in the constructor, that should be appended to + // the message before output. + std::string extra_; + + // If time it takes to write to stream is more than this, log one + // additional warning about it. + uint32 warn_slow_logs_delay_; + + // Global lock for the logging subsystem + static CriticalSection crit_; + + // dbg_sev_ is the thresholds for those output targets + // min_sev_ is the minimum (most verbose) of those levels, and is used + // as a short-circuit in the logging macros to identify messages that won't + // be logged. + // ctx_sev_ is the minimum level at which file context is displayed + static int min_sev_, dbg_sev_, ctx_sev_; + + // The output streams and their associated severities + static StreamList streams_; + + // Flags for formatting options + static bool thread_, timestamp_; + + // are we in diagnostic mode (as defined by the app)? + static bool is_diagnostic_mode_; + + DISALLOW_EVIL_CONSTRUCTORS(LogMessage); +}; + +////////////////////////////////////////////////////////////////////// +// Logging Helpers +////////////////////////////////////////////////////////////////////// + +class LogMultilineState { + public: + size_t unprintable_count_[2]; + LogMultilineState() { + unprintable_count_[0] = unprintable_count_[1] = 0; + } +}; + +// When possible, pass optional state variable to track various data across +// multiple calls to LogMultiline. Otherwise, pass NULL. +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state); + +////////////////////////////////////////////////////////////////////// +// Macros which automatically disable logging when LOGGING == 0 +////////////////////////////////////////////////////////////////////// + +// If LOGGING is not explicitly defined, default to enabled in debug mode +#if !defined(LOGGING) +#if defined(_DEBUG) && !defined(NDEBUG) +#define LOGGING 1 +#else +#define LOGGING 0 +#endif +#endif // !defined(LOGGING) + +#ifndef LOG +#if LOGGING + +// The following non-obvious technique for implementation of a +// conditional log stream was stolen from google3/base/logging.h. + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". + +class LogMessageVoidify { + public: + LogMessageVoidify() { } + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream&) { } +}; + +#define LOG_SEVERITY_PRECONDITION(sev) \ + !(rtc::LogMessage::Loggable(sev)) \ + ? (void) 0 \ + : rtc::LogMessageVoidify() & + +#define LOG(sev) \ + LOG_SEVERITY_PRECONDITION(rtc::sev) \ + rtc::LogMessage(__FILE__, __LINE__, rtc::sev).stream() + +// The _V version is for when a variable is passed in. It doesn't do the +// namespace concatination. +#define LOG_V(sev) \ + LOG_SEVERITY_PRECONDITION(sev) \ + rtc::LogMessage(__FILE__, __LINE__, sev).stream() + +// The _F version prefixes the message with the current function name. +#if (defined(__GNUC__) && defined(_DEBUG)) || defined(WANT_PRETTY_LOG_F) +#define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": " +#define LOG_T_F(sev) LOG(sev) << this << ": " << __PRETTY_FUNCTION__ << ": " +#else +#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": " +#define LOG_T_F(sev) LOG(sev) << this << ": " << __FUNCTION__ << ": " +#endif + +#define LOG_CHECK_LEVEL(sev) \ + rtc::LogCheckLevel(rtc::sev) +#define LOG_CHECK_LEVEL_V(sev) \ + rtc::LogCheckLevel(sev) +inline bool LogCheckLevel(LoggingSeverity sev) { + return (LogMessage::GetMinLogSeverity() <= sev); +} + +#define LOG_E(sev, ctx, err, ...) \ + LOG_SEVERITY_PRECONDITION(rtc::sev) \ + rtc::LogMessage(__FILE__, __LINE__, rtc::sev, \ + rtc::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \ + .stream() + +#define LOG_T(sev) LOG(sev) << this << ": " + +#else // !LOGGING + +// Hopefully, the compiler will optimize away some of this code. +// Note: syntax of "1 ? (void)0 : LogMessage" was causing errors in g++, +// converted to "while (false)" +#define LOG(sev) \ + while (false)rtc:: LogMessage(NULL, 0, rtc::sev).stream() +#define LOG_V(sev) \ + while (false) rtc::LogMessage(NULL, 0, sev).stream() +#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": " +#define LOG_CHECK_LEVEL(sev) \ + false +#define LOG_CHECK_LEVEL_V(sev) \ + false + +#define LOG_E(sev, ctx, err, ...) \ + while (false) rtc::LogMessage(__FILE__, __LINE__, rtc::sev, \ + rtc::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \ + .stream() + +#define LOG_T(sev) LOG(sev) << this << ": " +#define LOG_T_F(sev) LOG(sev) << this << ": " << __FUNCTION__ << +#endif // !LOGGING + +#define LOG_ERRNO_EX(sev, err) \ + LOG_E(sev, ERRNO, err) +#define LOG_ERRNO(sev) \ + LOG_ERRNO_EX(sev, errno) + +#if defined(WEBRTC_WIN) +#define LOG_GLE_EX(sev, err) \ + LOG_E(sev, HRESULT, err) +#define LOG_GLE(sev) \ + LOG_GLE_EX(sev, GetLastError()) +#define LOG_GLEM(sev, mod) \ + LOG_E(sev, HRESULT, GetLastError(), mod) +#define LOG_ERR_EX(sev, err) \ + LOG_GLE_EX(sev, err) +#define LOG_ERR(sev) \ + LOG_GLE(sev) +#define LAST_SYSTEM_ERROR \ + (::GetLastError()) +#elif __native_client__ +#define LOG_ERR_EX(sev, err) \ + LOG(sev) +#define LOG_ERR(sev) \ + LOG(sev) +#define LAST_SYSTEM_ERROR \ + (0) +#elif defined(WEBRTC_POSIX) +#define LOG_ERR_EX(sev, err) \ + LOG_ERRNO_EX(sev, err) +#define LOG_ERR(sev) \ + LOG_ERRNO(sev) +#define LAST_SYSTEM_ERROR \ + (errno) +#endif // WEBRTC_WIN + +#define PLOG(sev, err) \ + LOG_ERR_EX(sev, err) + +// TODO(?): Add an "assert" wrapper that logs in the same manner. + +#endif // LOG + +} // namespace rtc + +#endif // WEBRTC_BASE_LOGGING_H_ diff --git a/webrtc/base/logging_unittest.cc b/webrtc/base/logging_unittest.cc new file mode 100644 index 000000000..59630d746 --- /dev/null +++ b/webrtc/base/logging_unittest.cc @@ -0,0 +1,138 @@ +/* + * Copyright 2004 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 "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Test basic logging operation. We should get the INFO log but not the VERBOSE. +// We should restore the correct global state at the end. +TEST(LogTest, SingleStream) { + int sev = LogMessage::GetLogToStream(NULL); + + std::string str; + StringStream stream(str); + LogMessage::AddLogToStream(&stream, LS_INFO); + EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream)); + + LOG(LS_INFO) << "INFO"; + LOG(LS_VERBOSE) << "VERBOSE"; + EXPECT_NE(std::string::npos, str.find("INFO")); + EXPECT_EQ(std::string::npos, str.find("VERBOSE")); + + LogMessage::RemoveLogToStream(&stream); + EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream)); + + EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL)); +} + +// Test using multiple log streams. The INFO stream should get the INFO message, +// the VERBOSE stream should get the INFO and the VERBOSE. +// We should restore the correct global state at the end. +TEST(LogTest, MultipleStreams) { + int sev = LogMessage::GetLogToStream(NULL); + + std::string str1, str2; + StringStream stream1(str1), stream2(str2); + LogMessage::AddLogToStream(&stream1, LS_INFO); + LogMessage::AddLogToStream(&stream2, LS_VERBOSE); + EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream1)); + EXPECT_EQ(LS_VERBOSE, LogMessage::GetLogToStream(&stream2)); + + LOG(LS_INFO) << "INFO"; + LOG(LS_VERBOSE) << "VERBOSE"; + + EXPECT_NE(std::string::npos, str1.find("INFO")); + EXPECT_EQ(std::string::npos, str1.find("VERBOSE")); + EXPECT_NE(std::string::npos, str2.find("INFO")); + EXPECT_NE(std::string::npos, str2.find("VERBOSE")); + + LogMessage::RemoveLogToStream(&stream2); + LogMessage::RemoveLogToStream(&stream1); + EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream2)); + EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream1)); + + EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL)); +} + +// Ensure we don't crash when adding/removing streams while threads are going. +// We should restore the correct global state at the end. +class LogThread : public Thread { + public: + virtual ~LogThread() { + Stop(); + } + + private: + void Run() { + // LS_SENSITIVE to avoid cluttering up any real logging going on + LOG(LS_SENSITIVE) << "LOG"; + } +}; + +TEST(LogTest, MultipleThreads) { + int sev = LogMessage::GetLogToStream(NULL); + + LogThread thread1, thread2, thread3; + thread1.Start(); + thread2.Start(); + thread3.Start(); + + NullStream stream1, stream2, stream3; + for (int i = 0; i < 1000; ++i) { + LogMessage::AddLogToStream(&stream1, LS_INFO); + LogMessage::AddLogToStream(&stream2, LS_VERBOSE); + LogMessage::AddLogToStream(&stream3, LS_SENSITIVE); + LogMessage::RemoveLogToStream(&stream1); + LogMessage::RemoveLogToStream(&stream2); + LogMessage::RemoveLogToStream(&stream3); + } + + EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL)); +} + + +TEST(LogTest, WallClockStartTime) { + uint32 time = LogMessage::WallClockStartTime(); + // Expect the time to be in a sensible range, e.g. > 2012-01-01. + EXPECT_GT(time, 1325376000u); +} + +// Test the time required to write 1000 80-character logs to an unbuffered file. +TEST(LogTest, Perf) { + Pathname path; + EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL)); + path.SetPathname(Filesystem::TempFilename(path, "ut")); + + FileStream stream; + EXPECT_TRUE(stream.Open(path.pathname(), "wb", NULL)); + stream.DisableBuffering(); + LogMessage::AddLogToStream(&stream, LS_SENSITIVE); + + uint32 start = Time(), finish; + std::string message('X', 80); + for (int i = 0; i < 1000; ++i) { + LOG(LS_SENSITIVE) << message; + } + finish = Time(); + + LogMessage::RemoveLogToStream(&stream); + stream.Close(); + Filesystem::DeleteFile(path); + + LOG(LS_INFO) << "Average log time: " << TimeDiff(finish, start) << " us"; +} + +} // namespace rtc diff --git a/webrtc/base/macasyncsocket.cc b/webrtc/base/macasyncsocket.cc new file mode 100644 index 000000000..ee982ffff --- /dev/null +++ b/webrtc/base/macasyncsocket.cc @@ -0,0 +1,477 @@ +/* + * Copyright 2010 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. + */ +// +// MacAsyncSocket is a kind of AsyncSocket. It does not support the SOCK_DGRAM +// type (yet). It works asynchronously, which means that users of this socket +// should connect to the various events declared in asyncsocket.h to receive +// notifications about this socket. It uses CFSockets for signals, but prefers +// the basic bsd socket operations rather than their CFSocket wrappers when +// possible. + +#include +#include + +#include "webrtc/base/macasyncsocket.h" + +#include "webrtc/base/logging.h" +#include "webrtc/base/macsocketserver.h" + +namespace rtc { + +static const int kCallbackFlags = kCFSocketReadCallBack | + kCFSocketConnectCallBack | + kCFSocketWriteCallBack; + +MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family) + : ss_(ss), + socket_(NULL), + native_socket_(INVALID_SOCKET), + source_(NULL), + current_callbacks_(0), + disabled_(false), + error_(0), + state_(CS_CLOSED), + resolver_(NULL) { + Initialize(family); +} + +MacAsyncSocket::~MacAsyncSocket() { + Close(); +} + +// Returns the address to which the socket is bound. If the socket is not +// bound, then the any-address is returned. +SocketAddress MacAsyncSocket::GetLocalAddress() const { + SocketAddress address; + + // The CFSocket doesn't pick up on implicit binds from the connect call. + // Calling bind in before connect explicitly causes errors, so just query + // the underlying bsd socket. + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(native_socket_, + reinterpret_cast(&addr), &addrlen); + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } + return address; +} + +// Returns the address to which the socket is connected. If the socket is not +// connected, then the any-address is returned. +SocketAddress MacAsyncSocket::GetRemoteAddress() const { + SocketAddress address; + + // Use native_socket for consistency with GetLocalAddress. + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(native_socket_, + reinterpret_cast(&addr), &addrlen); + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } + return address; +} + +// Bind the socket to a local address. +int MacAsyncSocket::Bind(const SocketAddress& address) { + sockaddr_storage saddr = {0}; + size_t len = address.ToSockAddrStorage(&saddr); + int err = ::bind(native_socket_, reinterpret_cast(&saddr), len); + if (err == SOCKET_ERROR) error_ = errno; + return err; +} + +void MacAsyncSocket::OnResolveResult(SignalThread* thread) { + if (thread != resolver_) { + return; + } + int error = resolver_->GetError(); + if (error == 0) { + error = DoConnect(resolver_->address()); + } else { + Close(); + } + if (error) { + error_ = error; + SignalCloseEvent(this, error_); + } +} + +// Connect to a remote address. +int MacAsyncSocket::Connect(const SocketAddress& addr) { + // TODO(djw): Consolidate all the connect->resolve->doconnect implementations. + if (state_ != CS_CLOSED) { + SetError(EALREADY); + return SOCKET_ERROR; + } + if (addr.IsUnresolved()) { + LOG(LS_VERBOSE) << "Resolving addr in MacAsyncSocket::Connect"; + resolver_ = new AsyncResolver(); + resolver_->SignalWorkDone.connect(this, + &MacAsyncSocket::OnResolveResult); + resolver_->Start(addr); + state_ = CS_CONNECTING; + return 0; + } + return DoConnect(addr); +} + +int MacAsyncSocket::DoConnect(const SocketAddress& addr) { + if (!valid()) { + Initialize(addr.family()); + if (!valid()) + return SOCKET_ERROR; + } + + sockaddr_storage saddr; + size_t len = addr.ToSockAddrStorage(&saddr); + int result = ::connect(native_socket_, reinterpret_cast(&saddr), + len); + + if (result != SOCKET_ERROR) { + state_ = CS_CONNECTED; + } else { + error_ = errno; + if (error_ == EINPROGRESS) { + state_ = CS_CONNECTING; + result = 0; + } + } + return result; +} + +// Send to the remote end we're connected to. +int MacAsyncSocket::Send(const void* buffer, size_t length) { + if (!valid()) { + return SOCKET_ERROR; + } + + int sent = ::send(native_socket_, buffer, length, 0); + + if (sent == SOCKET_ERROR) { + error_ = errno; + + if (IsBlocking()) { + // Reenable the writable callback (once), since we are flow controlled. + CFSocketEnableCallBacks(socket_, kCallbackFlags); + current_callbacks_ = kCallbackFlags; + } + } + return sent; +} + +// Send to the given address. We may or may not be connected to anyone. +int MacAsyncSocket::SendTo(const void* buffer, size_t length, + const SocketAddress& address) { + if (!valid()) { + return SOCKET_ERROR; + } + + sockaddr_storage saddr; + size_t len = address.ToSockAddrStorage(&saddr); + int sent = ::sendto(native_socket_, buffer, length, 0, + reinterpret_cast(&saddr), len); + + if (sent == SOCKET_ERROR) { + error_ = errno; + } + + return sent; +} + +// Read data received from the remote end we're connected to. +int MacAsyncSocket::Recv(void* buffer, size_t length) { + int received = ::recv(native_socket_, reinterpret_cast(buffer), + length, 0); + if (received == SOCKET_ERROR) error_ = errno; + + // Recv should only be called when there is data to read + ASSERT((received != 0) || (length == 0)); + return received; +} + +// Read data received from any remote party +int MacAsyncSocket::RecvFrom(void* buffer, size_t length, + SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + int received = ::recvfrom(native_socket_, reinterpret_cast(buffer), + length, 0, reinterpret_cast(&saddr), + &addr_len); + if (received >= 0 && out_addr != NULL) { + SocketAddressFromSockAddrStorage(saddr, out_addr); + } else if (received == SOCKET_ERROR) { + error_ = errno; + } + return received; +} + +int MacAsyncSocket::Listen(int backlog) { + if (!valid()) { + return SOCKET_ERROR; + } + + int res = ::listen(native_socket_, backlog); + if (res != SOCKET_ERROR) + state_ = CS_CONNECTING; + else + error_ = errno; + + return res; +} + +MacAsyncSocket* MacAsyncSocket::Accept(SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + + int socket_fd = ::accept(native_socket_, reinterpret_cast(&saddr), + &addr_len); + if (socket_fd == INVALID_SOCKET) { + error_ = errno; + return NULL; + } + + MacAsyncSocket* s = new MacAsyncSocket(ss_, saddr.ss_family, socket_fd); + if (s && s->valid()) { + s->state_ = CS_CONNECTED; + if (out_addr) + SocketAddressFromSockAddrStorage(saddr, out_addr); + } else { + delete s; + s = NULL; + } + return s; +} + +int MacAsyncSocket::Close() { + if (source_ != NULL) { + CFRunLoopSourceInvalidate(source_); + CFRelease(source_); + if (ss_) ss_->UnregisterSocket(this); + source_ = NULL; + } + + if (socket_ != NULL) { + CFSocketInvalidate(socket_); + CFRelease(socket_); + socket_ = NULL; + } + + if (resolver_) { + resolver_->Destroy(false); + resolver_ = NULL; + } + + native_socket_ = INVALID_SOCKET; // invalidates the socket + error_ = 0; + state_ = CS_CLOSED; + return 0; +} + +int MacAsyncSocket::EstimateMTU(uint16* mtu) { + ASSERT(false && "NYI"); + return -1; +} + +int MacAsyncSocket::GetError() const { + return error_; +} + +void MacAsyncSocket::SetError(int error) { + error_ = error; +} + +Socket::ConnState MacAsyncSocket::GetState() const { + return state_; +} + +int MacAsyncSocket::GetOption(Option opt, int* value) { + ASSERT(false && "NYI"); + return -1; +} + +int MacAsyncSocket::SetOption(Option opt, int value) { + ASSERT(false && "NYI"); + return -1; +} + +void MacAsyncSocket::EnableCallbacks() { + if (valid()) { + disabled_ = false; + CFSocketEnableCallBacks(socket_, current_callbacks_); + } +} + +void MacAsyncSocket::DisableCallbacks() { + if (valid()) { + disabled_ = true; + CFSocketDisableCallBacks(socket_, kCallbackFlags); + } +} + +MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family, + int native_socket) + : ss_(ss), + socket_(NULL), + native_socket_(native_socket), + source_(NULL), + current_callbacks_(0), + disabled_(false), + error_(0), + state_(CS_CLOSED), + resolver_(NULL) { + Initialize(family); +} + +// Create a new socket, wrapping the native socket if provided or creating one +// otherwise. In case of any failure, consume the native socket. We assume the +// wrapped socket is in the closed state. If this is not the case you must +// update the state_ field for this socket yourself. +void MacAsyncSocket::Initialize(int family) { + CFSocketContext ctx = { 0 }; + ctx.info = this; + + // First create the CFSocket + CFSocketRef cf_socket = NULL; + bool res = false; + if (native_socket_ == INVALID_SOCKET) { + cf_socket = CFSocketCreate(kCFAllocatorDefault, + family, SOCK_STREAM, IPPROTO_TCP, + kCallbackFlags, MacAsyncSocketCallBack, &ctx); + } else { + cf_socket = CFSocketCreateWithNative(kCFAllocatorDefault, + native_socket_, kCallbackFlags, + MacAsyncSocketCallBack, &ctx); + } + + if (cf_socket) { + res = true; + socket_ = cf_socket; + native_socket_ = CFSocketGetNative(cf_socket); + current_callbacks_ = kCallbackFlags; + } + + if (res) { + // Make the underlying socket asynchronous + res = (-1 != ::fcntl(native_socket_, F_SETFL, + ::fcntl(native_socket_, F_GETFL, 0) | O_NONBLOCK)); + } + + if (res) { + // Add this socket to the run loop, at priority 1 so that it will be + // queued behind any pending signals. + source_ = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 1); + res = (source_ != NULL); + if (!res) errno = EINVAL; + } + + if (res) { + if (ss_) ss_->RegisterSocket(this); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopCommonModes); + } + + if (!res) { + int error = errno; + Close(); // Clears error_. + error_ = error; + } +} + +// Call CFRelease on the result when done using it +CFDataRef MacAsyncSocket::CopyCFAddress(const SocketAddress& address) { + sockaddr_storage saddr; + size_t len = address.ToSockAddrStorage(&saddr); + + const UInt8* bytes = reinterpret_cast(&saddr); + + CFDataRef cf_address = CFDataCreate(kCFAllocatorDefault, + bytes, len); + + ASSERT(cf_address != NULL); + return cf_address; +} + +void MacAsyncSocket::MacAsyncSocketCallBack(CFSocketRef s, + CFSocketCallBackType callbackType, + CFDataRef address, + const void* data, + void* info) { + MacAsyncSocket* this_socket = + reinterpret_cast(info); + ASSERT(this_socket != NULL && this_socket->socket_ == s); + + // Don't signal any socket messages if the socketserver is not listening on + // them. When we are reenabled they will be requeued and will fire again. + if (this_socket->disabled_) + return; + + switch (callbackType) { + case kCFSocketReadCallBack: + // This callback is invoked in one of 3 situations: + // 1. A new connection is waiting to be accepted. + // 2. The remote end closed the connection (a recv will return 0). + // 3. Data is available to read. + // 4. The connection closed unhappily (recv will return -1). + if (this_socket->state_ == CS_CONNECTING) { + // Case 1. + this_socket->SignalReadEvent(this_socket); + } else { + char ch, amt; + amt = ::recv(this_socket->native_socket_, &ch, 1, MSG_PEEK); + if (amt == 0) { + // Case 2. + this_socket->state_ = CS_CLOSED; + + // Disable additional callbacks or we will signal close twice. + CFSocketDisableCallBacks(this_socket->socket_, kCFSocketReadCallBack); + this_socket->current_callbacks_ &= ~kCFSocketReadCallBack; + this_socket->SignalCloseEvent(this_socket, 0); + } else if (amt > 0) { + // Case 3. + this_socket->SignalReadEvent(this_socket); + } else { + // Case 4. + int error = errno; + if (error == EAGAIN) { + // Observed in practice. Let's hope it's a spurious or out of date + // signal, since we just eat it. + } else { + this_socket->error_ = error; + this_socket->SignalCloseEvent(this_socket, error); + } + } + } + break; + + case kCFSocketConnectCallBack: + if (data != NULL) { + // An error occured in the background while connecting + this_socket->error_ = errno; + this_socket->state_ = CS_CLOSED; + this_socket->SignalCloseEvent(this_socket, this_socket->error_); + } else { + this_socket->state_ = CS_CONNECTED; + this_socket->SignalConnectEvent(this_socket); + } + break; + + case kCFSocketWriteCallBack: + // Update our callback tracking. Write doesn't reenable, so it's off now. + this_socket->current_callbacks_ &= ~kCFSocketWriteCallBack; + this_socket->SignalWriteEvent(this_socket); + break; + + default: + ASSERT(false && "Invalid callback type for socket"); + } +} + +} // namespace rtc diff --git a/webrtc/base/macasyncsocket.h b/webrtc/base/macasyncsocket.h new file mode 100644 index 000000000..bf8386546 --- /dev/null +++ b/webrtc/base/macasyncsocket.h @@ -0,0 +1,97 @@ +/* + * Copyright 2008 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. + */ +// MacAsyncSocket is a kind of AsyncSocket. It only creates sockets +// of the TCP type, and does not (yet) support listen and accept. It works +// asynchronously, which means that users of this socket should connect to +// the various events declared in asyncsocket.h to receive notifications about +// this socket. + +#ifndef WEBRTC_BASE_MACASYNCSOCKET_H__ +#define WEBRTC_BASE_MACASYNCSOCKET_H__ + +#include + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/nethelpers.h" + +namespace rtc { + +class MacBaseSocketServer; + +class MacAsyncSocket : public AsyncSocket, public sigslot::has_slots<> { + public: + MacAsyncSocket(MacBaseSocketServer* ss, int family); + virtual ~MacAsyncSocket(); + + bool valid() const { return source_ != NULL; } + + // Socket interface + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Bind(const SocketAddress& addr); + virtual int Connect(const SocketAddress& addr); + virtual int Send(const void* buffer, size_t length); + virtual int SendTo(const void* buffer, size_t length, + const SocketAddress& addr); + virtual int Recv(void* buffer, size_t length); + virtual int RecvFrom(void* buffer, size_t length, SocketAddress* out_addr); + virtual int Listen(int backlog); + virtual MacAsyncSocket* Accept(SocketAddress* out_addr); + virtual int Close(); + virtual int GetError() const; + virtual void SetError(int error); + virtual ConnState GetState() const; + virtual int EstimateMTU(uint16* mtu); + virtual int GetOption(Option opt, int* value); + virtual int SetOption(Option opt, int value); + + // For the MacBaseSocketServer to disable callbacks when process_io is false. + void EnableCallbacks(); + void DisableCallbacks(); + + protected: + void OnResolveResult(SignalThread* thread); + int DoConnect(const SocketAddress& addr); + + private: + // Creates an async socket from an existing bsd socket + MacAsyncSocket(MacBaseSocketServer* ss, int family, int native_socket); + + // Attaches the socket to the CFRunloop and sets the wrapped bsd socket + // to async mode + void Initialize(int family); + + // Translate the SocketAddress into a CFDataRef to pass to CF socket + // functions. Caller must call CFRelease on the result when done. + static CFDataRef CopyCFAddress(const SocketAddress& address); + + // Callback for the underlying CFSocketRef. + static void MacAsyncSocketCallBack(CFSocketRef s, + CFSocketCallBackType callbackType, + CFDataRef address, + const void* data, + void* info); + + MacBaseSocketServer* ss_; + CFSocketRef socket_; + int native_socket_; + CFRunLoopSourceRef source_; + int current_callbacks_; + bool disabled_; + int error_; + ConnState state_; + AsyncResolver* resolver_; + + DISALLOW_EVIL_CONSTRUCTORS(MacAsyncSocket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MACASYNCSOCKET_H__ diff --git a/webrtc/base/maccocoasocketserver.h b/webrtc/base/maccocoasocketserver.h new file mode 100644 index 000000000..d5deac153 --- /dev/null +++ b/webrtc/base/maccocoasocketserver.h @@ -0,0 +1,48 @@ +/* + * Copyright 2007 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. + */ + +// A libjingle compatible SocketServer for OSX/iOS/Cocoa. + +#ifndef WEBRTC_BASE_MACCOCOASOCKETSERVER_H_ +#define WEBRTC_BASE_MACCOCOASOCKETSERVER_H_ + +#include "webrtc/base/macsocketserver.h" + +#ifdef __OBJC__ +@class NSTimer, MacCocoaSocketServerHelperRtc; +#else +class NSTimer; +class MacCocoaSocketServerHelperRtc; +#endif + +namespace rtc { + +// A socketserver implementation that wraps the main cocoa +// application loop accessed through [NSApp run]. +class MacCocoaSocketServer : public MacBaseSocketServer { + public: + explicit MacCocoaSocketServer(); + virtual ~MacCocoaSocketServer(); + + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + private: + MacCocoaSocketServerHelperRtc* helper_; + NSTimer* timer_; // Weak. + // The count of how many times we're inside the NSApplication main loop. + int run_count_; + + DISALLOW_EVIL_CONSTRUCTORS(MacCocoaSocketServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MACCOCOASOCKETSERVER_H_ diff --git a/webrtc/base/maccocoasocketserver.mm b/webrtc/base/maccocoasocketserver.mm new file mode 100644 index 000000000..123ffdc52 --- /dev/null +++ b/webrtc/base/maccocoasocketserver.mm @@ -0,0 +1,140 @@ +/* + * Copyright 2012 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. + */ +#import "webrtc/base/maccocoasocketserver.h" + +#import +#import +#include + +#include "webrtc/base/scoped_autorelease_pool.h" + +// MacCocoaSocketServerHelperRtc serves as a delegate to NSMachPort or a target for +// a timeout. +@interface MacCocoaSocketServerHelperRtc : NSObject { + // This is a weak reference. This works fine since the + // rtc::MacCocoaSocketServer owns this object. + rtc::MacCocoaSocketServer* socketServer_; // Weak. +} +@end + +@implementation MacCocoaSocketServerHelperRtc +- (id)initWithSocketServer:(rtc::MacCocoaSocketServer*)ss { + self = [super init]; + if (self) { + socketServer_ = ss; + } + return self; +} + +- (void)timerFired:(NSTimer*)timer { + socketServer_->WakeUp(); +} + +- (void)breakMainloop { + [NSApp stop:self]; + // NSApp stop only exits after finishing processing of the + // current event. Since we're potentially in a timer callback + // and not an NSEvent handler, we need to trigger a dummy one + // and turn the loop over. We may be able to skip this if we're + // on the ss' thread and not inside the app loop already. + NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:event atStart:NO]; +} +@end + +namespace rtc { + +MacCocoaSocketServer::MacCocoaSocketServer() { + helper_ = [[MacCocoaSocketServerHelperRtc alloc] initWithSocketServer:this]; + timer_ = nil; + run_count_ = 0; + + // Initialize the shared NSApplication + [NSApplication sharedApplication]; +} + +MacCocoaSocketServer::~MacCocoaSocketServer() { + [timer_ invalidate]; + [timer_ release]; + [helper_ release]; +} + +// ::Wait is reentrant, for example when blocking on another thread while +// responding to I/O. Calls to [NSApp] MUST be made from the main thread +// only! +bool MacCocoaSocketServer::Wait(int cms, bool process_io) { + rtc::ScopedAutoreleasePool pool; + if (!process_io && cms == 0) { + // No op. + return true; + } + if ([NSApp isRunning]) { + // Only allow reentrant waiting if we're in a blocking send. + ASSERT(!process_io && cms == kForever); + } + + if (!process_io) { + // No way to listen to common modes and not get socket events, unless + // we disable each one's callbacks. + EnableSocketCallbacks(false); + } + + if (kForever != cms) { + // Install a timer that fires wakeup after cms has elapsed. + timer_ = + [NSTimer scheduledTimerWithTimeInterval:cms / 1000.0 + target:helper_ + selector:@selector(timerFired:) + userInfo:nil + repeats:NO]; + [timer_ retain]; + } + + // Run until WakeUp is called, which will call stop and exit this loop. + run_count_++; + [NSApp run]; + run_count_--; + + if (!process_io) { + // Reenable them. Hopefully this won't cause spurious callbacks or + // missing ones while they were disabled. + EnableSocketCallbacks(true); + } + + return true; +} + +// Can be called from any thread. Post a message back to the main thread to +// break out of the NSApp loop. +void MacCocoaSocketServer::WakeUp() { + if (timer_ != nil) { + [timer_ invalidate]; + [timer_ release]; + timer_ = nil; + } + + // [NSApp isRunning] returns unexpected results when called from another + // thread. Maintain our own count of how many times to break the main loop. + if (run_count_ > 0) { + [helper_ performSelectorOnMainThread:@selector(breakMainloop) + withObject:nil + waitUntilDone:false]; + } +} + +} // namespace rtc diff --git a/webrtc/base/maccocoasocketserver_unittest.mm b/webrtc/base/maccocoasocketserver_unittest.mm new file mode 100644 index 000000000..932b4a14f --- /dev/null +++ b/webrtc/base/maccocoasocketserver_unittest.mm @@ -0,0 +1,50 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/maccocoasocketserver.h" + +namespace rtc { + +class WakeThread : public Thread { + public: + WakeThread(SocketServer* ss) : ss_(ss) { + } + virtual ~WakeThread() { + Stop(); + } + void Run() { + ss_->WakeUp(); + } + private: + SocketServer* ss_; +}; + +// Test that MacCocoaSocketServer::Wait works as expected. +TEST(MacCocoaSocketServer, TestWait) { + MacCocoaSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCocoaSocketServer::Wakeup works as expected. +TEST(MacCocoaSocketServer, TestWakeup) { + MacCFSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +} // namespace rtc diff --git a/webrtc/base/maccocoathreadhelper.h b/webrtc/base/maccocoathreadhelper.h new file mode 100644 index 000000000..255d081ce --- /dev/null +++ b/webrtc/base/maccocoathreadhelper.h @@ -0,0 +1,27 @@ +/* + * Copyright 2008 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. + */ + +// Helper function for using Cocoa with Posix threads. This header should be +// included from C/C++ files that want to use some Cocoa functionality without +// using the .mm extension (mostly for files that are compiled on multiple +// platforms). + +#ifndef WEBRTC_BASE_MACCOCOATHREADHELPER_H__ +#define WEBRTC_BASE_MACCOCOATHREADHELPER_H__ + +namespace rtc { + +// Cocoa must be "put into multithreading mode" before Cocoa functionality can +// be used on POSIX threads. This function does that. +void InitCocoaMultiThreading(); + +} // namespace rtc + +#endif // WEBRTC_BASE_MACCOCOATHREADHELPER_H__ diff --git a/webrtc/base/maccocoathreadhelper.mm b/webrtc/base/maccocoathreadhelper.mm new file mode 100644 index 000000000..7bf9e9206 --- /dev/null +++ b/webrtc/base/maccocoathreadhelper.mm @@ -0,0 +1,44 @@ +/* + * Copyright 2007 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. + */ +// Helper function for using Cocoa with Posix threading. + +#import +#import + +#import "webrtc/base/maccocoathreadhelper.h" + +namespace rtc { + +// Cocoa must be "put into multithreading mode" before Cocoa functionality can +// be used on POSIX threads. The way to do that is to spawn one thread that may +// immediately exit. +void InitCocoaMultiThreading() { + if ([NSThread isMultiThreaded] == NO) { + // The sole purpose of this autorelease pool is to avoid a console + // message on Leopard that tells us we're autoreleasing the thread + // with no autorelease pool in place; we can't set up an autorelease + // pool before this, because this is executed from an initializer, + // which is run before main. This means we leak an autorelease pool, + // and one thread, and if other objects are set up in initializers after + // this they'll be silently added to this pool and never released. + + // Doing NSAutoreleasePool* hack = [[NSAutoreleasePool alloc] init]; + // causes unused variable error. + NSAutoreleasePool* hack; + hack = [[NSAutoreleasePool alloc] init]; + [NSThread detachNewThreadSelector:@selector(class) + toTarget:[NSObject class] + withObject:nil]; + } + + assert([NSThread isMultiThreaded]); +} + +} // namespace rtc diff --git a/webrtc/base/macconversion.cc b/webrtc/base/macconversion.cc new file mode 100644 index 000000000..75d11a803 --- /dev/null +++ b/webrtc/base/macconversion.cc @@ -0,0 +1,159 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/macconversion.h" + +bool p_convertHostCFStringRefToCPPString( + const CFStringRef cfstr, std::string& cppstr) { + bool result = false; + + // First this must be non-null, + if (NULL != cfstr) { + // it must actually *be* a CFString, and not something just masquerading + // as one, + if (CFGetTypeID(cfstr) == CFStringGetTypeID()) { + // and we must be able to get the characters out of it. + // (The cfstr owns this buffer; it came from somewhere else, + // so someone else gets to take care of getting rid of the cfstr, + // and then this buffer will go away automatically.) + unsigned length = CFStringGetLength(cfstr); + char* buf = new char[1 + length]; + if (CFStringGetCString(cfstr, buf, 1 + length, kCFStringEncodingASCII)) { + if (strlen(buf) == length) { + cppstr.assign(buf); + result = true; + } + } + delete [] buf; + } + } + + return result; +} + +bool p_convertCFNumberToInt(CFNumberRef cfn, int* i) { + bool converted = false; + + // It must not be null. + if (NULL != cfn) { + // It must actually *be* a CFNumber and not something just masquerading + // as one. + if (CFGetTypeID(cfn) == CFNumberGetTypeID()) { + CFNumberType ntype = CFNumberGetType(cfn); + switch (ntype) { + case kCFNumberSInt8Type: + SInt8 sint8; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint8)); + if (converted) *i = static_cast(sint8); + break; + case kCFNumberSInt16Type: + SInt16 sint16; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint16)); + if (converted) *i = static_cast(sint16); + break; + case kCFNumberSInt32Type: + SInt32 sint32; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint32)); + if (converted) *i = static_cast(sint32); + break; + case kCFNumberSInt64Type: + SInt64 sint64; + converted = CFNumberGetValue(cfn, ntype, static_cast(&sint64)); + if (converted) *i = static_cast(sint64); + break; + case kCFNumberFloat32Type: + Float32 float32; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&float32)); + if (converted) *i = static_cast(float32); + break; + case kCFNumberFloat64Type: + Float64 float64; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&float64)); + if (converted) *i = static_cast(float64); + break; + case kCFNumberCharType: + char charvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&charvalue)); + if (converted) *i = static_cast(charvalue); + break; + case kCFNumberShortType: + short shortvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&shortvalue)); + if (converted) *i = static_cast(shortvalue); + break; + case kCFNumberIntType: + int intvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&intvalue)); + if (converted) *i = static_cast(intvalue); + break; + case kCFNumberLongType: + long longvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&longvalue)); + if (converted) *i = static_cast(longvalue); + break; + case kCFNumberLongLongType: + long long llvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&llvalue)); + if (converted) *i = static_cast(llvalue); + break; + case kCFNumberFloatType: + float floatvalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&floatvalue)); + if (converted) *i = static_cast(floatvalue); + break; + case kCFNumberDoubleType: + double doublevalue; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&doublevalue)); + if (converted) *i = static_cast(doublevalue); + break; + case kCFNumberCFIndexType: + CFIndex cfindex; + converted = CFNumberGetValue(cfn, ntype, + static_cast(&cfindex)); + if (converted) *i = static_cast(cfindex); + break; + default: + LOG(LS_ERROR) << "got unknown type."; + break; + } + } + } + + return converted; +} + +bool p_isCFNumberTrue(CFNumberRef cfn) { + // We assume it's false until proven otherwise. + bool result = false; + int asInt; + bool converted = p_convertCFNumberToInt(cfn, &asInt); + + if (converted && (0 != asInt)) { + result = true; + } + + return result; +} + +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) diff --git a/webrtc/base/macconversion.h b/webrtc/base/macconversion.h new file mode 100644 index 000000000..a96ed2298 --- /dev/null +++ b/webrtc/base/macconversion.h @@ -0,0 +1,39 @@ +/* + * Copyright 2004 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_BASE_MACCONVERSION_H_ +#define WEBRTC_BASE_MACCONVERSION_H_ + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + +#include + +#include + +// given a CFStringRef, attempt to convert it to a C++ string. +// returns true if it succeeds, false otherwise. +// We can safely assume, given our context, that the string is +// going to be in ASCII, because it will either be an IP address, +// or a domain name, which is guaranteed to be ASCII-representable. +bool p_convertHostCFStringRefToCPPString(const CFStringRef cfstr, + std::string& cppstr); + +// Convert the CFNumber to an integer, putting the integer in the location +// given, and returhing true, if the conversion succeeds. +// If given a NULL or a non-CFNumber, returns false. +// This is pretty aggresive about trying to convert to int. +bool p_convertCFNumberToInt(CFNumberRef cfn, int* i); + +// given a CFNumberRef, determine if it represents a true value. +bool p_isCFNumberTrue(CFNumberRef cfn); + +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +#endif // WEBRTC_BASE_MACCONVERSION_H_ diff --git a/webrtc/base/macsocketserver.cc b/webrtc/base/macsocketserver.cc new file mode 100644 index 000000000..c7ab6e44d --- /dev/null +++ b/webrtc/base/macsocketserver.cc @@ -0,0 +1,378 @@ +/* + * Copyright 2007 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 "webrtc/base/macsocketserver.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/macasyncsocket.h" +#include "webrtc/base/macutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MacBaseSocketServer +/////////////////////////////////////////////////////////////////////////////// + +MacBaseSocketServer::MacBaseSocketServer() { +} + +MacBaseSocketServer::~MacBaseSocketServer() { +} + +AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int family, int type) { + if (SOCK_STREAM != type) + return NULL; + + MacAsyncSocket* socket = new MacAsyncSocket(this, family); + if (!socket->valid()) { + delete socket; + return NULL; + } + return socket; +} + +void MacBaseSocketServer::RegisterSocket(MacAsyncSocket* s) { + sockets_.insert(s); +} + +void MacBaseSocketServer::UnregisterSocket(MacAsyncSocket* s) { + VERIFY(1 == sockets_.erase(s)); // found 1 +} + +bool MacBaseSocketServer::SetPosixSignalHandler(int signum, + void (*handler)(int)) { + Dispatcher* dispatcher = signal_dispatcher(); + if (!PhysicalSocketServer::SetPosixSignalHandler(signum, handler)) { + return false; + } + + // Only register the FD once, when the first custom handler is installed. + if (!dispatcher && (dispatcher = signal_dispatcher())) { + CFFileDescriptorContext ctx = { 0 }; + ctx.info = this; + + CFFileDescriptorRef desc = CFFileDescriptorCreate( + kCFAllocatorDefault, + dispatcher->GetDescriptor(), + false, + &MacBaseSocketServer::FileDescriptorCallback, + &ctx); + if (!desc) { + return false; + } + + CFFileDescriptorEnableCallBacks(desc, kCFFileDescriptorReadCallBack); + CFRunLoopSourceRef ref = + CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, desc, 0); + + if (!ref) { + CFRelease(desc); + return false; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), ref, kCFRunLoopCommonModes); + CFRelease(desc); + CFRelease(ref); + } + + return true; +} + +// Used to disable socket events from waking our message queue when +// process_io is false. Does not disable signal event handling though. +void MacBaseSocketServer::EnableSocketCallbacks(bool enable) { + for (std::set::iterator it = sockets().begin(); + it != sockets().end(); ++it) { + if (enable) { + (*it)->EnableCallbacks(); + } else { + (*it)->DisableCallbacks(); + } + } +} + +void MacBaseSocketServer::FileDescriptorCallback(CFFileDescriptorRef fd, + CFOptionFlags flags, + void* context) { + MacBaseSocketServer* this_ss = + reinterpret_cast(context); + ASSERT(this_ss); + Dispatcher* signal_dispatcher = this_ss->signal_dispatcher(); + ASSERT(signal_dispatcher); + + signal_dispatcher->OnPreEvent(DE_READ); + signal_dispatcher->OnEvent(DE_READ, 0); + CFFileDescriptorEnableCallBacks(fd, kCFFileDescriptorReadCallBack); +} + + +/////////////////////////////////////////////////////////////////////////////// +// MacCFSocketServer +/////////////////////////////////////////////////////////////////////////////// + +void WakeUpCallback(void* info) { + MacCFSocketServer* server = static_cast(info); + ASSERT(NULL != server); + server->OnWakeUpCallback(); +} + +MacCFSocketServer::MacCFSocketServer() + : run_loop_(CFRunLoopGetCurrent()), + wake_up_(NULL) { + CFRunLoopSourceContext ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.info = this; + ctx.perform = &WakeUpCallback; + wake_up_ = CFRunLoopSourceCreate(NULL, 0, &ctx); + ASSERT(NULL != wake_up_); + if (wake_up_) { + CFRunLoopAddSource(run_loop_, wake_up_, kCFRunLoopCommonModes); + } +} + +MacCFSocketServer::~MacCFSocketServer() { + if (wake_up_) { + CFRunLoopSourceInvalidate(wake_up_); + CFRelease(wake_up_); + } +} + +bool MacCFSocketServer::Wait(int cms, bool process_io) { + ASSERT(CFRunLoopGetCurrent() == run_loop_); + + if (!process_io && cms == 0) { + // No op. + return true; + } + + if (!process_io) { + // No way to listen to common modes and not get socket events, unless + // we disable each one's callbacks. + EnableSocketCallbacks(false); + } + + SInt32 result; + if (kForever == cms) { + do { + // Would prefer to run in a custom mode that only listens to wake_up, + // but we have qtkit sending work to the main thread which is effectively + // blocked here, causing deadlock. Thus listen to the common modes. + // TODO: If QTKit becomes thread safe, do the above. + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000000, false); + } while (result != kCFRunLoopRunFinished && result != kCFRunLoopRunStopped); + } else { + // TODO: In the case of 0ms wait, this will only process one event, so we + // may want to loop until it returns TimedOut. + CFTimeInterval seconds = cms / 1000.0; + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, false); + } + + if (!process_io) { + // Reenable them. Hopefully this won't cause spurious callbacks or + // missing ones while they were disabled. + EnableSocketCallbacks(true); + } + + if (kCFRunLoopRunFinished == result) { + return false; + } + return true; +} + +void MacCFSocketServer::WakeUp() { + if (wake_up_) { + CFRunLoopSourceSignal(wake_up_); + CFRunLoopWakeUp(run_loop_); + } +} + +void MacCFSocketServer::OnWakeUpCallback() { + ASSERT(run_loop_ == CFRunLoopGetCurrent()); + CFRunLoopStop(run_loop_); +} + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonSocketServer +/////////////////////////////////////////////////////////////////////////////// +#ifndef CARBON_DEPRECATED + +const UInt32 kEventClassSocketServer = 'MCSS'; +const UInt32 kEventWakeUp = 'WAKE'; +const EventTypeSpec kEventWakeUpSpec[] = { + { kEventClassSocketServer, kEventWakeUp } +}; + +std::string DecodeEvent(EventRef event) { + std::string str; + DecodeFourChar(::GetEventClass(event), &str); + str.push_back(':'); + DecodeFourChar(::GetEventKind(event), &str); + return str; +} + +MacCarbonSocketServer::MacCarbonSocketServer() + : event_queue_(GetCurrentEventQueue()), wake_up_(NULL) { + VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0, + kEventAttributeUserEvent, &wake_up_)); +} + +MacCarbonSocketServer::~MacCarbonSocketServer() { + if (wake_up_) { + ReleaseEvent(wake_up_); + } +} + +bool MacCarbonSocketServer::Wait(int cms, bool process_io) { + ASSERT(GetCurrentEventQueue() == event_queue_); + + // Listen to all events if we're processing I/O. + // Only listen for our wakeup event if we're not. + UInt32 num_types = 0; + const EventTypeSpec* events = NULL; + if (!process_io) { + num_types = GetEventTypeCount(kEventWakeUpSpec); + events = kEventWakeUpSpec; + } + + EventTargetRef target = GetEventDispatcherTarget(); + EventTimeout timeout = + (kForever == cms) ? kEventDurationForever : cms / 1000.0; + EventTimeout end_time = GetCurrentEventTime() + timeout; + + bool done = false; + while (!done) { + EventRef event; + OSStatus result = ReceiveNextEvent(num_types, events, timeout, true, + &event); + if (noErr == result) { + if (wake_up_ != event) { + LOG_F(LS_VERBOSE) << "Dispatching event: " << DecodeEvent(event); + result = SendEventToEventTarget(event, target); + if ((noErr != result) && (eventNotHandledErr != result)) { + LOG_E(LS_ERROR, OS, result) << "SendEventToEventTarget"; + } + } else { + done = true; + } + ReleaseEvent(event); + } else if (eventLoopTimedOutErr == result) { + ASSERT(cms != kForever); + done = true; + } else if (eventLoopQuitErr == result) { + // Ignore this... we get spurious quits for a variety of reasons. + LOG_E(LS_VERBOSE, OS, result) << "ReceiveNextEvent"; + } else { + // Some strange error occurred. Log it. + LOG_E(LS_WARNING, OS, result) << "ReceiveNextEvent"; + return false; + } + if (kForever != cms) { + timeout = end_time - GetCurrentEventTime(); + } + } + return true; +} + +void MacCarbonSocketServer::WakeUp() { + if (!IsEventInQueue(event_queue_, wake_up_)) { + RetainEvent(wake_up_); + OSStatus result = PostEventToQueue(event_queue_, wake_up_, + kEventPriorityStandard); + if (noErr != result) { + LOG_E(LS_ERROR, OS, result) << "PostEventToQueue"; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonAppSocketServer +/////////////////////////////////////////////////////////////////////////////// + +MacCarbonAppSocketServer::MacCarbonAppSocketServer() + : event_queue_(GetCurrentEventQueue()) { + // Install event handler + VERIFY(noErr == InstallApplicationEventHandler( + NewEventHandlerUPP(WakeUpEventHandler), 1, kEventWakeUpSpec, this, + &event_handler_)); + + // Install a timer and set it idle to begin with. + VERIFY(noErr == InstallEventLoopTimer(GetMainEventLoop(), + kEventDurationForever, + kEventDurationForever, + NewEventLoopTimerUPP(TimerHandler), + this, + &timer_)); +} + +MacCarbonAppSocketServer::~MacCarbonAppSocketServer() { + RemoveEventLoopTimer(timer_); + RemoveEventHandler(event_handler_); +} + +OSStatus MacCarbonAppSocketServer::WakeUpEventHandler( + EventHandlerCallRef next, EventRef event, void *data) { + QuitApplicationEventLoop(); + return noErr; +} + +void MacCarbonAppSocketServer::TimerHandler( + EventLoopTimerRef timer, void *data) { + QuitApplicationEventLoop(); +} + +bool MacCarbonAppSocketServer::Wait(int cms, bool process_io) { + if (!process_io && cms == 0) { + // No op. + return true; + } + if (kForever != cms) { + // Start a timer. + OSStatus error = + SetEventLoopTimerNextFireTime(timer_, cms / 1000.0); + if (error != noErr) { + LOG(LS_ERROR) << "Failed setting next fire time."; + } + } + if (!process_io) { + // No way to listen to common modes and not get socket events, unless + // we disable each one's callbacks. + EnableSocketCallbacks(false); + } + RunApplicationEventLoop(); + if (!process_io) { + // Reenable them. Hopefully this won't cause spurious callbacks or + // missing ones while they were disabled. + EnableSocketCallbacks(true); + } + return true; +} + +void MacCarbonAppSocketServer::WakeUp() { + // TODO: No-op if there's already a WakeUp in flight. + EventRef wake_up; + VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0, + kEventAttributeUserEvent, &wake_up)); + OSStatus result = PostEventToQueue(event_queue_, wake_up, + kEventPriorityStandard); + if (noErr != result) { + LOG_E(LS_ERROR, OS, result) << "PostEventToQueue"; + } + ReleaseEvent(wake_up); +} + +#endif +} // namespace rtc diff --git a/webrtc/base/macsocketserver.h b/webrtc/base/macsocketserver.h new file mode 100644 index 000000000..8eebac6c6 --- /dev/null +++ b/webrtc/base/macsocketserver.h @@ -0,0 +1,136 @@ +/* + * Copyright 2007 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_BASE_MACSOCKETSERVER_H__ +#define WEBRTC_BASE_MACSOCKETSERVER_H__ + +#include +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) // Invalid on IOS +#include +#endif +#include "webrtc/base/physicalsocketserver.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MacBaseSocketServer +/////////////////////////////////////////////////////////////////////////////// +class MacAsyncSocket; + +class MacBaseSocketServer : public PhysicalSocketServer { + public: + MacBaseSocketServer(); + virtual ~MacBaseSocketServer(); + + // SocketServer Interface + virtual Socket* CreateSocket(int type) { return NULL; } + virtual Socket* CreateSocket(int family, int type) { return NULL; } + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual bool Wait(int cms, bool process_io) = 0; + virtual void WakeUp() = 0; + + void RegisterSocket(MacAsyncSocket* socket); + void UnregisterSocket(MacAsyncSocket* socket); + + // PhysicalSocketServer Overrides + virtual bool SetPosixSignalHandler(int signum, void (*handler)(int)); + + protected: + void EnableSocketCallbacks(bool enable); + const std::set& sockets() { + return sockets_; + } + + private: + static void FileDescriptorCallback(CFFileDescriptorRef ref, + CFOptionFlags flags, + void* context); + + std::set sockets_; +}; + +// Core Foundation implementation of the socket server. While idle it +// will run the current CF run loop. When the socket server has work +// to do the run loop will be paused. Does not support Carbon or Cocoa +// UI interaction. +class MacCFSocketServer : public MacBaseSocketServer { + public: + MacCFSocketServer(); + virtual ~MacCFSocketServer(); + + // SocketServer Interface + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + void OnWakeUpCallback(); + + private: + CFRunLoopRef run_loop_; + CFRunLoopSourceRef wake_up_; +}; + +#ifndef CARBON_DEPRECATED + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonSocketServer +/////////////////////////////////////////////////////////////////////////////// + +// Interacts with the Carbon event queue. While idle it will block, +// waiting for events. When the socket server has work to do, it will +// post a 'wake up' event to the queue, causing the thread to exit the +// event loop until the next call to Wait. Other events are dispatched +// to their target. Supports Carbon and Cocoa UI interaction. +class MacCarbonSocketServer : public MacBaseSocketServer { + public: + MacCarbonSocketServer(); + virtual ~MacCarbonSocketServer(); + + // SocketServer Interface + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + private: + EventQueueRef event_queue_; + EventRef wake_up_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// MacCarbonAppSocketServer +/////////////////////////////////////////////////////////////////////////////// + +// Runs the Carbon application event loop on the current thread while +// idle. When the socket server has work to do, it will post an event +// to the queue, causing the thread to exit the event loop until the +// next call to Wait. Other events are automatically dispatched to +// their target. +class MacCarbonAppSocketServer : public MacBaseSocketServer { + public: + MacCarbonAppSocketServer(); + virtual ~MacCarbonAppSocketServer(); + + // SocketServer Interface + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + private: + static OSStatus WakeUpEventHandler(EventHandlerCallRef next, EventRef event, + void *data); + static void TimerHandler(EventLoopTimerRef timer, void *data); + + EventQueueRef event_queue_; + EventHandlerRef event_handler_; + EventLoopTimerRef timer_; +}; + +#endif +} // namespace rtc + +#endif // WEBRTC_BASE_MACSOCKETSERVER_H__ diff --git a/webrtc/base/macsocketserver_unittest.cc b/webrtc/base/macsocketserver_unittest.cc new file mode 100644 index 000000000..e98be918c --- /dev/null +++ b/webrtc/base/macsocketserver_unittest.cc @@ -0,0 +1,237 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socket_unittest.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/macsocketserver.h" + +namespace rtc { + +class WakeThread : public Thread { + public: + WakeThread(SocketServer* ss) : ss_(ss) { + } + virtual ~WakeThread() { + Stop(); + } + void Run() { + ss_->WakeUp(); + } + private: + SocketServer* ss_; +}; + +#ifndef CARBON_DEPRECATED + +// Test that MacCFSocketServer::Wait works as expected. +TEST(MacCFSocketServerTest, TestWait) { + MacCFSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCFSocketServer::Wakeup works as expected. +TEST(MacCFSocketServerTest, TestWakeup) { + MacCFSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +// Test that MacCarbonSocketServer::Wait works as expected. +TEST(MacCarbonSocketServerTest, TestWait) { + MacCarbonSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCarbonSocketServer::Wakeup works as expected. +TEST(MacCarbonSocketServerTest, TestWakeup) { + MacCarbonSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +// Test that MacCarbonAppSocketServer::Wait works as expected. +TEST(MacCarbonAppSocketServerTest, TestWait) { + MacCarbonAppSocketServer server; + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that MacCarbonAppSocketServer::Wakeup works as expected. +TEST(MacCarbonAppSocketServerTest, TestWakeup) { + MacCarbonAppSocketServer server; + WakeThread thread(&server); + uint32 start = Time(); + thread.Start(); + server.Wait(10000, true); + EXPECT_LT(TimeSince(start), 10000); +} + +#endif + +// Test that MacAsyncSocket passes all the generic Socket tests. +class MacAsyncSocketTest : public SocketTest { + protected: + MacAsyncSocketTest() + : server_(CreateSocketServer()), + scope_(server_.get()) {} + // Override for other implementations of MacBaseSocketServer. + virtual MacBaseSocketServer* CreateSocketServer() { + return new MacCFSocketServer(); + }; + rtc::scoped_ptr server_; + SocketServerScope scope_; +}; + +TEST_F(MacAsyncSocketTest, TestConnectIPv4) { + SocketTest::TestConnectIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectIPv6) { + SocketTest::TestConnectIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookupIPv4) { + SocketTest::TestConnectWithDnsLookupIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookupIPv6) { + SocketTest::TestConnectWithDnsLookupIPv6(); +} + +// BUG=https://code.google.com/p/webrtc/issues/detail?id=2272 +TEST_F(MacAsyncSocketTest, DISABLED_TestConnectFailIPv4) { + SocketTest::TestConnectFailIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectFailIPv6) { + SocketTest::TestConnectFailIPv6(); +} + +// Reenable once we have mac async dns +TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFailIPv4) { + SocketTest::TestConnectWithDnsLookupFailIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFailIPv6) { + SocketTest::TestConnectWithDnsLookupFailIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocketIPv4) { + SocketTest::TestConnectWithClosedSocketIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocketIPv6) { + SocketTest::TestConnectWithClosedSocketIPv6(); +} + +// Flaky at the moment (10% failure rate). Seems the client doesn't get +// signalled in a timely manner... +TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnectIPv4) { + SocketTest::TestServerCloseDuringConnectIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnectIPv6) { + SocketTest::TestServerCloseDuringConnectIPv6(); +} +// Flaky at the moment (0.5% failure rate). Seems the client doesn't get +// signalled in a timely manner... +TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnectIPv4) { + SocketTest::TestClientCloseDuringConnectIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnectIPv6) { + SocketTest::TestClientCloseDuringConnectIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestServerCloseIPv4) { + SocketTest::TestServerCloseIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestServerCloseIPv6) { + SocketTest::TestServerCloseIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestCloseInClosedCallbackIPv4) { + SocketTest::TestCloseInClosedCallbackIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestCloseInClosedCallbackIPv6) { + SocketTest::TestCloseInClosedCallbackIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestTcpIPv4) { + SocketTest::TestTcpIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestTcpIPv6) { + SocketTest::TestTcpIPv6(); +} + +TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallbackIPv4) { + SocketTest::TestSingleFlowControlCallbackIPv4(); +} + +TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallbackIPv6) { + SocketTest::TestSingleFlowControlCallbackIPv6(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestUdpIPv4) { + SocketTest::TestUdpIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestUdpIPv6) { + SocketTest::TestUdpIPv6(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptionsIPv4) { + SocketTest::TestGetSetOptionsIPv4(); +} + +TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptionsIPv6) { + SocketTest::TestGetSetOptionsIPv6(); +} + +#ifndef CARBON_DEPRECATED +class MacCarbonAppAsyncSocketTest : public MacAsyncSocketTest { + virtual MacBaseSocketServer* CreateSocketServer() { + return new MacCarbonAppSocketServer(); + }; +}; + +TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} +#endif +} // namespace rtc diff --git a/webrtc/base/macutils.cc b/webrtc/base/macutils.cc new file mode 100644 index 000000000..6e436d4a8 --- /dev/null +++ b/webrtc/base/macutils.cc @@ -0,0 +1,221 @@ +/* + * Copyright 2007 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 "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/macutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +bool ToUtf8(const CFStringRef str16, std::string* str8) { + if ((NULL == str16) || (NULL == str8)) { + return false; + } + size_t maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str16), + kCFStringEncodingUTF8) + 1; + scoped_ptr buffer(new char[maxlen]); + if (!buffer || !CFStringGetCString(str16, buffer.get(), maxlen, + kCFStringEncodingUTF8)) { + return false; + } + str8->assign(buffer.get()); + return true; +} + +bool ToUtf16(const std::string& str8, CFStringRef* str16) { + if (NULL == str16) { + return false; + } + *str16 = CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast(str8.data()), + str8.length(), kCFStringEncodingUTF8, + false); + return NULL != *str16; +} + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +void DecodeFourChar(UInt32 fc, std::string* out) { + std::stringstream ss; + ss << '\''; + bool printable = true; + for (int i = 3; i >= 0; --i) { + char ch = (fc >> (8 * i)) & 0xFF; + if (isprint(static_cast(ch))) { + ss << ch; + } else { + printable = false; + break; + } + } + if (printable) { + ss << '\''; + } else { + ss.str(""); + ss << "0x" << std::hex << fc; + } + out->append(ss.str()); +} + +static bool GetGestalt(OSType ostype, int* value) { + ASSERT(NULL != value); + SInt32 native_value; + OSStatus result = Gestalt(ostype, &native_value); + if (noErr == result) { + *value = native_value; + return true; + } + std::string str; + DecodeFourChar(ostype, &str); + LOG_E(LS_ERROR, OS, result) << "Gestalt(" << str << ")"; + return false; +} + +bool GetOSVersion(int* major, int* minor, int* bugfix) { + ASSERT(major && minor && bugfix); + if (!GetGestalt(gestaltSystemVersion, major)) { + return false; + } + if (*major < 0x1040) { + *bugfix = *major & 0xF; + *minor = (*major >> 4) & 0xF; + *major = (*major >> 8); + return true; + } + return GetGestalt(gestaltSystemVersionMajor, major) && + GetGestalt(gestaltSystemVersionMinor, minor) && + GetGestalt(gestaltSystemVersionBugFix, bugfix); +} + +MacOSVersionName GetOSVersionName() { + int major = 0, minor = 0, bugfix = 0; + if (!GetOSVersion(&major, &minor, &bugfix)) { + return kMacOSUnknown; + } + if (major > 10) { + return kMacOSNewer; + } + if ((major < 10) || (minor < 3)) { + return kMacOSOlder; + } + switch (minor) { + case 3: + return kMacOSPanther; + case 4: + return kMacOSTiger; + case 5: + return kMacOSLeopard; + case 6: + return kMacOSSnowLeopard; + case 7: + return kMacOSLion; + case 8: + return kMacOSMountainLion; + case 9: + return kMacOSMavericks; + } + return kMacOSNewer; +} + +bool GetQuickTimeVersion(std::string* out) { + int ver; + if (!GetGestalt(gestaltQuickTimeVersion, &ver)) { + return false; + } + + std::stringstream ss; + ss << std::hex << ver; + *out = ss.str(); + return true; +} + +bool RunAppleScript(const std::string& script) { + // TODO(thaloun): Add a .mm file that contains something like this: + // NSString source from script + // NSAppleScript* appleScript = [[NSAppleScript alloc] initWithSource:&source] + // if (appleScript != nil) { + // [appleScript executeAndReturnError:nil] + // [appleScript release] +#ifndef CARBON_DEPRECATED + ComponentInstance component = NULL; + AEDesc script_desc; + AEDesc result_data; + OSStatus err; + OSAID script_id, result_id; + + AECreateDesc(typeNull, NULL, 0, &script_desc); + AECreateDesc(typeNull, NULL, 0, &result_data); + script_id = kOSANullScript; + result_id = kOSANullScript; + + component = OpenDefaultComponent(kOSAComponentType, typeAppleScript); + if (component == NULL) { + LOG(LS_ERROR) << "Failed opening Apple Script component"; + return false; + } + err = AECreateDesc(typeUTF8Text, script.data(), script.size(), &script_desc); + if (err != noErr) { + CloseComponent(component); + LOG(LS_ERROR) << "Failed creating Apple Script description"; + return false; + } + + err = OSACompile(component, &script_desc, kOSAModeCanInteract, &script_id); + if (err != noErr) { + AEDisposeDesc(&script_desc); + if (script_id != kOSANullScript) { + OSADispose(component, script_id); + } + CloseComponent(component); + LOG(LS_ERROR) << "Error compiling Apple Script"; + return false; + } + + err = OSAExecute(component, script_id, kOSANullScript, kOSAModeCanInteract, + &result_id); + + if (err == errOSAScriptError) { + LOG(LS_ERROR) << "Error when executing Apple Script: " << script; + AECreateDesc(typeNull, NULL, 0, &result_data); + OSAScriptError(component, kOSAErrorMessage, typeChar, &result_data); + int len = AEGetDescDataSize(&result_data); + char* data = (char*) malloc(len); + if (data != NULL) { + err = AEGetDescData(&result_data, data, len); + LOG(LS_ERROR) << "Script error: " << data; + } + AEDisposeDesc(&script_desc); + AEDisposeDesc(&result_data); + return false; + } + AEDisposeDesc(&script_desc); + if (script_id != kOSANullScript) { + OSADispose(component, script_id); + } + if (result_id != kOSANullScript) { + OSADispose(component, result_id); + } + CloseComponent(component); + return true; +#else + // TODO(thaloun): Support applescripts with the NSAppleScript API. + return false; +#endif // CARBON_DEPRECATED +} +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/macutils.h b/webrtc/base/macutils.h new file mode 100644 index 000000000..35c3d1870 --- /dev/null +++ b/webrtc/base/macutils.h @@ -0,0 +1,59 @@ +/* + * Copyright 2007 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_BASE_MACUTILS_H__ +#define WEBRTC_BASE_MACUTILS_H__ + +#include +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#endif +#include + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +// Note that some of these functions work for both iOS and Mac OS X. The ones +// that are specific to Mac are #ifdef'ed as such. + +bool ToUtf8(const CFStringRef str16, std::string* str8); +bool ToUtf16(const std::string& str8, CFStringRef* str16); + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +void DecodeFourChar(UInt32 fc, std::string* out); + +enum MacOSVersionName { + kMacOSUnknown, // ??? + kMacOSOlder, // 10.2- + kMacOSPanther, // 10.3 + kMacOSTiger, // 10.4 + kMacOSLeopard, // 10.5 + kMacOSSnowLeopard, // 10.6 + kMacOSLion, // 10.7 + kMacOSMountainLion, // 10.8 + kMacOSMavericks, // 10.9 + kMacOSNewer, // 10.10+ +}; + +bool GetOSVersion(int* major, int* minor, int* bugfix); +MacOSVersionName GetOSVersionName(); +bool GetQuickTimeVersion(std::string* version); + +// Runs the given apple script. Only supports scripts that does not +// require user interaction. +bool RunAppleScript(const std::string& script); +#endif + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_MACUTILS_H__ diff --git a/webrtc/base/macutils_unittest.cc b/webrtc/base/macutils_unittest.cc new file mode 100644 index 000000000..7150bf355 --- /dev/null +++ b/webrtc/base/macutils_unittest.cc @@ -0,0 +1,43 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/macutils.h" + +TEST(MacUtilsTest, GetOsVersionName) { + rtc::MacOSVersionName ver = rtc::GetOSVersionName(); + LOG(LS_INFO) << "GetOsVersionName " << ver; + EXPECT_NE(rtc::kMacOSUnknown, ver); +} + +TEST(MacUtilsTest, GetQuickTimeVersion) { + std::string version; + EXPECT_TRUE(rtc::GetQuickTimeVersion(&version)); + LOG(LS_INFO) << "GetQuickTimeVersion " << version; +} + +TEST(MacUtilsTest, RunAppleScriptCompileError) { + std::string script("set value to to 5"); + EXPECT_FALSE(rtc::RunAppleScript(script)); +} + +TEST(MacUtilsTest, RunAppleScriptRuntimeError) { + std::string script("set value to 5 / 0"); + EXPECT_FALSE(rtc::RunAppleScript(script)); +} + +#ifdef CARBON_DEPRECATED +TEST(MacUtilsTest, DISABLED_RunAppleScriptSuccess) { +#else +TEST(MacUtilsTest, RunAppleScriptSuccess) { +#endif + std::string script("set value to 5"); + EXPECT_TRUE(rtc::RunAppleScript(script)); +} diff --git a/webrtc/base/macwindowpicker.cc b/webrtc/base/macwindowpicker.cc new file mode 100644 index 000000000..bb97d20f1 --- /dev/null +++ b/webrtc/base/macwindowpicker.cc @@ -0,0 +1,256 @@ +/* + * Copyright 2010 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 "webrtc/base/macwindowpicker.h" + +#include +#include +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/macutils.h" + +namespace rtc { + +static const char* kCoreGraphicsName = + "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" + "CoreGraphics.framework/CoreGraphics"; + +static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo"; +static const char* kWindowListCreateDescriptionFromArray = + "CGWindowListCreateDescriptionFromArray"; + +// Function pointer for holding the CGWindowListCopyWindowInfo function. +typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption, + CGWindowID); + +// Function pointer for holding the CGWindowListCreateDescriptionFromArray +// function. +typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef); + +MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL), + get_window_list_desc_(NULL) { +} + +MacWindowPicker::~MacWindowPicker() { + if (lib_handle_ != NULL) { + dlclose(lib_handle_); + } +} + +bool MacWindowPicker::Init() { + // TODO: If this class grows to use more dynamically functions + // from the CoreGraphics framework, consider using + // webrtc/base/latebindingsymboltable.h. + lib_handle_ = dlopen(kCoreGraphicsName, RTLD_NOW); + if (lib_handle_ == NULL) { + LOG(LS_ERROR) << "Could not load CoreGraphics"; + return false; + } + + get_window_list_ = dlsym(lib_handle_, kWindowListCopyWindowInfo); + get_window_list_desc_ = + dlsym(lib_handle_, kWindowListCreateDescriptionFromArray); + if (get_window_list_ == NULL || get_window_list_desc_ == NULL) { + // The CGWindowListCopyWindowInfo and the + // CGWindowListCreateDescriptionFromArray functions was introduced + // in Leopard(10.5) so this is a normal failure on Tiger. + LOG(LS_INFO) << "Failed to load Core Graphics symbols"; + dlclose(lib_handle_); + lib_handle_ = NULL; + return false; + } + + return true; +} + +bool MacWindowPicker::IsVisible(const WindowId& id) { + // Init if we're not already inited. + if (get_window_list_desc_ == NULL && !Init()) { + return false; + } + CGWindowID ids[1]; + ids[0] = id.id(); + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); + + CFArrayRef window_array = + reinterpret_cast( + get_window_list_desc_)(window_id_array); + if (window_array == NULL || 0 == CFArrayGetCount(window_array)) { + // Could not find the window. It might have been closed. + LOG(LS_INFO) << "Window not found"; + CFRelease(window_id_array); + return false; + } + + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(window_array, 0)); + CFBooleanRef is_visible = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowIsOnscreen)); + + // Check that the window is visible. If not we might crash. + bool visible = false; + if (is_visible != NULL) { + visible = CFBooleanGetValue(is_visible); + } + CFRelease(window_id_array); + CFRelease(window_array); + return visible; +} + +bool MacWindowPicker::MoveToFront(const WindowId& id) { + // Init if we're not already initialized. + if (get_window_list_desc_ == NULL && !Init()) { + return false; + } + CGWindowID ids[1]; + ids[0] = id.id(); + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); + + CFArrayRef window_array = + reinterpret_cast( + get_window_list_desc_)(window_id_array); + if (window_array == NULL || 0 == CFArrayGetCount(window_array)) { + // Could not find the window. It might have been closed. + LOG(LS_INFO) << "Window not found"; + CFRelease(window_id_array); + return false; + } + + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(window_array, 0)); + CFStringRef window_name_ref = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef application_pid = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowOwnerPID)); + + int pid_val; + CFNumberGetValue(application_pid, kCFNumberIntType, &pid_val); + std::string window_name; + ToUtf8(window_name_ref, &window_name); + + // Build an applescript that sets the selected window to front + // within the application. Then set the application to front. + bool result = true; + std::stringstream ss; + ss << "tell application \"System Events\"\n" + << "set proc to the first item of (every process whose unix id is " + << pid_val + << ")\n" + << "tell proc to perform action \"AXRaise\" of window \"" + << window_name + << "\"\n" + << "set the frontmost of proc to true\n" + << "end tell"; + if (!RunAppleScript(ss.str())) { + // This might happen to for example X applications where the X + // server spawns of processes with their own PID but the X server + // is still registered as owner to the application windows. As a + // workaround, we put the X server process to front, meaning that + // all X applications will show up. The drawback with this + // workaround is that the application that we really wanted to set + // to front might be behind another X application. + ProcessSerialNumber psn; + pid_t pid = pid_val; + int res = GetProcessForPID(pid, &psn); + if (res != 0) { + LOG(LS_ERROR) << "Failed getting process for pid"; + result = false; + } + res = SetFrontProcess(&psn); + if (res != 0) { + LOG(LS_ERROR) << "Failed setting process to front"; + result = false; + } + } + CFRelease(window_id_array); + CFRelease(window_array); + return result; +} + +bool MacWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { + const uint32_t kMaxDisplays = 128; + CGDirectDisplayID active_displays[kMaxDisplays]; + uint32_t display_count = 0; + + CGError err = CGGetActiveDisplayList(kMaxDisplays, + active_displays, + &display_count); + if (err != kCGErrorSuccess) { + LOG_E(LS_ERROR, OS, err) << "Failed to enumerate the active displays."; + return false; + } + for (uint32_t i = 0; i < display_count; ++i) { + DesktopId id(active_displays[i], static_cast(i)); + // TODO: Figure out an appropriate desktop title. + DesktopDescription desc(id, ""); + desc.set_primary(CGDisplayIsMain(id.id())); + descriptions->push_back(desc); + } + return display_count > 0; +} + +bool MacWindowPicker::GetDesktopDimensions(const DesktopId& id, + int* width, + int* height) { + *width = CGDisplayPixelsWide(id.id()); + *height = CGDisplayPixelsHigh(id.id()); + return true; +} + +bool MacWindowPicker::GetWindowList(WindowDescriptionList* descriptions) { + // Init if we're not already inited. + if (get_window_list_ == NULL && !Init()) { + return false; + } + + // Only get onscreen, non-desktop windows. + CFArrayRef window_array = + reinterpret_cast(get_window_list_)( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (window_array == NULL) { + return false; + } + + // Check windows to make sure they have an id, title, and use window layer 0. + CFIndex i; + CFIndex count = CFArrayGetCount(window_array); + for (i = 0; i < count; ++i) { + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(window_array, i)); + CFStringRef window_title = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef window_id = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowNumber)); + CFNumberRef window_layer = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowLayer)); + if (window_title != NULL && window_id != NULL && window_layer != NULL) { + std::string title_str; + int id_val, layer_val; + ToUtf8(window_title, &title_str); + CFNumberGetValue(window_id, kCFNumberIntType, &id_val); + CFNumberGetValue(window_layer, kCFNumberIntType, &layer_val); + + // Discard windows without a title. + if (layer_val == 0 && title_str.length() > 0) { + WindowId id(static_cast(id_val)); + WindowDescription desc(id, title_str); + descriptions->push_back(desc); + } + } + } + + CFRelease(window_array); + return true; +} + +} // namespace rtc diff --git a/webrtc/base/macwindowpicker.h b/webrtc/base/macwindowpicker.h new file mode 100644 index 000000000..9a44747d2 --- /dev/null +++ b/webrtc/base/macwindowpicker.h @@ -0,0 +1,37 @@ +/* + * Copyright 2010 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_BASE_MACWINDOWPICKER_H_ +#define WEBRTC_BASE_MACWINDOWPICKER_H_ + +#include "webrtc/base/windowpicker.h" + +namespace rtc { + +class MacWindowPicker : public WindowPicker { + public: + MacWindowPicker(); + ~MacWindowPicker(); + virtual bool Init(); + virtual bool IsVisible(const WindowId& id); + virtual bool MoveToFront(const WindowId& id); + virtual bool GetWindowList(WindowDescriptionList* descriptions); + virtual bool GetDesktopList(DesktopDescriptionList* descriptions); + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height); + + private: + void* lib_handle_; + void* get_window_list_; + void* get_window_list_desc_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MACWINDOWPICKER_H_ diff --git a/webrtc/base/macwindowpicker_unittest.cc b/webrtc/base/macwindowpicker_unittest.cc new file mode 100644 index 000000000..7140f0231 --- /dev/null +++ b/webrtc/base/macwindowpicker_unittest.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/macutils.h" +#include "webrtc/base/macwindowpicker.h" +#include "webrtc/base/windowpicker.h" + +#if !defined(WEBRTC_MAC) || defined(WEBRTC_IOS) +#error Only for WEBRTC_MAC && !WEBRTC_IOS +#endif + +namespace rtc { + +bool IsLeopardOrLater() { + return GetOSVersionName() >= kMacOSLeopard; +} + +// Test that this works on new versions and fails acceptably on old versions. +TEST(MacWindowPickerTest, TestGetWindowList) { + MacWindowPicker picker, picker2; + WindowDescriptionList descriptions; + if (IsLeopardOrLater()) { + EXPECT_TRUE(picker.Init()); + EXPECT_TRUE(picker.GetWindowList(&descriptions)); + EXPECT_TRUE(picker2.GetWindowList(&descriptions)); // Init is optional + } else { + EXPECT_FALSE(picker.Init()); + EXPECT_FALSE(picker.GetWindowList(&descriptions)); + EXPECT_FALSE(picker2.GetWindowList(&descriptions)); + } +} + +// TODO: Add verification of the actual parsing, ie, add +// functionality to inject a fake get_window_array function which +// provide a pre-constructed list of windows. + +} // namespace rtc diff --git a/webrtc/base/mathutils.h b/webrtc/base/mathutils.h new file mode 100644 index 000000000..e2b21261d --- /dev/null +++ b/webrtc/base/mathutils.h @@ -0,0 +1,20 @@ +/* + * Copyright 2005 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_BASE_MATHUTILS_H_ +#define WEBRTC_BASE_MATHUTILS_H_ + +#include + +#ifndef M_PI +#define M_PI 3.14159265359f +#endif + +#endif // WEBRTC_BASE_MATHUTILS_H_ diff --git a/webrtc/base/md5.cc b/webrtc/base/md5.cc new file mode 100644 index 000000000..54128907a --- /dev/null +++ b/webrtc/base/md5.cc @@ -0,0 +1,222 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +// Changes from original C code: +// Ported to C++, type casting, Google code style. + +#include "webrtc/base/md5.h" + +// TODO: Avoid memcmpy - hash directly from memory. +#include // for memcpy(). + +#include "webrtc/base/byteorder.h" // for ARCH_CPU_LITTLE_ENDIAN. + +namespace rtc { + +#ifdef ARCH_CPU_LITTLE_ENDIAN +#define ByteReverse(buf, len) // Nothing. +#else // ARCH_CPU_BIG_ENDIAN +static void ByteReverse(uint32* buf, int len) { + for (int i = 0; i < len; ++i) { + buf[i] = rtc::GetLE32(&buf[i]); + } +} +#endif + +// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious +// initialization constants. +void MD5Init(MD5Context* ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +// Update context to reflect the concatenation of another buffer full of bytes. +void MD5Update(MD5Context* ctx, const uint8* buf, size_t len) { + // Update bitcount. + uint32 t = ctx->bits[0]; + if ((ctx->bits[0] = t + (static_cast(len) << 3)) < t) { + ctx->bits[1]++; // Carry from low to high. + } + ctx->bits[1] += static_cast(len >> 29); + t = (t >> 3) & 0x3f; // Bytes already in shsInfo->data. + + // Handle any leading odd-sized chunks. + if (t) { + uint8* p = reinterpret_cast(ctx->in) + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + ByteReverse(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + } + + // Process data in 64-byte chunks. + while (len >= 64) { + memcpy(ctx->in, buf, 64); + ByteReverse(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + // Handle any remaining bytes of data. + memcpy(ctx->in, buf, len); +} + +// Final wrapup - pad to 64-byte boundary with the bit pattern. +// 1 0* (64-bit count of bits processed, MSB-first) +void MD5Final(MD5Context* ctx, uint8 digest[16]) { + // Compute number of bytes mod 64. + uint32 count = (ctx->bits[0] >> 3) & 0x3F; + + // Set the first char of padding to 0x80. This is safe since there is + // always at least one byte free. + uint8* p = reinterpret_cast(ctx->in) + count; + *p++ = 0x80; + + // Bytes of padding needed to make 64 bytes. + count = 64 - 1 - count; + + // Pad out to 56 mod 64. + if (count < 8) { + // Two lots of padding: Pad the first block to 64 bytes. + memset(p, 0, count); + ByteReverse(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + + // Now fill the next block with 56 bytes. + memset(ctx->in, 0, 56); + } else { + // Pad block to 56 bytes. + memset(p, 0, count - 8); + } + ByteReverse(ctx->in, 14); + + // Append length in bits and transform. + ctx->in[14] = ctx->bits[0]; + ctx->in[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, ctx->in); + ByteReverse(ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); // In case it's sensitive. +} + +// The four core functions - F1 is optimized somewhat. +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +// The core of the MD5 algorithm, this alters an existing MD5 hash to +// reflect the addition of 16 longwords of new data. MD5Update blocks +// the data and converts bytes into longwords for this routine. +void MD5Transform(uint32 buf[4], const uint32 in[16]) { + uint32 a = buf[0]; + uint32 b = buf[1]; + uint32 c = buf[2]; + uint32 d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9] + 0xeb86d391, 21); + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +} // namespace rtc diff --git a/webrtc/base/md5.h b/webrtc/base/md5.h new file mode 100644 index 000000000..418e214c7 --- /dev/null +++ b/webrtc/base/md5.h @@ -0,0 +1,45 @@ +/* + * This is the header file for the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + */ + +// Changes(fbarchard): Ported to C++ and Google style guide. +// Made context first parameter in MD5Final for consistency with Sha1. +// Changes(hellner): added rtc namespace + +#ifndef WEBRTC_BASE_MD5_H_ +#define WEBRTC_BASE_MD5_H_ + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Canonical name for a MD5 context structure, used in many crypto libs. +typedef struct MD5Context MD5_CTX; + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + uint32 in[16]; +}; + +void MD5Init(MD5Context* context); +void MD5Update(MD5Context* context, const uint8* data, size_t len); +void MD5Final(MD5Context* context, uint8 digest[16]); +void MD5Transform(uint32 buf[4], const uint32 in[16]); + +} // namespace rtc + +#endif // WEBRTC_BASE_MD5_H_ diff --git a/webrtc/base/md5digest.h b/webrtc/base/md5digest.h new file mode 100644 index 000000000..5e8580222 --- /dev/null +++ b/webrtc/base/md5digest.h @@ -0,0 +1,46 @@ +/* + * Copyright 2012 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_BASE_MD5DIGEST_H_ +#define WEBRTC_BASE_MD5DIGEST_H_ + +#include "webrtc/base/md5.h" +#include "webrtc/base/messagedigest.h" + +namespace rtc { + +// A simple wrapper for our MD5 implementation. +class Md5Digest : public MessageDigest { + public: + enum { kSize = 16 }; + Md5Digest() { + MD5Init(&ctx_); + } + virtual size_t Size() const { + return kSize; + } + virtual void Update(const void* buf, size_t len) { + MD5Update(&ctx_, static_cast(buf), len); + } + virtual size_t Finish(void* buf, size_t len) { + if (len < kSize) { + return 0; + } + MD5Final(&ctx_, static_cast(buf)); + MD5Init(&ctx_); // Reset for next use. + return kSize; + } + private: + MD5_CTX ctx_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MD5DIGEST_H_ diff --git a/webrtc/base/md5digest_unittest.cc b/webrtc/base/md5digest_unittest.cc new file mode 100644 index 000000000..67c62db62 --- /dev/null +++ b/webrtc/base/md5digest_unittest.cc @@ -0,0 +1,79 @@ +/* + * Copyright 2012 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 "webrtc/base/md5digest.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +std::string Md5(const std::string& input) { + Md5Digest md5; + return ComputeDigest(&md5, input); +} + +TEST(Md5DigestTest, TestSize) { + Md5Digest md5; + EXPECT_EQ(16, static_cast(Md5Digest::kSize)); + EXPECT_EQ(16U, md5.Size()); +} + +TEST(Md5DigestTest, TestBasic) { + // These are the standard MD5 test vectors from RFC 1321. + EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", Md5("")); + EXPECT_EQ("0cc175b9c0f1b6a831c399e269772661", Md5("a")); + EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", Md5("abc")); + EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", Md5("message digest")); + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", + Md5("abcdefghijklmnopqrstuvwxyz")); +} + +TEST(Md5DigestTest, TestMultipleUpdates) { + Md5Digest md5; + std::string input = "abcdefghijklmnopqrstuvwxyz"; + char output[Md5Digest::kSize]; + for (size_t i = 0; i < input.size(); ++i) { + md5.Update(&input[i], 1); + } + EXPECT_EQ(md5.Size(), md5.Finish(output, sizeof(output))); + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", + hex_encode(output, sizeof(output))); +} + +TEST(Md5DigestTest, TestReuse) { + Md5Digest md5; + std::string input = "message digest"; + EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", ComputeDigest(&md5, input)); + input = "abcdefghijklmnopqrstuvwxyz"; + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", ComputeDigest(&md5, input)); +} + +TEST(Md5DigestTest, TestBufferTooSmall) { + Md5Digest md5; + std::string input = "abcdefghijklmnopqrstuvwxyz"; + char output[Md5Digest::kSize - 1]; + md5.Update(input.c_str(), input.size()); + EXPECT_EQ(0U, md5.Finish(output, sizeof(output))); +} + +TEST(Md5DigestTest, TestBufferConst) { + Md5Digest md5; + const int kLongSize = 1000000; + std::string input(kLongSize, '\0'); + for (int i = 0; i < kLongSize; ++i) { + input[i] = static_cast(i); + } + md5.Update(input.c_str(), input.size()); + for (int i = 0; i < kLongSize; ++i) { + EXPECT_EQ(static_cast(i), input[i]); + } +} + +} // namespace rtc diff --git a/webrtc/base/messagedigest.cc b/webrtc/base/messagedigest.cc new file mode 100644 index 000000000..dc3e1006a --- /dev/null +++ b/webrtc/base/messagedigest.cc @@ -0,0 +1,180 @@ +/* + * Copyright 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 "webrtc/base/messagedigest.h" + +#include + +#include "webrtc/base/sslconfig.h" +#if SSL_USE_OPENSSL +#include "webrtc/base/openssldigest.h" +#else +#include "webrtc/base/md5digest.h" +#include "webrtc/base/sha1digest.h" +#endif +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +// From RFC 4572. +const char DIGEST_MD5[] = "md5"; +const char DIGEST_SHA_1[] = "sha-1"; +const char DIGEST_SHA_224[] = "sha-224"; +const char DIGEST_SHA_256[] = "sha-256"; +const char DIGEST_SHA_384[] = "sha-384"; +const char DIGEST_SHA_512[] = "sha-512"; + +static const size_t kBlockSize = 64; // valid for SHA-256 and down + +MessageDigest* MessageDigestFactory::Create(const std::string& alg) { +#if SSL_USE_OPENSSL + MessageDigest* digest = new OpenSSLDigest(alg); + if (digest->Size() == 0) { // invalid algorithm + delete digest; + digest = NULL; + } + return digest; +#else + MessageDigest* digest = NULL; + if (alg == DIGEST_MD5) { + digest = new Md5Digest(); + } else if (alg == DIGEST_SHA_1) { + digest = new Sha1Digest(); + } + return digest; +#endif +} + +bool IsFips180DigestAlgorithm(const std::string& alg) { + // These are the FIPS 180 algorithms. According to RFC 4572 Section 5, + // "Self-signed certificates (for which legacy certificates are not a + // consideration) MUST use one of the FIPS 180 algorithms (SHA-1, + // SHA-224, SHA-256, SHA-384, or SHA-512) as their signature algorithm, + // and thus also MUST use it to calculate certificate fingerprints." + return alg == DIGEST_SHA_1 || + alg == DIGEST_SHA_224 || + alg == DIGEST_SHA_256 || + alg == DIGEST_SHA_384 || + alg == DIGEST_SHA_512; +} + +size_t ComputeDigest(MessageDigest* digest, const void* input, size_t in_len, + void* output, size_t out_len) { + digest->Update(input, in_len); + return digest->Finish(output, out_len); +} + +size_t ComputeDigest(const std::string& alg, const void* input, size_t in_len, + void* output, size_t out_len) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + return (digest) ? + ComputeDigest(digest.get(), input, in_len, output, out_len) : + 0; +} + +std::string ComputeDigest(MessageDigest* digest, const std::string& input) { + scoped_ptr output(new char[digest->Size()]); + ComputeDigest(digest, input.data(), input.size(), + output.get(), digest->Size()); + return hex_encode(output.get(), digest->Size()); +} + +bool ComputeDigest(const std::string& alg, const std::string& input, + std::string* output) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + if (!digest) { + return false; + } + *output = ComputeDigest(digest.get(), input); + return true; +} + +std::string ComputeDigest(const std::string& alg, const std::string& input) { + std::string output; + ComputeDigest(alg, input, &output); + return output; +} + +// Compute a RFC 2104 HMAC: H(K XOR opad, H(K XOR ipad, text)) +size_t ComputeHmac(MessageDigest* digest, + const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len) { + // We only handle algorithms with a 64-byte blocksize. + // TODO: Add BlockSize() method to MessageDigest. + size_t block_len = kBlockSize; + if (digest->Size() > 32) { + return 0; + } + // Copy the key to a block-sized buffer to simplify padding. + // If the key is longer than a block, hash it and use the result instead. + scoped_ptr new_key(new uint8[block_len]); + if (key_len > block_len) { + ComputeDigest(digest, key, key_len, new_key.get(), block_len); + memset(new_key.get() + digest->Size(), 0, block_len - digest->Size()); + } else { + memcpy(new_key.get(), key, key_len); + memset(new_key.get() + key_len, 0, block_len - key_len); + } + // Set up the padding from the key, salting appropriately for each padding. + scoped_ptr o_pad(new uint8[block_len]), i_pad(new uint8[block_len]); + for (size_t i = 0; i < block_len; ++i) { + o_pad[i] = 0x5c ^ new_key[i]; + i_pad[i] = 0x36 ^ new_key[i]; + } + // Inner hash; hash the inner padding, and then the input buffer. + scoped_ptr inner(new uint8[digest->Size()]); + digest->Update(i_pad.get(), block_len); + digest->Update(input, in_len); + digest->Finish(inner.get(), digest->Size()); + // Outer hash; hash the outer padding, and then the result of the inner hash. + digest->Update(o_pad.get(), block_len); + digest->Update(inner.get(), digest->Size()); + return digest->Finish(output, out_len); +} + +size_t ComputeHmac(const std::string& alg, const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + if (!digest) { + return 0; + } + return ComputeHmac(digest.get(), key, key_len, + input, in_len, output, out_len); +} + +std::string ComputeHmac(MessageDigest* digest, const std::string& key, + const std::string& input) { + scoped_ptr output(new char[digest->Size()]); + ComputeHmac(digest, key.data(), key.size(), + input.data(), input.size(), output.get(), digest->Size()); + return hex_encode(output.get(), digest->Size()); +} + +bool ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input, std::string* output) { + scoped_ptr digest(MessageDigestFactory::Create(alg)); + if (!digest) { + return false; + } + *output = ComputeHmac(digest.get(), key, input); + return true; +} + +std::string ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input) { + std::string output; + ComputeHmac(alg, key, input, &output); + return output; +} + +} // namespace rtc diff --git a/webrtc/base/messagedigest.h b/webrtc/base/messagedigest.h new file mode 100644 index 000000000..5cfcb4772 --- /dev/null +++ b/webrtc/base/messagedigest.h @@ -0,0 +1,109 @@ +/* + * Copyright 2004 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_BASE_MESSAGEDIGEST_H_ +#define WEBRTC_BASE_MESSAGEDIGEST_H_ + +#include + +namespace rtc { + +// Definitions for the digest algorithms. +extern const char DIGEST_MD5[]; +extern const char DIGEST_SHA_1[]; +extern const char DIGEST_SHA_224[]; +extern const char DIGEST_SHA_256[]; +extern const char DIGEST_SHA_384[]; +extern const char DIGEST_SHA_512[]; + +// A general class for computing hashes. +class MessageDigest { + public: + enum { kMaxSize = 64 }; // Maximum known size (SHA-512) + virtual ~MessageDigest() {} + // Returns the digest output size (e.g. 16 bytes for MD5). + virtual size_t Size() const = 0; + // Updates the digest with |len| bytes from |buf|. + virtual void Update(const void* buf, size_t len) = 0; + // Outputs the digest value to |buf| with length |len|. + // Returns the number of bytes written, i.e., Size(). + virtual size_t Finish(void* buf, size_t len) = 0; +}; + +// A factory class for creating digest objects. +class MessageDigestFactory { + public: + static MessageDigest* Create(const std::string& alg); +}; + +// A whitelist of approved digest algorithms from RFC 4572 (FIPS 180). +bool IsFips180DigestAlgorithm(const std::string& alg); + +// Functions to create hashes. + +// Computes the hash of |in_len| bytes of |input|, using the |digest| hash +// implementation, and outputs the hash to the buffer |output|, which is +// |out_len| bytes long. Returns the number of bytes written to |output| if +// successful, or 0 if |out_len| was too small. +size_t ComputeDigest(MessageDigest* digest, const void* input, size_t in_len, + void* output, size_t out_len); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns 0 if there is no +// digest with the given name. +size_t ComputeDigest(const std::string& alg, const void* input, size_t in_len, + void* output, size_t out_len); +// Computes the hash of |input| using the |digest| hash implementation, and +// returns it as a hex-encoded string. +std::string ComputeDigest(MessageDigest* digest, const std::string& input); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns empty string if +// there is no digest with the given name. +std::string ComputeDigest(const std::string& alg, const std::string& input); +// Like the previous function, but returns an explicit result code. +bool ComputeDigest(const std::string& alg, const std::string& input, + std::string* output); + +// Shorthand way to compute a hex-encoded hash using MD5. +inline std::string MD5(const std::string& input) { + return ComputeDigest(DIGEST_MD5, input); +} + +// Functions to compute RFC 2104 HMACs. + +// Computes the HMAC of |in_len| bytes of |input|, using the |digest| hash +// implementation and |key_len| bytes of |key| to key the HMAC, and outputs +// the HMAC to the buffer |output|, which is |out_len| bytes long. Returns the +// number of bytes written to |output| if successful, or 0 if |out_len| was too +// small. +size_t ComputeHmac(MessageDigest* digest, const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns 0 if there is no +// digest with the given name. +size_t ComputeHmac(const std::string& alg, const void* key, size_t key_len, + const void* input, size_t in_len, + void* output, size_t out_len); +// Computes the HMAC of |input| using the |digest| hash implementation and |key| +// to key the HMAC, and returns it as a hex-encoded string. +std::string ComputeHmac(MessageDigest* digest, const std::string& key, + const std::string& input); +// Like the previous function, but creates a digest implementation based on +// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns empty string if +// there is no digest with the given name. +std::string ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input); +// Like the previous function, but returns an explicit result code. +bool ComputeHmac(const std::string& alg, const std::string& key, + const std::string& input, std::string* output); + +} // namespace rtc + +#endif // WEBRTC_BASE_MESSAGEDIGEST_H_ diff --git a/webrtc/base/messagedigest_unittest.cc b/webrtc/base/messagedigest_unittest.cc new file mode 100644 index 000000000..86cf688ce --- /dev/null +++ b/webrtc/base/messagedigest_unittest.cc @@ -0,0 +1,151 @@ +/* + * Copyright 2012 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 "webrtc/base/gunit.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +// Test vectors from RFC 1321. +TEST(MessageDigestTest, TestMd5Digest) { + // Test the string versions of the APIs. + EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", + ComputeDigest(DIGEST_MD5, "")); + EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", + ComputeDigest(DIGEST_MD5, "abc")); + EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", + ComputeDigest(DIGEST_MD5, "abcdefghijklmnopqrstuvwxyz")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + char output[16]; + EXPECT_EQ(sizeof(output), + ComputeDigest(DIGEST_MD5, "abc", 3, output, sizeof(output))); + EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeDigest(DIGEST_MD5, "abc", 3, output, sizeof(output) - 1)); +} + +// Test vectors from RFC 3174. +TEST(MessageDigestTest, TestSha1Digest) { + // Test the string versions of the APIs. + EXPECT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709", + ComputeDigest(DIGEST_SHA_1, "")); + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", + ComputeDigest(DIGEST_SHA_1, "abc")); + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + ComputeDigest(DIGEST_SHA_1, + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + char output[20]; + EXPECT_EQ(sizeof(output), + ComputeDigest(DIGEST_SHA_1, "abc", 3, output, sizeof(output))); + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeDigest(DIGEST_SHA_1, "abc", 3, output, sizeof(output) - 1)); +} + +// Test that we fail properly if a bad digest algorithm is specified. +TEST(MessageDigestTest, TestBadDigest) { + std::string output; + EXPECT_FALSE(ComputeDigest("sha-9000", "abc", &output)); + EXPECT_EQ("", ComputeDigest("sha-9000", "abc")); +} + +// Test vectors from RFC 2202. +TEST(MessageDigestTest, TestMd5Hmac) { + // Test the string versions of the APIs. + EXPECT_EQ("9294727a3638bb1c13f48ef8158bfc9d", + ComputeHmac(DIGEST_MD5, std::string(16, '\x0b'), "Hi There")); + EXPECT_EQ("750c783e6ab0b503eaa86e310a5db738", + ComputeHmac(DIGEST_MD5, "Jefe", "what do ya want for nothing?")); + EXPECT_EQ("56be34521d144c88dbb8c733f0e8b3f6", + ComputeHmac(DIGEST_MD5, std::string(16, '\xaa'), + std::string(50, '\xdd'))); + EXPECT_EQ("697eaf0aca3a3aea3a75164746ffaa79", + ComputeHmac(DIGEST_MD5, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", + std::string(50, '\xcd'))); + EXPECT_EQ("56461ef2342edc00f9bab995690efd4c", + ComputeHmac(DIGEST_MD5, std::string(16, '\x0c'), + "Test With Truncation")); + EXPECT_EQ("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd", + ComputeHmac(DIGEST_MD5, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key - Hash Key First")); + EXPECT_EQ("6f630fad67cda0ee1fb1f562db3aa53e", + ComputeHmac(DIGEST_MD5, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key and Larger " + "Than One Block-Size Data")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + std::string key(16, '\x0b'); + std::string input("Hi There"); + char output[16]; + EXPECT_EQ(sizeof(output), + ComputeHmac(DIGEST_MD5, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output))); + EXPECT_EQ("9294727a3638bb1c13f48ef8158bfc9d", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeHmac(DIGEST_MD5, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output) - 1)); +} + +// Test vectors from RFC 2202. +TEST(MessageDigestTest, TestSha1Hmac) { + // Test the string versions of the APIs. + EXPECT_EQ("b617318655057264e28bc0b6fb378c8ef146be00", + ComputeHmac(DIGEST_SHA_1, std::string(20, '\x0b'), "Hi There")); + EXPECT_EQ("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", + ComputeHmac(DIGEST_SHA_1, "Jefe", "what do ya want for nothing?")); + EXPECT_EQ("125d7342b9ac11cd91a39af48aa17b4f63f175d3", + ComputeHmac(DIGEST_SHA_1, std::string(20, '\xaa'), + std::string(50, '\xdd'))); + EXPECT_EQ("4c9007f4026250c6bc8414f9bf50c86c2d7235da", + ComputeHmac(DIGEST_SHA_1, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", + std::string(50, '\xcd'))); + EXPECT_EQ("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04", + ComputeHmac(DIGEST_SHA_1, std::string(20, '\x0c'), + "Test With Truncation")); + EXPECT_EQ("aa4ae5e15272d00e95705637ce8a3b55ed402112", + ComputeHmac(DIGEST_SHA_1, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key - Hash Key First")); + EXPECT_EQ("e8e99d0f45237d786d6bbaa7965c7808bbff1a91", + ComputeHmac(DIGEST_SHA_1, std::string(80, '\xaa'), + "Test Using Larger Than Block-Size Key and Larger " + "Than One Block-Size Data")); + + // Test the raw buffer versions of the APIs; also check output buffer size. + std::string key(20, '\x0b'); + std::string input("Hi There"); + char output[20]; + EXPECT_EQ(sizeof(output), + ComputeHmac(DIGEST_SHA_1, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output))); + EXPECT_EQ("b617318655057264e28bc0b6fb378c8ef146be00", + hex_encode(output, sizeof(output))); + EXPECT_EQ(0U, + ComputeHmac(DIGEST_SHA_1, key.c_str(), key.size(), + input.c_str(), input.size(), output, sizeof(output) - 1)); +} + +TEST(MessageDigestTest, TestBadHmac) { + std::string output; + EXPECT_FALSE(ComputeHmac("sha-9000", "key", "abc", &output)); + EXPECT_EQ("", ComputeHmac("sha-9000", "key", "abc")); +} + +} // namespace rtc diff --git a/webrtc/base/messagehandler.cc b/webrtc/base/messagehandler.cc new file mode 100644 index 000000000..be5bb7f8f --- /dev/null +++ b/webrtc/base/messagehandler.cc @@ -0,0 +1,20 @@ +/* + * Copyright 2004 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 "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" + +namespace rtc { + +MessageHandler::~MessageHandler() { + MessageQueueManager::Clear(this); +} + +} // namespace rtc diff --git a/webrtc/base/messagehandler.h b/webrtc/base/messagehandler.h new file mode 100644 index 000000000..123c85097 --- /dev/null +++ b/webrtc/base/messagehandler.h @@ -0,0 +1,68 @@ +/* + * Copyright 2004 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_BASE_MESSAGEHANDLER_H_ +#define WEBRTC_BASE_MESSAGEHANDLER_H_ + +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +struct Message; + +// Messages get dispatched to a MessageHandler + +class MessageHandler { + public: + virtual ~MessageHandler(); + virtual void OnMessage(Message* msg) = 0; + + protected: + MessageHandler() {} + + private: + DISALLOW_COPY_AND_ASSIGN(MessageHandler); +}; + +// Helper class to facilitate executing a functor on a thread. +template +class FunctorMessageHandler : public MessageHandler { + public: + explicit FunctorMessageHandler(const FunctorT& functor) + : functor_(functor) {} + virtual void OnMessage(Message* msg) { + result_ = functor_(); + } + const ReturnT& result() const { return result_; } + + private: + FunctorT functor_; + ReturnT result_; +}; + +// Specialization for ReturnT of void. +template +class FunctorMessageHandler : public MessageHandler { + public: + explicit FunctorMessageHandler(const FunctorT& functor) + : functor_(functor) {} + virtual void OnMessage(Message* msg) { + functor_(); + } + void result() const {} + + private: + FunctorT functor_; +}; + + +} // namespace rtc + +#endif // WEBRTC_BASE_MESSAGEHANDLER_H_ diff --git a/webrtc/base/messagequeue.cc b/webrtc/base/messagequeue.cc new file mode 100644 index 000000000..1b312ff7a --- /dev/null +++ b/webrtc/base/messagequeue.cc @@ -0,0 +1,384 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagequeue.h" +#if defined(__native_client__) +#include "webrtc/base/nullsocketserver.h" +typedef rtc::NullSocketServer DefaultSocketServer; +#else +#include "webrtc/base/physicalsocketserver.h" +typedef rtc::PhysicalSocketServer DefaultSocketServer; +#endif + +namespace rtc { + +const uint32 kMaxMsgLatency = 150; // 150 ms + +//------------------------------------------------------------------ +// MessageQueueManager + +MessageQueueManager* MessageQueueManager::instance_ = NULL; + +MessageQueueManager* MessageQueueManager::Instance() { + // Note: This is not thread safe, but it is first called before threads are + // spawned. + if (!instance_) + instance_ = new MessageQueueManager; + return instance_; +} + +bool MessageQueueManager::IsInitialized() { + return instance_ != NULL; +} + +MessageQueueManager::MessageQueueManager() { +} + +MessageQueueManager::~MessageQueueManager() { +} + +void MessageQueueManager::Add(MessageQueue *message_queue) { + return Instance()->AddInternal(message_queue); +} +void MessageQueueManager::AddInternal(MessageQueue *message_queue) { + // MessageQueueManager methods should be non-reentrant, so we + // ASSERT that is the case. If any of these ASSERT, please + // contact bpm or jbeda. + ASSERT(!crit_.CurrentThreadIsOwner()); + CritScope cs(&crit_); + message_queues_.push_back(message_queue); +} + +void MessageQueueManager::Remove(MessageQueue *message_queue) { + // If there isn't a message queue manager instance, then there isn't a queue + // to remove. + if (!instance_) return; + return Instance()->RemoveInternal(message_queue); +} +void MessageQueueManager::RemoveInternal(MessageQueue *message_queue) { + ASSERT(!crit_.CurrentThreadIsOwner()); // See note above. + // If this is the last MessageQueue, destroy the manager as well so that + // we don't leak this object at program shutdown. As mentioned above, this is + // not thread-safe, but this should only happen at program termination (when + // the ThreadManager is destroyed, and threads are no longer active). + bool destroy = false; + { + CritScope cs(&crit_); + std::vector::iterator iter; + iter = std::find(message_queues_.begin(), message_queues_.end(), + message_queue); + if (iter != message_queues_.end()) { + message_queues_.erase(iter); + } + destroy = message_queues_.empty(); + } + if (destroy) { + instance_ = NULL; + delete this; + } +} + +void MessageQueueManager::Clear(MessageHandler *handler) { + // If there isn't a message queue manager instance, then there aren't any + // queues to remove this handler from. + if (!instance_) return; + return Instance()->ClearInternal(handler); +} +void MessageQueueManager::ClearInternal(MessageHandler *handler) { + ASSERT(!crit_.CurrentThreadIsOwner()); // See note above. + CritScope cs(&crit_); + std::vector::iterator iter; + for (iter = message_queues_.begin(); iter != message_queues_.end(); iter++) + (*iter)->Clear(handler); +} + +//------------------------------------------------------------------ +// MessageQueue + +MessageQueue::MessageQueue(SocketServer* ss) + : ss_(ss), fStop_(false), fPeekKeep_(false), + dmsgq_next_num_(0) { + if (!ss_) { + // Currently, MessageQueue holds a socket server, and is the base class for + // Thread. It seems like it makes more sense for Thread to hold the socket + // server, and provide it to the MessageQueue, since the Thread controls + // the I/O model, and MQ is agnostic to those details. Anyway, this causes + // messagequeue_unittest to depend on network libraries... yuck. + default_ss_.reset(new DefaultSocketServer()); + ss_ = default_ss_.get(); + } + ss_->SetMessageQueue(this); + MessageQueueManager::Add(this); +} + +MessageQueue::~MessageQueue() { + // The signal is done from here to ensure + // that it always gets called when the queue + // is going away. + SignalQueueDestroyed(); + MessageQueueManager::Remove(this); + Clear(NULL); + if (ss_) { + ss_->SetMessageQueue(NULL); + } +} + +void MessageQueue::set_socketserver(SocketServer* ss) { + ss_ = ss ? ss : default_ss_.get(); + ss_->SetMessageQueue(this); +} + +void MessageQueue::Quit() { + fStop_ = true; + ss_->WakeUp(); +} + +bool MessageQueue::IsQuitting() { + return fStop_; +} + +void MessageQueue::Restart() { + fStop_ = false; +} + +bool MessageQueue::Peek(Message *pmsg, int cmsWait) { + if (fPeekKeep_) { + *pmsg = msgPeek_; + return true; + } + if (!Get(pmsg, cmsWait)) + return false; + msgPeek_ = *pmsg; + fPeekKeep_ = true; + return true; +} + +bool MessageQueue::Get(Message *pmsg, int cmsWait, bool process_io) { + // Return and clear peek if present + // Always return the peek if it exists so there is Peek/Get symmetry + + if (fPeekKeep_) { + *pmsg = msgPeek_; + fPeekKeep_ = false; + return true; + } + + // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch + + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = Time(); + uint32 msCurrent = msStart; + while (true) { + // Check for sent messages + ReceiveSends(); + + // Check for posted events + int cmsDelayNext = kForever; + bool first_pass = true; + while (true) { + // All queue operations need to be locked, but nothing else in this loop + // (specifically handling disposed message) can happen inside the crit. + // Otherwise, disposed MessageHandlers will cause deadlocks. + { + CritScope cs(&crit_); + // On the first pass, check for delayed messages that have been + // triggered and calculate the next trigger time. + if (first_pass) { + first_pass = false; + while (!dmsgq_.empty()) { + if (TimeIsLater(msCurrent, dmsgq_.top().msTrigger_)) { + cmsDelayNext = TimeDiff(dmsgq_.top().msTrigger_, msCurrent); + break; + } + msgq_.push_back(dmsgq_.top().msg_); + dmsgq_.pop(); + } + } + // Pull a message off the message queue, if available. + if (msgq_.empty()) { + break; + } else { + *pmsg = msgq_.front(); + msgq_.pop_front(); + } + } // crit_ is released here. + + // Log a warning for time-sensitive messages that we're late to deliver. + if (pmsg->ts_sensitive) { + int32 delay = TimeDiff(msCurrent, pmsg->ts_sensitive); + if (delay > 0) { + LOG_F(LS_WARNING) << "id: " << pmsg->message_id << " delay: " + << (delay + kMaxMsgLatency) << "ms"; + } + } + // If this was a dispose message, delete it and skip it. + if (MQID_DISPOSE == pmsg->message_id) { + ASSERT(NULL == pmsg->phandler); + delete pmsg->pdata; + *pmsg = Message(); + continue; + } + return true; + } + + if (fStop_) + break; + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == kForever) { + cmsNext = cmsDelayNext; + } else { + cmsNext = _max(0, cmsTotal - cmsElapsed); + if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext)) + cmsNext = cmsDelayNext; + } + + // Wait and multiplex in the meantime + if (!ss_->Wait(cmsNext, process_io)) + return false; + + // If the specified timeout expired, return + + msCurrent = Time(); + cmsElapsed = TimeDiff(msCurrent, msStart); + if (cmsWait != kForever) { + if (cmsElapsed >= cmsWait) + return false; + } + } + return false; +} + +void MessageQueue::ReceiveSends() { +} + +void MessageQueue::Post(MessageHandler *phandler, uint32 id, + MessageData *pdata, bool time_sensitive) { + if (fStop_) + return; + + // Keep thread safe + // Add the message to the end of the queue + // Signal for the multiplexer to return + + CritScope cs(&crit_); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + if (time_sensitive) { + msg.ts_sensitive = Time() + kMaxMsgLatency; + } + msgq_.push_back(msg); + ss_->WakeUp(); +} + +void MessageQueue::DoDelayPost(int cmsDelay, uint32 tstamp, + MessageHandler *phandler, uint32 id, MessageData* pdata) { + if (fStop_) + return; + + // Keep thread safe + // Add to the priority queue. Gets sorted soonest first. + // Signal for the multiplexer to return. + + CritScope cs(&crit_); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + DelayedMessage dmsg(cmsDelay, tstamp, dmsgq_next_num_, msg); + dmsgq_.push(dmsg); + // If this message queue processes 1 message every millisecond for 50 days, + // we will wrap this number. Even then, only messages with identical times + // will be misordered, and then only briefly. This is probably ok. + VERIFY(0 != ++dmsgq_next_num_); + ss_->WakeUp(); +} + +int MessageQueue::GetDelay() { + CritScope cs(&crit_); + + if (!msgq_.empty()) + return 0; + + if (!dmsgq_.empty()) { + int delay = TimeUntil(dmsgq_.top().msTrigger_); + if (delay < 0) + delay = 0; + return delay; + } + + return kForever; +} + +void MessageQueue::Clear(MessageHandler *phandler, uint32 id, + MessageList* removed) { + CritScope cs(&crit_); + + // Remove messages with phandler + + if (fPeekKeep_ && msgPeek_.Match(phandler, id)) { + if (removed) { + removed->push_back(msgPeek_); + } else { + delete msgPeek_.pdata; + } + fPeekKeep_ = false; + } + + // Remove from ordered message queue + + for (MessageList::iterator it = msgq_.begin(); it != msgq_.end();) { + if (it->Match(phandler, id)) { + if (removed) { + removed->push_back(*it); + } else { + delete it->pdata; + } + it = msgq_.erase(it); + } else { + ++it; + } + } + + // Remove from priority queue. Not directly iterable, so use this approach + + PriorityQueue::container_type::iterator new_end = dmsgq_.container().begin(); + for (PriorityQueue::container_type::iterator it = new_end; + it != dmsgq_.container().end(); ++it) { + if (it->msg_.Match(phandler, id)) { + if (removed) { + removed->push_back(it->msg_); + } else { + delete it->msg_.pdata; + } + } else { + *new_end++ = *it; + } + } + dmsgq_.container().erase(new_end, dmsgq_.container().end()); + dmsgq_.reheap(); +} + +void MessageQueue::Dispatch(Message *pmsg) { + pmsg->phandler->OnMessage(pmsg); +} + +} // namespace rtc diff --git a/webrtc/base/messagequeue.h b/webrtc/base/messagequeue.h new file mode 100644 index 000000000..41c1e24b0 --- /dev/null +++ b/webrtc/base/messagequeue.h @@ -0,0 +1,254 @@ +/* + * Copyright 2004 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_BASE_MESSAGEQUEUE_H_ +#define WEBRTC_BASE_MESSAGEQUEUE_H_ + +#include + +#include +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +struct Message; +class MessageQueue; + +// MessageQueueManager does cleanup of of message queues + +class MessageQueueManager { + public: + static void Add(MessageQueue *message_queue); + static void Remove(MessageQueue *message_queue); + static void Clear(MessageHandler *handler); + + // For testing purposes, we expose whether or not the MessageQueueManager + // instance has been initialized. It has no other use relative to the rest of + // the functions of this class, which auto-initialize the underlying + // MessageQueueManager instance when necessary. + static bool IsInitialized(); + + private: + static MessageQueueManager* Instance(); + + MessageQueueManager(); + ~MessageQueueManager(); + + void AddInternal(MessageQueue *message_queue); + void RemoveInternal(MessageQueue *message_queue); + void ClearInternal(MessageHandler *handler); + + static MessageQueueManager* instance_; + // This list contains all live MessageQueues. + std::vector message_queues_; + CriticalSection crit_; +}; + +// Derive from this for specialized data +// App manages lifetime, except when messages are purged + +class MessageData { + public: + MessageData() {} + virtual ~MessageData() {} +}; + +template +class TypedMessageData : public MessageData { + public: + explicit TypedMessageData(const T& data) : data_(data) { } + const T& data() const { return data_; } + T& data() { return data_; } + private: + T data_; +}; + +// Like TypedMessageData, but for pointers that require a delete. +template +class ScopedMessageData : public MessageData { + public: + explicit ScopedMessageData(T* data) : data_(data) { } + const scoped_ptr& data() const { return data_; } + scoped_ptr& data() { return data_; } + private: + scoped_ptr data_; +}; + +// Like ScopedMessageData, but for reference counted pointers. +template +class ScopedRefMessageData : public MessageData { + public: + explicit ScopedRefMessageData(T* data) : data_(data) { } + const scoped_refptr& data() const { return data_; } + scoped_refptr& data() { return data_; } + private: + scoped_refptr data_; +}; + +template +inline MessageData* WrapMessageData(const T& data) { + return new TypedMessageData(data); +} + +template +inline const T& UseMessageData(MessageData* data) { + return static_cast< TypedMessageData* >(data)->data(); +} + +template +class DisposeData : public MessageData { + public: + explicit DisposeData(T* data) : data_(data) { } + virtual ~DisposeData() { delete data_; } + private: + T* data_; +}; + +const uint32 MQID_ANY = static_cast(-1); +const uint32 MQID_DISPOSE = static_cast(-2); + +// No destructor + +struct Message { + Message() { + memset(this, 0, sizeof(*this)); + } + inline bool Match(MessageHandler* handler, uint32 id) const { + return (handler == NULL || handler == phandler) + && (id == MQID_ANY || id == message_id); + } + MessageHandler *phandler; + uint32 message_id; + MessageData *pdata; + uint32 ts_sensitive; +}; + +typedef std::list MessageList; + +// DelayedMessage goes into a priority queue, sorted by trigger time. Messages +// with the same trigger time are processed in num_ (FIFO) order. + +class DelayedMessage { + public: + DelayedMessage(int delay, uint32 trigger, uint32 num, const Message& msg) + : cmsDelay_(delay), msTrigger_(trigger), num_(num), msg_(msg) { } + + bool operator< (const DelayedMessage& dmsg) const { + return (dmsg.msTrigger_ < msTrigger_) + || ((dmsg.msTrigger_ == msTrigger_) && (dmsg.num_ < num_)); + } + + int cmsDelay_; // for debugging + uint32 msTrigger_; + uint32 num_; + Message msg_; +}; + +class MessageQueue { + public: + explicit MessageQueue(SocketServer* ss = NULL); + virtual ~MessageQueue(); + + SocketServer* socketserver() { return ss_; } + void set_socketserver(SocketServer* ss); + + // Note: The behavior of MessageQueue has changed. When a MQ is stopped, + // futher Posts and Sends will fail. However, any pending Sends and *ready* + // Posts (as opposed to unexpired delayed Posts) will be delivered before + // Get (or Peek) returns false. By guaranteeing delivery of those messages, + // we eliminate the race condition when an MessageHandler and MessageQueue + // may be destroyed independently of each other. + virtual void Quit(); + virtual bool IsQuitting(); + virtual void Restart(); + + // Get() will process I/O until: + // 1) A message is available (returns true) + // 2) cmsWait seconds have elapsed (returns false) + // 3) Stop() is called (returns false) + virtual bool Get(Message *pmsg, int cmsWait = kForever, + bool process_io = true); + virtual bool Peek(Message *pmsg, int cmsWait = 0); + virtual void Post(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL, bool time_sensitive = false); + virtual void PostDelayed(int cmsDelay, MessageHandler *phandler, + uint32 id = 0, MessageData *pdata = NULL) { + return DoDelayPost(cmsDelay, TimeAfter(cmsDelay), phandler, id, pdata); + } + virtual void PostAt(uint32 tstamp, MessageHandler *phandler, + uint32 id = 0, MessageData *pdata = NULL) { + return DoDelayPost(TimeUntil(tstamp), tstamp, phandler, id, pdata); + } + virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY, + MessageList* removed = NULL); + virtual void Dispatch(Message *pmsg); + virtual void ReceiveSends(); + + // Amount of time until the next message can be retrieved + virtual int GetDelay(); + + bool empty() const { return size() == 0u; } + size_t size() const { + CritScope cs(&crit_); // msgq_.size() is not thread safe. + return msgq_.size() + dmsgq_.size() + (fPeekKeep_ ? 1u : 0u); + } + + // Internally posts a message which causes the doomed object to be deleted + template void Dispose(T* doomed) { + if (doomed) { + Post(NULL, MQID_DISPOSE, new DisposeData(doomed)); + } + } + + // When this signal is sent out, any references to this queue should + // no longer be used. + sigslot::signal0<> SignalQueueDestroyed; + + protected: + class PriorityQueue : public std::priority_queue { + public: + container_type& container() { return c; } + void reheap() { make_heap(c.begin(), c.end(), comp); } + }; + + void DoDelayPost(int cmsDelay, uint32 tstamp, MessageHandler *phandler, + uint32 id, MessageData* pdata); + + // The SocketServer is not owned by MessageQueue. + SocketServer* ss_; + // If a server isn't supplied in the constructor, use this one. + scoped_ptr default_ss_; + bool fStop_; + bool fPeekKeep_; + Message msgPeek_; + MessageList msgq_; + PriorityQueue dmsgq_; + uint32 dmsgq_next_num_; + mutable CriticalSection crit_; + + private: + DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MESSAGEQUEUE_H_ diff --git a/webrtc/base/messagequeue_unittest.cc b/webrtc/base/messagequeue_unittest.cc new file mode 100644 index 000000000..78024e0b2 --- /dev/null +++ b/webrtc/base/messagequeue_unittest.cc @@ -0,0 +1,140 @@ +/* + * Copyright 2004 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 "webrtc/base/messagequeue.h" + +#include "webrtc/base/bind.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/nullsocketserver.h" + +using namespace rtc; + +class MessageQueueTest: public testing::Test, public MessageQueue { + public: + bool IsLocked_Worker() { + if (!crit_.TryEnter()) { + return true; + } + crit_.Leave(); + return false; + } + bool IsLocked() { + // We have to do this on a worker thread, or else the TryEnter will + // succeed, since our critical sections are reentrant. + Thread worker; + worker.Start(); + return worker.Invoke( + rtc::Bind(&MessageQueueTest::IsLocked_Worker, this)); + } +}; + +struct DeletedLockChecker { + DeletedLockChecker(MessageQueueTest* test, bool* was_locked, bool* deleted) + : test(test), was_locked(was_locked), deleted(deleted) { } + ~DeletedLockChecker() { + *deleted = true; + *was_locked = test->IsLocked(); + } + MessageQueueTest* test; + bool* was_locked; + bool* deleted; +}; + +static void DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder( + MessageQueue* q) { + EXPECT_TRUE(q != NULL); + TimeStamp now = Time(); + q->PostAt(now, NULL, 3); + q->PostAt(now - 2, NULL, 0); + q->PostAt(now - 1, NULL, 1); + q->PostAt(now, NULL, 4); + q->PostAt(now - 1, NULL, 2); + + Message msg; + for (size_t i=0; i<5; ++i) { + memset(&msg, 0, sizeof(msg)); + EXPECT_TRUE(q->Get(&msg, 0)); + EXPECT_EQ(i, msg.message_id); + } + + EXPECT_FALSE(q->Get(&msg, 0)); // No more messages +} + +TEST_F(MessageQueueTest, + DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder) { + MessageQueue q; + DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(&q); + NullSocketServer nullss; + MessageQueue q_nullss(&nullss); + DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(&q_nullss); +} + +TEST_F(MessageQueueTest, DisposeNotLocked) { + bool was_locked = true; + bool deleted = false; + DeletedLockChecker* d = new DeletedLockChecker(this, &was_locked, &deleted); + Dispose(d); + Message msg; + EXPECT_FALSE(Get(&msg, 0)); + EXPECT_TRUE(deleted); + EXPECT_FALSE(was_locked); +} + +class DeletedMessageHandler : public MessageHandler { + public: + explicit DeletedMessageHandler(bool* deleted) : deleted_(deleted) { } + ~DeletedMessageHandler() { + *deleted_ = true; + } + void OnMessage(Message* msg) { } + private: + bool* deleted_; +}; + +TEST_F(MessageQueueTest, DiposeHandlerWithPostedMessagePending) { + bool deleted = false; + DeletedMessageHandler *handler = new DeletedMessageHandler(&deleted); + // First, post a dispose. + Dispose(handler); + // Now, post a message, which should *not* be returned by Get(). + Post(handler, 1); + Message msg; + EXPECT_FALSE(Get(&msg, 0)); + EXPECT_TRUE(deleted); +} + +struct UnwrapMainThreadScope { + UnwrapMainThreadScope() : rewrap_(Thread::Current() != NULL) { + if (rewrap_) ThreadManager::Instance()->UnwrapCurrentThread(); + } + ~UnwrapMainThreadScope() { + if (rewrap_) ThreadManager::Instance()->WrapCurrentThread(); + } + private: + bool rewrap_; +}; + +TEST(MessageQueueManager, Clear) { + UnwrapMainThreadScope s; + if (MessageQueueManager::IsInitialized()) { + LOG(LS_INFO) << "Unable to run MessageQueueManager::Clear test, since the " + << "MessageQueueManager was already initialized by some " + << "other test in this run."; + return; + } + bool deleted = false; + DeletedMessageHandler* handler = new DeletedMessageHandler(&deleted); + delete handler; + EXPECT_TRUE(deleted); + EXPECT_FALSE(MessageQueueManager::IsInitialized()); +} diff --git a/webrtc/base/move.h b/webrtc/base/move.h new file mode 100644 index 000000000..6d59cc583 --- /dev/null +++ b/webrtc/base/move.h @@ -0,0 +1,213 @@ +/* + * Copyright 2012 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 THIRD_PARTY_WEBRTC_FILES_WEBRTC_BASE_MOVE_H_ +#define THIRD_PARTY_WEBRTC_FILES_WEBRTC_BASE_MOVE_H_ + +// Macro with the boilerplate that makes a type move-only in C++03. +// +// USAGE +// +// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create +// a "move-only" type. Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be +// the first line in a class declaration. +// +// A class using this macro must call .Pass() (or somehow be an r-value already) +// before it can be: +// +// * Passed as a function argument +// * Used as the right-hand side of an assignment +// * Returned from a function +// +// Each class will still need to define their own "move constructor" and "move +// operator=" to make this useful. Here's an example of the macro, the move +// constructor, and the move operator= from the scoped_ptr class: +// +// template +// class scoped_ptr { +// TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) +// public: +// scoped_ptr(RValue& other) : ptr_(other.release()) { } +// scoped_ptr& operator=(RValue& other) { +// swap(other); +// return *this; +// } +// }; +// +// Note that the constructor must NOT be marked explicit. +// +// For consistency, the second parameter to the macro should always be RValue +// unless you have a strong reason to do otherwise. It is only exposed as a +// macro parameter so that the move constructor and move operator= don't look +// like they're using a phantom type. +// +// +// HOW THIS WORKS +// +// For a thorough explanation of this technique, see: +// +// http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor +// +// The summary is that we take advantage of 2 properties: +// +// 1) non-const references will not bind to r-values. +// 2) C++ can apply one user-defined conversion when initializing a +// variable. +// +// The first lets us disable the copy constructor and assignment operator +// by declaring private version of them with a non-const reference parameter. +// +// For l-values, direct initialization still fails like in +// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment +// operators are private. +// +// For r-values, the situation is different. The copy constructor and +// assignment operator are not viable due to (1), so we are trying to call +// a non-existent constructor and non-existing operator= rather than a private +// one. Since we have not committed an error quite yet, we can provide an +// alternate conversion sequence and a constructor. We add +// +// * a private struct named "RValue" +// * a user-defined conversion "operator RValue()" +// * a "move constructor" and "move operator=" that take the RValue& as +// their sole parameter. +// +// Only r-values will trigger this sequence and execute our "move constructor" +// or "move operator=." L-values will match the private copy constructor and +// operator= first giving a "private in this context" error. This combination +// gives us a move-only type. +// +// For signaling a destructive transfer of data from an l-value, we provide a +// method named Pass() which creates an r-value for the current instance +// triggering the move constructor or move operator=. +// +// Other ways to get r-values is to use the result of an expression like a +// function call. +// +// Here's an example with comments explaining what gets triggered where: +// +// class Foo { +// TALK_MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue); +// +// public: +// ... API ... +// Foo(RValue other); // Move constructor. +// Foo& operator=(RValue rhs); // Move operator= +// }; +// +// Foo MakeFoo(); // Function that returns a Foo. +// +// Foo f; +// Foo f_copy(f); // ERROR: Foo(Foo&) is private in this context. +// Foo f_assign; +// f_assign = f; // ERROR: operator=(Foo&) is private in this context. +// +// +// Foo f(MakeFoo()); // R-value so alternate conversion executed. +// Foo f_copy(f.Pass()); // R-value so alternate conversion executed. +// f = f_copy.Pass(); // R-value so alternate conversion executed. +// +// +// IMPLEMENTATION SUBTLETIES WITH RValue +// +// The RValue struct is just a container for a pointer back to the original +// object. It should only ever be created as a temporary, and no external +// class should ever declare it or use it in a parameter. +// +// It is tempting to want to use the RValue type in function parameters, but +// excluding the limited usage here for the move constructor and move +// operator=, doing so would mean that the function could take both r-values +// and l-values equially which is unexpected. See COMPARED To Boost.Move for +// more details. +// +// An alternate, and incorrect, implementation of the RValue class used by +// Boost.Move makes RValue a fieldless child of the move-only type. RValue& +// is then used in place of RValue in the various operators. The RValue& is +// "created" by doing *reinterpret_cast(this). This has the appeal +// of never creating a temporary RValue struct even with optimizations +// disabled. Also, by virtue of inheritance you can treat the RValue +// reference as if it were the move-only type itself. Unfortunately, +// using the result of this reinterpret_cast<> is actually undefined behavior +// due to C++98 5.2.10.7. In certain compilers (e.g., NaCl) the optimizer +// will generate non-working code. +// +// In optimized builds, both implementations generate the same assembly so we +// choose the one that adheres to the standard. +// +// +// COMPARED TO C++11 +// +// In C++11, you would implement this functionality using an r-value reference +// and our .Pass() method would be replaced with a call to std::move(). +// +// This emulation also has a deficiency where it uses up the single +// user-defined conversion allowed by C++ during initialization. This can +// cause problems in some API edge cases. For instance, in scoped_ptr, it is +// impossible to make a function "void Foo(scoped_ptr p)" accept a +// value of type scoped_ptr even if you add a constructor to +// scoped_ptr<> that would make it look like it should work. C++11 does not +// have this deficiency. +// +// +// COMPARED TO Boost.Move +// +// Our implementation similar to Boost.Move, but we keep the RValue struct +// private to the move-only type, and we don't use the reinterpret_cast<> hack. +// +// In Boost.Move, RValue is the boost::rv<> template. This type can be used +// when writing APIs like: +// +// void MyFunc(boost::rv& f) +// +// that can take advantage of rv<> to avoid extra copies of a type. However you +// would still be able to call this version of MyFunc with an l-value: +// +// Foo f; +// MyFunc(f); // Uh oh, we probably just destroyed |f| w/o calling Pass(). +// +// unless someone is very careful to also declare a parallel override like: +// +// void MyFunc(const Foo& f) +// +// that would catch the l-values first. This was declared unsafe in C++11 and +// a C++11 compiler will explicitly fail MyFunc(f). Unfortunately, we cannot +// ensure this in C++03. +// +// Since we have no need for writing such APIs yet, our implementation keeps +// RValue private and uses a .Pass() method to do the conversion instead of +// trying to write a version of "std::move()." Writing an API like std::move() +// would require the RValue struct to be public. +// +// +// CAVEATS +// +// If you include a move-only type as a field inside a class that does not +// explicitly declare a copy constructor, the containing class's implicit +// copy constructor will change from Containing(const Containing&) to +// Containing(Containing&). This can cause some unexpected errors. +// +// http://llvm.org/bugs/show_bug.cgi?id=11528 +// +// The workaround is to explicitly declare your copy constructor. +// +#define TALK_MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \ + private: \ + struct rvalue_type { \ + explicit rvalue_type(type* object) : object(object) {} \ + type* object; \ + }; \ + type(type&); \ + void operator=(type&); \ + public: \ + operator rvalue_type() { return rvalue_type(this); } \ + type Pass() { return type(rvalue_type(this)); } \ + private: + +#endif // THIRD_PARTY_WEBRTC_FILES_WEBRTC_BASE_MOVE_H_ diff --git a/webrtc/base/multipart.cc b/webrtc/base/multipart.cc new file mode 100644 index 000000000..0d73880e4 --- /dev/null +++ b/webrtc/base/multipart.cc @@ -0,0 +1,253 @@ +/* + * Copyright 2004 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 "webrtc/base/common.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/multipart.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MultipartStream +/////////////////////////////////////////////////////////////////////////////// + +MultipartStream::MultipartStream(const std::string& type, + const std::string& boundary) + : type_(type), + boundary_(boundary), + adding_(true), + current_(0), + position_(0) { + // The content type should be multipart/*. + ASSERT(0 == strncmp(type_.c_str(), "multipart/", 10)); +} + +MultipartStream::~MultipartStream() { + Close(); +} + +void MultipartStream::GetContentType(std::string* content_type) { + ASSERT(NULL != content_type); + content_type->assign(type_); + content_type->append("; boundary="); + content_type->append(boundary_); +} + +bool MultipartStream::AddPart(StreamInterface* data_stream, + const std::string& content_disposition, + const std::string& content_type) { + if (!AddPart("", content_disposition, content_type)) + return false; + parts_.push_back(data_stream); + data_stream->SignalEvent.connect(this, &MultipartStream::OnEvent); + return true; +} + +bool MultipartStream::AddPart(const std::string& data, + const std::string& content_disposition, + const std::string& content_type) { + ASSERT(adding_); + if (!adding_) + return false; + std::stringstream ss; + if (!parts_.empty()) { + ss << "\r\n"; + } + ss << "--" << boundary_ << "\r\n"; + if (!content_disposition.empty()) { + ss << ToString(HH_CONTENT_DISPOSITION) << ": " + << content_disposition << "\r\n"; + } + if (!content_type.empty()) { + ss << ToString(HH_CONTENT_TYPE) << ": " + << content_type << "\r\n"; + } + ss << "\r\n" << data; + parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size())); + return true; +} + +void MultipartStream::EndParts() { + ASSERT(adding_); + if (!adding_) + return; + + std::stringstream ss; + if (!parts_.empty()) { + ss << "\r\n"; + } + ss << "--" << boundary_ << "--" << "\r\n"; + parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size())); + + ASSERT(0 == current_); + ASSERT(0 == position_); + adding_ = false; + SignalEvent(this, SE_OPEN | SE_READ, 0); +} + +size_t MultipartStream::GetPartSize(const std::string& data, + const std::string& content_disposition, + const std::string& content_type) const { + size_t size = 0; + if (!parts_.empty()) { + size += 2; // for "\r\n"; + } + size += boundary_.size() + 4; // for "--boundary_\r\n"; + if (!content_disposition.empty()) { + // for ToString(HH_CONTENT_DISPOSITION): content_disposition\r\n + size += std::string(ToString(HH_CONTENT_DISPOSITION)).size() + 2 + + content_disposition.size() + 2; + } + if (!content_type.empty()) { + // for ToString(HH_CONTENT_TYPE): content_type\r\n + size += std::string(ToString(HH_CONTENT_TYPE)).size() + 2 + + content_type.size() + 2; + } + size += 2 + data.size(); // for \r\ndata + return size; +} + +size_t MultipartStream::GetEndPartSize() const { + size_t size = 0; + if (!parts_.empty()) { + size += 2; // for "\r\n"; + } + size += boundary_.size() + 6; // for "--boundary_--\r\n"; + return size; +} + +// +// StreamInterface +// + +StreamState MultipartStream::GetState() const { + if (adding_) { + return SS_OPENING; + } + return (current_ < parts_.size()) ? SS_OPEN : SS_CLOSED; +} + +StreamResult MultipartStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (adding_) { + return SR_BLOCK; + } + size_t local_read; + if (!read) read = &local_read; + while (current_ < parts_.size()) { + StreamResult result = parts_[current_]->Read(buffer, buffer_len, read, + error); + if (SR_EOS != result) { + if (SR_SUCCESS == result) { + position_ += *read; + } + return result; + } + ++current_; + } + return SR_EOS; +} + +StreamResult MultipartStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (error) { + *error = -1; + } + return SR_ERROR; +} + +void MultipartStream::Close() { + for (size_t i = 0; i < parts_.size(); ++i) { + delete parts_[i]; + } + parts_.clear(); + adding_ = false; + current_ = 0; + position_ = 0; +} + +bool MultipartStream::SetPosition(size_t position) { + if (adding_) { + return false; + } + size_t part_size, part_offset = 0; + for (size_t i = 0; i < parts_.size(); ++i) { + if (!parts_[i]->GetSize(&part_size)) { + return false; + } + if (part_offset + part_size > position) { + for (size_t j = i+1; j < _min(parts_.size(), current_+1); ++j) { + if (!parts_[j]->Rewind()) { + return false; + } + } + if (!parts_[i]->SetPosition(position - part_offset)) { + return false; + } + current_ = i; + position_ = position; + return true; + } + part_offset += part_size; + } + return false; +} + +bool MultipartStream::GetPosition(size_t* position) const { + if (position) { + *position = position_; + } + return true; +} + +bool MultipartStream::GetSize(size_t* size) const { + size_t part_size, total_size = 0; + for (size_t i = 0; i < parts_.size(); ++i) { + if (!parts_[i]->GetSize(&part_size)) { + return false; + } + total_size += part_size; + } + if (size) { + *size = total_size; + } + return true; +} + +bool MultipartStream::GetAvailable(size_t* size) const { + if (adding_) { + return false; + } + size_t part_size, total_size = 0; + for (size_t i = current_; i < parts_.size(); ++i) { + if (!parts_[i]->GetAvailable(&part_size)) { + return false; + } + total_size += part_size; + } + if (size) { + *size = total_size; + } + return true; +} + +// +// StreamInterface Slots +// + +void MultipartStream::OnEvent(StreamInterface* stream, int events, int error) { + if (adding_ || (current_ >= parts_.size()) || (parts_[current_] != stream)) { + return; + } + SignalEvent(this, events, error); +} + +} // namespace rtc diff --git a/webrtc/base/multipart.h b/webrtc/base/multipart.h new file mode 100644 index 000000000..a41f596ff --- /dev/null +++ b/webrtc/base/multipart.h @@ -0,0 +1,79 @@ +/* + * Copyright 2004 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_BASE_MULTIPART_H__ +#define WEBRTC_BASE_MULTIPART_H__ + +#include +#include + +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// MultipartStream - Implements an RFC2046 multipart stream by concatenating +// the supplied parts together, and adding the correct boundaries. +/////////////////////////////////////////////////////////////////////////////// + +class MultipartStream : public StreamInterface, public sigslot::has_slots<> { + public: + MultipartStream(const std::string& type, const std::string& boundary); + virtual ~MultipartStream(); + + void GetContentType(std::string* content_type); + + // Note: If content_disposition and/or content_type are the empty string, + // they will be omitted. + bool AddPart(StreamInterface* data_stream, + const std::string& content_disposition, + const std::string& content_type); + bool AddPart(const std::string& data, + const std::string& content_disposition, + const std::string& content_type); + void EndParts(); + + // Calculates the size of a part before actually adding the part. + size_t GetPartSize(const std::string& data, + const std::string& content_disposition, + const std::string& content_type) const; + size_t GetEndPartSize() const; + + // StreamInterface + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + + private: + typedef std::vector PartList; + + // StreamInterface Slots + void OnEvent(StreamInterface* stream, int events, int error); + + std::string type_, boundary_; + PartList parts_; + bool adding_; + size_t current_; // The index into parts_ of the current read position. + size_t position_; // The current read position in bytes. + + DISALLOW_COPY_AND_ASSIGN(MultipartStream); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_MULTIPART_H__ diff --git a/webrtc/base/multipart_unittest.cc b/webrtc/base/multipart_unittest.cc new file mode 100644 index 000000000..38e111493 --- /dev/null +++ b/webrtc/base/multipart_unittest.cc @@ -0,0 +1,125 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/multipart.h" + +namespace rtc { + +static const std::string kTestMultipartBoundary = "123456789987654321"; +static const std::string kTestContentType = + "multipart/form-data; boundary=123456789987654321"; +static const char kTestData[] = "This is a test."; +static const char kTestStreamContent[] = "This is a test stream."; + +TEST(MultipartTest, TestBasicOperations) { + MultipartStream multipart("multipart/form-data", kTestMultipartBoundary); + std::string content_type; + multipart.GetContentType(&content_type); + EXPECT_EQ(kTestContentType, content_type); + + EXPECT_EQ(rtc::SS_OPENING, multipart.GetState()); + + // The multipart stream contains only --boundary--\r\n + size_t end_part_size = multipart.GetEndPartSize(); + multipart.EndParts(); + EXPECT_EQ(rtc::SS_OPEN, multipart.GetState()); + size_t size; + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(end_part_size, size); + + // Write is not supported. + EXPECT_EQ(rtc::SR_ERROR, + multipart.Write(kTestData, sizeof(kTestData), NULL, NULL)); + + multipart.Close(); + EXPECT_EQ(rtc::SS_CLOSED, multipart.GetState()); + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(0U, size); +} + +TEST(MultipartTest, TestAddAndRead) { + MultipartStream multipart("multipart/form-data", kTestMultipartBoundary); + + size_t part_size = + multipart.GetPartSize(kTestData, "form-data; name=\"text\"", "text"); + EXPECT_TRUE(multipart.AddPart(kTestData, "form-data; name=\"text\"", "text")); + size_t size; + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(part_size, size); + + rtc::scoped_ptr stream( + new rtc::MemoryStream(kTestStreamContent)); + size_t stream_size = 0; + EXPECT_TRUE(stream->GetSize(&stream_size)); + part_size += + multipart.GetPartSize("", "form-data; name=\"stream\"", "stream"); + part_size += stream_size; + + EXPECT_TRUE(multipart.AddPart( + new rtc::MemoryStream(kTestStreamContent), + "form-data; name=\"stream\"", + "stream")); + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(part_size, size); + + // In adding state, block read. + char buffer[1024]; + EXPECT_EQ(rtc::SR_BLOCK, + multipart.Read(buffer, sizeof(buffer), NULL, NULL)); + // Write is not supported. + EXPECT_EQ(rtc::SR_ERROR, + multipart.Write(buffer, sizeof(buffer), NULL, NULL)); + + part_size += multipart.GetEndPartSize(); + multipart.EndParts(); + EXPECT_TRUE(multipart.GetSize(&size)); + EXPECT_EQ(part_size, size); + + // Read the multipart stream into StringStream + std::string str; + rtc::StringStream str_stream(str); + EXPECT_EQ(rtc::SR_SUCCESS, + Flow(&multipart, buffer, sizeof(buffer), &str_stream)); + EXPECT_EQ(size, str.length()); + + // Search three boundaries and two parts in the order. + size_t pos = 0; + pos = str.find(kTestMultipartBoundary); + EXPECT_NE(std::string::npos, pos); + pos += kTestMultipartBoundary.length(); + + pos = str.find(kTestData, pos); + EXPECT_NE(std::string::npos, pos); + pos += sizeof(kTestData); + + pos = str.find(kTestMultipartBoundary, pos); + EXPECT_NE(std::string::npos, pos); + pos += kTestMultipartBoundary.length(); + + pos = str.find(kTestStreamContent, pos); + EXPECT_NE(std::string::npos, pos); + pos += sizeof(kTestStreamContent); + + pos = str.find(kTestMultipartBoundary, pos); + EXPECT_NE(std::string::npos, pos); + pos += kTestMultipartBoundary.length(); + + pos = str.find(kTestMultipartBoundary, pos); + EXPECT_EQ(std::string::npos, pos); +} + +} // namespace rtc diff --git a/webrtc/base/nat_unittest.cc b/webrtc/base/nat_unittest.cc new file mode 100644 index 000000000..8b9d8a150 --- /dev/null +++ b/webrtc/base/nat_unittest.cc @@ -0,0 +1,345 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/natsocketfactory.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/network.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/virtualsocketserver.h" + +using namespace rtc; + +bool CheckReceive( + TestClient* client, bool should_receive, const char* buf, size_t size) { + return (should_receive) ? + client->CheckNextPacket(buf, size, 0) : + client->CheckNoPacket(); +} + +TestClient* CreateTestClient( + SocketFactory* factory, const SocketAddress& local_addr) { + AsyncUDPSocket* socket = AsyncUDPSocket::Create(factory, local_addr); + return new TestClient(socket); +} + +// Tests that when sending from internal_addr to external_addrs through the +// NAT type specified by nat_type, all external addrs receive the sent packet +// and, if exp_same is true, all use the same mapped-address on the NAT. +void TestSend( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4], + NATType nat_type, bool exp_same) { + Thread th_int(internal); + Thread th_ext(external); + + SocketAddress server_addr = internal_addr; + server_addr.SetPort(0); // Auto-select a port + NATServer* nat = new NATServer( + nat_type, internal, server_addr, external, external_addrs[0]); + NATSocketFactory* natsf = new NATSocketFactory(internal, + nat->internal_address()); + + TestClient* in = CreateTestClient(natsf, internal_addr); + TestClient* out[4]; + for (int i = 0; i < 4; i++) + out[i] = CreateTestClient(external, external_addrs[i]); + + th_int.Start(); + th_ext.Start(); + + const char* buf = "filter_test"; + size_t len = strlen(buf); + + in->SendTo(buf, len, out[0]->address()); + SocketAddress trans_addr; + EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr)); + + for (int i = 1; i < 4; i++) { + in->SendTo(buf, len, out[i]->address()); + SocketAddress trans_addr2; + EXPECT_TRUE(out[i]->CheckNextPacket(buf, len, &trans_addr2)); + bool are_same = (trans_addr == trans_addr2); + ASSERT_EQ(are_same, exp_same) << "same translated address"; + ASSERT_NE(AF_UNSPEC, trans_addr.family()); + ASSERT_NE(AF_UNSPEC, trans_addr2.family()); + } + + th_int.Stop(); + th_ext.Stop(); + + delete nat; + delete natsf; + delete in; + for (int i = 0; i < 4; i++) + delete out[i]; +} + +// Tests that when sending from external_addrs to internal_addr, the packet +// is delivered according to the specified filter_ip and filter_port rules. +void TestRecv( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4], + NATType nat_type, bool filter_ip, bool filter_port) { + Thread th_int(internal); + Thread th_ext(external); + + SocketAddress server_addr = internal_addr; + server_addr.SetPort(0); // Auto-select a port + NATServer* nat = new NATServer( + nat_type, internal, server_addr, external, external_addrs[0]); + NATSocketFactory* natsf = new NATSocketFactory(internal, + nat->internal_address()); + + TestClient* in = CreateTestClient(natsf, internal_addr); + TestClient* out[4]; + for (int i = 0; i < 4; i++) + out[i] = CreateTestClient(external, external_addrs[i]); + + th_int.Start(); + th_ext.Start(); + + const char* buf = "filter_test"; + size_t len = strlen(buf); + + in->SendTo(buf, len, out[0]->address()); + SocketAddress trans_addr; + EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr)); + + out[1]->SendTo(buf, len, trans_addr); + EXPECT_TRUE(CheckReceive(in, !filter_ip, buf, len)); + + out[2]->SendTo(buf, len, trans_addr); + EXPECT_TRUE(CheckReceive(in, !filter_port, buf, len)); + + out[3]->SendTo(buf, len, trans_addr); + EXPECT_TRUE(CheckReceive(in, !filter_ip && !filter_port, buf, len)); + + th_int.Stop(); + th_ext.Stop(); + + delete nat; + delete natsf; + delete in; + for (int i = 0; i < 4; i++) + delete out[i]; +} + +// Tests that NATServer allocates bindings properly. +void TestBindings( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4]) { + TestSend(internal, internal_addr, external, external_addrs, + NAT_OPEN_CONE, true); + TestSend(internal, internal_addr, external, external_addrs, + NAT_ADDR_RESTRICTED, true); + TestSend(internal, internal_addr, external, external_addrs, + NAT_PORT_RESTRICTED, true); + TestSend(internal, internal_addr, external, external_addrs, + NAT_SYMMETRIC, false); +} + +// Tests that NATServer filters packets properly. +void TestFilters( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4]) { + TestRecv(internal, internal_addr, external, external_addrs, + NAT_OPEN_CONE, false, false); + TestRecv(internal, internal_addr, external, external_addrs, + NAT_ADDR_RESTRICTED, true, false); + TestRecv(internal, internal_addr, external, external_addrs, + NAT_PORT_RESTRICTED, true, true); + TestRecv(internal, internal_addr, external, external_addrs, + NAT_SYMMETRIC, true, true); +} + +bool TestConnectivity(const SocketAddress& src, const IPAddress& dst) { + // The physical NAT tests require connectivity to the selected ip from the + // internal address used for the NAT. Things like firewalls can break that, so + // check to see if it's worth even trying with this ip. + scoped_ptr pss(new PhysicalSocketServer()); + scoped_ptr client(pss->CreateAsyncSocket(src.family(), + SOCK_DGRAM)); + scoped_ptr server(pss->CreateAsyncSocket(src.family(), + SOCK_DGRAM)); + if (client->Bind(SocketAddress(src.ipaddr(), 0)) != 0 || + server->Bind(SocketAddress(dst, 0)) != 0) { + return false; + } + const char* buf = "hello other socket"; + size_t len = strlen(buf); + int sent = client->SendTo(buf, len, server->GetLocalAddress()); + SocketAddress addr; + const size_t kRecvBufSize = 64; + char recvbuf[kRecvBufSize]; + Thread::Current()->SleepMs(100); + int received = server->RecvFrom(recvbuf, kRecvBufSize, &addr); + return received == sent && ::memcmp(buf, recvbuf, len) == 0; +} + +void TestPhysicalInternal(const SocketAddress& int_addr) { + BasicNetworkManager network_manager; + network_manager.set_ipv6_enabled(true); + network_manager.StartUpdating(); + // Process pending messages so the network list is updated. + Thread::Current()->ProcessMessages(0); + + std::vector networks; + network_manager.GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_WARNING) << "Not enough network adapters for test."; + return; + } + + SocketAddress ext_addr1(int_addr); + SocketAddress ext_addr2; + // Find an available IP with matching family. The test breaks if int_addr + // can't talk to ip, so check for connectivity as well. + for (std::vector::iterator it = networks.begin(); + it != networks.end(); ++it) { + const IPAddress& ip = (*it)->ip(); + if (ip.family() == int_addr.family() && TestConnectivity(int_addr, ip)) { + ext_addr2.SetIP(ip); + break; + } + } + if (ext_addr2.IsNil()) { + LOG(LS_WARNING) << "No available IP of same family as " << int_addr; + return; + } + + LOG(LS_INFO) << "selected ip " << ext_addr2.ipaddr(); + + SocketAddress ext_addrs[4] = { + SocketAddress(ext_addr1), + SocketAddress(ext_addr2), + SocketAddress(ext_addr1), + SocketAddress(ext_addr2) + }; + + scoped_ptr int_pss(new PhysicalSocketServer()); + scoped_ptr ext_pss(new PhysicalSocketServer()); + + TestBindings(int_pss.get(), int_addr, ext_pss.get(), ext_addrs); + TestFilters(int_pss.get(), int_addr, ext_pss.get(), ext_addrs); +} + +TEST(NatTest, TestPhysicalIPv4) { + TestPhysicalInternal(SocketAddress("127.0.0.1", 0)); +} + +TEST(NatTest, TestPhysicalIPv6) { + if (HasIPv6Enabled()) { + TestPhysicalInternal(SocketAddress("::1", 0)); + } else { + LOG(LS_WARNING) << "No IPv6, skipping"; + } +} + +class TestVirtualSocketServer : public VirtualSocketServer { + public: + explicit TestVirtualSocketServer(SocketServer* ss) + : VirtualSocketServer(ss), + ss_(ss) {} + // Expose this publicly + IPAddress GetNextIP(int af) { return VirtualSocketServer::GetNextIP(af); } + + private: + scoped_ptr ss_; +}; + +void TestVirtualInternal(int family) { + scoped_ptr int_vss(new TestVirtualSocketServer( + new PhysicalSocketServer())); + scoped_ptr ext_vss(new TestVirtualSocketServer( + new PhysicalSocketServer())); + + SocketAddress int_addr; + SocketAddress ext_addrs[4]; + int_addr.SetIP(int_vss->GetNextIP(family)); + ext_addrs[0].SetIP(ext_vss->GetNextIP(int_addr.family())); + ext_addrs[1].SetIP(ext_vss->GetNextIP(int_addr.family())); + ext_addrs[2].SetIP(ext_addrs[0].ipaddr()); + ext_addrs[3].SetIP(ext_addrs[1].ipaddr()); + + TestBindings(int_vss.get(), int_addr, ext_vss.get(), ext_addrs); + TestFilters(int_vss.get(), int_addr, ext_vss.get(), ext_addrs); +} + +TEST(NatTest, TestVirtualIPv4) { + TestVirtualInternal(AF_INET); +} + +TEST(NatTest, TestVirtualIPv6) { + if (HasIPv6Enabled()) { + TestVirtualInternal(AF_INET6); + } else { + LOG(LS_WARNING) << "No IPv6, skipping"; + } +} + +// TODO: Finish this test +class NatTcpTest : public testing::Test, public sigslot::has_slots<> { + public: + NatTcpTest() : connected_(false) {} + virtual void SetUp() { + int_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer()); + ext_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer()); + nat_ = new NATServer(NAT_OPEN_CONE, int_vss_, SocketAddress(), + ext_vss_, SocketAddress()); + natsf_ = new NATSocketFactory(int_vss_, nat_->internal_address()); + } + void OnConnectEvent(AsyncSocket* socket) { + connected_ = true; + } + void OnAcceptEvent(AsyncSocket* socket) { + accepted_ = server_->Accept(NULL); + } + void OnCloseEvent(AsyncSocket* socket, int error) { + } + void ConnectEvents() { + server_->SignalReadEvent.connect(this, &NatTcpTest::OnAcceptEvent); + client_->SignalConnectEvent.connect(this, &NatTcpTest::OnConnectEvent); + } + TestVirtualSocketServer* int_vss_; + TestVirtualSocketServer* ext_vss_; + NATServer* nat_; + NATSocketFactory* natsf_; + AsyncSocket* client_; + AsyncSocket* server_; + AsyncSocket* accepted_; + bool connected_; +}; + +TEST_F(NatTcpTest, DISABLED_TestConnectOut) { + server_ = ext_vss_->CreateAsyncSocket(SOCK_STREAM); + server_->Bind(SocketAddress()); + server_->Listen(5); + + client_ = int_vss_->CreateAsyncSocket(SOCK_STREAM); + EXPECT_GE(0, client_->Bind(SocketAddress())); + EXPECT_GE(0, client_->Connect(server_->GetLocalAddress())); + + + ConnectEvents(); + + EXPECT_TRUE_WAIT(connected_, 1000); + EXPECT_EQ(client_->GetRemoteAddress(), server_->GetLocalAddress()); + EXPECT_EQ(client_->GetRemoteAddress(), accepted_->GetLocalAddress()); + EXPECT_EQ(client_->GetLocalAddress(), accepted_->GetRemoteAddress()); + + client_->Close(); +} +//#endif diff --git a/webrtc/base/natserver.cc b/webrtc/base/natserver.cc new file mode 100644 index 000000000..0ce04d70b --- /dev/null +++ b/webrtc/base/natserver.cc @@ -0,0 +1,186 @@ +/* + * Copyright 2004 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 "webrtc/base/natsocketfactory.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +RouteCmp::RouteCmp(NAT* nat) : symmetric(nat->IsSymmetric()) { +} + +size_t RouteCmp::operator()(const SocketAddressPair& r) const { + size_t h = r.source().Hash(); + if (symmetric) + h ^= r.destination().Hash(); + return h; +} + +bool RouteCmp::operator()( + const SocketAddressPair& r1, const SocketAddressPair& r2) const { + if (r1.source() < r2.source()) + return true; + if (r2.source() < r1.source()) + return false; + if (symmetric && (r1.destination() < r2.destination())) + return true; + if (symmetric && (r2.destination() < r1.destination())) + return false; + return false; +} + +AddrCmp::AddrCmp(NAT* nat) + : use_ip(nat->FiltersIP()), use_port(nat->FiltersPort()) { +} + +size_t AddrCmp::operator()(const SocketAddress& a) const { + size_t h = 0; + if (use_ip) + h ^= HashIP(a.ipaddr()); + if (use_port) + h ^= a.port() | (a.port() << 16); + return h; +} + +bool AddrCmp::operator()( + const SocketAddress& a1, const SocketAddress& a2) const { + if (use_ip && (a1.ipaddr() < a2.ipaddr())) + return true; + if (use_ip && (a2.ipaddr() < a1.ipaddr())) + return false; + if (use_port && (a1.port() < a2.port())) + return true; + if (use_port && (a2.port() < a1.port())) + return false; + return false; +} + +NATServer::NATServer( + NATType type, SocketFactory* internal, const SocketAddress& internal_addr, + SocketFactory* external, const SocketAddress& external_ip) + : external_(external), external_ip_(external_ip.ipaddr(), 0) { + nat_ = NAT::Create(type); + + server_socket_ = AsyncUDPSocket::Create(internal, internal_addr); + server_socket_->SignalReadPacket.connect(this, &NATServer::OnInternalPacket); + + int_map_ = new InternalMap(RouteCmp(nat_)); + ext_map_ = new ExternalMap(); +} + +NATServer::~NATServer() { + for (InternalMap::iterator iter = int_map_->begin(); + iter != int_map_->end(); + iter++) + delete iter->second; + + delete nat_; + delete server_socket_; + delete int_map_; + delete ext_map_; +} + +void NATServer::OnInternalPacket( + AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& addr, const PacketTime& packet_time) { + + // Read the intended destination from the wire. + SocketAddress dest_addr; + size_t length = UnpackAddressFromNAT(buf, size, &dest_addr); + + // Find the translation for these addresses (allocating one if necessary). + SocketAddressPair route(addr, dest_addr); + InternalMap::iterator iter = int_map_->find(route); + if (iter == int_map_->end()) { + Translate(route); + iter = int_map_->find(route); + } + ASSERT(iter != int_map_->end()); + + // Allow the destination to send packets back to the source. + iter->second->WhitelistInsert(dest_addr); + + // Send the packet to its intended destination. + rtc::PacketOptions options; + iter->second->socket->SendTo(buf + length, size - length, dest_addr, options); +} + +void NATServer::OnExternalPacket( + AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& remote_addr, const PacketTime& packet_time) { + + SocketAddress local_addr = socket->GetLocalAddress(); + + // Find the translation for this addresses. + ExternalMap::iterator iter = ext_map_->find(local_addr); + ASSERT(iter != ext_map_->end()); + + // Allow the NAT to reject this packet. + if (ShouldFilterOut(iter->second, remote_addr)) { + LOG(LS_INFO) << "Packet from " << remote_addr.ToSensitiveString() + << " was filtered out by the NAT."; + return; + } + + // Forward this packet to the internal address. + // First prepend the address in a quasi-STUN format. + scoped_ptr real_buf(new char[size + kNATEncodedIPv6AddressSize]); + size_t addrlength = PackAddressForNAT(real_buf.get(), + size + kNATEncodedIPv6AddressSize, + remote_addr); + // Copy the data part after the address. + rtc::PacketOptions options; + memcpy(real_buf.get() + addrlength, buf, size); + server_socket_->SendTo(real_buf.get(), size + addrlength, + iter->second->route.source(), options); +} + +void NATServer::Translate(const SocketAddressPair& route) { + AsyncUDPSocket* socket = AsyncUDPSocket::Create(external_, external_ip_); + + if (!socket) { + LOG(LS_ERROR) << "Couldn't find a free port!"; + return; + } + + TransEntry* entry = new TransEntry(route, socket, nat_); + (*int_map_)[route] = entry; + (*ext_map_)[socket->GetLocalAddress()] = entry; + socket->SignalReadPacket.connect(this, &NATServer::OnExternalPacket); +} + +bool NATServer::ShouldFilterOut(TransEntry* entry, + const SocketAddress& ext_addr) { + return entry->WhitelistContains(ext_addr); +} + +NATServer::TransEntry::TransEntry( + const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat) + : route(r), socket(s) { + whitelist = new AddressSet(AddrCmp(nat)); +} + +NATServer::TransEntry::~TransEntry() { + delete whitelist; + delete socket; +} + +void NATServer::TransEntry::WhitelistInsert(const SocketAddress& addr) { + CritScope cs(&crit_); + whitelist->insert(addr); +} + +bool NATServer::TransEntry::WhitelistContains(const SocketAddress& ext_addr) { + CritScope cs(&crit_); + return whitelist->find(ext_addr) == whitelist->end(); +} + +} // namespace rtc diff --git a/webrtc/base/natserver.h b/webrtc/base/natserver.h new file mode 100644 index 000000000..1db77dacf --- /dev/null +++ b/webrtc/base/natserver.h @@ -0,0 +1,110 @@ +/* + * Copyright 2004 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_BASE_NATSERVER_H_ +#define WEBRTC_BASE_NATSERVER_H_ + +#include +#include + +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/socketaddresspair.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/socketfactory.h" +#include "webrtc/base/nattypes.h" + +namespace rtc { + +// Change how routes (socketaddress pairs) are compared based on the type of +// NAT. The NAT server maintains a hashtable of the routes that it knows +// about. So these affect which routes are treated the same. +struct RouteCmp { + explicit RouteCmp(NAT* nat); + size_t operator()(const SocketAddressPair& r) const; + bool operator()( + const SocketAddressPair& r1, const SocketAddressPair& r2) const; + + bool symmetric; +}; + +// Changes how addresses are compared based on the filtering rules of the NAT. +struct AddrCmp { + explicit AddrCmp(NAT* nat); + size_t operator()(const SocketAddress& r) const; + bool operator()(const SocketAddress& r1, const SocketAddress& r2) const; + + bool use_ip; + bool use_port; +}; + +// Implements the NAT device. It listens for packets on the internal network, +// translates them, and sends them out over the external network. + +const int NAT_SERVER_PORT = 4237; + +class NATServer : public sigslot::has_slots<> { + public: + NATServer( + NATType type, SocketFactory* internal, const SocketAddress& internal_addr, + SocketFactory* external, const SocketAddress& external_ip); + ~NATServer(); + + SocketAddress internal_address() const { + return server_socket_->GetLocalAddress(); + } + + // Packets received on one of the networks. + void OnInternalPacket(AsyncPacketSocket* socket, const char* buf, + size_t size, const SocketAddress& addr, + const PacketTime& packet_time); + void OnExternalPacket(AsyncPacketSocket* socket, const char* buf, + size_t size, const SocketAddress& remote_addr, + const PacketTime& packet_time); + + private: + typedef std::set AddressSet; + + /* Records a translation and the associated external socket. */ + struct TransEntry { + TransEntry(const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat); + ~TransEntry(); + + void WhitelistInsert(const SocketAddress& addr); + bool WhitelistContains(const SocketAddress& ext_addr); + + SocketAddressPair route; + AsyncUDPSocket* socket; + AddressSet* whitelist; + CriticalSection crit_; + }; + + typedef std::map InternalMap; + typedef std::map ExternalMap; + + /* Creates a new entry that translates the given route. */ + void Translate(const SocketAddressPair& route); + + /* Determines whether the NAT would filter out a packet from this address. */ + bool ShouldFilterOut(TransEntry* entry, const SocketAddress& ext_addr); + + NAT* nat_; + SocketFactory* internal_; + SocketFactory* external_; + SocketAddress external_ip_; + AsyncUDPSocket* server_socket_; + AsyncSocket* tcp_server_socket_; + InternalMap* int_map_; + ExternalMap* ext_map_; + DISALLOW_EVIL_CONSTRUCTORS(NATServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NATSERVER_H_ diff --git a/webrtc/base/natsocketfactory.cc b/webrtc/base/natsocketfactory.cc new file mode 100644 index 000000000..b5ae67b25 --- /dev/null +++ b/webrtc/base/natsocketfactory.cc @@ -0,0 +1,487 @@ +/* + * Copyright 2004 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 "webrtc/base/natsocketfactory.h" + +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/virtualsocketserver.h" + +namespace rtc { + +// Packs the given socketaddress into the buffer in buf, in the quasi-STUN +// format that the natserver uses. +// Returns 0 if an invalid address is passed. +size_t PackAddressForNAT(char* buf, size_t buf_size, + const SocketAddress& remote_addr) { + const IPAddress& ip = remote_addr.ipaddr(); + int family = ip.family(); + buf[0] = 0; + buf[1] = family; + // Writes the port. + *(reinterpret_cast(&buf[2])) = HostToNetwork16(remote_addr.port()); + if (family == AF_INET) { + ASSERT(buf_size >= kNATEncodedIPv4AddressSize); + in_addr v4addr = ip.ipv4_address(); + memcpy(&buf[4], &v4addr, kNATEncodedIPv4AddressSize - 4); + return kNATEncodedIPv4AddressSize; + } else if (family == AF_INET6) { + ASSERT(buf_size >= kNATEncodedIPv6AddressSize); + in6_addr v6addr = ip.ipv6_address(); + memcpy(&buf[4], &v6addr, kNATEncodedIPv6AddressSize - 4); + return kNATEncodedIPv6AddressSize; + } + return 0U; +} + +// Decodes the remote address from a packet that has been encoded with the nat's +// quasi-STUN format. Returns the length of the address (i.e., the offset into +// data where the original packet starts). +size_t UnpackAddressFromNAT(const char* buf, size_t buf_size, + SocketAddress* remote_addr) { + ASSERT(buf_size >= 8); + ASSERT(buf[0] == 0); + int family = buf[1]; + uint16 port = NetworkToHost16(*(reinterpret_cast(&buf[2]))); + if (family == AF_INET) { + const in_addr* v4addr = reinterpret_cast(&buf[4]); + *remote_addr = SocketAddress(IPAddress(*v4addr), port); + return kNATEncodedIPv4AddressSize; + } else if (family == AF_INET6) { + ASSERT(buf_size >= 20); + const in6_addr* v6addr = reinterpret_cast(&buf[4]); + *remote_addr = SocketAddress(IPAddress(*v6addr), port); + return kNATEncodedIPv6AddressSize; + } + return 0U; +} + + +// NATSocket +class NATSocket : public AsyncSocket, public sigslot::has_slots<> { + public: + explicit NATSocket(NATInternalSocketFactory* sf, int family, int type) + : sf_(sf), family_(family), type_(type), connected_(false), + socket_(NULL), buf_(NULL), size_(0) { + } + + virtual ~NATSocket() { + delete socket_; + delete[] buf_; + } + + virtual SocketAddress GetLocalAddress() const { + return (socket_) ? socket_->GetLocalAddress() : SocketAddress(); + } + + virtual SocketAddress GetRemoteAddress() const { + return remote_addr_; // will be NIL if not connected + } + + virtual int Bind(const SocketAddress& addr) { + if (socket_) { // already bound, bubble up error + return -1; + } + + int result; + socket_ = sf_->CreateInternalSocket(family_, type_, addr, &server_addr_); + result = (socket_) ? socket_->Bind(addr) : -1; + if (result >= 0) { + socket_->SignalConnectEvent.connect(this, &NATSocket::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &NATSocket::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &NATSocket::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &NATSocket::OnCloseEvent); + } else { + server_addr_.Clear(); + delete socket_; + socket_ = NULL; + } + + return result; + } + + virtual int Connect(const SocketAddress& addr) { + if (!socket_) { // socket must be bound, for now + return -1; + } + + int result = 0; + if (type_ == SOCK_STREAM) { + result = socket_->Connect(server_addr_.IsNil() ? addr : server_addr_); + } else { + connected_ = true; + } + + if (result >= 0) { + remote_addr_ = addr; + } + + return result; + } + + virtual int Send(const void* data, size_t size) { + ASSERT(connected_); + return SendTo(data, size, remote_addr_); + } + + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr) { + ASSERT(!connected_ || addr == remote_addr_); + if (server_addr_.IsNil() || type_ == SOCK_STREAM) { + return socket_->SendTo(data, size, addr); + } + // This array will be too large for IPv4 packets, but only by 12 bytes. + scoped_ptr buf(new char[size + kNATEncodedIPv6AddressSize]); + size_t addrlength = PackAddressForNAT(buf.get(), + size + kNATEncodedIPv6AddressSize, + addr); + size_t encoded_size = size + addrlength; + memcpy(buf.get() + addrlength, data, size); + int result = socket_->SendTo(buf.get(), encoded_size, server_addr_); + if (result >= 0) { + ASSERT(result == static_cast(encoded_size)); + result = result - static_cast(addrlength); + } + return result; + } + + virtual int Recv(void* data, size_t size) { + SocketAddress addr; + return RecvFrom(data, size, &addr); + } + + virtual int RecvFrom(void* data, size_t size, SocketAddress *out_addr) { + if (server_addr_.IsNil() || type_ == SOCK_STREAM) { + return socket_->RecvFrom(data, size, out_addr); + } + // Make sure we have enough room to read the requested amount plus the + // largest possible header address. + SocketAddress remote_addr; + Grow(size + kNATEncodedIPv6AddressSize); + + // Read the packet from the socket. + int result = socket_->RecvFrom(buf_, size_, &remote_addr); + if (result >= 0) { + ASSERT(remote_addr == server_addr_); + + // TODO: we need better framing so we know how many bytes we can + // return before we need to read the next address. For UDP, this will be + // fine as long as the reader always reads everything in the packet. + ASSERT((size_t)result < size_); + + // Decode the wire packet into the actual results. + SocketAddress real_remote_addr; + size_t addrlength = + UnpackAddressFromNAT(buf_, result, &real_remote_addr); + memcpy(data, buf_ + addrlength, result - addrlength); + + // Make sure this packet should be delivered before returning it. + if (!connected_ || (real_remote_addr == remote_addr_)) { + if (out_addr) + *out_addr = real_remote_addr; + result = result - static_cast(addrlength); + } else { + LOG(LS_ERROR) << "Dropping packet from unknown remote address: " + << real_remote_addr.ToString(); + result = 0; // Tell the caller we didn't read anything + } + } + + return result; + } + + virtual int Close() { + int result = 0; + if (socket_) { + result = socket_->Close(); + if (result >= 0) { + connected_ = false; + remote_addr_ = SocketAddress(); + delete socket_; + socket_ = NULL; + } + } + return result; + } + + virtual int Listen(int backlog) { + return socket_->Listen(backlog); + } + virtual AsyncSocket* Accept(SocketAddress *paddr) { + return socket_->Accept(paddr); + } + virtual int GetError() const { + return socket_->GetError(); + } + virtual void SetError(int error) { + socket_->SetError(error); + } + virtual ConnState GetState() const { + return connected_ ? CS_CONNECTED : CS_CLOSED; + } + virtual int EstimateMTU(uint16* mtu) { + return socket_->EstimateMTU(mtu); + } + virtual int GetOption(Option opt, int* value) { + return socket_->GetOption(opt, value); + } + virtual int SetOption(Option opt, int value) { + return socket_->SetOption(opt, value); + } + + void OnConnectEvent(AsyncSocket* socket) { + // If we're NATed, we need to send a request with the real addr to use. + ASSERT(socket == socket_); + if (server_addr_.IsNil()) { + connected_ = true; + SignalConnectEvent(this); + } else { + SendConnectRequest(); + } + } + void OnReadEvent(AsyncSocket* socket) { + // If we're NATed, we need to process the connect reply. + ASSERT(socket == socket_); + if (type_ == SOCK_STREAM && !server_addr_.IsNil() && !connected_) { + HandleConnectReply(); + } else { + SignalReadEvent(this); + } + } + void OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalWriteEvent(this); + } + void OnCloseEvent(AsyncSocket* socket, int error) { + ASSERT(socket == socket_); + SignalCloseEvent(this, error); + } + + private: + // Makes sure the buffer is at least the given size. + void Grow(size_t new_size) { + if (size_ < new_size) { + delete[] buf_; + size_ = new_size; + buf_ = new char[size_]; + } + } + + // Sends the destination address to the server to tell it to connect. + void SendConnectRequest() { + char buf[256]; + size_t length = PackAddressForNAT(buf, ARRAY_SIZE(buf), remote_addr_); + socket_->Send(buf, length); + } + + // Handles the byte sent back from the server and fires the appropriate event. + void HandleConnectReply() { + char code; + socket_->Recv(&code, sizeof(code)); + if (code == 0) { + SignalConnectEvent(this); + } else { + Close(); + SignalCloseEvent(this, code); + } + } + + NATInternalSocketFactory* sf_; + int family_; + int type_; + bool connected_; + SocketAddress remote_addr_; + SocketAddress server_addr_; // address of the NAT server + AsyncSocket* socket_; + char* buf_; + size_t size_; +}; + +// NATSocketFactory +NATSocketFactory::NATSocketFactory(SocketFactory* factory, + const SocketAddress& nat_addr) + : factory_(factory), nat_addr_(nat_addr) { +} + +Socket* NATSocketFactory::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* NATSocketFactory::CreateSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketFactory::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* NATSocketFactory::CreateAsyncSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketFactory::CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr) { + *nat_addr = nat_addr_; + return factory_->CreateAsyncSocket(family, type); +} + +// NATSocketServer +NATSocketServer::NATSocketServer(SocketServer* server) + : server_(server), msg_queue_(NULL) { +} + +NATSocketServer::Translator* NATSocketServer::GetTranslator( + const SocketAddress& ext_ip) { + return nats_.Get(ext_ip); +} + +NATSocketServer::Translator* NATSocketServer::AddTranslator( + const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) { + // Fail if a translator already exists with this extternal address. + if (nats_.Get(ext_ip)) + return NULL; + + return nats_.Add(ext_ip, new Translator(this, type, int_ip, server_, ext_ip)); +} + +void NATSocketServer::RemoveTranslator( + const SocketAddress& ext_ip) { + nats_.Remove(ext_ip); +} + +Socket* NATSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* NATSocketServer::CreateSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* NATSocketServer::CreateAsyncSocket(int family, int type) { + return new NATSocket(this, family, type); +} + +AsyncSocket* NATSocketServer::CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr) { + AsyncSocket* socket = NULL; + Translator* nat = nats_.FindClient(local_addr); + if (nat) { + socket = nat->internal_factory()->CreateAsyncSocket(family, type); + *nat_addr = (type == SOCK_STREAM) ? + nat->internal_tcp_address() : nat->internal_address(); + } else { + socket = server_->CreateAsyncSocket(family, type); + } + return socket; +} + +// NATSocketServer::Translator +NATSocketServer::Translator::Translator( + NATSocketServer* server, NATType type, const SocketAddress& int_ip, + SocketFactory* ext_factory, const SocketAddress& ext_ip) + : server_(server) { + // Create a new private network, and a NATServer running on the private + // network that bridges to the external network. Also tell the private + // network to use the same message queue as us. + VirtualSocketServer* internal_server = new VirtualSocketServer(server_); + internal_server->SetMessageQueue(server_->queue()); + internal_factory_.reset(internal_server); + nat_server_.reset(new NATServer(type, internal_server, int_ip, + ext_factory, ext_ip)); +} + + +NATSocketServer::Translator* NATSocketServer::Translator::GetTranslator( + const SocketAddress& ext_ip) { + return nats_.Get(ext_ip); +} + +NATSocketServer::Translator* NATSocketServer::Translator::AddTranslator( + const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) { + // Fail if a translator already exists with this extternal address. + if (nats_.Get(ext_ip)) + return NULL; + + AddClient(ext_ip); + return nats_.Add(ext_ip, + new Translator(server_, type, int_ip, server_, ext_ip)); +} +void NATSocketServer::Translator::RemoveTranslator( + const SocketAddress& ext_ip) { + nats_.Remove(ext_ip); + RemoveClient(ext_ip); +} + +bool NATSocketServer::Translator::AddClient( + const SocketAddress& int_ip) { + // Fail if a client already exists with this internal address. + if (clients_.find(int_ip) != clients_.end()) + return false; + + clients_.insert(int_ip); + return true; +} + +void NATSocketServer::Translator::RemoveClient( + const SocketAddress& int_ip) { + std::set::iterator it = clients_.find(int_ip); + if (it != clients_.end()) { + clients_.erase(it); + } +} + +NATSocketServer::Translator* NATSocketServer::Translator::FindClient( + const SocketAddress& int_ip) { + // See if we have the requested IP, or any of our children do. + return (clients_.find(int_ip) != clients_.end()) ? + this : nats_.FindClient(int_ip); +} + +// NATSocketServer::TranslatorMap +NATSocketServer::TranslatorMap::~TranslatorMap() { + for (TranslatorMap::iterator it = begin(); it != end(); ++it) { + delete it->second; + } +} + +NATSocketServer::Translator* NATSocketServer::TranslatorMap::Get( + const SocketAddress& ext_ip) { + TranslatorMap::iterator it = find(ext_ip); + return (it != end()) ? it->second : NULL; +} + +NATSocketServer::Translator* NATSocketServer::TranslatorMap::Add( + const SocketAddress& ext_ip, Translator* nat) { + (*this)[ext_ip] = nat; + return nat; +} + +void NATSocketServer::TranslatorMap::Remove( + const SocketAddress& ext_ip) { + TranslatorMap::iterator it = find(ext_ip); + if (it != end()) { + delete it->second; + erase(it); + } +} + +NATSocketServer::Translator* NATSocketServer::TranslatorMap::FindClient( + const SocketAddress& int_ip) { + Translator* nat = NULL; + for (TranslatorMap::iterator it = begin(); it != end() && !nat; ++it) { + nat = it->second->FindClient(int_ip); + } + return nat; +} + +} // namespace rtc diff --git a/webrtc/base/natsocketfactory.h b/webrtc/base/natsocketfactory.h new file mode 100644 index 000000000..6a8e20fe1 --- /dev/null +++ b/webrtc/base/natsocketfactory.h @@ -0,0 +1,166 @@ +/* + * Copyright 2004 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_BASE_NATSOCKETFACTORY_H_ +#define WEBRTC_BASE_NATSOCKETFACTORY_H_ + +#include +#include +#include + +#include "webrtc/base/natserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/socketserver.h" + +namespace rtc { + +const size_t kNATEncodedIPv4AddressSize = 8U; +const size_t kNATEncodedIPv6AddressSize = 20U; + +// Used by the NAT socket implementation. +class NATInternalSocketFactory { + public: + virtual ~NATInternalSocketFactory() {} + virtual AsyncSocket* CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr) = 0; +}; + +// Creates sockets that will send all traffic through a NAT, using an existing +// NATServer instance running at nat_addr. The actual data is sent using sockets +// from a socket factory, given to the constructor. +class NATSocketFactory : public SocketFactory, public NATInternalSocketFactory { + public: + NATSocketFactory(SocketFactory* factory, const SocketAddress& nat_addr); + + // SocketFactory implementation + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + // NATInternalSocketFactory implementation + virtual AsyncSocket* CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr); + + private: + SocketFactory* factory_; + SocketAddress nat_addr_; + DISALLOW_EVIL_CONSTRUCTORS(NATSocketFactory); +}; + +// Creates sockets that will send traffic through a NAT depending on what +// address they bind to. This can be used to simulate a client on a NAT sending +// to a client that is not behind a NAT. +// Note that the internal addresses of clients must be unique. This is because +// there is only one socketserver per thread, and the Bind() address is used to +// figure out which NAT (if any) the socket should talk to. +// +// Example with 3 NATs (2 cascaded), and 3 clients. +// ss->AddTranslator("1.2.3.4", "192.168.0.1", NAT_ADDR_RESTRICTED); +// ss->AddTranslator("99.99.99.99", "10.0.0.1", NAT_SYMMETRIC)-> +// AddTranslator("10.0.0.2", "192.168.1.1", NAT_OPEN_CONE); +// ss->GetTranslator("1.2.3.4")->AddClient("1.2.3.4", "192.168.0.2"); +// ss->GetTranslator("99.99.99.99")->AddClient("10.0.0.3"); +// ss->GetTranslator("99.99.99.99")->GetTranslator("10.0.0.2")-> +// AddClient("192.168.1.2"); +class NATSocketServer : public SocketServer, public NATInternalSocketFactory { + public: + class Translator; + // holds a list of NATs + class TranslatorMap : private std::map { + public: + ~TranslatorMap(); + Translator* Get(const SocketAddress& ext_ip); + Translator* Add(const SocketAddress& ext_ip, Translator*); + void Remove(const SocketAddress& ext_ip); + Translator* FindClient(const SocketAddress& int_ip); + }; + + // a specific NAT + class Translator { + public: + Translator(NATSocketServer* server, NATType type, + const SocketAddress& int_addr, SocketFactory* ext_factory, + const SocketAddress& ext_addr); + + SocketFactory* internal_factory() { return internal_factory_.get(); } + SocketAddress internal_address() const { + return nat_server_->internal_address(); + } + SocketAddress internal_tcp_address() const { + return SocketAddress(); // nat_server_->internal_tcp_address(); + } + + Translator* GetTranslator(const SocketAddress& ext_ip); + Translator* AddTranslator(const SocketAddress& ext_ip, + const SocketAddress& int_ip, NATType type); + void RemoveTranslator(const SocketAddress& ext_ip); + + bool AddClient(const SocketAddress& int_ip); + void RemoveClient(const SocketAddress& int_ip); + + // Looks for the specified client in this or a child NAT. + Translator* FindClient(const SocketAddress& int_ip); + + private: + NATSocketServer* server_; + scoped_ptr internal_factory_; + scoped_ptr nat_server_; + TranslatorMap nats_; + std::set clients_; + }; + + explicit NATSocketServer(SocketServer* ss); + + SocketServer* socketserver() { return server_; } + MessageQueue* queue() { return msg_queue_; } + + Translator* GetTranslator(const SocketAddress& ext_ip); + Translator* AddTranslator(const SocketAddress& ext_ip, + const SocketAddress& int_ip, NATType type); + void RemoveTranslator(const SocketAddress& ext_ip); + + // SocketServer implementation + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual void SetMessageQueue(MessageQueue* queue) { + msg_queue_ = queue; + server_->SetMessageQueue(queue); + } + virtual bool Wait(int cms, bool process_io) { + return server_->Wait(cms, process_io); + } + virtual void WakeUp() { + server_->WakeUp(); + } + + // NATInternalSocketFactory implementation + virtual AsyncSocket* CreateInternalSocket(int family, int type, + const SocketAddress& local_addr, SocketAddress* nat_addr); + + private: + SocketServer* server_; + MessageQueue* msg_queue_; + TranslatorMap nats_; + DISALLOW_EVIL_CONSTRUCTORS(NATSocketServer); +}; + +// Free-standing NAT helper functions. +size_t PackAddressForNAT(char* buf, size_t buf_size, + const SocketAddress& remote_addr); +size_t UnpackAddressFromNAT(const char* buf, size_t buf_size, + SocketAddress* remote_addr); +} // namespace rtc + +#endif // WEBRTC_BASE_NATSOCKETFACTORY_H_ diff --git a/webrtc/base/nattypes.cc b/webrtc/base/nattypes.cc new file mode 100644 index 000000000..fedb78dc5 --- /dev/null +++ b/webrtc/base/nattypes.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2004 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 "webrtc/base/nattypes.h" + +namespace rtc { + +class SymmetricNAT : public NAT { +public: + bool IsSymmetric() { return true; } + bool FiltersIP() { return true; } + bool FiltersPort() { return true; } +}; + +class OpenConeNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return false; } + bool FiltersPort() { return false; } +}; + +class AddressRestrictedNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return true; } + bool FiltersPort() { return false; } +}; + +class PortRestrictedNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return true; } + bool FiltersPort() { return true; } +}; + +NAT* NAT::Create(NATType type) { + switch (type) { + case NAT_OPEN_CONE: return new OpenConeNAT(); + case NAT_ADDR_RESTRICTED: return new AddressRestrictedNAT(); + case NAT_PORT_RESTRICTED: return new PortRestrictedNAT(); + case NAT_SYMMETRIC: return new SymmetricNAT(); + default: assert(0); return 0; + } +} + +} // namespace rtc diff --git a/webrtc/base/nattypes.h b/webrtc/base/nattypes.h new file mode 100644 index 000000000..27e4b2f45 --- /dev/null +++ b/webrtc/base/nattypes.h @@ -0,0 +1,47 @@ +/* + * Copyright 2004 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_BASE_NATTYPE_H__ +#define WEBRTC_BASE_NATTYPE_H__ + +namespace rtc { + +/* Identifies each type of NAT that can be simulated. */ +enum NATType { + NAT_OPEN_CONE, + NAT_ADDR_RESTRICTED, + NAT_PORT_RESTRICTED, + NAT_SYMMETRIC +}; + +// Implements the rules for each specific type of NAT. +class NAT { +public: + virtual ~NAT() { } + + // Determines whether this NAT uses both source and destination address when + // checking whether a mapping already exists. + virtual bool IsSymmetric() = 0; + + // Determines whether this NAT drops packets received from a different IP + // the one last sent to. + virtual bool FiltersIP() = 0; + + // Determines whether this NAT drops packets received from a different port + // the one last sent to. + virtual bool FiltersPort() = 0; + + // Returns an implementation of the given type of NAT. + static NAT* Create(NATType type); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NATTYPE_H__ diff --git a/webrtc/base/nethelpers.cc b/webrtc/base/nethelpers.cc new file mode 100644 index 000000000..5d4802dfd --- /dev/null +++ b/webrtc/base/nethelpers.cc @@ -0,0 +1,150 @@ +/* + * Copyright 2008 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 "webrtc/base/nethelpers.h" + +#if defined(WEBRTC_WIN) +#include +#include +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/signalthread.h" + +namespace rtc { + +int ResolveHostname(const std::string& hostname, int family, + std::vector* addresses) { +#ifdef __native_client__ + ASSERT(false); + LOG(LS_WARNING) << "ResolveHostname() is not implemented for NaCl"; + return -1; +#else // __native_client__ + if (!addresses) { + return -1; + } + addresses->clear(); + struct addrinfo* result = NULL; + struct addrinfo hints = {0}; + // TODO(djw): For now this is IPv4 only so existing users remain unaffected. + hints.ai_family = AF_INET; + hints.ai_flags = AI_ADDRCONFIG; + int ret = getaddrinfo(hostname.c_str(), NULL, &hints, &result); + if (ret != 0) { + return ret; + } + struct addrinfo* cursor = result; + for (; cursor; cursor = cursor->ai_next) { + if (family == AF_UNSPEC || cursor->ai_family == family) { + IPAddress ip; + if (IPFromAddrInfo(cursor, &ip)) { + addresses->push_back(ip); + } + } + } + freeaddrinfo(result); + return 0; +#endif // !__native_client__ +} + +// AsyncResolver +AsyncResolver::AsyncResolver() : error_(-1) { +} + +void AsyncResolver::Start(const SocketAddress& addr) { + addr_ = addr; + // SignalThred Start will kickoff the resolve process. + SignalThread::Start(); +} + +bool AsyncResolver::GetResolvedAddress(int family, SocketAddress* addr) const { + if (error_ != 0 || addresses_.empty()) + return false; + + *addr = addr_; + for (size_t i = 0; i < addresses_.size(); ++i) { + if (family == addresses_[i].family()) { + addr->SetResolvedIP(addresses_[i]); + return true; + } + } + return false; +} + +void AsyncResolver::DoWork() { + error_ = ResolveHostname(addr_.hostname().c_str(), addr_.family(), + &addresses_); +} + +void AsyncResolver::OnWorkDone() { + SignalDone(this); +} + +const char* inet_ntop(int af, const void *src, char* dst, socklen_t size) { +#if defined(WEBRTC_WIN) + return win32_inet_ntop(af, src, dst, size); +#else + return ::inet_ntop(af, src, dst, size); +#endif +} + +int inet_pton(int af, const char* src, void *dst) { +#if defined(WEBRTC_WIN) + return win32_inet_pton(af, src, dst); +#else + return ::inet_pton(af, src, dst); +#endif +} + +bool HasIPv6Enabled() { +#if !defined(WEBRTC_WIN) + // We only need to check this for Windows XP (so far). + return true; +#else + if (IsWindowsVistaOrLater()) { + return true; + } + if (!IsWindowsXpOrLater()) { + return false; + } + DWORD protbuff_size = 4096; + scoped_ptr protocols; + LPWSAPROTOCOL_INFOW protocol_infos = NULL; + int requested_protocols[2] = {AF_INET6, 0}; + + int err = 0; + int ret = 0; + // Check for protocols in a do-while loop until we provide a buffer large + // enough. (WSCEnumProtocols sets protbuff_size to its desired value). + // It is extremely unlikely that this will loop more than once. + do { + protocols.reset(new char[protbuff_size]); + protocol_infos = reinterpret_cast(protocols.get()); + ret = WSCEnumProtocols(requested_protocols, protocol_infos, + &protbuff_size, &err); + } while (ret == SOCKET_ERROR && err == WSAENOBUFS); + + if (ret == SOCKET_ERROR) { + return false; + } + + // Even if ret is positive, check specifically for IPv6. + // Non-IPv6 enabled WinXP will still return a RAW protocol. + for (int i = 0; i < ret; ++i) { + if (protocol_infos[i].iAddressFamily == AF_INET6) { + return true; + } + } + return false; +#endif +} +} // namespace rtc diff --git a/webrtc/base/nethelpers.h b/webrtc/base/nethelpers.h new file mode 100644 index 000000000..d39400c2f --- /dev/null +++ b/webrtc/base/nethelpers.h @@ -0,0 +1,65 @@ +/* + * Copyright 2008 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_BASE_NETHELPERS_H_ +#define WEBRTC_BASE_NETHELPERS_H_ + +#if defined(WEBRTC_POSIX) +#include +#include +#elif WEBRTC_WIN +#include // NOLINT +#endif + +#include + +#include "webrtc/base/asyncresolverinterface.h" +#include "webrtc/base/signalthread.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +class AsyncResolverTest; + +// AsyncResolver will perform async DNS resolution, signaling the result on +// the SignalDone from AsyncResolverInterface when the operation completes. +class AsyncResolver : public SignalThread, public AsyncResolverInterface { + public: + AsyncResolver(); + virtual ~AsyncResolver() {} + + virtual void Start(const SocketAddress& addr); + virtual bool GetResolvedAddress(int family, SocketAddress* addr) const; + virtual int GetError() const { return error_; } + virtual void Destroy(bool wait) { SignalThread::Destroy(wait); } + + const std::vector& addresses() const { return addresses_; } + void set_error(int error) { error_ = error; } + + protected: + virtual void DoWork(); + virtual void OnWorkDone(); + + private: + SocketAddress addr_; + std::vector addresses_; + int error_; +}; + +// rtc namespaced wrappers for inet_ntop and inet_pton so we can avoid +// the windows-native versions of these. +const char* inet_ntop(int af, const void *src, char* dst, socklen_t size); +int inet_pton(int af, const char* src, void *dst); + +bool HasIPv6Enabled(); +} // namespace rtc + +#endif // WEBRTC_BASE_NETHELPERS_H_ diff --git a/webrtc/base/network.cc b/webrtc/base/network.cc new file mode 100644 index 000000000..d94c69eae --- /dev/null +++ b/webrtc/base/network.cc @@ -0,0 +1,658 @@ +/* + * Copyright 2004 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "webrtc/base/network.h" + +#if defined(WEBRTC_POSIX) +// linux/if.h can't be included at the same time as the posix sys/if.h, and +// it's transitively required by linux/route.h, so include that version on +// linux instead of the standard posix one. +#if defined(WEBRTC_LINUX) +#include +#include +#elif !defined(__native_client__) +#include +#endif +#include +#include +#include +#include +#include + +#if defined(WEBRTC_ANDROID) +#include "webrtc/base/ifaddrs-android.h" +#elif !defined(__native_client__) +#include +#endif + +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif + +#include + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socket.h" // includes something that makes windows happy +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/thread.h" + +namespace rtc { +namespace { + +const uint32 kUpdateNetworksMessage = 1; +const uint32 kSignalNetworksMessage = 2; + +// Fetch list of networks every two seconds. +const int kNetworksUpdateIntervalMs = 2000; + +const int kHighestNetworkPreference = 127; + +bool CompareNetworks(const Network* a, const Network* b) { + if (a->prefix_length() == b->prefix_length()) { + if (a->name() == b->name()) { + return a->prefix() < b->prefix(); + } + } + return a->name() < b->name(); +} + +bool SortNetworks(const Network* a, const Network* b) { + // Network types will be preferred above everything else while sorting + // Networks. + + // Networks are sorted first by type. + if (a->type() != b->type()) { + return a->type() < b->type(); + } + + // After type, networks are sorted by IP address precedence values + // from RFC 3484-bis + if (IPAddressPrecedence(a->ip()) != IPAddressPrecedence(b->ip())) { + return IPAddressPrecedence(a->ip()) > IPAddressPrecedence(b->ip()); + } + + // TODO(mallinath) - Add VPN and Link speed conditions while sorting. + + // Networks are sorted last by key. + return a->key() > b->key(); +} + +std::string AdapterTypeToString(AdapterType type) { + switch (type) { + case ADAPTER_TYPE_UNKNOWN: + return "Unknown"; + case ADAPTER_TYPE_ETHERNET: + return "Ethernet"; + case ADAPTER_TYPE_WIFI: + return "Wifi"; + case ADAPTER_TYPE_CELLULAR: + return "Cellular"; + case ADAPTER_TYPE_VPN: + return "VPN"; + default: + ASSERT(false); + return std::string(); + } +} + +} // namespace + +std::string MakeNetworkKey(const std::string& name, const IPAddress& prefix, + int prefix_length) { + std::ostringstream ost; + ost << name << "%" << prefix.ToString() << "/" << prefix_length; + return ost.str(); +} + +NetworkManager::NetworkManager() { +} + +NetworkManager::~NetworkManager() { +} + +NetworkManagerBase::NetworkManagerBase() : ipv6_enabled_(true) { +} + +NetworkManagerBase::~NetworkManagerBase() { + for (NetworkMap::iterator i = networks_map_.begin(); + i != networks_map_.end(); ++i) { + delete i->second; + } +} + +void NetworkManagerBase::GetNetworks(NetworkList* result) const { + *result = networks_; +} + +void NetworkManagerBase::MergeNetworkList(const NetworkList& new_networks, + bool* changed) { + // Sort the list so that we can detect when it changes. + typedef std::pair > address_list; + std::map address_map; + NetworkList list(new_networks); + NetworkList merged_list; + std::sort(list.begin(), list.end(), CompareNetworks); + + *changed = false; + + if (networks_.size() != list.size()) + *changed = true; + + // First, build a set of network-keys to the ipaddresses. + for (uint32 i = 0; i < list.size(); ++i) { + bool might_add_to_merged_list = false; + std::string key = MakeNetworkKey(list[i]->name(), + list[i]->prefix(), + list[i]->prefix_length()); + if (address_map.find(key) == address_map.end()) { + address_map[key] = address_list(list[i], std::vector()); + might_add_to_merged_list = true; + } + const std::vector& addresses = list[i]->GetIPs(); + address_list& current_list = address_map[key]; + for (std::vector::const_iterator it = addresses.begin(); + it != addresses.end(); + ++it) { + current_list.second.push_back(*it); + } + if (!might_add_to_merged_list) { + delete list[i]; + } + } + + // Next, look for existing network objects to re-use. + for (std::map::iterator it = address_map.begin(); + it != address_map.end(); + ++it) { + const std::string& key = it->first; + Network* net = it->second.first; + NetworkMap::iterator existing = networks_map_.find(key); + if (existing == networks_map_.end()) { + // This network is new. Place it in the network map. + merged_list.push_back(net); + networks_map_[key] = net; + *changed = true; + } else { + // This network exists in the map already. Reset its IP addresses. + *changed = existing->second->SetIPs(it->second.second, *changed); + merged_list.push_back(existing->second); + if (existing->second != net) { + delete net; + } + } + } + networks_ = merged_list; + + // If the network lists changes, we resort it. + if (changed) { + std::sort(networks_.begin(), networks_.end(), SortNetworks); + // Now network interfaces are sorted, we should set the preference value + // for each of the interfaces we are planning to use. + // Preference order of network interfaces might have changed from previous + // sorting due to addition of higher preference network interface. + // Since we have already sorted the network interfaces based on our + // requirements, we will just assign a preference value starting with 127, + // in decreasing order. + int pref = kHighestNetworkPreference; + for (NetworkList::const_iterator iter = networks_.begin(); + iter != networks_.end(); ++iter) { + (*iter)->set_preference(pref); + if (pref > 0) { + --pref; + } else { + LOG(LS_ERROR) << "Too many network interfaces to handle!"; + break; + } + } + } +} + +BasicNetworkManager::BasicNetworkManager() + : thread_(NULL), sent_first_update_(false), start_count_(0), + ignore_non_default_routes_(false) { +} + +BasicNetworkManager::~BasicNetworkManager() { +} + +#if defined(__native_client__) + +bool BasicNetworkManager::CreateNetworks(bool include_ignored, + NetworkList* networks) const { + ASSERT(false); + LOG(LS_WARNING) << "BasicNetworkManager doesn't work on NaCl yet"; + return false; +} + +#elif defined(WEBRTC_POSIX) +void BasicNetworkManager::ConvertIfAddrs(struct ifaddrs* interfaces, + bool include_ignored, + NetworkList* networks) const { + NetworkMap current_networks; + for (struct ifaddrs* cursor = interfaces; + cursor != NULL; cursor = cursor->ifa_next) { + IPAddress prefix; + IPAddress mask; + IPAddress ip; + int scope_id = 0; + + // Some interfaces may not have address assigned. + if (!cursor->ifa_addr || !cursor->ifa_netmask) + continue; + + switch (cursor->ifa_addr->sa_family) { + case AF_INET: { + ip = IPAddress( + reinterpret_cast(cursor->ifa_addr)->sin_addr); + mask = IPAddress( + reinterpret_cast(cursor->ifa_netmask)->sin_addr); + break; + } + case AF_INET6: { + if (ipv6_enabled()) { + ip = IPAddress( + reinterpret_cast(cursor->ifa_addr)->sin6_addr); + mask = IPAddress( + reinterpret_cast(cursor->ifa_netmask)->sin6_addr); + scope_id = + reinterpret_cast(cursor->ifa_addr)->sin6_scope_id; + break; + } else { + continue; + } + } + default: { + continue; + } + } + + int prefix_length = CountIPMaskBits(mask); + prefix = TruncateIP(ip, prefix_length); + std::string key = MakeNetworkKey(std::string(cursor->ifa_name), + prefix, prefix_length); + NetworkMap::iterator existing_network = current_networks.find(key); + if (existing_network == current_networks.end()) { + scoped_ptr network(new Network(cursor->ifa_name, + cursor->ifa_name, + prefix, + prefix_length)); + network->set_scope_id(scope_id); + network->AddIP(ip); + bool ignored = ((cursor->ifa_flags & IFF_LOOPBACK) || + IsIgnoredNetwork(*network)); + network->set_ignored(ignored); + if (include_ignored || !network->ignored()) { + networks->push_back(network.release()); + } + } else { + (*existing_network).second->AddIP(ip); + } + } +} + +bool BasicNetworkManager::CreateNetworks(bool include_ignored, + NetworkList* networks) const { + struct ifaddrs* interfaces; + int error = getifaddrs(&interfaces); + if (error != 0) { + LOG_ERR(LERROR) << "getifaddrs failed to gather interface data: " << error; + return false; + } + + ConvertIfAddrs(interfaces, include_ignored, networks); + + freeifaddrs(interfaces); + return true; +} + +#elif defined(WEBRTC_WIN) + +unsigned int GetPrefix(PIP_ADAPTER_PREFIX prefixlist, + const IPAddress& ip, IPAddress* prefix) { + IPAddress current_prefix; + IPAddress best_prefix; + unsigned int best_length = 0; + while (prefixlist) { + // Look for the longest matching prefix in the prefixlist. + if (prefixlist->Address.lpSockaddr == NULL || + prefixlist->Address.lpSockaddr->sa_family != ip.family()) { + prefixlist = prefixlist->Next; + continue; + } + switch (prefixlist->Address.lpSockaddr->sa_family) { + case AF_INET: { + sockaddr_in* v4_addr = + reinterpret_cast(prefixlist->Address.lpSockaddr); + current_prefix = IPAddress(v4_addr->sin_addr); + break; + } + case AF_INET6: { + sockaddr_in6* v6_addr = + reinterpret_cast(prefixlist->Address.lpSockaddr); + current_prefix = IPAddress(v6_addr->sin6_addr); + break; + } + default: { + prefixlist = prefixlist->Next; + continue; + } + } + if (TruncateIP(ip, prefixlist->PrefixLength) == current_prefix && + prefixlist->PrefixLength > best_length) { + best_prefix = current_prefix; + best_length = prefixlist->PrefixLength; + } + prefixlist = prefixlist->Next; + } + *prefix = best_prefix; + return best_length; +} + +bool BasicNetworkManager::CreateNetworks(bool include_ignored, + NetworkList* networks) const { + NetworkMap current_networks; + // MSDN recommends a 15KB buffer for the first try at GetAdaptersAddresses. + size_t buffer_size = 16384; + scoped_ptr adapter_info(new char[buffer_size]); + PIP_ADAPTER_ADDRESSES adapter_addrs = + reinterpret_cast(adapter_info.get()); + int adapter_flags = (GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_PREFIX); + int ret = 0; + do { + adapter_info.reset(new char[buffer_size]); + adapter_addrs = reinterpret_cast(adapter_info.get()); + ret = GetAdaptersAddresses(AF_UNSPEC, adapter_flags, + 0, adapter_addrs, + reinterpret_cast(&buffer_size)); + } while (ret == ERROR_BUFFER_OVERFLOW); + if (ret != ERROR_SUCCESS) { + return false; + } + int count = 0; + while (adapter_addrs) { + if (adapter_addrs->OperStatus == IfOperStatusUp) { + PIP_ADAPTER_UNICAST_ADDRESS address = adapter_addrs->FirstUnicastAddress; + PIP_ADAPTER_PREFIX prefixlist = adapter_addrs->FirstPrefix; + std::string name; + std::string description; +#ifdef _DEBUG + name = ToUtf8(adapter_addrs->FriendlyName, + wcslen(adapter_addrs->FriendlyName)); +#endif + description = ToUtf8(adapter_addrs->Description, + wcslen(adapter_addrs->Description)); + for (; address; address = address->Next) { +#ifndef _DEBUG + name = rtc::ToString(count); +#endif + + IPAddress ip; + int scope_id = 0; + scoped_ptr network; + switch (address->Address.lpSockaddr->sa_family) { + case AF_INET: { + sockaddr_in* v4_addr = + reinterpret_cast(address->Address.lpSockaddr); + ip = IPAddress(v4_addr->sin_addr); + break; + } + case AF_INET6: { + if (ipv6_enabled()) { + sockaddr_in6* v6_addr = + reinterpret_cast(address->Address.lpSockaddr); + scope_id = v6_addr->sin6_scope_id; + ip = IPAddress(v6_addr->sin6_addr); + break; + } else { + continue; + } + } + default: { + continue; + } + } + + IPAddress prefix; + int prefix_length = GetPrefix(prefixlist, ip, &prefix); + std::string key = MakeNetworkKey(name, prefix, prefix_length); + NetworkMap::iterator existing_network = current_networks.find(key); + if (existing_network == current_networks.end()) { + scoped_ptr network(new Network(name, + description, + prefix, + prefix_length)); + network->set_scope_id(scope_id); + network->AddIP(ip); + bool ignore = ((adapter_addrs->IfType == IF_TYPE_SOFTWARE_LOOPBACK) || + IsIgnoredNetwork(*network)); + network->set_ignored(ignore); + if (include_ignored || !network->ignored()) { + networks->push_back(network.release()); + } + } else { + (*existing_network).second->AddIP(ip); + } + } + // Count is per-adapter - all 'Networks' created from the same + // adapter need to have the same name. + ++count; + } + adapter_addrs = adapter_addrs->Next; + } + return true; +} +#endif // WEBRTC_WIN + +#if defined(WEBRTC_LINUX) +bool IsDefaultRoute(const std::string& network_name) { + FileStream fs; + if (!fs.Open("/proc/net/route", "r", NULL)) { + LOG(LS_WARNING) << "Couldn't read /proc/net/route, skipping default " + << "route check (assuming everything is a default route)."; + return true; + } else { + std::string line; + while (fs.ReadLine(&line) == SR_SUCCESS) { + char iface_name[256]; + unsigned int iface_ip, iface_gw, iface_mask, iface_flags; + if (sscanf(line.c_str(), + "%255s %8X %8X %4X %*d %*u %*d %8X", + iface_name, &iface_ip, &iface_gw, + &iface_flags, &iface_mask) == 5 && + network_name == iface_name && + iface_mask == 0 && + (iface_flags & (RTF_UP | RTF_HOST)) == RTF_UP) { + return true; + } + } + } + return false; +} +#endif + +bool BasicNetworkManager::IsIgnoredNetwork(const Network& network) const { + // Ignore networks on the explicit ignore list. + for (size_t i = 0; i < network_ignore_list_.size(); ++i) { + if (network.name() == network_ignore_list_[i]) { + return true; + } + } +#if defined(WEBRTC_POSIX) + // Filter out VMware interfaces, typically named vmnet1 and vmnet8 + if (strncmp(network.name().c_str(), "vmnet", 5) == 0 || + strncmp(network.name().c_str(), "vnic", 4) == 0) { + return true; + } +#if defined(WEBRTC_LINUX) + // Make sure this is a default route, if we're ignoring non-defaults. + if (ignore_non_default_routes_ && !IsDefaultRoute(network.name())) { + return true; + } +#endif +#elif defined(WEBRTC_WIN) + // Ignore any HOST side vmware adapters with a description like: + // VMware Virtual Ethernet Adapter for VMnet1 + // but don't ignore any GUEST side adapters with a description like: + // VMware Accelerated AMD PCNet Adapter #2 + if (strstr(network.description().c_str(), "VMnet") != NULL) { + return true; + } +#endif + + // Ignore any networks with a 0.x.y.z IP + if (network.prefix().family() == AF_INET) { + return (network.prefix().v4AddressAsHostOrderInteger() < 0x01000000); + } + return false; +} + +void BasicNetworkManager::StartUpdating() { + thread_ = Thread::Current(); + if (start_count_) { + // If network interfaces are already discovered and signal is sent, + // we should trigger network signal immediately for the new clients + // to start allocating ports. + if (sent_first_update_) + thread_->Post(this, kSignalNetworksMessage); + } else { + thread_->Post(this, kUpdateNetworksMessage); + } + ++start_count_; +} + +void BasicNetworkManager::StopUpdating() { + ASSERT(Thread::Current() == thread_); + if (!start_count_) + return; + + --start_count_; + if (!start_count_) { + thread_->Clear(this); + sent_first_update_ = false; + } +} + +void BasicNetworkManager::OnMessage(Message* msg) { + switch (msg->message_id) { + case kUpdateNetworksMessage: { + DoUpdateNetworks(); + break; + } + case kSignalNetworksMessage: { + SignalNetworksChanged(); + break; + } + default: + ASSERT(false); + } +} + +void BasicNetworkManager::DoUpdateNetworks() { + if (!start_count_) + return; + + ASSERT(Thread::Current() == thread_); + + NetworkList list; + if (!CreateNetworks(false, &list)) { + SignalError(); + } else { + bool changed; + MergeNetworkList(list, &changed); + if (changed || !sent_first_update_) { + SignalNetworksChanged(); + sent_first_update_ = true; + } + } + + thread_->PostDelayed(kNetworksUpdateIntervalMs, this, kUpdateNetworksMessage); +} + +void BasicNetworkManager::DumpNetworks(bool include_ignored) { + NetworkList list; + CreateNetworks(include_ignored, &list); + LOG(LS_INFO) << "NetworkManager detected " << list.size() << " networks:"; + for (size_t i = 0; i < list.size(); ++i) { + const Network* network = list[i]; + if (!network->ignored() || include_ignored) { + LOG(LS_INFO) << network->ToString() << ": " + << network->description() + << ((network->ignored()) ? ", Ignored" : ""); + } + } + // Release the network list created previously. + // Do this in a seperated for loop for better readability. + for (size_t i = 0; i < list.size(); ++i) { + delete list[i]; + } +} + +Network::Network(const std::string& name, const std::string& desc, + const IPAddress& prefix, int prefix_length) + : name_(name), description_(desc), prefix_(prefix), + prefix_length_(prefix_length), + key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0), + ignored_(false), type_(ADAPTER_TYPE_UNKNOWN), preference_(0) { +} + +Network::Network(const std::string& name, const std::string& desc, + const IPAddress& prefix, int prefix_length, AdapterType type) + : name_(name), description_(desc), prefix_(prefix), + prefix_length_(prefix_length), + key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0), + ignored_(false), type_(type), preference_(0) { +} + +std::string Network::ToString() const { + std::stringstream ss; + // Print out the first space-terminated token of the network desc, plus + // the IP address. + ss << "Net[" << description_.substr(0, description_.find(' ')) + << ":" << prefix_.ToSensitiveString() << "/" << prefix_length_ + << ":" << AdapterTypeToString(type_) << "]"; + return ss.str(); +} + +// Sets the addresses of this network. Returns true if the address set changed. +// Change detection is short circuited if the changed argument is true. +bool Network::SetIPs(const std::vector& ips, bool changed) { + changed = changed || ips.size() != ips_.size(); + // Detect changes with a nested loop; n-squared but we expect on the order + // of 2-3 addresses per network. + for (std::vector::const_iterator it = ips.begin(); + !changed && it != ips.end(); + ++it) { + bool found = false; + for (std::vector::iterator inner_it = ips_.begin(); + !found && inner_it != ips_.end(); + ++inner_it) { + if (*it == *inner_it) { + found = true; + } + } + changed = !found; + } + ips_ = ips; + return changed; +} + +} // namespace rtc diff --git a/webrtc/base/network.h b/webrtc/base/network.h new file mode 100644 index 000000000..855b1b74a --- /dev/null +++ b/webrtc/base/network.h @@ -0,0 +1,245 @@ +/* + * Copyright 2004 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_BASE_NETWORK_H_ +#define WEBRTC_BASE_NETWORK_H_ + +#include +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/ipaddress.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/sigslot.h" + +#if defined(WEBRTC_POSIX) +struct ifaddrs; +#endif // defined(WEBRTC_POSIX) + +namespace rtc { + +class Network; +class Thread; + +enum AdapterType { + // This enum resembles the one in Chromium net::ConnectionType. + ADAPTER_TYPE_UNKNOWN = 0, + ADAPTER_TYPE_ETHERNET = 1, + ADAPTER_TYPE_WIFI = 2, + ADAPTER_TYPE_CELLULAR = 3, + ADAPTER_TYPE_VPN = 4 +}; + +// Makes a string key for this network. Used in the network manager's maps. +// Network objects are keyed on interface name, network prefix and the +// length of that prefix. +std::string MakeNetworkKey(const std::string& name, const IPAddress& prefix, + int prefix_length); + +// Generic network manager interface. It provides list of local +// networks. +class NetworkManager { + public: + typedef std::vector NetworkList; + + NetworkManager(); + virtual ~NetworkManager(); + + // Called when network list is updated. + sigslot::signal0<> SignalNetworksChanged; + + // Indicates a failure when getting list of network interfaces. + sigslot::signal0<> SignalError; + + // Start/Stop monitoring of network interfaces + // list. SignalNetworksChanged or SignalError is emitted immidiately + // after StartUpdating() is called. After that SignalNetworksChanged + // is emitted wheneven list of networks changes. + virtual void StartUpdating() = 0; + virtual void StopUpdating() = 0; + + // Returns the current list of networks available on this machine. + // UpdateNetworks() must be called before this method is called. + // It makes sure that repeated calls return the same object for a + // given network, so that quality is tracked appropriately. Does not + // include ignored networks. + virtual void GetNetworks(NetworkList* networks) const = 0; + + // Dumps a list of networks available to LS_INFO. + virtual void DumpNetworks(bool include_ignored) {} +}; + +// Base class for NetworkManager implementations. +class NetworkManagerBase : public NetworkManager { + public: + NetworkManagerBase(); + virtual ~NetworkManagerBase(); + + virtual void GetNetworks(std::vector* networks) const; + bool ipv6_enabled() const { return ipv6_enabled_; } + void set_ipv6_enabled(bool enabled) { ipv6_enabled_ = enabled; } + + protected: + typedef std::map NetworkMap; + // Updates |networks_| with the networks listed in |list|. If + // |network_map_| already has a Network object for a network listed + // in the |list| then it is reused. Accept ownership of the Network + // objects in the |list|. |changed| will be set to true if there is + // any change in the network list. + void MergeNetworkList(const NetworkList& list, bool* changed); + + private: + friend class NetworkTest; + void DoUpdateNetworks(); + + NetworkList networks_; + NetworkMap networks_map_; + bool ipv6_enabled_; +}; + +// Basic implementation of the NetworkManager interface that gets list +// of networks using OS APIs. +class BasicNetworkManager : public NetworkManagerBase, + public MessageHandler { + public: + BasicNetworkManager(); + virtual ~BasicNetworkManager(); + + virtual void StartUpdating(); + virtual void StopUpdating(); + + // Logs the available networks. + virtual void DumpNetworks(bool include_ignored); + + // MessageHandler interface. + virtual void OnMessage(Message* msg); + bool started() { return start_count_ > 0; } + + // Sets the network ignore list, which is empty by default. Any network on + // the ignore list will be filtered from network enumeration results. + void set_network_ignore_list(const std::vector& list) { + network_ignore_list_ = list; + } +#if defined(WEBRTC_LINUX) + // Sets the flag for ignoring non-default routes. + void set_ignore_non_default_routes(bool value) { + ignore_non_default_routes_ = true; + } +#endif + + protected: +#if defined(WEBRTC_POSIX) + // Separated from CreateNetworks for tests. + void ConvertIfAddrs(ifaddrs* interfaces, + bool include_ignored, + NetworkList* networks) const; +#endif // defined(WEBRTC_POSIX) + + // Creates a network object for each network available on the machine. + bool CreateNetworks(bool include_ignored, NetworkList* networks) const; + + // Determines if a network should be ignored. + bool IsIgnoredNetwork(const Network& network) const; + + private: + friend class NetworkTest; + + void DoUpdateNetworks(); + + Thread* thread_; + bool sent_first_update_; + int start_count_; + std::vector network_ignore_list_; + bool ignore_non_default_routes_; +}; + +// Represents a Unix-type network interface, with a name and single address. +class Network { + public: + Network(const std::string& name, const std::string& description, + const IPAddress& prefix, int prefix_length); + + Network(const std::string& name, const std::string& description, + const IPAddress& prefix, int prefix_length, AdapterType type); + + // Returns the name of the interface this network is associated wtih. + const std::string& name() const { return name_; } + + // Returns the OS-assigned name for this network. This is useful for + // debugging but should not be sent over the wire (for privacy reasons). + const std::string& description() const { return description_; } + + // Returns the prefix for this network. + const IPAddress& prefix() const { return prefix_; } + // Returns the length, in bits, of this network's prefix. + int prefix_length() const { return prefix_length_; } + + // |key_| has unique value per network interface. Used in sorting network + // interfaces. Key is derived from interface name and it's prefix. + std::string key() const { return key_; } + + // Returns the Network's current idea of the 'best' IP it has. + // 'Best' currently means the first one added. + // TODO: We should be preferring temporary addresses. + // Returns an unset IP if this network has no active addresses. + IPAddress ip() const { + if (ips_.size() == 0) { + return IPAddress(); + } + return ips_.at(0); + } + // Adds an active IP address to this network. Does not check for duplicates. + void AddIP(const IPAddress& ip) { ips_.push_back(ip); } + + // Sets the network's IP address list. Returns true if new IP addresses were + // detected. Passing true to already_changed skips this check. + bool SetIPs(const std::vector& ips, bool already_changed); + // Get the list of IP Addresses associated with this network. + const std::vector& GetIPs() { return ips_;} + // Clear the network's list of addresses. + void ClearIPs() { ips_.clear(); } + + // Returns the scope-id of the network's address. + // Should only be relevant for link-local IPv6 addresses. + int scope_id() const { return scope_id_; } + void set_scope_id(int id) { scope_id_ = id; } + + // Indicates whether this network should be ignored, perhaps because + // the IP is 0, or the interface is one we know is invalid. + bool ignored() const { return ignored_; } + void set_ignored(bool ignored) { ignored_ = ignored; } + + AdapterType type() const { return type_; } + int preference() const { return preference_; } + void set_preference(int preference) { preference_ = preference; } + + // Debugging description of this network + std::string ToString() const; + + private: + std::string name_; + std::string description_; + IPAddress prefix_; + int prefix_length_; + std::string key_; + std::vector ips_; + int scope_id_; + bool ignored_; + AdapterType type_; + int preference_; + + friend class NetworkManager; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NETWORK_H_ diff --git a/webrtc/base/network_unittest.cc b/webrtc/base/network_unittest.cc new file mode 100644 index 000000000..431f8b4ea --- /dev/null +++ b/webrtc/base/network_unittest.cc @@ -0,0 +1,617 @@ +/* + * Copyright 2004 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 "webrtc/base/network.h" + +#include +#if defined(WEBRTC_POSIX) +#include +#if !defined(WEBRTC_ANDROID) +#include +#else +#include "webrtc/base/ifaddrs-android.h" +#endif +#endif +#include "webrtc/base/gunit.h" +#if defined(WEBRTC_WIN) +#include "webrtc/base/logging.h" // For LOG_GLE +#endif + +namespace rtc { + +class NetworkTest : public testing::Test, public sigslot::has_slots<> { + public: + NetworkTest() : callback_called_(false) {} + + void OnNetworksChanged() { + callback_called_ = true; + } + + void MergeNetworkList(BasicNetworkManager& network_manager, + const NetworkManager::NetworkList& list, + bool* changed ) { + network_manager.MergeNetworkList(list, changed); + } + + bool IsIgnoredNetwork(BasicNetworkManager& network_manager, + const Network& network) { + return network_manager.IsIgnoredNetwork(network); + } + + NetworkManager::NetworkList GetNetworks( + const BasicNetworkManager& network_manager, bool include_ignored) { + NetworkManager::NetworkList list; + network_manager.CreateNetworks(include_ignored, &list); + return list; + } + +#if defined(WEBRTC_POSIX) + // Separated from CreateNetworks for tests. + static void CallConvertIfAddrs(const BasicNetworkManager& network_manager, + struct ifaddrs* interfaces, + bool include_ignored, + NetworkManager::NetworkList* networks) { + network_manager.ConvertIfAddrs(interfaces, include_ignored, networks); + } +#endif // defined(WEBRTC_POSIX) + + protected: + bool callback_called_; +}; + +// Test that the Network ctor works properly. +TEST_F(NetworkTest, TestNetworkConstruct) { + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + EXPECT_EQ("test_eth0", ipv4_network1.name()); + EXPECT_EQ("Test Network Adapter 1", ipv4_network1.description()); + EXPECT_EQ(IPAddress(0x12345600U), ipv4_network1.prefix()); + EXPECT_EQ(24, ipv4_network1.prefix_length()); + EXPECT_FALSE(ipv4_network1.ignored()); +} + +// Tests that our ignore function works properly. +TEST_F(NetworkTest, TestNetworkIgnore) { + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + Network ipv4_network2("test_eth1", "Test Network Adapter 2", + IPAddress(0x00010000U), 16); + BasicNetworkManager network_manager; + EXPECT_FALSE(IsIgnoredNetwork(network_manager, ipv4_network1)); + EXPECT_TRUE(IsIgnoredNetwork(network_manager, ipv4_network2)); +} + +TEST_F(NetworkTest, TestIgnoreList) { + Network ignore_me("ignore_me", "Ignore me please!", + IPAddress(0x12345600U), 24); + Network include_me("include_me", "Include me please!", + IPAddress(0x12345600U), 24); + BasicNetworkManager network_manager; + EXPECT_FALSE(IsIgnoredNetwork(network_manager, ignore_me)); + EXPECT_FALSE(IsIgnoredNetwork(network_manager, include_me)); + std::vector ignore_list; + ignore_list.push_back("ignore_me"); + network_manager.set_network_ignore_list(ignore_list); + EXPECT_TRUE(IsIgnoredNetwork(network_manager, ignore_me)); + EXPECT_FALSE(IsIgnoredNetwork(network_manager, include_me)); +} + +// Test is failing on Windows opt: b/11288214 +TEST_F(NetworkTest, DISABLED_TestCreateNetworks) { + BasicNetworkManager manager; + NetworkManager::NetworkList result = GetNetworks(manager, true); + // We should be able to bind to any addresses we find. + NetworkManager::NetworkList::iterator it; + for (it = result.begin(); + it != result.end(); + ++it) { + sockaddr_storage storage; + memset(&storage, 0, sizeof(storage)); + IPAddress ip = (*it)->ip(); + SocketAddress bindaddress(ip, 0); + bindaddress.SetScopeID((*it)->scope_id()); + // TODO(thaloun): Use rtc::AsyncSocket once it supports IPv6. + int fd = static_cast(socket(ip.family(), SOCK_STREAM, IPPROTO_TCP)); + if (fd > 0) { + size_t ipsize = bindaddress.ToSockAddrStorage(&storage); + EXPECT_GE(ipsize, 0U); + int success = ::bind(fd, + reinterpret_cast(&storage), + static_cast(ipsize)); +#if defined(WEBRTC_WIN) + if (success) LOG_GLE(LS_ERROR) << "Socket bind failed."; +#endif + EXPECT_EQ(0, success); +#if defined(WEBRTC_WIN) + closesocket(fd); +#else + close(fd); +#endif + } + delete (*it); + } +} + +// Test that UpdateNetworks succeeds. +TEST_F(NetworkTest, TestUpdateNetworks) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + manager.StartUpdating(); + Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(callback_called_); + callback_called_ = false; + // Callback should be triggered immediately when StartUpdating + // is called, after network update signal is already sent. + manager.StartUpdating(); + EXPECT_TRUE(manager.started()); + Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(callback_called_); + manager.StopUpdating(); + EXPECT_TRUE(manager.started()); + manager.StopUpdating(); + EXPECT_FALSE(manager.started()); + manager.StopUpdating(); + EXPECT_FALSE(manager.started()); + callback_called_ = false; + // Callback should be triggered immediately after StartUpdating is called + // when start_count_ is reset to 0. + manager.StartUpdating(); + Thread::Current()->ProcessMessages(0); + EXPECT_TRUE(callback_called_); +} + +// Verify that MergeNetworkList() merges network lists properly. +TEST_F(NetworkTest, TestBasicMergeNetworkList) { + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + Network ipv4_network2("test_eth1", "Test Network Adapter 2", + IPAddress(0x00010000U), 16); + ipv4_network1.AddIP(IPAddress(0x12345678)); + ipv4_network2.AddIP(IPAddress(0x00010004)); + BasicNetworkManager manager; + + // Add ipv4_network1 to the list of networks. + NetworkManager::NetworkList list; + list.push_back(new Network(ipv4_network1)); + bool changed; + MergeNetworkList(manager, list, &changed); + EXPECT_TRUE(changed); + list.clear(); + + manager.GetNetworks(&list); + EXPECT_EQ(1U, list.size()); + EXPECT_EQ(ipv4_network1.ToString(), list[0]->ToString()); + Network* net1 = list[0]; + list.clear(); + + // Replace ipv4_network1 with ipv4_network2. + list.push_back(new Network(ipv4_network2)); + MergeNetworkList(manager, list, &changed); + EXPECT_TRUE(changed); + list.clear(); + + manager.GetNetworks(&list); + EXPECT_EQ(1U, list.size()); + EXPECT_EQ(ipv4_network2.ToString(), list[0]->ToString()); + Network* net2 = list[0]; + list.clear(); + + // Add Network2 back. + list.push_back(new Network(ipv4_network1)); + list.push_back(new Network(ipv4_network2)); + MergeNetworkList(manager, list, &changed); + EXPECT_TRUE(changed); + list.clear(); + + // Verify that we get previous instances of Network objects. + manager.GetNetworks(&list); + EXPECT_EQ(2U, list.size()); + EXPECT_TRUE((net1 == list[0] && net2 == list[1]) || + (net1 == list[1] && net2 == list[0])); + list.clear(); + + // Call MergeNetworkList() again and verify that we don't get update + // notification. + list.push_back(new Network(ipv4_network2)); + list.push_back(new Network(ipv4_network1)); + MergeNetworkList(manager, list, &changed); + EXPECT_FALSE(changed); + list.clear(); + + // Verify that we get previous instances of Network objects. + manager.GetNetworks(&list); + EXPECT_EQ(2U, list.size()); + EXPECT_TRUE((net1 == list[0] && net2 == list[1]) || + (net1 == list[1] && net2 == list[0])); + list.clear(); +} + +// Sets up some test IPv6 networks and appends them to list. +// Four networks are added - public and link local, for two interfaces. +void SetupNetworks(NetworkManager::NetworkList* list) { + IPAddress ip; + IPAddress prefix; + EXPECT_TRUE(IPFromString("fe80::1234:5678:abcd:ef12", &ip)); + EXPECT_TRUE(IPFromString("fe80::", &prefix)); + // First, fake link-locals. + Network ipv6_eth0_linklocalnetwork("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth0_linklocalnetwork.AddIP(ip); + EXPECT_TRUE(IPFromString("fe80::5678:abcd:ef12:3456", &ip)); + Network ipv6_eth1_linklocalnetwork("test_eth1", "Test NetworkAdapter 2", + prefix, 64); + ipv6_eth1_linklocalnetwork.AddIP(ip); + // Public networks: + EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c3", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth0_publicnetwork1_ip1("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth0_publicnetwork1_ip1.AddIP(ip); + EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:abcd:efab:cdef", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth1_publicnetwork1_ip1("test_eth1", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth1_publicnetwork1_ip1.AddIP(ip); + list->push_back(new Network(ipv6_eth0_linklocalnetwork)); + list->push_back(new Network(ipv6_eth1_linklocalnetwork)); + list->push_back(new Network(ipv6_eth0_publicnetwork1_ip1)); + list->push_back(new Network(ipv6_eth1_publicnetwork1_ip1)); +} + +// Test that the basic network merging case works. +TEST_F(NetworkTest, TestIPv6MergeNetworkList) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(original_list.size(), list.size()); + // Verify that the original members are in the merged list. + for (NetworkManager::NetworkList::iterator it = original_list.begin(); + it != original_list.end(); ++it) { + EXPECT_NE(list.end(), std::find(list.begin(), list.end(), *it)); + } +} + +// Tests that when two network lists that describe the same set of networks are +// merged, that the changed callback is not called, and that the original +// objects remain in the result list. +TEST_F(NetworkTest, TestNoChangeMerge) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + // Second list that describes the same networks but with new objects. + NetworkManager::NetworkList second_list; + SetupNetworks(&second_list); + changed = false; + MergeNetworkList(manager, second_list, &changed); + EXPECT_FALSE(changed); + NetworkManager::NetworkList resulting_list; + manager.GetNetworks(&resulting_list); + EXPECT_EQ(original_list.size(), resulting_list.size()); + // Verify that the original members are in the merged list. + for (NetworkManager::NetworkList::iterator it = original_list.begin(); + it != original_list.end(); ++it) { + EXPECT_NE(resulting_list.end(), + std::find(resulting_list.begin(), resulting_list.end(), *it)); + } + // Doublecheck that the new networks aren't in the list. + for (NetworkManager::NetworkList::iterator it = second_list.begin(); + it != second_list.end(); ++it) { + EXPECT_EQ(resulting_list.end(), + std::find(resulting_list.begin(), resulting_list.end(), *it)); + } +} + +// Test that we can merge a network that is the same as another network but with +// a different IP. The original network should remain in the list, but have its +// IP changed. +TEST_F(NetworkTest, MergeWithChangedIP) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + // Make a network that we're going to change. + IPAddress ip; + EXPECT_TRUE(IPFromString("2401:fa01:4:1000:be30:faa:fee:faa", &ip)); + IPAddress prefix = TruncateIP(ip, 64); + Network* network_to_change = new Network("test_eth0", + "Test Network Adapter 1", + prefix, 64); + Network* changed_network = new Network(*network_to_change); + network_to_change->AddIP(ip); + IPAddress changed_ip; + EXPECT_TRUE(IPFromString("2401:fa01:4:1000:be30:f00:f00:f00", &changed_ip)); + changed_network->AddIP(changed_ip); + original_list.push_back(network_to_change); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + NetworkManager::NetworkList second_list; + SetupNetworks(&second_list); + second_list.push_back(changed_network); + changed = false; + MergeNetworkList(manager, second_list, &changed); + EXPECT_TRUE(changed); + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(original_list.size(), list.size()); + // Make sure the original network is still in the merged list. + EXPECT_NE(list.end(), + std::find(list.begin(), list.end(), network_to_change)); + EXPECT_EQ(changed_ip, network_to_change->GetIPs().at(0)); +} + +// Testing a similar case to above, but checking that a network can be updated +// with additional IPs (not just a replacement). +TEST_F(NetworkTest, TestMultipleIPMergeNetworkList) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + IPAddress ip; + IPAddress check_ip; + IPAddress prefix; + // Add a second IP to the public network on eth0 (2401:fa00:4:1000/64). + EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c6", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth0_publicnetwork1_ip2("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + // This is the IP that already existed in the public network on eth0. + EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c3", &check_ip)); + ipv6_eth0_publicnetwork1_ip2.AddIP(ip); + original_list.push_back(new Network(ipv6_eth0_publicnetwork1_ip2)); + changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + // There should still be four networks. + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(4U, list.size()); + // Check the gathered IPs. + int matchcount = 0; + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->ToString() == original_list[2]->ToString()) { + ++matchcount; + EXPECT_EQ(1, matchcount); + // This should be the same network object as before. + EXPECT_EQ((*it), original_list[2]); + // But with two addresses now. + EXPECT_EQ(2U, (*it)->GetIPs().size()); + EXPECT_NE((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + check_ip)); + EXPECT_NE((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + ip)); + } else { + // Check the IP didn't get added anywhere it wasn't supposed to. + EXPECT_EQ((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + ip)); + } + } +} + +// Test that merge correctly distinguishes multiple networks on an interface. +TEST_F(NetworkTest, TestMultiplePublicNetworksOnOneInterfaceMerge) { + BasicNetworkManager manager; + manager.SignalNetworksChanged.connect( + static_cast(this), &NetworkTest::OnNetworksChanged); + NetworkManager::NetworkList original_list; + SetupNetworks(&original_list); + bool changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + IPAddress ip; + IPAddress prefix; + // A second network for eth0. + EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:5bff:fee5:c3", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth0_publicnetwork2_ip1("test_eth0", "Test NetworkAdapter 1", + prefix, 64); + ipv6_eth0_publicnetwork2_ip1.AddIP(ip); + original_list.push_back(new Network(ipv6_eth0_publicnetwork2_ip1)); + changed = false; + MergeNetworkList(manager, original_list, &changed); + EXPECT_TRUE(changed); + // There should be five networks now. + NetworkManager::NetworkList list; + manager.GetNetworks(&list); + EXPECT_EQ(5U, list.size()); + // Check the resulting addresses. + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->prefix() == ipv6_eth0_publicnetwork2_ip1.prefix() && + (*it)->name() == ipv6_eth0_publicnetwork2_ip1.name()) { + // Check the new network has 1 IP and that it's the correct one. + EXPECT_EQ(1U, (*it)->GetIPs().size()); + EXPECT_EQ(ip, (*it)->GetIPs().at(0)); + } else { + // Check the IP didn't get added anywhere it wasn't supposed to. + EXPECT_EQ((*it)->GetIPs().end(), + std::find((*it)->GetIPs().begin(), + (*it)->GetIPs().end(), + ip)); + } + } +} + +// Test that DumpNetworks works. +TEST_F(NetworkTest, TestDumpNetworks) { + BasicNetworkManager manager; + manager.DumpNetworks(true); +} + +// Test that we can toggle IPv6 on and off. +TEST_F(NetworkTest, TestIPv6Toggle) { + BasicNetworkManager manager; + bool ipv6_found = false; + NetworkManager::NetworkList list; +#if !defined(WEBRTC_WIN) + // There should be at least one IPv6 network (fe80::/64 should be in there). + // TODO(thaloun): Disabling this test on windows for the moment as the test + // machines don't seem to have IPv6 installed on them at all. + manager.set_ipv6_enabled(true); + list = GetNetworks(manager, true); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->prefix().family() == AF_INET6) { + ipv6_found = true; + break; + } + } + EXPECT_TRUE(ipv6_found); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } +#endif + ipv6_found = false; + manager.set_ipv6_enabled(false); + list = GetNetworks(manager, true); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + if ((*it)->prefix().family() == AF_INET6) { + ipv6_found = true; + break; + } + } + EXPECT_FALSE(ipv6_found); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } +} + +TEST_F(NetworkTest, TestNetworkListSorting) { + BasicNetworkManager manager; + Network ipv4_network1("test_eth0", "Test Network Adapter 1", + IPAddress(0x12345600U), 24); + ipv4_network1.AddIP(IPAddress(0x12345600U)); + + IPAddress ip; + IPAddress prefix; + EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:abcd:efab:cdef", &ip)); + prefix = TruncateIP(ip, 64); + Network ipv6_eth1_publicnetwork1_ip1("test_eth1", "Test NetworkAdapter 2", + prefix, 64); + ipv6_eth1_publicnetwork1_ip1.AddIP(ip); + + NetworkManager::NetworkList list; + list.push_back(new Network(ipv4_network1)); + list.push_back(new Network(ipv6_eth1_publicnetwork1_ip1)); + Network* net1 = list[0]; + Network* net2 = list[1]; + + bool changed = false; + MergeNetworkList(manager, list, &changed); + ASSERT_TRUE(changed); + // After sorting IPv6 network should be higher order than IPv4 networks. + EXPECT_TRUE(net1->preference() < net2->preference()); +} + +TEST_F(NetworkTest, TestNetworkAdapterTypes) { + Network wifi("wlan0", "Wireless Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_WIFI); + EXPECT_EQ(ADAPTER_TYPE_WIFI, wifi.type()); + Network ethernet("eth0", "Ethernet", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_ETHERNET); + EXPECT_EQ(ADAPTER_TYPE_ETHERNET, ethernet.type()); + Network cellular("test_cell", "Cellular Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_CELLULAR); + EXPECT_EQ(ADAPTER_TYPE_CELLULAR, cellular.type()); + Network vpn("bridge_test", "VPN Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_VPN); + EXPECT_EQ(ADAPTER_TYPE_VPN, vpn.type()); + Network unknown("test", "Test Adapter", IPAddress(0x12345600U), 24, + ADAPTER_TYPE_UNKNOWN); + EXPECT_EQ(ADAPTER_TYPE_UNKNOWN, unknown.type()); +} + +#if defined(WEBRTC_POSIX) +// Verify that we correctly handle interfaces with no address. +TEST_F(NetworkTest, TestConvertIfAddrsNoAddress) { + ifaddrs list; + memset(&list, 0, sizeof(list)); + list.ifa_name = const_cast("test_iface"); + + NetworkManager::NetworkList result; + BasicNetworkManager manager; + CallConvertIfAddrs(manager, &list, true, &result); + EXPECT_TRUE(result.empty()); +} +#endif // defined(WEBRTC_POSIX) + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +// If you want to test non-default routes, you can do the following on a linux +// machine: +// 1) Load the dummy network driver: +// sudo modprobe dummy +// sudo ifconfig dummy0 127.0.0.1 +// 2) Run this test and confirm the output says it found a dummy route (and +// passes). +// 3) When done: +// sudo rmmmod dummy +TEST_F(NetworkTest, TestIgnoreNonDefaultRoutes) { + BasicNetworkManager manager; + NetworkManager::NetworkList list; + list = GetNetworks(manager, false); + bool found_dummy = false; + LOG(LS_INFO) << "Looking for dummy network: "; + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + LOG(LS_INFO) << " Network name: " << (*it)->name(); + found_dummy |= (*it)->name().find("dummy0") != std::string::npos; + } + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } + if (!found_dummy) { + LOG(LS_INFO) << "No dummy found, quitting."; + return; + } + LOG(LS_INFO) << "Found dummy, running again while ignoring non-default " + << "routes."; + manager.set_ignore_non_default_routes(true); + list = GetNetworks(manager, false); + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + LOG(LS_INFO) << " Network name: " << (*it)->name(); + EXPECT_TRUE((*it)->name().find("dummy0") == std::string::npos); + } + for (NetworkManager::NetworkList::iterator it = list.begin(); + it != list.end(); ++it) { + delete (*it); + } +} +#endif + +} // namespace rtc diff --git a/webrtc/base/nssidentity.cc b/webrtc/base/nssidentity.cc new file mode 100644 index 000000000..77635a2fc --- /dev/null +++ b/webrtc/base/nssidentity.cc @@ -0,0 +1,521 @@ +/* + * Copyright 2012 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 + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_NSS_SSL_H + +#include "webrtc/base/nssidentity.h" + +#include "cert.h" +#include "cryptohi.h" +#include "keyhi.h" +#include "nss.h" +#include "pk11pub.h" +#include "sechash.h" + +#include "webrtc/base/logging.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/nssstreamadapter.h" +#include "webrtc/base/safe_conversions.h" + +namespace rtc { + +// Certificate validity lifetime in seconds. +static const int CERTIFICATE_LIFETIME = 60*60*24*30; // 30 days, arbitrarily +// Certificate validity window in seconds. +// This is to compensate for slightly incorrect system clocks. +static const int CERTIFICATE_WINDOW = -60*60*24; + +NSSKeyPair::~NSSKeyPair() { + if (privkey_) + SECKEY_DestroyPrivateKey(privkey_); + if (pubkey_) + SECKEY_DestroyPublicKey(pubkey_); +} + +NSSKeyPair *NSSKeyPair::Generate() { + SECKEYPrivateKey *privkey = NULL; + SECKEYPublicKey *pubkey = NULL; + PK11RSAGenParams rsaparams; + rsaparams.keySizeInBits = 1024; + rsaparams.pe = 0x010001; // 65537 -- a common RSA public exponent. + + privkey = PK11_GenerateKeyPair(NSSContext::GetSlot(), + CKM_RSA_PKCS_KEY_PAIR_GEN, + &rsaparams, &pubkey, PR_FALSE /*permanent*/, + PR_FALSE /*sensitive*/, NULL); + if (!privkey) { + LOG(LS_ERROR) << "Couldn't generate key pair"; + return NULL; + } + + return new NSSKeyPair(privkey, pubkey); +} + +// Just make a copy. +NSSKeyPair *NSSKeyPair::GetReference() { + SECKEYPrivateKey *privkey = SECKEY_CopyPrivateKey(privkey_); + if (!privkey) + return NULL; + + SECKEYPublicKey *pubkey = SECKEY_CopyPublicKey(pubkey_); + if (!pubkey) { + SECKEY_DestroyPrivateKey(privkey); + return NULL; + } + + return new NSSKeyPair(privkey, pubkey); +} + +NSSCertificate::NSSCertificate(CERTCertificate* cert) + : certificate_(CERT_DupCertificate(cert)) { + ASSERT(certificate_ != NULL); +} + +static void DeleteCert(SSLCertificate* cert) { + delete cert; +} + +NSSCertificate::NSSCertificate(CERTCertList* cert_list) { + // Copy the first cert into certificate_. + CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + certificate_ = CERT_DupCertificate(node->cert); + + // Put any remaining certificates into the chain. + node = CERT_LIST_NEXT(node); + std::vector certs; + for (; !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { + certs.push_back(new NSSCertificate(node->cert)); + } + + if (!certs.empty()) + chain_.reset(new SSLCertChain(certs)); + + // The SSLCertChain constructor copies its input, so now we have to delete + // the originals. + std::for_each(certs.begin(), certs.end(), DeleteCert); +} + +NSSCertificate::NSSCertificate(CERTCertificate* cert, SSLCertChain* chain) + : certificate_(CERT_DupCertificate(cert)) { + ASSERT(certificate_ != NULL); + if (chain) + chain_.reset(chain->Copy()); +} + + +NSSCertificate *NSSCertificate::FromPEMString(const std::string &pem_string) { + std::string der; + if (!SSLIdentity::PemToDer(kPemTypeCertificate, pem_string, &der)) + return NULL; + + SECItem der_cert; + der_cert.data = reinterpret_cast(const_cast( + der.data())); + der_cert.len = checked_cast(der.size()); + CERTCertificate *cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + &der_cert, NULL, PR_FALSE, PR_TRUE); + + if (!cert) + return NULL; + + NSSCertificate* ret = new NSSCertificate(cert); + CERT_DestroyCertificate(cert); + return ret; +} + +NSSCertificate *NSSCertificate::GetReference() const { + return new NSSCertificate(certificate_, chain_.get()); +} + +std::string NSSCertificate::ToPEMString() const { + return SSLIdentity::DerToPem(kPemTypeCertificate, + certificate_->derCert.data, + certificate_->derCert.len); +} + +void NSSCertificate::ToDER(Buffer* der_buffer) const { + der_buffer->SetData(certificate_->derCert.data, certificate_->derCert.len); +} + +static bool Certifies(CERTCertificate* parent, CERTCertificate* child) { + // TODO(bemasc): Identify stricter validation checks to use here. In the + // context of some future identity standard, it might make sense to check + // the certificates' roles, expiration dates, self-signatures (if + // self-signed), certificate transparency logging, or many other attributes. + // NOTE: Future changes to this validation may reject some previously allowed + // certificate chains. Users should be advised not to deploy chained + // certificates except in controlled environments until the validity + // requirements are finalized. + + // Check that the parent's name is the same as the child's claimed issuer. + SECComparison name_status = + CERT_CompareName(&child->issuer, &parent->subject); + if (name_status != SECEqual) + return false; + + // Extract the parent's public key, or fail if the key could not be read + // (e.g. certificate is corrupted). + SECKEYPublicKey* parent_key = CERT_ExtractPublicKey(parent); + if (!parent_key) + return false; + + // Check that the parent's privkey was actually used to generate the child's + // signature. + SECStatus verified = CERT_VerifySignedDataWithPublicKey( + &child->signatureWrap, parent_key, NULL); + SECKEY_DestroyPublicKey(parent_key); + return verified == SECSuccess; +} + +bool NSSCertificate::IsValidChain(const CERTCertList* cert_list) { + CERTCertListNode* child = CERT_LIST_HEAD(cert_list); + for (CERTCertListNode* parent = CERT_LIST_NEXT(child); + !CERT_LIST_END(parent, cert_list); + child = parent, parent = CERT_LIST_NEXT(parent)) { + if (!Certifies(parent->cert, child->cert)) + return false; + } + return true; +} + +bool NSSCertificate::GetDigestLength(const std::string& algorithm, + size_t* length) { + const SECHashObject *ho; + + if (!GetDigestObject(algorithm, &ho)) + return false; + + *length = ho->length; + + return true; +} + +bool NSSCertificate::GetSignatureDigestAlgorithm(std::string* algorithm) const { + // The function sec_DecodeSigAlg in NSS provides this mapping functionality. + // Unfortunately it is private, so the functionality must be duplicated here. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=925165 . + SECOidTag sig_alg = SECOID_GetAlgorithmTag(&certificate_->signature); + switch (sig_alg) { + case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_MD5; + break; + case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION: + case SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE: + case SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE: + case SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE: + case SEC_OID_MISSI_DSS: + case SEC_OID_MISSI_KEA_DSS: + case SEC_OID_MISSI_KEA_DSS_OLD: + case SEC_OID_MISSI_DSS_OLD: + *algorithm = DIGEST_SHA_1; + break; + case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE: + case SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST: + *algorithm = DIGEST_SHA_224; + break; + case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE: + case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST: + *algorithm = DIGEST_SHA_256; + break; + case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE: + case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_SHA_384; + break; + case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE: + case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_SHA_512; + break; + default: + // Unknown algorithm. There are several unhandled options that are less + // common and more complex. + algorithm->clear(); + return false; + } + return true; +} + +bool NSSCertificate::ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + const SECHashObject *ho; + + if (!GetDigestObject(algorithm, &ho)) + return false; + + if (size < ho->length) // Sanity check for fit + return false; + + SECStatus rv = HASH_HashBuf(ho->type, digest, + certificate_->derCert.data, + certificate_->derCert.len); + if (rv != SECSuccess) + return false; + + *length = ho->length; + + return true; +} + +bool NSSCertificate::GetChain(SSLCertChain** chain) const { + if (!chain_) + return false; + + *chain = chain_->Copy(); + return true; +} + +bool NSSCertificate::Equals(const NSSCertificate *tocompare) const { + if (!certificate_->derCert.len) + return false; + if (!tocompare->certificate_->derCert.len) + return false; + + if (certificate_->derCert.len != tocompare->certificate_->derCert.len) + return false; + + return memcmp(certificate_->derCert.data, + tocompare->certificate_->derCert.data, + certificate_->derCert.len) == 0; +} + + +bool NSSCertificate::GetDigestObject(const std::string &algorithm, + const SECHashObject **hop) { + const SECHashObject *ho; + HASH_HashType hash_type; + + if (algorithm == DIGEST_SHA_1) { + hash_type = HASH_AlgSHA1; + // HASH_AlgSHA224 is not supported in the chromium linux build system. +#if 0 + } else if (algorithm == DIGEST_SHA_224) { + hash_type = HASH_AlgSHA224; +#endif + } else if (algorithm == DIGEST_SHA_256) { + hash_type = HASH_AlgSHA256; + } else if (algorithm == DIGEST_SHA_384) { + hash_type = HASH_AlgSHA384; + } else if (algorithm == DIGEST_SHA_512) { + hash_type = HASH_AlgSHA512; + } else { + return false; + } + + ho = HASH_GetHashObject(hash_type); + + ASSERT(ho->length >= 20); // Can't happen + *hop = ho; + + return true; +} + + +NSSIdentity* NSSIdentity::GenerateInternal(const SSLIdentityParams& params) { + std::string subject_name_string = "CN=" + params.common_name; + CERTName *subject_name = CERT_AsciiToName( + const_cast(subject_name_string.c_str())); + NSSIdentity *identity = NULL; + CERTSubjectPublicKeyInfo *spki = NULL; + CERTCertificateRequest *certreq = NULL; + CERTValidity *validity = NULL; + CERTCertificate *certificate = NULL; + NSSKeyPair *keypair = NSSKeyPair::Generate(); + SECItem inner_der; + SECStatus rv; + PLArenaPool* arena; + SECItem signed_cert; + PRTime now = PR_Now(); + PRTime not_before = + now + static_cast(params.not_before) * PR_USEC_PER_SEC; + PRTime not_after = + now + static_cast(params.not_after) * PR_USEC_PER_SEC; + + inner_der.len = 0; + inner_der.data = NULL; + + if (!keypair) { + LOG(LS_ERROR) << "Couldn't generate key pair"; + goto fail; + } + + if (!subject_name) { + LOG(LS_ERROR) << "Couldn't convert subject name " << subject_name; + goto fail; + } + + spki = SECKEY_CreateSubjectPublicKeyInfo(keypair->pubkey()); + if (!spki) { + LOG(LS_ERROR) << "Couldn't create SPKI"; + goto fail; + } + + certreq = CERT_CreateCertificateRequest(subject_name, spki, NULL); + if (!certreq) { + LOG(LS_ERROR) << "Couldn't create certificate signing request"; + goto fail; + } + + validity = CERT_CreateValidity(not_before, not_after); + if (!validity) { + LOG(LS_ERROR) << "Couldn't create validity"; + goto fail; + } + + unsigned long serial; + // Note: This serial in principle could collide, but it's unlikely + rv = PK11_GenerateRandom(reinterpret_cast(&serial), + sizeof(serial)); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't generate random serial"; + goto fail; + } + + certificate = CERT_CreateCertificate(serial, subject_name, validity, certreq); + if (!certificate) { + LOG(LS_ERROR) << "Couldn't create certificate"; + goto fail; + } + + arena = certificate->arena; + + rv = SECOID_SetAlgorithmID(arena, &certificate->signature, + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, NULL); + if (rv != SECSuccess) + goto fail; + + // Set version to X509v3. + *(certificate->version.data) = 2; + certificate->version.len = 1; + + if (!SEC_ASN1EncodeItem(arena, &inner_der, certificate, + SEC_ASN1_GET(CERT_CertificateTemplate))) + goto fail; + + rv = SEC_DerSignData(arena, &signed_cert, inner_der.data, inner_der.len, + keypair->privkey(), + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't sign certificate"; + goto fail; + } + certificate->derCert = signed_cert; + + identity = new NSSIdentity(keypair, new NSSCertificate(certificate)); + + goto done; + + fail: + delete keypair; + + done: + if (certificate) CERT_DestroyCertificate(certificate); + if (subject_name) CERT_DestroyName(subject_name); + if (spki) SECKEY_DestroySubjectPublicKeyInfo(spki); + if (certreq) CERT_DestroyCertificateRequest(certreq); + if (validity) CERT_DestroyValidity(validity); + return identity; +} + +NSSIdentity* NSSIdentity::Generate(const std::string &common_name) { + SSLIdentityParams params; + params.common_name = common_name; + params.not_before = CERTIFICATE_WINDOW; + params.not_after = CERTIFICATE_LIFETIME; + return GenerateInternal(params); +} + +NSSIdentity* NSSIdentity::GenerateForTest(const SSLIdentityParams& params) { + return GenerateInternal(params); +} + +SSLIdentity* NSSIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + std::string private_key_der; + if (!SSLIdentity::PemToDer( + kPemTypeRsaPrivateKey, private_key, &private_key_der)) + return NULL; + + SECItem private_key_item; + private_key_item.data = reinterpret_cast( + const_cast(private_key_der.c_str())); + private_key_item.len = checked_cast(private_key_der.size()); + + const unsigned int key_usage = KU_KEY_ENCIPHERMENT | KU_DATA_ENCIPHERMENT | + KU_DIGITAL_SIGNATURE; + + SECKEYPrivateKey* privkey = NULL; + SECStatus rv = + PK11_ImportDERPrivateKeyInfoAndReturnKey(NSSContext::GetSlot(), + &private_key_item, + NULL, NULL, PR_FALSE, PR_FALSE, + key_usage, &privkey, NULL); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't import private key"; + return NULL; + } + + SECKEYPublicKey *pubkey = SECKEY_ConvertToPublicKey(privkey); + if (rv != SECSuccess) { + SECKEY_DestroyPrivateKey(privkey); + LOG(LS_ERROR) << "Couldn't convert private key to public key"; + return NULL; + } + + // Assign to a scoped_ptr so we don't leak on error. + scoped_ptr keypair(new NSSKeyPair(privkey, pubkey)); + + scoped_ptr cert(NSSCertificate::FromPEMString(certificate)); + if (!cert) { + LOG(LS_ERROR) << "Couldn't parse certificate"; + return NULL; + } + + // TODO(ekr@rtfm.com): Check the public key against the certificate. + + return new NSSIdentity(keypair.release(), cert.release()); +} + +NSSIdentity *NSSIdentity::GetReference() const { + NSSKeyPair *keypair = keypair_->GetReference(); + if (!keypair) + return NULL; + + NSSCertificate *certificate = certificate_->GetReference(); + if (!certificate) { + delete keypair; + return NULL; + } + + return new NSSIdentity(keypair, certificate); +} + + +NSSCertificate &NSSIdentity::certificate() const { + return *certificate_; +} + + +} // rtc namespace + +#endif // HAVE_NSS_SSL_H + diff --git a/webrtc/base/nssidentity.h b/webrtc/base/nssidentity.h new file mode 100644 index 000000000..2c56c002b --- /dev/null +++ b/webrtc/base/nssidentity.h @@ -0,0 +1,130 @@ +/* + * Copyright 2004 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_BASE_NSSIDENTITY_H_ +#define WEBRTC_BASE_NSSIDENTITY_H_ + +#include + +#include "cert.h" +#include "nspr.h" +#include "hasht.h" +#include "keythi.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +class NSSKeyPair { + public: + NSSKeyPair(SECKEYPrivateKey* privkey, SECKEYPublicKey* pubkey) : + privkey_(privkey), pubkey_(pubkey) {} + ~NSSKeyPair(); + + // Generate a 1024-bit RSA key pair. + static NSSKeyPair* Generate(); + NSSKeyPair* GetReference(); + + SECKEYPrivateKey* privkey() const { return privkey_; } + SECKEYPublicKey * pubkey() const { return pubkey_; } + + private: + SECKEYPrivateKey* privkey_; + SECKEYPublicKey* pubkey_; + + DISALLOW_EVIL_CONSTRUCTORS(NSSKeyPair); +}; + + +class NSSCertificate : public SSLCertificate { + public: + static NSSCertificate* FromPEMString(const std::string& pem_string); + // The caller retains ownership of the argument to all the constructors, + // and the constructor makes a copy. + explicit NSSCertificate(CERTCertificate* cert); + explicit NSSCertificate(CERTCertList* cert_list); + virtual ~NSSCertificate() { + if (certificate_) + CERT_DestroyCertificate(certificate_); + } + + virtual NSSCertificate* GetReference() const; + + virtual std::string ToPEMString() const; + + virtual void ToDER(Buffer* der_buffer) const; + + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const; + + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const; + + virtual bool GetChain(SSLCertChain** chain) const; + + CERTCertificate* certificate() { return certificate_; } + + // Performs minimal checks to determine if the list is a valid chain. This + // only checks that each certificate certifies the preceding certificate, + // and ignores many other certificate features such as expiration dates. + static bool IsValidChain(const CERTCertList* cert_list); + + // Helper function to get the length of a digest + static bool GetDigestLength(const std::string& algorithm, size_t* length); + + // Comparison. Only the certificate itself is considered, not the chain. + bool Equals(const NSSCertificate* tocompare) const; + + private: + NSSCertificate(CERTCertificate* cert, SSLCertChain* chain); + static bool GetDigestObject(const std::string& algorithm, + const SECHashObject** hash_object); + + CERTCertificate* certificate_; + scoped_ptr chain_; + + DISALLOW_EVIL_CONSTRUCTORS(NSSCertificate); +}; + +// Represents a SSL key pair and certificate for NSS. +class NSSIdentity : public SSLIdentity { + public: + static NSSIdentity* Generate(const std::string& common_name); + static NSSIdentity* GenerateForTest(const SSLIdentityParams& params); + static SSLIdentity* FromPEMStrings(const std::string& private_key, + const std::string& certificate); + virtual ~NSSIdentity() { + LOG(LS_INFO) << "Destroying NSS identity"; + } + + virtual NSSIdentity* GetReference() const; + virtual NSSCertificate& certificate() const; + + NSSKeyPair* keypair() const { return keypair_.get(); } + + private: + NSSIdentity(NSSKeyPair* keypair, NSSCertificate* cert) : + keypair_(keypair), certificate_(cert) {} + + static NSSIdentity* GenerateInternal(const SSLIdentityParams& params); + + rtc::scoped_ptr keypair_; + rtc::scoped_ptr certificate_; + + DISALLOW_EVIL_CONSTRUCTORS(NSSIdentity); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NSSIDENTITY_H_ diff --git a/webrtc/base/nssstreamadapter.cc b/webrtc/base/nssstreamadapter.cc new file mode 100644 index 000000000..1d06c1c4f --- /dev/null +++ b/webrtc/base/nssstreamadapter.cc @@ -0,0 +1,1020 @@ +/* + * Copyright 2004 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 + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_NSS_SSL_H + +#include "webrtc/base/nssstreamadapter.h" + +#include "keyhi.h" +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" +#include "secerr.h" + +#ifdef NSS_SSL_RELATIVE_PATH +#include "ssl.h" +#include "sslerr.h" +#include "sslproto.h" +#else +#include "net/third_party/nss/ssl/ssl.h" +#include "net/third_party/nss/ssl/sslerr.h" +#include "net/third_party/nss/ssl/sslproto.h" +#endif + +#include "webrtc/base/nssidentity.h" +#include "webrtc/base/safe_conversions.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +PRDescIdentity NSSStreamAdapter::nspr_layer_identity = PR_INVALID_IO_LAYER; + +#define UNIMPLEMENTED \ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); \ + LOG(LS_ERROR) \ + << "Call to unimplemented function "<< __FUNCTION__; ASSERT(false) + +#ifdef SRTP_AES128_CM_HMAC_SHA1_80 +#define HAVE_DTLS_SRTP +#endif + +#ifdef HAVE_DTLS_SRTP +// SRTP cipher suite table +struct SrtpCipherMapEntry { + const char* external_name; + PRUint16 cipher_id; +}; + +// This isn't elegant, but it's better than an external reference +static const SrtpCipherMapEntry kSrtpCipherMap[] = { + {"AES_CM_128_HMAC_SHA1_80", SRTP_AES128_CM_HMAC_SHA1_80 }, + {"AES_CM_128_HMAC_SHA1_32", SRTP_AES128_CM_HMAC_SHA1_32 }, + {NULL, 0} +}; +#endif + + +// Implementation of NSPR methods +static PRStatus StreamClose(PRFileDesc *socket) { + ASSERT(!socket->lower); + socket->dtor(socket); + return PR_SUCCESS; +} + +static PRInt32 StreamRead(PRFileDesc *socket, void *buf, PRInt32 length) { + StreamInterface *stream = reinterpret_cast(socket->secret); + size_t read; + int error; + StreamResult result = stream->Read(buf, length, &read, &error); + if (result == SR_SUCCESS) { + return checked_cast(read); + } + + if (result == SR_EOS) { + return 0; + } + + if (result == SR_BLOCK) { + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + PR_SetError(PR_UNKNOWN_ERROR, error); + return -1; +} + +static PRInt32 StreamWrite(PRFileDesc *socket, const void *buf, + PRInt32 length) { + StreamInterface *stream = reinterpret_cast(socket->secret); + size_t written; + int error; + StreamResult result = stream->Write(buf, length, &written, &error); + if (result == SR_SUCCESS) { + return checked_cast(written); + } + + if (result == SR_BLOCK) { + LOG(LS_INFO) << + "NSSStreamAdapter: write to underlying transport would block"; + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + LOG(LS_ERROR) << "Write error"; + PR_SetError(PR_UNKNOWN_ERROR, error); + return -1; +} + +static PRInt32 StreamAvailable(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +PRInt64 StreamAvailable64(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamSync(PRFileDesc *socket) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PROffset32 StreamSeek(PRFileDesc *socket, PROffset32 offset, + PRSeekWhence how) { + UNIMPLEMENTED; + return -1; +} + +static PROffset64 StreamSeek64(PRFileDesc *socket, PROffset64 offset, + PRSeekWhence how) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamFileInfo(PRFileDesc *socket, PRFileInfo *info) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamFileInfo64(PRFileDesc *socket, PRFileInfo64 *info) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRInt32 StreamWritev(PRFileDesc *socket, const PRIOVec *iov, + PRInt32 iov_size, PRIntervalTime timeout) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamConnect(PRFileDesc *socket, const PRNetAddr *addr, + PRIntervalTime timeout) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRFileDesc *StreamAccept(PRFileDesc *sd, PRNetAddr *addr, + PRIntervalTime timeout) { + UNIMPLEMENTED; + return NULL; +} + +static PRStatus StreamBind(PRFileDesc *socket, const PRNetAddr *addr) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamListen(PRFileDesc *socket, PRIntn depth) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamShutdown(PRFileDesc *socket, PRIntn how) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +// Note: this is always nonblocking and ignores the timeout. +// TODO(ekr@rtfm.com): In future verify that the socket is +// actually in non-blocking mode. +// This function does not support peek. +static PRInt32 StreamRecv(PRFileDesc *socket, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime to) { + ASSERT(flags == 0); + + if (flags != 0) { + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return -1; + } + + return StreamRead(socket, buf, amount); +} + +// Note: this is always nonblocking and assumes a zero timeout. +// This function does not support peek. +static PRInt32 StreamSend(PRFileDesc *socket, const void *buf, + PRInt32 amount, PRIntn flags, + PRIntervalTime to) { + ASSERT(flags == 0); + + return StreamWrite(socket, buf, amount); +} + +static PRInt32 StreamRecvfrom(PRFileDesc *socket, void *buf, + PRInt32 amount, PRIntn flags, + PRNetAddr *addr, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamSendto(PRFileDesc *socket, const void *buf, + PRInt32 amount, PRIntn flags, + const PRNetAddr *addr, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRInt16 StreamPoll(PRFileDesc *socket, PRInt16 in_flags, + PRInt16 *out_flags) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamAcceptRead(PRFileDesc *sd, PRFileDesc **nd, + PRNetAddr **raddr, + void *buf, PRInt32 amount, PRIntervalTime t) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamTransmitFile(PRFileDesc *sd, PRFileDesc *socket, + const void *headers, PRInt32 hlen, + PRTransmitFileFlags flags, PRIntervalTime t) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamGetPeerName(PRFileDesc *socket, PRNetAddr *addr) { + // TODO(ekr@rtfm.com): Modify to return unique names for each channel + // somehow, as opposed to always the same static address. The current + // implementation messes up the session cache, which is why it's off + // elsewhere + addr->inet.family = PR_AF_INET; + addr->inet.port = 0; + addr->inet.ip = 0; + + return PR_SUCCESS; +} + +static PRStatus StreamGetSockName(PRFileDesc *socket, PRNetAddr *addr) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamGetSockOption(PRFileDesc *socket, PRSocketOptionData *opt) { + switch (opt->option) { + case PR_SockOpt_Nonblocking: + opt->value.non_blocking = PR_TRUE; + return PR_SUCCESS; + default: + UNIMPLEMENTED; + break; + } + + return PR_FAILURE; +} + +// Imitate setting socket options. These are mostly noops. +static PRStatus StreamSetSockOption(PRFileDesc *socket, + const PRSocketOptionData *opt) { + switch (opt->option) { + case PR_SockOpt_Nonblocking: + return PR_SUCCESS; + case PR_SockOpt_NoDelay: + return PR_SUCCESS; + default: + UNIMPLEMENTED; + break; + } + + return PR_FAILURE; +} + +static PRInt32 StreamSendfile(PRFileDesc *out, PRSendFileData *in, + PRTransmitFileFlags flags, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamConnectContinue(PRFileDesc *socket, PRInt16 flags) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRIntn StreamReserved(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +static const struct PRIOMethods nss_methods = { + PR_DESC_LAYERED, + StreamClose, + StreamRead, + StreamWrite, + StreamAvailable, + StreamAvailable64, + StreamSync, + StreamSeek, + StreamSeek64, + StreamFileInfo, + StreamFileInfo64, + StreamWritev, + StreamConnect, + StreamAccept, + StreamBind, + StreamListen, + StreamShutdown, + StreamRecv, + StreamSend, + StreamRecvfrom, + StreamSendto, + StreamPoll, + StreamAcceptRead, + StreamTransmitFile, + StreamGetSockName, + StreamGetPeerName, + StreamReserved, + StreamReserved, + StreamGetSockOption, + StreamSetSockOption, + StreamSendfile, + StreamConnectContinue, + StreamReserved, + StreamReserved, + StreamReserved, + StreamReserved +}; + +NSSStreamAdapter::NSSStreamAdapter(StreamInterface *stream) + : SSLStreamAdapterHelper(stream), + ssl_fd_(NULL), + cert_ok_(false) { +} + +bool NSSStreamAdapter::Init() { + if (nspr_layer_identity == PR_INVALID_IO_LAYER) { + nspr_layer_identity = PR_GetUniqueIdentity("nssstreamadapter"); + } + PRFileDesc *pr_fd = PR_CreateIOLayerStub(nspr_layer_identity, &nss_methods); + if (!pr_fd) + return false; + pr_fd->secret = reinterpret_cast(stream()); + + PRFileDesc *ssl_fd; + if (ssl_mode_ == SSL_MODE_DTLS) { + ssl_fd = DTLS_ImportFD(NULL, pr_fd); + } else { + ssl_fd = SSL_ImportFD(NULL, pr_fd); + } + ASSERT(ssl_fd != NULL); // This should never happen + if (!ssl_fd) { + PR_Close(pr_fd); + return false; + } + + SECStatus rv; + // Turn on security. + rv = SSL_OptionSet(ssl_fd, SSL_SECURITY, PR_TRUE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error enabling security on SSL Socket"; + return false; + } + + // Disable SSLv2. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SSL2, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling SSL2"; + return false; + } + + // Disable caching. + // TODO(ekr@rtfm.com): restore this when I have the caching + // identity set. + rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling cache"; + return false; + } + + // Disable session tickets. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error enabling tickets"; + return false; + } + + // Disable renegotiation. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION, + SSL_RENEGOTIATE_NEVER); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling renegotiation"; + return false; + } + + // Disable false start. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling false start"; + return false; + } + + ssl_fd_ = ssl_fd; + + return true; +} + +NSSStreamAdapter::~NSSStreamAdapter() { + if (ssl_fd_) + PR_Close(ssl_fd_); +}; + + +int NSSStreamAdapter::BeginSSL() { + SECStatus rv; + + if (!Init()) { + Error("Init", -1, false); + return -1; + } + + ASSERT(state_ == SSL_CONNECTING); + // The underlying stream has been opened. If we are in peer-to-peer mode + // then a peer certificate must have been specified by now. + ASSERT(!ssl_server_name_.empty() || + peer_certificate_.get() != NULL || + !peer_certificate_digest_algorithm_.empty()); + LOG(LS_INFO) << "BeginSSL: " + << (!ssl_server_name_.empty() ? ssl_server_name_ : + "with peer"); + + if (role_ == SSL_CLIENT) { + LOG(LS_INFO) << "BeginSSL: as client"; + + rv = SSL_GetClientAuthDataHook(ssl_fd_, GetClientAuthDataHook, + this); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } else { + LOG(LS_INFO) << "BeginSSL: as server"; + NSSIdentity *identity; + + if (identity_.get()) { + identity = static_cast(identity_.get()); + } else { + LOG(LS_ERROR) << "Can't be an SSL server without an identity"; + Error("BeginSSL", -1, false); + return -1; + } + rv = SSL_ConfigSecureServer(ssl_fd_, identity->certificate().certificate(), + identity->keypair()->privkey(), + kt_rsa); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // Insist on a certificate from the client + rv = SSL_OptionSet(ssl_fd_, SSL_REQUEST_CERTIFICATE, PR_TRUE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + rv = SSL_OptionSet(ssl_fd_, SSL_REQUIRE_CERTIFICATE, PR_TRUE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } + + // Set the version range. + SSLVersionRange vrange; + vrange.min = (ssl_mode_ == SSL_MODE_DTLS) ? + SSL_LIBRARY_VERSION_TLS_1_1 : + SSL_LIBRARY_VERSION_TLS_1_0; + vrange.max = SSL_LIBRARY_VERSION_TLS_1_1; + + rv = SSL_VersionRangeSet(ssl_fd_, &vrange); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // SRTP +#ifdef HAVE_DTLS_SRTP + if (!srtp_ciphers_.empty()) { + rv = SSL_SetSRTPCiphers( + ssl_fd_, &srtp_ciphers_[0], + checked_cast(srtp_ciphers_.size())); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } +#endif + + // Certificate validation + rv = SSL_AuthCertificateHook(ssl_fd_, AuthCertificateHook, this); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // Now start the handshake + rv = SSL_ResetHandshake(ssl_fd_, role_ == SSL_SERVER ? PR_TRUE : PR_FALSE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + return ContinueSSL(); +} + +int NSSStreamAdapter::ContinueSSL() { + LOG(LS_INFO) << "ContinueSSL"; + ASSERT(state_ == SSL_CONNECTING); + + // Clear the DTLS timer + Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT); + + SECStatus rv = SSL_ForceHandshake(ssl_fd_); + + if (rv == SECSuccess) { + LOG(LS_INFO) << "Handshake complete"; + + ASSERT(cert_ok_); + if (!cert_ok_) { + Error("ContinueSSL", -1, true); + return -1; + } + + state_ = SSL_CONNECTED; + StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0); + return 0; + } + + PRInt32 err = PR_GetError(); + switch (err) { + case SSL_ERROR_RX_MALFORMED_HANDSHAKE: + if (ssl_mode_ != SSL_MODE_DTLS) { + Error("ContinueSSL", -1, true); + return -1; + } else { + LOG(LS_INFO) << "Malformed DTLS message. Ignoring."; + // Fall through + } + case PR_WOULD_BLOCK_ERROR: + LOG(LS_INFO) << "Would have blocked"; + if (ssl_mode_ == SSL_MODE_DTLS) { + PRIntervalTime timeout; + + SECStatus rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout); + if (rv == SECSuccess) { + LOG(LS_INFO) << "Timeout is " << timeout << " ms"; + Thread::Current()->PostDelayed(PR_IntervalToMilliseconds(timeout), + this, MSG_DTLS_TIMEOUT, 0); + } + } + + return 0; + default: + LOG(LS_INFO) << "Error " << err; + break; + } + + Error("ContinueSSL", -1, true); + return -1; +} + +void NSSStreamAdapter::Cleanup() { + if (state_ != SSL_ERROR) { + state_ = SSL_CLOSED; + } + + if (ssl_fd_) { + PR_Close(ssl_fd_); + ssl_fd_ = NULL; + } + + identity_.reset(); + peer_certificate_.reset(); + + Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT); +} + +StreamResult NSSStreamAdapter::Read(void* data, size_t data_len, + size_t* read, int* error) { + // SSL_CONNECTED sanity check. + switch (state_) { + case SSL_NONE: + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_CLOSED: + return SR_EOS; + + case SSL_ERROR: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + PRInt32 rv = PR_Read(ssl_fd_, data, checked_cast(data_len)); + + if (rv == 0) { + return SR_EOS; + } + + // Error + if (rv < 0) { + PRInt32 err = PR_GetError(); + + switch (err) { + case PR_WOULD_BLOCK_ERROR: + return SR_BLOCK; + default: + Error("Read", -1, false); + *error = err; // libjingle semantics are that this is impl-specific + return SR_ERROR; + } + } + + // Success + *read = rv; + + return SR_SUCCESS; +} + +StreamResult NSSStreamAdapter::Write(const void* data, size_t data_len, + size_t* written, int* error) { + // SSL_CONNECTED sanity check. + switch (state_) { + case SSL_NONE: + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + case SSL_CLOSED: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + PRInt32 rv = PR_Write(ssl_fd_, data, checked_cast(data_len)); + + // Error + if (rv < 0) { + PRInt32 err = PR_GetError(); + + switch (err) { + case PR_WOULD_BLOCK_ERROR: + return SR_BLOCK; + default: + Error("Write", -1, false); + *error = err; // libjingle semantics are that this is impl-specific + return SR_ERROR; + } + } + + // Success + *written = rv; + + return SR_SUCCESS; +} + +void NSSStreamAdapter::OnEvent(StreamInterface* stream, int events, + int err) { + int events_to_signal = 0; + int signal_error = 0; + ASSERT(stream == this->stream()); + if ((events & SE_OPEN)) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent SE_OPEN"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + events_to_signal |= SE_OPEN; + } else { + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, true); + return; + } + } + } + if ((events & (SE_READ|SE_WRITE))) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent" + << ((events & SE_READ) ? " SE_READ" : "") + << ((events & SE_WRITE) ? " SE_WRITE" : ""); + if (state_ == SSL_NONE) { + events_to_signal |= events & (SE_READ|SE_WRITE); + } else if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err, true); + return; + } + } else if (state_ == SSL_CONNECTED) { + if (events & SE_WRITE) { + LOG(LS_INFO) << " -- onStreamWriteable"; + events_to_signal |= SE_WRITE; + } + if (events & SE_READ) { + LOG(LS_INFO) << " -- onStreamReadable"; + events_to_signal |= SE_READ; + } + } + } + if ((events & SE_CLOSE)) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent(SE_CLOSE, " << err << ")"; + Cleanup(); + events_to_signal |= SE_CLOSE; + // SE_CLOSE is the only event that uses the final parameter to OnEvent(). + ASSERT(signal_error == 0); + signal_error = err; + } + if (events_to_signal) + StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error); +} + +void NSSStreamAdapter::OnMessage(Message* msg) { + // Process our own messages and then pass others to the superclass + if (MSG_DTLS_TIMEOUT == msg->message_id) { + LOG(LS_INFO) << "DTLS timeout expired"; + ContinueSSL(); + } else { + StreamInterface::OnMessage(msg); + } +} + +// Certificate verification callback. Called to check any certificate +SECStatus NSSStreamAdapter::AuthCertificateHook(void *arg, + PRFileDesc *fd, + PRBool checksig, + PRBool isServer) { + LOG(LS_INFO) << "NSSStreamAdapter::AuthCertificateHook"; + // SSL_PeerCertificate returns a pointer that is owned by the caller, and + // the NSSCertificate constructor copies its argument, so |raw_peer_cert| + // must be destroyed in this function. + CERTCertificate* raw_peer_cert = SSL_PeerCertificate(fd); + NSSCertificate peer_cert(raw_peer_cert); + CERT_DestroyCertificate(raw_peer_cert); + + NSSStreamAdapter *stream = reinterpret_cast(arg); + stream->cert_ok_ = false; + + // Read the peer's certificate chain. + CERTCertList* cert_list = SSL_PeerCertificateChain(fd); + ASSERT(cert_list != NULL); + + // If the peer provided multiple certificates, check that they form a valid + // chain as defined by RFC 5246 Section 7.4.2: "Each following certificate + // MUST directly certify the one preceding it.". This check does NOT + // verify other requirements, such as whether the chain reaches a trusted + // root, self-signed certificates have valid signatures, certificates are not + // expired, etc. + // Even if the chain is valid, the leaf certificate must still match a + // provided certificate or digest. + if (!NSSCertificate::IsValidChain(cert_list)) { + CERT_DestroyCertList(cert_list); + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + return SECFailure; + } + + if (stream->peer_certificate_.get()) { + LOG(LS_INFO) << "Checking against specified certificate"; + + // The peer certificate was specified + if (reinterpret_cast(stream->peer_certificate_.get())-> + Equals(&peer_cert)) { + LOG(LS_INFO) << "Accepted peer certificate"; + stream->cert_ok_ = true; + } + } else if (!stream->peer_certificate_digest_algorithm_.empty()) { + LOG(LS_INFO) << "Checking against specified digest"; + // The peer certificate digest was specified + unsigned char digest[64]; // Maximum size + size_t digest_length; + + if (!peer_cert.ComputeDigest( + stream->peer_certificate_digest_algorithm_, + digest, sizeof(digest), &digest_length)) { + LOG(LS_ERROR) << "Digest computation failed"; + } else { + Buffer computed_digest(digest, digest_length); + if (computed_digest == stream->peer_certificate_digest_value_) { + LOG(LS_INFO) << "Accepted peer certificate"; + stream->cert_ok_ = true; + } + } + } else { + // Other modes, but we haven't implemented yet + // TODO(ekr@rtfm.com): Implement real certificate validation + UNIMPLEMENTED; + } + + if (!stream->cert_ok_ && stream->ignore_bad_cert()) { + LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + stream->cert_ok_ = true; + } + + if (stream->cert_ok_) + stream->peer_certificate_.reset(new NSSCertificate(cert_list)); + + CERT_DestroyCertList(cert_list); + + if (stream->cert_ok_) + return SECSuccess; + + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + return SECFailure; +} + + +SECStatus NSSStreamAdapter::GetClientAuthDataHook(void *arg, PRFileDesc *fd, + CERTDistNames *caNames, + CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey) { + LOG(LS_INFO) << "Client cert requested"; + NSSStreamAdapter *stream = reinterpret_cast(arg); + + if (!stream->identity_.get()) { + LOG(LS_ERROR) << "No identity available"; + return SECFailure; + } + + NSSIdentity *identity = static_cast(stream->identity_.get()); + // Destroyed internally by NSS + *pRetCert = CERT_DupCertificate(identity->certificate().certificate()); + *pRetKey = SECKEY_CopyPrivateKey(identity->keypair()->privkey()); + + return SECSuccess; +} + +// RFC 5705 Key Exporter +bool NSSStreamAdapter::ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + SECStatus rv = SSL_ExportKeyingMaterial( + ssl_fd_, + label.c_str(), + checked_cast(label.size()), + use_context, + context, + checked_cast(context_len), + result, + checked_cast(result_len)); + + return rv == SECSuccess; +} + +bool NSSStreamAdapter::SetDtlsSrtpCiphers( + const std::vector& ciphers) { +#ifdef HAVE_DTLS_SRTP + std::vector internal_ciphers; + if (state_ != SSL_NONE) + return false; + + for (std::vector::const_iterator cipher = ciphers.begin(); + cipher != ciphers.end(); ++cipher) { + bool found = false; + for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; entry->cipher_id; + ++entry) { + if (*cipher == entry->external_name) { + found = true; + internal_ciphers.push_back(entry->cipher_id); + break; + } + } + + if (!found) { + LOG(LS_ERROR) << "Could not find cipher: " << *cipher; + return false; + } + } + + if (internal_ciphers.empty()) + return false; + + srtp_ciphers_ = internal_ciphers; + + return true; +#else + return false; +#endif +} + +bool NSSStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) { +#ifdef HAVE_DTLS_SRTP + ASSERT(state_ == SSL_CONNECTED); + if (state_ != SSL_CONNECTED) + return false; + + PRUint16 selected_cipher; + + SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, &selected_cipher); + if (rv == SECFailure) + return false; + + for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; + entry->cipher_id; ++entry) { + if (selected_cipher == entry->cipher_id) { + *cipher = entry->external_name; + return true; + } + } + + ASSERT(false); // This should never happen +#endif + return false; +} + + +bool NSSContext::initialized; +NSSContext *NSSContext::global_nss_context; + +// Static initialization and shutdown +NSSContext *NSSContext::Instance() { + if (!global_nss_context) { + NSSContext *new_ctx = new NSSContext(); + + if (!(new_ctx->slot_ = PK11_GetInternalSlot())) { + delete new_ctx; + goto fail; + } + + global_nss_context = new_ctx; + } + + fail: + return global_nss_context; +} + + + +bool NSSContext::InitializeSSL(VerificationCallback callback) { + ASSERT(!callback); + + if (!initialized) { + SECStatus rv; + + rv = NSS_NoDB_Init(NULL); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't initialize NSS error=" << + PORT_GetError(); + return false; + } + + NSS_SetDomesticPolicy(); + + initialized = true; + } + + return true; +} + +bool NSSContext::InitializeSSLThread() { + // Not needed + return true; +} + +bool NSSContext::CleanupSSL() { + // Not needed + return true; +} + +bool NSSStreamAdapter::HaveDtls() { + return true; +} + +bool NSSStreamAdapter::HaveDtlsSrtp() { +#ifdef HAVE_DTLS_SRTP + return true; +#else + return false; +#endif +} + +bool NSSStreamAdapter::HaveExporter() { + return true; +} + +} // namespace rtc + +#endif // HAVE_NSS_SSL_H diff --git a/webrtc/base/nssstreamadapter.h b/webrtc/base/nssstreamadapter.h new file mode 100644 index 000000000..210a47933 --- /dev/null +++ b/webrtc/base/nssstreamadapter.h @@ -0,0 +1,111 @@ +/* + * Copyright 2004 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_BASE_NSSSTREAMADAPTER_H_ +#define WEBRTC_BASE_NSSSTREAMADAPTER_H_ + +#include +#include + +#include "nspr.h" +#include "nss.h" +#include "secmodt.h" + +#include "webrtc/base/buffer.h" +#include "webrtc/base/nssidentity.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/sslstreamadapterhelper.h" + +namespace rtc { + +// Singleton +class NSSContext { + public: + NSSContext() {} + ~NSSContext() { + } + + static PK11SlotInfo *GetSlot() { + return Instance() ? Instance()->slot_: NULL; + } + + static NSSContext *Instance(); + static bool InitializeSSL(VerificationCallback callback); + static bool InitializeSSLThread(); + static bool CleanupSSL(); + + private: + PK11SlotInfo *slot_; // The PKCS-11 slot + static bool initialized; // Was this initialized? + static NSSContext *global_nss_context; // The global context +}; + + +class NSSStreamAdapter : public SSLStreamAdapterHelper { + public: + explicit NSSStreamAdapter(StreamInterface* stream); + virtual ~NSSStreamAdapter(); + bool Init(); + + virtual StreamResult Read(void* data, size_t data_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + void OnMessage(Message *msg); + + // Key Extractor interface + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len); + + // DTLS-SRTP interface + virtual bool SetDtlsSrtpCiphers(const std::vector& ciphers); + virtual bool GetDtlsSrtpCipher(std::string* cipher); + + // Capabilities interfaces + static bool HaveDtls(); + static bool HaveDtlsSrtp(); + static bool HaveExporter(); + + protected: + // Override SSLStreamAdapter + virtual void OnEvent(StreamInterface* stream, int events, int err); + + // Override SSLStreamAdapterHelper + virtual int BeginSSL(); + virtual void Cleanup(); + virtual bool GetDigestLength(const std::string& algorithm, size_t* length) { + return NSSCertificate::GetDigestLength(algorithm, length); + } + + private: + int ContinueSSL(); + static SECStatus AuthCertificateHook(void *arg, PRFileDesc *fd, + PRBool checksig, PRBool isServer); + static SECStatus GetClientAuthDataHook(void *arg, PRFileDesc *fd, + CERTDistNames *caNames, + CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey); + + PRFileDesc *ssl_fd_; // NSS's SSL file descriptor + static bool initialized; // Was InitializeSSL() called? + bool cert_ok_; // Did we get and check a cert + std::vector srtp_ciphers_; // SRTP cipher list + + static PRDescIdentity nspr_layer_identity; // The NSPR layer identity +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NSSSTREAMADAPTER_H_ diff --git a/webrtc/base/nullsocketserver.h b/webrtc/base/nullsocketserver.h new file mode 100644 index 000000000..5378e4315 --- /dev/null +++ b/webrtc/base/nullsocketserver.h @@ -0,0 +1,61 @@ +/* + * Copyright 2012 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_BASE_NULLSOCKETSERVER_H_ +#define WEBRTC_BASE_NULLSOCKETSERVER_H_ + +#include "webrtc/base/event.h" +#include "webrtc/base/physicalsocketserver.h" + +namespace rtc { + +// NullSocketServer + +class NullSocketServer : public rtc::SocketServer { + public: + NullSocketServer() : event_(false, false) {} + + virtual bool Wait(int cms, bool process_io) { + event_.Wait(cms); + return true; + } + + virtual void WakeUp() { + event_.Set(); + } + + virtual rtc::Socket* CreateSocket(int type) { + ASSERT(false); + return NULL; + } + + virtual rtc::Socket* CreateSocket(int family, int type) { + ASSERT(false); + return NULL; + } + + virtual rtc::AsyncSocket* CreateAsyncSocket(int type) { + ASSERT(false); + return NULL; + } + + virtual rtc::AsyncSocket* CreateAsyncSocket(int family, int type) { + ASSERT(false); + return NULL; + } + + + private: + rtc::Event event_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NULLSOCKETSERVER_H_ diff --git a/webrtc/base/nullsocketserver_unittest.cc b/webrtc/base/nullsocketserver_unittest.cc new file mode 100644 index 000000000..fe21f6ad0 --- /dev/null +++ b/webrtc/base/nullsocketserver_unittest.cc @@ -0,0 +1,47 @@ +/* + * Copyright 2012 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 "webrtc/base/gunit.h" +#include "webrtc/base/nullsocketserver.h" + +namespace rtc { + +static const uint32 kTimeout = 5000U; + +class NullSocketServerTest + : public testing::Test, + public MessageHandler { + public: + NullSocketServerTest() {} + protected: + virtual void OnMessage(Message* message) { + ss_.WakeUp(); + } + NullSocketServer ss_; +}; + +TEST_F(NullSocketServerTest, WaitAndSet) { + Thread thread; + EXPECT_TRUE(thread.Start()); + thread.Post(this, 0); + // The process_io will be ignored. + const bool process_io = true; + EXPECT_TRUE_WAIT(ss_.Wait(rtc::kForever, process_io), kTimeout); +} + +TEST_F(NullSocketServerTest, TestWait) { + uint32 start = Time(); + ss_.Wait(200, true); + // The actual wait time is dependent on the resolution of the timer used by + // the Event class. Allow for the event to signal ~20ms early. + EXPECT_GE(TimeSince(start), 180); +} + +} // namespace rtc diff --git a/webrtc/base/openssl.h b/webrtc/base/openssl.h new file mode 100644 index 000000000..2071619d5 --- /dev/null +++ b/webrtc/base/openssl.h @@ -0,0 +1,20 @@ +/* + * Copyright 2013 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_BASE_OPENSSL_H_ +#define WEBRTC_BASE_OPENSSL_H_ + +#include + +#if (OPENSSL_VERSION_NUMBER < 0x10000000L) +#error OpenSSL is older than 1.0.0, which is the minimum supported version. +#endif + +#endif // WEBRTC_BASE_OPENSSL_H_ diff --git a/webrtc/base/openssladapter.cc b/webrtc/base/openssladapter.cc new file mode 100644 index 000000000..3618aadaa --- /dev/null +++ b/webrtc/base/openssladapter.cc @@ -0,0 +1,884 @@ +/* + * Copyright 2008 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. + */ + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/openssladapter.h" + +#if defined(WEBRTC_POSIX) +#include +#endif + +// Must be included first before openssl headers. +#include "webrtc/base/win32.h" // NOLINT + +#include +#include +#include +#include +#include +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/openssl.h" +#include "webrtc/base/sslroots.h" +#include "webrtc/base/stringutils.h" + +// TODO: Use a nicer abstraction for mutex. + +#if defined(WEBRTC_WIN) + #define MUTEX_TYPE HANDLE + #define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL) + #define MUTEX_CLEANUP(x) CloseHandle(x) + #define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE) + #define MUTEX_UNLOCK(x) ReleaseMutex(x) + #define THREAD_ID GetCurrentThreadId() +#elif defined(WEBRTC_POSIX) + #define MUTEX_TYPE pthread_mutex_t + #define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL) + #define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x)) + #define MUTEX_LOCK(x) pthread_mutex_lock(&(x)) + #define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x)) + #define THREAD_ID pthread_self() +#else + #error You must define mutex operations appropriate for your platform! +#endif + +struct CRYPTO_dynlock_value { + MUTEX_TYPE mutex; +}; + +////////////////////////////////////////////////////////////////////// +// SocketBIO +////////////////////////////////////////////////////////////////////// + +static int socket_write(BIO* h, const char* buf, int num); +static int socket_read(BIO* h, char* buf, int size); +static int socket_puts(BIO* h, const char* str); +static long socket_ctrl(BIO* h, int cmd, long arg1, void* arg2); +static int socket_new(BIO* h); +static int socket_free(BIO* data); + +static BIO_METHOD methods_socket = { + BIO_TYPE_BIO, + "socket", + socket_write, + socket_read, + socket_puts, + 0, + socket_ctrl, + socket_new, + socket_free, + NULL, +}; + +static BIO_METHOD* BIO_s_socket2() { return(&methods_socket); } + +static BIO* BIO_new_socket(rtc::AsyncSocket* socket) { + BIO* ret = BIO_new(BIO_s_socket2()); + if (ret == NULL) { + return NULL; + } + ret->ptr = socket; + return ret; +} + +static int socket_new(BIO* b) { + b->shutdown = 0; + b->init = 1; + b->num = 0; // 1 means socket closed + b->ptr = 0; + return 1; +} + +static int socket_free(BIO* b) { + if (b == NULL) + return 0; + return 1; +} + +static int socket_read(BIO* b, char* out, int outl) { + if (!out) + return -1; + rtc::AsyncSocket* socket = static_cast(b->ptr); + BIO_clear_retry_flags(b); + int result = socket->Recv(out, outl); + if (result > 0) { + return result; + } else if (result == 0) { + b->num = 1; + } else if (socket->IsBlocking()) { + BIO_set_retry_read(b); + } + return -1; +} + +static int socket_write(BIO* b, const char* in, int inl) { + if (!in) + return -1; + rtc::AsyncSocket* socket = static_cast(b->ptr); + BIO_clear_retry_flags(b); + int result = socket->Send(in, inl); + if (result > 0) { + return result; + } else if (socket->IsBlocking()) { + BIO_set_retry_write(b); + } + return -1; +} + +static int socket_puts(BIO* b, const char* str) { + return socket_write(b, str, strlen(str)); +} + +static long socket_ctrl(BIO* b, int cmd, long num, void* ptr) { + RTC_UNUSED(num); + RTC_UNUSED(ptr); + + switch (cmd) { + case BIO_CTRL_RESET: + return 0; + case BIO_CTRL_EOF: + return b->num; + case BIO_CTRL_WPENDING: + case BIO_CTRL_PENDING: + return 0; + case BIO_CTRL_FLUSH: + return 1; + default: + return 0; + } +} + +///////////////////////////////////////////////////////////////////////////// +// OpenSSLAdapter +///////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +// This array will store all of the mutexes available to OpenSSL. +static MUTEX_TYPE* mutex_buf = NULL; + +static void locking_function(int mode, int n, const char * file, int line) { + if (mode & CRYPTO_LOCK) { + MUTEX_LOCK(mutex_buf[n]); + } else { + MUTEX_UNLOCK(mutex_buf[n]); + } +} + +static unsigned long id_function() { // NOLINT + // Use old-style C cast because THREAD_ID's type varies with the platform, + // in some cases requiring static_cast, and in others requiring + // reinterpret_cast. + return (unsigned long)THREAD_ID; // NOLINT +} + +static CRYPTO_dynlock_value* dyn_create_function(const char* file, int line) { + CRYPTO_dynlock_value* value = new CRYPTO_dynlock_value; + if (!value) + return NULL; + MUTEX_SETUP(value->mutex); + return value; +} + +static void dyn_lock_function(int mode, CRYPTO_dynlock_value* l, + const char* file, int line) { + if (mode & CRYPTO_LOCK) { + MUTEX_LOCK(l->mutex); + } else { + MUTEX_UNLOCK(l->mutex); + } +} + +static void dyn_destroy_function(CRYPTO_dynlock_value* l, + const char* file, int line) { + MUTEX_CLEANUP(l->mutex); + delete l; +} + +VerificationCallback OpenSSLAdapter::custom_verify_callback_ = NULL; + +bool OpenSSLAdapter::InitializeSSL(VerificationCallback callback) { + if (!InitializeSSLThread() || !SSL_library_init()) + return false; +#if !defined(ADDRESS_SANITIZER) || !defined(WEBRTC_MAC) || defined(WEBRTC_IOS) + // Loading the error strings crashes mac_asan. Omit this debugging aid there. + SSL_load_error_strings(); +#endif + ERR_load_BIO_strings(); + OpenSSL_add_all_algorithms(); + RAND_poll(); + custom_verify_callback_ = callback; + return true; +} + +bool OpenSSLAdapter::InitializeSSLThread() { + mutex_buf = new MUTEX_TYPE[CRYPTO_num_locks()]; + if (!mutex_buf) + return false; + for (int i = 0; i < CRYPTO_num_locks(); ++i) + MUTEX_SETUP(mutex_buf[i]); + + // we need to cast our id_function to return an unsigned long -- pthread_t is + // a pointer + CRYPTO_set_id_callback(id_function); + CRYPTO_set_locking_callback(locking_function); + CRYPTO_set_dynlock_create_callback(dyn_create_function); + CRYPTO_set_dynlock_lock_callback(dyn_lock_function); + CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); + return true; +} + +bool OpenSSLAdapter::CleanupSSL() { + if (!mutex_buf) + return false; + CRYPTO_set_id_callback(NULL); + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_dynlock_create_callback(NULL); + CRYPTO_set_dynlock_lock_callback(NULL); + CRYPTO_set_dynlock_destroy_callback(NULL); + for (int i = 0; i < CRYPTO_num_locks(); ++i) + MUTEX_CLEANUP(mutex_buf[i]); + delete [] mutex_buf; + mutex_buf = NULL; + return true; +} + +OpenSSLAdapter::OpenSSLAdapter(AsyncSocket* socket) + : SSLAdapter(socket), + state_(SSL_NONE), + ssl_read_needs_write_(false), + ssl_write_needs_read_(false), + restartable_(false), + ssl_(NULL), ssl_ctx_(NULL), + custom_verification_succeeded_(false) { +} + +OpenSSLAdapter::~OpenSSLAdapter() { + Cleanup(); +} + +int +OpenSSLAdapter::StartSSL(const char* hostname, bool restartable) { + if (state_ != SSL_NONE) + return -1; + + ssl_host_name_ = hostname; + restartable_ = restartable; + + if (socket_->GetState() != Socket::CS_CONNECTED) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +int +OpenSSLAdapter::BeginSSL() { + LOG(LS_INFO) << "BeginSSL: " << ssl_host_name_; + ASSERT(state_ == SSL_CONNECTING); + + int err = 0; + BIO* bio = NULL; + + // First set up the context + if (!ssl_ctx_) + ssl_ctx_ = SetupSSLContext(); + + if (!ssl_ctx_) { + err = -1; + goto ssl_error; + } + + bio = BIO_new_socket(static_cast(socket_)); + if (!bio) { + err = -1; + goto ssl_error; + } + + ssl_ = SSL_new(ssl_ctx_); + if (!ssl_) { + err = -1; + goto ssl_error; + } + + SSL_set_app_data(ssl_, this); + + SSL_set_bio(ssl_, bio, bio); + SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + // the SSL object owns the bio now + bio = NULL; + + // Do the connect + err = ContinueSSL(); + if (err != 0) + goto ssl_error; + + return err; + +ssl_error: + Cleanup(); + if (bio) + BIO_free(bio); + + return err; +} + +int +OpenSSLAdapter::ContinueSSL() { + ASSERT(state_ == SSL_CONNECTING); + + int code = SSL_connect(ssl_); + switch (SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + if (!SSLPostConnectionCheck(ssl_, ssl_host_name_.c_str())) { + LOG(LS_ERROR) << "TLS post connection check failed"; + // make sure we close the socket + Cleanup(); + // The connect failed so return -1 to shut down the socket + return -1; + } + + state_ = SSL_CONNECTED; + AsyncSocketAdapter::OnConnectEvent(this); +#if 0 // TODO: worry about this + // Don't let ourselves go away during the callbacks + PRefPtr lock(this); + LOG(LS_INFO) << " -- onStreamReadable"; + AsyncSocketAdapter::OnReadEvent(this); + LOG(LS_INFO) << " -- onStreamWriteable"; + AsyncSocketAdapter::OnWriteEvent(this); +#endif + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + + case SSL_ERROR_ZERO_RETURN: + default: + LOG(LS_WARNING) << "ContinueSSL -- error " << code; + return (code != 0) ? code : -1; + } + + return 0; +} + +void +OpenSSLAdapter::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "OpenSSLAdapter::Error(" + << context << ", " << err << ")"; + state_ = SSL_ERROR; + SetError(err); + if (signal) + AsyncSocketAdapter::OnCloseEvent(this, err); +} + +void +OpenSSLAdapter::Cleanup() { + LOG(LS_INFO) << "Cleanup"; + + state_ = SSL_NONE; + ssl_read_needs_write_ = false; + ssl_write_needs_read_ = false; + custom_verification_succeeded_ = false; + + if (ssl_) { + SSL_free(ssl_); + ssl_ = NULL; + } + + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + ssl_ctx_ = NULL; + } +} + +// +// AsyncSocket Implementation +// + +int +OpenSSLAdapter::Send(const void* pv, size_t cb) { + //LOG(LS_INFO) << "OpenSSLAdapter::Send(" << cb << ")"; + + switch (state_) { + case SSL_NONE: + return AsyncSocketAdapter::Send(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + // OpenSSL will return an error if we try to write zero bytes + if (cb == 0) + return 0; + + ssl_write_needs_read_ = false; + + int code = SSL_write(ssl_, pv, cb); + switch (SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + //LOG(LS_INFO) << " -- success"; + return code; + case SSL_ERROR_WANT_READ: + //LOG(LS_INFO) << " -- error want read"; + ssl_write_needs_read_ = true; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_WANT_WRITE: + //LOG(LS_INFO) << " -- error want write"; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_ZERO_RETURN: + //LOG(LS_INFO) << " -- remote side closed"; + SetError(EWOULDBLOCK); + // do we need to signal closure? + break; + default: + //LOG(LS_INFO) << " -- error " << code; + Error("SSL_write", (code ? code : -1), false); + break; + } + + return SOCKET_ERROR; +} + +int +OpenSSLAdapter::Recv(void* pv, size_t cb) { + //LOG(LS_INFO) << "OpenSSLAdapter::Recv(" << cb << ")"; + switch (state_) { + + case SSL_NONE: + return AsyncSocketAdapter::Recv(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + // Don't trust OpenSSL with zero byte reads + if (cb == 0) + return 0; + + ssl_read_needs_write_ = false; + + int code = SSL_read(ssl_, pv, cb); + switch (SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + //LOG(LS_INFO) << " -- success"; + return code; + case SSL_ERROR_WANT_READ: + //LOG(LS_INFO) << " -- error want read"; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_WANT_WRITE: + //LOG(LS_INFO) << " -- error want write"; + ssl_read_needs_write_ = true; + SetError(EWOULDBLOCK); + break; + case SSL_ERROR_ZERO_RETURN: + //LOG(LS_INFO) << " -- remote side closed"; + SetError(EWOULDBLOCK); + // do we need to signal closure? + break; + default: + //LOG(LS_INFO) << " -- error " << code; + Error("SSL_read", (code ? code : -1), false); + break; + } + + return SOCKET_ERROR; +} + +int +OpenSSLAdapter::Close() { + Cleanup(); + state_ = restartable_ ? SSL_WAIT : SSL_NONE; + return AsyncSocketAdapter::Close(); +} + +Socket::ConnState +OpenSSLAdapter::GetState() const { + //if (signal_close_) + // return CS_CONNECTED; + ConnState state = socket_->GetState(); + if ((state == CS_CONNECTED) + && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING))) + state = CS_CONNECTING; + return state; +} + +void +OpenSSLAdapter::OnConnectEvent(AsyncSocket* socket) { + LOG(LS_INFO) << "OpenSSLAdapter::OnConnectEvent"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + AsyncSocketAdapter::OnConnectEvent(socket); + return; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + AsyncSocketAdapter::OnCloseEvent(socket, err); + } +} + +void +OpenSSLAdapter::OnReadEvent(AsyncSocket* socket) { + //LOG(LS_INFO) << "OpenSSLAdapter::OnReadEvent"; + + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err); + } + return; + } + + if (state_ != SSL_CONNECTED) + return; + + // Don't let ourselves go away during the callbacks + //PRefPtr lock(this); // TODO: fix this + if (ssl_write_needs_read_) { + //LOG(LS_INFO) << " -- onStreamWriteable"; + AsyncSocketAdapter::OnWriteEvent(socket); + } + + //LOG(LS_INFO) << " -- onStreamReadable"; + AsyncSocketAdapter::OnReadEvent(socket); +} + +void +OpenSSLAdapter::OnWriteEvent(AsyncSocket* socket) { + //LOG(LS_INFO) << "OpenSSLAdapter::OnWriteEvent"; + + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnWriteEvent(socket); + return; + } + + if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err); + } + return; + } + + if (state_ != SSL_CONNECTED) + return; + + // Don't let ourselves go away during the callbacks + //PRefPtr lock(this); // TODO: fix this + + if (ssl_read_needs_write_) { + //LOG(LS_INFO) << " -- onStreamReadable"; + AsyncSocketAdapter::OnReadEvent(socket); + } + + //LOG(LS_INFO) << " -- onStreamWriteable"; + AsyncSocketAdapter::OnWriteEvent(socket); +} + +void +OpenSSLAdapter::OnCloseEvent(AsyncSocket* socket, int err) { + LOG(LS_INFO) << "OpenSSLAdapter::OnCloseEvent(" << err << ")"; + AsyncSocketAdapter::OnCloseEvent(socket, err); +} + +// This code is taken from the "Network Security with OpenSSL" +// sample in chapter 5 + +bool OpenSSLAdapter::VerifyServerName(SSL* ssl, const char* host, + bool ignore_bad_cert) { + if (!host) + return false; + + // Checking the return from SSL_get_peer_certificate here is not strictly + // necessary. With our setup, it is not possible for it to return + // NULL. However, it is good form to check the return. + X509* certificate = SSL_get_peer_certificate(ssl); + if (!certificate) + return false; + + // Logging certificates is extremely verbose. So it is disabled by default. +#ifdef LOG_CERTIFICATES + { + LOG(LS_INFO) << "Certificate from server:"; + BIO* mem = BIO_new(BIO_s_mem()); + X509_print_ex(mem, certificate, XN_FLAG_SEP_CPLUS_SPC, X509_FLAG_NO_HEADER); + BIO_write(mem, "\0", 1); + char* buffer; + BIO_get_mem_data(mem, &buffer); + LOG(LS_INFO) << buffer; + BIO_free(mem); + + char* cipher_description = + SSL_CIPHER_description(SSL_get_current_cipher(ssl), NULL, 128); + LOG(LS_INFO) << "Cipher: " << cipher_description; + OPENSSL_free(cipher_description); + } +#endif + + bool ok = false; + int extension_count = X509_get_ext_count(certificate); + for (int i = 0; i < extension_count; ++i) { + X509_EXTENSION* extension = X509_get_ext(certificate, i); + int extension_nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension)); + + if (extension_nid == NID_subject_alt_name) { + const X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension); + if (!meth) + break; + + void* ext_str = NULL; + + // We assign this to a local variable, instead of passing the address + // directly to ASN1_item_d2i. + // See http://readlist.com/lists/openssl.org/openssl-users/0/4761.html. + unsigned char* ext_value_data = extension->value->data; + + const unsigned char **ext_value_data_ptr = + (const_cast(&ext_value_data)); + + if (meth->it) { + ext_str = ASN1_item_d2i(NULL, ext_value_data_ptr, + extension->value->length, + ASN1_ITEM_ptr(meth->it)); + } else { + ext_str = meth->d2i(NULL, ext_value_data_ptr, extension->value->length); + } + + STACK_OF(CONF_VALUE)* value = meth->i2v(meth, ext_str, NULL); + for (int j = 0; j < sk_CONF_VALUE_num(value); ++j) { + CONF_VALUE* nval = sk_CONF_VALUE_value(value, j); + // The value for nval can contain wildcards + if (!strcmp(nval->name, "DNS") && string_match(host, nval->value)) { + ok = true; + break; + } + } + sk_CONF_VALUE_pop_free(value, X509V3_conf_free); + value = NULL; + + if (meth->it) { + ASN1_item_free(reinterpret_cast(ext_str), + ASN1_ITEM_ptr(meth->it)); + } else { + meth->ext_free(ext_str); + } + ext_str = NULL; + } + if (ok) + break; + } + + char data[256]; + X509_name_st* subject; + if (!ok + && ((subject = X509_get_subject_name(certificate)) != NULL) + && (X509_NAME_get_text_by_NID(subject, NID_commonName, + data, sizeof(data)) > 0)) { + data[sizeof(data)-1] = 0; + if (_stricmp(data, host) == 0) + ok = true; + } + + X509_free(certificate); + + // This should only ever be turned on for debugging and development. + if (!ok && ignore_bad_cert) { + LOG(LS_WARNING) << "TLS certificate check FAILED. " + << "Allowing connection anyway."; + ok = true; + } + + return ok; +} + +bool OpenSSLAdapter::SSLPostConnectionCheck(SSL* ssl, const char* host) { + bool ok = VerifyServerName(ssl, host, ignore_bad_cert()); + + if (ok) { + ok = (SSL_get_verify_result(ssl) == X509_V_OK || + custom_verification_succeeded_); + } + + if (!ok && ignore_bad_cert()) { + LOG(LS_INFO) << "Other TLS post connection checks failed."; + ok = true; + } + + return ok; +} + +#if _DEBUG + +// We only use this for tracing and so it is only needed in debug mode + +void +OpenSSLAdapter::SSLInfoCallback(const SSL* s, int where, int ret) { + const char* str = "undefined"; + int w = where & ~SSL_ST_MASK; + if (w & SSL_ST_CONNECT) { + str = "SSL_connect"; + } else if (w & SSL_ST_ACCEPT) { + str = "SSL_accept"; + } + if (where & SSL_CB_LOOP) { + LOG(LS_INFO) << str << ":" << SSL_state_string_long(s); + } else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + LOG(LS_INFO) << "SSL3 alert " << str + << ":" << SSL_alert_type_string_long(ret) + << ":" << SSL_alert_desc_string_long(ret); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) { + LOG(LS_INFO) << str << ":failed in " << SSL_state_string_long(s); + } else if (ret < 0) { + LOG(LS_INFO) << str << ":error in " << SSL_state_string_long(s); + } + } +} + +#endif // _DEBUG + +int +OpenSSLAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { +#if _DEBUG + if (!ok) { + char data[256]; + X509* cert = X509_STORE_CTX_get_current_cert(store); + int depth = X509_STORE_CTX_get_error_depth(store); + int err = X509_STORE_CTX_get_error(store); + + LOG(LS_INFO) << "Error with certificate at depth: " << depth; + X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data)); + LOG(LS_INFO) << " issuer = " << data; + X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data)); + LOG(LS_INFO) << " subject = " << data; + LOG(LS_INFO) << " err = " << err + << ":" << X509_verify_cert_error_string(err); + } +#endif + + // Get our stream pointer from the store + SSL* ssl = reinterpret_cast( + X509_STORE_CTX_get_ex_data(store, + SSL_get_ex_data_X509_STORE_CTX_idx())); + + OpenSSLAdapter* stream = + reinterpret_cast(SSL_get_app_data(ssl)); + + if (!ok && custom_verify_callback_) { + void* cert = + reinterpret_cast(X509_STORE_CTX_get_current_cert(store)); + if (custom_verify_callback_(cert)) { + stream->custom_verification_succeeded_ = true; + LOG(LS_INFO) << "validated certificate using custom callback"; + ok = true; + } + } + + // Should only be used for debugging and development. + if (!ok && stream->ignore_bad_cert()) { + LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + ok = 1; + } + + return ok; +} + +bool OpenSSLAdapter::ConfigureTrustedRootCertificates(SSL_CTX* ctx) { + // Add the root cert that we care about to the SSL context + int count_of_added_certs = 0; + for (int i = 0; i < ARRAY_SIZE(kSSLCertCertificateList); i++) { + const unsigned char* cert_buffer = kSSLCertCertificateList[i]; + size_t cert_buffer_len = kSSLCertCertificateSizeList[i]; + X509* cert = d2i_X509(NULL, &cert_buffer, cert_buffer_len); + if (cert) { + int return_value = X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert); + if (return_value == 0) { + LOG(LS_WARNING) << "Unable to add certificate."; + } else { + count_of_added_certs++; + } + X509_free(cert); + } + } + return count_of_added_certs > 0; +} + +SSL_CTX* +OpenSSLAdapter::SetupSSLContext() { + SSL_CTX* ctx = SSL_CTX_new(TLSv1_client_method()); + if (ctx == NULL) { + unsigned long error = ERR_get_error(); // NOLINT: type used by OpenSSL. + LOG(LS_WARNING) << "SSL_CTX creation failed: " + << '"' << ERR_reason_error_string(error) << "\" " + << "(error=" << error << ')'; + return NULL; + } + if (!ConfigureTrustedRootCertificates(ctx)) { + SSL_CTX_free(ctx); + return NULL; + } + +#ifdef _DEBUG + SSL_CTX_set_info_callback(ctx, SSLInfoCallback); +#endif + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback); + SSL_CTX_set_verify_depth(ctx, 4); + SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + + return ctx; +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H diff --git a/webrtc/base/openssladapter.h b/webrtc/base/openssladapter.h new file mode 100644 index 000000000..d244a7f5c --- /dev/null +++ b/webrtc/base/openssladapter.h @@ -0,0 +1,88 @@ +/* + * Copyright 2004 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_BASE_OPENSSLADAPTER_H__ +#define WEBRTC_BASE_OPENSSLADAPTER_H__ + +#include +#include "webrtc/base/ssladapter.h" + +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct x509_store_ctx_st X509_STORE_CTX; + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class OpenSSLAdapter : public SSLAdapter { +public: + static bool InitializeSSL(VerificationCallback callback); + static bool InitializeSSLThread(); + static bool CleanupSSL(); + + OpenSSLAdapter(AsyncSocket* socket); + virtual ~OpenSSLAdapter(); + + virtual int StartSSL(const char* hostname, bool restartable); + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + virtual int Close(); + + // Note that the socket returns ST_CONNECTING while SSL is being negotiated. + virtual ConnState GetState() const; + +protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void OnReadEvent(AsyncSocket* socket); + virtual void OnWriteEvent(AsyncSocket* socket); + virtual void OnCloseEvent(AsyncSocket* socket, int err); + +private: + enum SSLState { + SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR + }; + + int BeginSSL(); + int ContinueSSL(); + void Error(const char* context, int err, bool signal = true); + void Cleanup(); + + static bool VerifyServerName(SSL* ssl, const char* host, + bool ignore_bad_cert); + bool SSLPostConnectionCheck(SSL* ssl, const char* host); +#if _DEBUG + static void SSLInfoCallback(const SSL* s, int where, int ret); +#endif // !_DEBUG + static int SSLVerifyCallback(int ok, X509_STORE_CTX* store); + static VerificationCallback custom_verify_callback_; + friend class OpenSSLStreamAdapter; // for custom_verify_callback_; + + static bool ConfigureTrustedRootCertificates(SSL_CTX* ctx); + static SSL_CTX* SetupSSLContext(); + + SSLState state_; + bool ssl_read_needs_write_; + bool ssl_write_needs_read_; + // If true, socket will retain SSL configuration after Close. + bool restartable_; + + SSL* ssl_; + SSL_CTX* ssl_ctx_; + std::string ssl_host_name_; + + bool custom_verification_succeeded_; +}; + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLADAPTER_H__ diff --git a/webrtc/base/openssldigest.cc b/webrtc/base/openssldigest.cc new file mode 100644 index 000000000..0d22f4329 --- /dev/null +++ b/webrtc/base/openssldigest.cc @@ -0,0 +1,122 @@ +/* + * Copyright 2004 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. + */ + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/openssldigest.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/openssl.h" + +namespace rtc { + +OpenSSLDigest::OpenSSLDigest(const std::string& algorithm) { + EVP_MD_CTX_init(&ctx_); + if (GetDigestEVP(algorithm, &md_)) { + EVP_DigestInit_ex(&ctx_, md_, NULL); + } else { + md_ = NULL; + } +} + +OpenSSLDigest::~OpenSSLDigest() { + EVP_MD_CTX_cleanup(&ctx_); +} + +size_t OpenSSLDigest::Size() const { + if (!md_) { + return 0; + } + return EVP_MD_size(md_); +} + +void OpenSSLDigest::Update(const void* buf, size_t len) { + if (!md_) { + return; + } + EVP_DigestUpdate(&ctx_, buf, len); +} + +size_t OpenSSLDigest::Finish(void* buf, size_t len) { + if (!md_ || len < Size()) { + return 0; + } + unsigned int md_len; + EVP_DigestFinal_ex(&ctx_, static_cast(buf), &md_len); + EVP_DigestInit_ex(&ctx_, md_, NULL); // prepare for future Update()s + ASSERT(md_len == Size()); + return md_len; +} + +bool OpenSSLDigest::GetDigestEVP(const std::string& algorithm, + const EVP_MD** mdp) { + const EVP_MD* md; + if (algorithm == DIGEST_MD5) { + md = EVP_md5(); + } else if (algorithm == DIGEST_SHA_1) { + md = EVP_sha1(); + } else if (algorithm == DIGEST_SHA_224) { + md = EVP_sha224(); + } else if (algorithm == DIGEST_SHA_256) { + md = EVP_sha256(); + } else if (algorithm == DIGEST_SHA_384) { + md = EVP_sha384(); + } else if (algorithm == DIGEST_SHA_512) { + md = EVP_sha512(); + } else { + return false; + } + + // Can't happen + ASSERT(EVP_MD_size(md) >= 16); + *mdp = md; + return true; +} + +bool OpenSSLDigest::GetDigestName(const EVP_MD* md, + std::string* algorithm) { + ASSERT(md != NULL); + ASSERT(algorithm != NULL); + + int md_type = EVP_MD_type(md); + if (md_type == NID_md5) { + *algorithm = DIGEST_MD5; + } else if (md_type == NID_sha1) { + *algorithm = DIGEST_SHA_1; + } else if (md_type == NID_sha224) { + *algorithm = DIGEST_SHA_224; + } else if (md_type == NID_sha256) { + *algorithm = DIGEST_SHA_256; + } else if (md_type == NID_sha384) { + *algorithm = DIGEST_SHA_384; + } else if (md_type == NID_sha512) { + *algorithm = DIGEST_SHA_512; + } else { + algorithm->clear(); + return false; + } + + return true; +} + +bool OpenSSLDigest::GetDigestSize(const std::string& algorithm, + size_t* length) { + const EVP_MD *md; + if (!GetDigestEVP(algorithm, &md)) + return false; + + *length = EVP_MD_size(md); + return true; +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H + diff --git a/webrtc/base/openssldigest.h b/webrtc/base/openssldigest.h new file mode 100644 index 000000000..c4b0d8aed --- /dev/null +++ b/webrtc/base/openssldigest.h @@ -0,0 +1,50 @@ +/* + * Copyright 2004 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_BASE_OPENSSLDIGEST_H_ +#define WEBRTC_BASE_OPENSSLDIGEST_H_ + +#include + +#include "webrtc/base/messagedigest.h" + +namespace rtc { + +// An implementation of the digest class that uses OpenSSL. +class OpenSSLDigest : public MessageDigest { + public: + // Creates an OpenSSLDigest with |algorithm| as the hash algorithm. + explicit OpenSSLDigest(const std::string& algorithm); + ~OpenSSLDigest(); + // Returns the digest output size (e.g. 16 bytes for MD5). + virtual size_t Size() const; + // Updates the digest with |len| bytes from |buf|. + virtual void Update(const void* buf, size_t len); + // Outputs the digest value to |buf| with length |len|. + virtual size_t Finish(void* buf, size_t len); + + // Helper function to look up a digest's EVP by name. + static bool GetDigestEVP(const std::string &algorithm, + const EVP_MD** md); + // Helper function to look up a digest's name by EVP. + static bool GetDigestName(const EVP_MD* md, + std::string* algorithm); + // Helper function to get the length of a digest. + static bool GetDigestSize(const std::string &algorithm, + size_t* len); + + private: + EVP_MD_CTX ctx_; + const EVP_MD* md_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLDIGEST_H_ diff --git a/webrtc/base/opensslidentity.cc b/webrtc/base/opensslidentity.cc new file mode 100644 index 000000000..915680ce2 --- /dev/null +++ b/webrtc/base/opensslidentity.cc @@ -0,0 +1,366 @@ +/* + * Copyright 2004 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. + */ + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/opensslidentity.h" + +// Must be included first before openssl headers. +#include "webrtc/base/win32.h" // NOLINT + +#include +#include +#include +#include +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/openssl.h" +#include "webrtc/base/openssldigest.h" + +namespace rtc { + +// We could have exposed a myriad of parameters for the crypto stuff, +// but keeping it simple seems best. + +// Strength of generated keys. Those are RSA. +static const int KEY_LENGTH = 1024; + +// Random bits for certificate serial number +static const int SERIAL_RAND_BITS = 64; + +// Certificate validity lifetime +static const int CERTIFICATE_LIFETIME = 60*60*24*30; // 30 days, arbitrarily +// Certificate validity window. +// This is to compensate for slightly incorrect system clocks. +static const int CERTIFICATE_WINDOW = -60*60*24; + +// Generate a key pair. Caller is responsible for freeing the returned object. +static EVP_PKEY* MakeKey() { + LOG(LS_INFO) << "Making key pair"; + EVP_PKEY* pkey = EVP_PKEY_new(); + // RSA_generate_key is deprecated. Use _ex version. + BIGNUM* exponent = BN_new(); + RSA* rsa = RSA_new(); + if (!pkey || !exponent || !rsa || + !BN_set_word(exponent, 0x10001) || // 65537 RSA exponent + !RSA_generate_key_ex(rsa, KEY_LENGTH, exponent, NULL) || + !EVP_PKEY_assign_RSA(pkey, rsa)) { + EVP_PKEY_free(pkey); + BN_free(exponent); + RSA_free(rsa); + return NULL; + } + // ownership of rsa struct was assigned, don't free it. + BN_free(exponent); + LOG(LS_INFO) << "Returning key pair"; + return pkey; +} + +// Generate a self-signed certificate, with the public key from the +// given key pair. Caller is responsible for freeing the returned object. +static X509* MakeCertificate(EVP_PKEY* pkey, const SSLIdentityParams& params) { + LOG(LS_INFO) << "Making certificate for " << params.common_name; + X509* x509 = NULL; + BIGNUM* serial_number = NULL; + X509_NAME* name = NULL; + + if ((x509=X509_new()) == NULL) + goto error; + + if (!X509_set_pubkey(x509, pkey)) + goto error; + + // serial number + // temporary reference to serial number inside x509 struct + ASN1_INTEGER* asn1_serial_number; + if ((serial_number = BN_new()) == NULL || + !BN_pseudo_rand(serial_number, SERIAL_RAND_BITS, 0, 0) || + (asn1_serial_number = X509_get_serialNumber(x509)) == NULL || + !BN_to_ASN1_INTEGER(serial_number, asn1_serial_number)) + goto error; + + if (!X509_set_version(x509, 0L)) // version 1 + goto error; + + // There are a lot of possible components for the name entries. In + // our P2P SSL mode however, the certificates are pre-exchanged + // (through the secure XMPP channel), and so the certificate + // identification is arbitrary. It can't be empty, so we set some + // arbitrary common_name. Note that this certificate goes out in + // clear during SSL negotiation, so there may be a privacy issue in + // putting anything recognizable here. + if ((name = X509_NAME_new()) == NULL || + !X509_NAME_add_entry_by_NID( + name, NID_commonName, MBSTRING_UTF8, + (unsigned char*)params.common_name.c_str(), -1, -1, 0) || + !X509_set_subject_name(x509, name) || + !X509_set_issuer_name(x509, name)) + goto error; + + if (!X509_gmtime_adj(X509_get_notBefore(x509), params.not_before) || + !X509_gmtime_adj(X509_get_notAfter(x509), params.not_after)) + goto error; + + if (!X509_sign(x509, pkey, EVP_sha1())) + goto error; + + BN_free(serial_number); + X509_NAME_free(name); + LOG(LS_INFO) << "Returning certificate"; + return x509; + + error: + BN_free(serial_number); + X509_NAME_free(name); + X509_free(x509); + return NULL; +} + +// This dumps the SSL error stack to the log. +static void LogSSLErrors(const std::string& prefix) { + char error_buf[200]; + unsigned long err; + + while ((err = ERR_get_error()) != 0) { + ERR_error_string_n(err, error_buf, sizeof(error_buf)); + LOG(LS_ERROR) << prefix << ": " << error_buf << "\n"; + } +} + +OpenSSLKeyPair* OpenSSLKeyPair::Generate() { + EVP_PKEY* pkey = MakeKey(); + if (!pkey) { + LogSSLErrors("Generating key pair"); + return NULL; + } + return new OpenSSLKeyPair(pkey); +} + +OpenSSLKeyPair::~OpenSSLKeyPair() { + EVP_PKEY_free(pkey_); +} + +void OpenSSLKeyPair::AddReference() { + CRYPTO_add(&pkey_->references, 1, CRYPTO_LOCK_EVP_PKEY); +} + +#ifdef _DEBUG +// Print a certificate to the log, for debugging. +static void PrintCert(X509* x509) { + BIO* temp_memory_bio = BIO_new(BIO_s_mem()); + if (!temp_memory_bio) { + LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; + return; + } + X509_print_ex(temp_memory_bio, x509, XN_FLAG_SEP_CPLUS_SPC, 0); + BIO_write(temp_memory_bio, "\0", 1); + char* buffer; + BIO_get_mem_data(temp_memory_bio, &buffer); + LOG(LS_VERBOSE) << buffer; + BIO_free(temp_memory_bio); +} +#endif + +OpenSSLCertificate* OpenSSLCertificate::Generate( + OpenSSLKeyPair* key_pair, const SSLIdentityParams& params) { + SSLIdentityParams actual_params(params); + if (actual_params.common_name.empty()) { + // Use a random string, arbitrarily 8chars long. + actual_params.common_name = CreateRandomString(8); + } + X509* x509 = MakeCertificate(key_pair->pkey(), actual_params); + if (!x509) { + LogSSLErrors("Generating certificate"); + return NULL; + } +#ifdef _DEBUG + PrintCert(x509); +#endif + OpenSSLCertificate* ret = new OpenSSLCertificate(x509); + X509_free(x509); + return ret; +} + +OpenSSLCertificate* OpenSSLCertificate::FromPEMString( + const std::string& pem_string) { + BIO* bio = BIO_new_mem_buf(const_cast(pem_string.c_str()), -1); + if (!bio) + return NULL; + BIO_set_mem_eof_return(bio, 0); + X509 *x509 = PEM_read_bio_X509(bio, NULL, NULL, + const_cast("\0")); + BIO_free(bio); // Frees the BIO, but not the pointed-to string. + + if (!x509) + return NULL; + + OpenSSLCertificate* ret = new OpenSSLCertificate(x509); + X509_free(x509); + return ret; +} + +// NOTE: This implementation only functions correctly after InitializeSSL +// and before CleanupSSL. +bool OpenSSLCertificate::GetSignatureDigestAlgorithm( + std::string* algorithm) const { + return OpenSSLDigest::GetDigestName( + EVP_get_digestbyobj(x509_->sig_alg->algorithm), algorithm); +} + +bool OpenSSLCertificate::ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + return ComputeDigest(x509_, algorithm, digest, size, length); +} + +bool OpenSSLCertificate::ComputeDigest(const X509* x509, + const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) { + const EVP_MD *md; + unsigned int n; + + if (!OpenSSLDigest::GetDigestEVP(algorithm, &md)) + return false; + + if (size < static_cast(EVP_MD_size(md))) + return false; + + X509_digest(x509, md, digest, &n); + + *length = n; + + return true; +} + +OpenSSLCertificate::~OpenSSLCertificate() { + X509_free(x509_); +} + +std::string OpenSSLCertificate::ToPEMString() const { + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) { + UNREACHABLE(); + return std::string(); + } + if (!PEM_write_bio_X509(bio, x509_)) { + BIO_free(bio); + UNREACHABLE(); + return std::string(); + } + BIO_write(bio, "\0", 1); + char* buffer; + BIO_get_mem_data(bio, &buffer); + std::string ret(buffer); + BIO_free(bio); + return ret; +} + +void OpenSSLCertificate::ToDER(Buffer* der_buffer) const { + // In case of failure, make sure to leave the buffer empty. + der_buffer->SetData(NULL, 0); + + // Calculates the DER representation of the certificate, from scratch. + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) { + UNREACHABLE(); + return; + } + if (!i2d_X509_bio(bio, x509_)) { + BIO_free(bio); + UNREACHABLE(); + return; + } + char* data; + size_t length = BIO_get_mem_data(bio, &data); + der_buffer->SetData(data, length); + BIO_free(bio); +} + +void OpenSSLCertificate::AddReference() const { + ASSERT(x509_ != NULL); + CRYPTO_add(&x509_->references, 1, CRYPTO_LOCK_X509); +} + +OpenSSLIdentity* OpenSSLIdentity::GenerateInternal( + const SSLIdentityParams& params) { + OpenSSLKeyPair *key_pair = OpenSSLKeyPair::Generate(); + if (key_pair) { + OpenSSLCertificate *certificate = OpenSSLCertificate::Generate( + key_pair, params); + if (certificate) + return new OpenSSLIdentity(key_pair, certificate); + delete key_pair; + } + LOG(LS_INFO) << "Identity generation failed"; + return NULL; +} + +OpenSSLIdentity* OpenSSLIdentity::Generate(const std::string& common_name) { + SSLIdentityParams params; + params.common_name = common_name; + params.not_before = CERTIFICATE_WINDOW; + params.not_after = CERTIFICATE_LIFETIME; + return GenerateInternal(params); +} + +OpenSSLIdentity* OpenSSLIdentity::GenerateForTest( + const SSLIdentityParams& params) { + return GenerateInternal(params); +} + +SSLIdentity* OpenSSLIdentity::FromPEMStrings( + const std::string& private_key, + const std::string& certificate) { + scoped_ptr cert( + OpenSSLCertificate::FromPEMString(certificate)); + if (!cert) { + LOG(LS_ERROR) << "Failed to create OpenSSLCertificate from PEM string."; + return NULL; + } + + BIO* bio = BIO_new_mem_buf(const_cast(private_key.c_str()), -1); + if (!bio) { + LOG(LS_ERROR) << "Failed to create a new BIO buffer."; + return NULL; + } + BIO_set_mem_eof_return(bio, 0); + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, + const_cast("\0")); + BIO_free(bio); // Frees the BIO, but not the pointed-to string. + + if (!pkey) { + LOG(LS_ERROR) << "Failed to create the private key from PEM string."; + return NULL; + } + + return new OpenSSLIdentity(new OpenSSLKeyPair(pkey), + cert.release()); +} + +bool OpenSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) { + // 1 is the documented success return code. + if (SSL_CTX_use_certificate(ctx, certificate_->x509()) != 1 || + SSL_CTX_use_PrivateKey(ctx, key_pair_->pkey()) != 1) { + LogSSLErrors("Configuring key and certificate"); + return false; + } + return true; +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H diff --git a/webrtc/base/opensslidentity.h b/webrtc/base/opensslidentity.h new file mode 100644 index 000000000..e52cd10a9 --- /dev/null +++ b/webrtc/base/opensslidentity.h @@ -0,0 +1,150 @@ +/* + * Copyright 2004 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_BASE_OPENSSLIDENTITY_H_ +#define WEBRTC_BASE_OPENSSLIDENTITY_H_ + +#include +#include + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslidentity.h" + +typedef struct ssl_ctx_st SSL_CTX; + +namespace rtc { + +// OpenSSLKeyPair encapsulates an OpenSSL EVP_PKEY* keypair object, +// which is reference counted inside the OpenSSL library. +class OpenSSLKeyPair { + public: + explicit OpenSSLKeyPair(EVP_PKEY* pkey) : pkey_(pkey) { + ASSERT(pkey_ != NULL); + } + + static OpenSSLKeyPair* Generate(); + + virtual ~OpenSSLKeyPair(); + + virtual OpenSSLKeyPair* GetReference() { + AddReference(); + return new OpenSSLKeyPair(pkey_); + } + + EVP_PKEY* pkey() const { return pkey_; } + + private: + void AddReference(); + + EVP_PKEY* pkey_; + + DISALLOW_EVIL_CONSTRUCTORS(OpenSSLKeyPair); +}; + +// OpenSSLCertificate encapsulates an OpenSSL X509* certificate object, +// which is also reference counted inside the OpenSSL library. +class OpenSSLCertificate : public SSLCertificate { + public: + // Caller retains ownership of the X509 object. + explicit OpenSSLCertificate(X509* x509) : x509_(x509) { + AddReference(); + } + + static OpenSSLCertificate* Generate(OpenSSLKeyPair* key_pair, + const SSLIdentityParams& params); + static OpenSSLCertificate* FromPEMString(const std::string& pem_string); + + virtual ~OpenSSLCertificate(); + + virtual OpenSSLCertificate* GetReference() const { + return new OpenSSLCertificate(x509_); + } + + X509* x509() const { return x509_; } + + virtual std::string ToPEMString() const; + + virtual void ToDER(Buffer* der_buffer) const; + + // Compute the digest of the certificate given algorithm + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const; + + // Compute the digest of a certificate as an X509 * + static bool ComputeDigest(const X509* x509, + const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length); + + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const; + + virtual bool GetChain(SSLCertChain** chain) const { + // Chains are not yet supported when using OpenSSL. + // OpenSSLStreamAdapter::SSLVerifyCallback currently requires the remote + // certificate to be self-signed. + return false; + } + + private: + void AddReference() const; + + X509* x509_; + + DISALLOW_EVIL_CONSTRUCTORS(OpenSSLCertificate); +}; + +// Holds a keypair and certificate together, and a method to generate +// them consistently. +class OpenSSLIdentity : public SSLIdentity { + public: + static OpenSSLIdentity* Generate(const std::string& common_name); + static OpenSSLIdentity* GenerateForTest(const SSLIdentityParams& params); + static SSLIdentity* FromPEMStrings(const std::string& private_key, + const std::string& certificate); + virtual ~OpenSSLIdentity() { } + + virtual const OpenSSLCertificate& certificate() const { + return *certificate_; + } + + virtual OpenSSLIdentity* GetReference() const { + return new OpenSSLIdentity(key_pair_->GetReference(), + certificate_->GetReference()); + } + + // Configure an SSL context object to use our key and certificate. + bool ConfigureIdentity(SSL_CTX* ctx); + + private: + OpenSSLIdentity(OpenSSLKeyPair* key_pair, + OpenSSLCertificate* certificate) + : key_pair_(key_pair), certificate_(certificate) { + ASSERT(key_pair != NULL); + ASSERT(certificate != NULL); + } + + static OpenSSLIdentity* GenerateInternal(const SSLIdentityParams& params); + + scoped_ptr key_pair_; + scoped_ptr certificate_; + + DISALLOW_EVIL_CONSTRUCTORS(OpenSSLIdentity); +}; + + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLIDENTITY_H_ diff --git a/webrtc/base/opensslstreamadapter.cc b/webrtc/base/opensslstreamadapter.cc new file mode 100644 index 000000000..5eaeb1b5f --- /dev/null +++ b/webrtc/base/opensslstreamadapter.cc @@ -0,0 +1,857 @@ +/* + * Copyright 2004 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. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_OPENSSL_SSL_H + +#include "webrtc/base/opensslstreamadapter.h" + +#include +#include +#include +#include +#include + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/openssl.h" +#include "webrtc/base/openssladapter.h" +#include "webrtc/base/openssldigest.h" +#include "webrtc/base/opensslidentity.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#if (OPENSSL_VERSION_NUMBER >= 0x10001000L) +#define HAVE_DTLS_SRTP +#endif + +#ifdef HAVE_DTLS_SRTP +// SRTP cipher suite table +struct SrtpCipherMapEntry { + const char* external_name; + const char* internal_name; +}; + +// This isn't elegant, but it's better than an external reference +static SrtpCipherMapEntry SrtpCipherMap[] = { + {"AES_CM_128_HMAC_SHA1_80", "SRTP_AES128_CM_SHA1_80"}, + {"AES_CM_128_HMAC_SHA1_32", "SRTP_AES128_CM_SHA1_32"}, + {NULL, NULL} +}; +#endif + +////////////////////////////////////////////////////////////////////// +// StreamBIO +////////////////////////////////////////////////////////////////////// + +static int stream_write(BIO* h, const char* buf, int num); +static int stream_read(BIO* h, char* buf, int size); +static int stream_puts(BIO* h, const char* str); +static long stream_ctrl(BIO* h, int cmd, long arg1, void* arg2); +static int stream_new(BIO* h); +static int stream_free(BIO* data); + +static BIO_METHOD methods_stream = { + BIO_TYPE_BIO, + "stream", + stream_write, + stream_read, + stream_puts, + 0, + stream_ctrl, + stream_new, + stream_free, + NULL, +}; + +static BIO_METHOD* BIO_s_stream() { return(&methods_stream); } + +static BIO* BIO_new_stream(StreamInterface* stream) { + BIO* ret = BIO_new(BIO_s_stream()); + if (ret == NULL) + return NULL; + ret->ptr = stream; + return ret; +} + +// bio methods return 1 (or at least non-zero) on success and 0 on failure. + +static int stream_new(BIO* b) { + b->shutdown = 0; + b->init = 1; + b->num = 0; // 1 means end-of-stream + b->ptr = 0; + return 1; +} + +static int stream_free(BIO* b) { + if (b == NULL) + return 0; + return 1; +} + +static int stream_read(BIO* b, char* out, int outl) { + if (!out) + return -1; + StreamInterface* stream = static_cast(b->ptr); + BIO_clear_retry_flags(b); + size_t read; + int error; + StreamResult result = stream->Read(out, outl, &read, &error); + if (result == SR_SUCCESS) { + return read; + } else if (result == SR_EOS) { + b->num = 1; + } else if (result == SR_BLOCK) { + BIO_set_retry_read(b); + } + return -1; +} + +static int stream_write(BIO* b, const char* in, int inl) { + if (!in) + return -1; + StreamInterface* stream = static_cast(b->ptr); + BIO_clear_retry_flags(b); + size_t written; + int error; + StreamResult result = stream->Write(in, inl, &written, &error); + if (result == SR_SUCCESS) { + return written; + } else if (result == SR_BLOCK) { + BIO_set_retry_write(b); + } + return -1; +} + +static int stream_puts(BIO* b, const char* str) { + return stream_write(b, str, strlen(str)); +} + +static long stream_ctrl(BIO* b, int cmd, long num, void* ptr) { + RTC_UNUSED(num); + RTC_UNUSED(ptr); + + switch (cmd) { + case BIO_CTRL_RESET: + return 0; + case BIO_CTRL_EOF: + return b->num; + case BIO_CTRL_WPENDING: + case BIO_CTRL_PENDING: + return 0; + case BIO_CTRL_FLUSH: + return 1; + default: + return 0; + } +} + +///////////////////////////////////////////////////////////////////////////// +// OpenSSLStreamAdapter +///////////////////////////////////////////////////////////////////////////// + +OpenSSLStreamAdapter::OpenSSLStreamAdapter(StreamInterface* stream) + : SSLStreamAdapter(stream), + state_(SSL_NONE), + role_(SSL_CLIENT), + ssl_read_needs_write_(false), ssl_write_needs_read_(false), + ssl_(NULL), ssl_ctx_(NULL), + custom_verification_succeeded_(false), + ssl_mode_(SSL_MODE_TLS) { +} + +OpenSSLStreamAdapter::~OpenSSLStreamAdapter() { + Cleanup(); +} + +void OpenSSLStreamAdapter::SetIdentity(SSLIdentity* identity) { + ASSERT(!identity_); + identity_.reset(static_cast(identity)); +} + +void OpenSSLStreamAdapter::SetServerRole(SSLRole role) { + role_ = role; +} + +bool OpenSSLStreamAdapter::GetPeerCertificate(SSLCertificate** cert) const { + if (!peer_certificate_) + return false; + + *cert = peer_certificate_->GetReference(); + return true; +} + +bool OpenSSLStreamAdapter::SetPeerCertificateDigest(const std::string + &digest_alg, + const unsigned char* + digest_val, + size_t digest_len) { + ASSERT(!peer_certificate_); + ASSERT(peer_certificate_digest_algorithm_.size() == 0); + ASSERT(ssl_server_name_.empty()); + size_t expected_len; + + if (!OpenSSLDigest::GetDigestSize(digest_alg, &expected_len)) { + LOG(LS_WARNING) << "Unknown digest algorithm: " << digest_alg; + return false; + } + if (expected_len != digest_len) + return false; + + peer_certificate_digest_value_.SetData(digest_val, digest_len); + peer_certificate_digest_algorithm_ = digest_alg; + + return true; +} + +// Key Extractor interface +bool OpenSSLStreamAdapter::ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { +#ifdef HAVE_DTLS_SRTP + int i; + + i = SSL_export_keying_material(ssl_, result, result_len, + label.c_str(), label.length(), + const_cast(context), + context_len, use_context); + + if (i != 1) + return false; + + return true; +#else + return false; +#endif +} + +bool OpenSSLStreamAdapter::SetDtlsSrtpCiphers( + const std::vector& ciphers) { +#ifdef HAVE_DTLS_SRTP + std::string internal_ciphers; + + if (state_ != SSL_NONE) + return false; + + for (std::vector::const_iterator cipher = ciphers.begin(); + cipher != ciphers.end(); ++cipher) { + bool found = false; + for (SrtpCipherMapEntry *entry = SrtpCipherMap; entry->internal_name; + ++entry) { + if (*cipher == entry->external_name) { + found = true; + if (!internal_ciphers.empty()) + internal_ciphers += ":"; + internal_ciphers += entry->internal_name; + break; + } + } + + if (!found) { + LOG(LS_ERROR) << "Could not find cipher: " << *cipher; + return false; + } + } + + if (internal_ciphers.empty()) + return false; + + srtp_ciphers_ = internal_ciphers; + return true; +#else + return false; +#endif +} + +bool OpenSSLStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) { +#ifdef HAVE_DTLS_SRTP + ASSERT(state_ == SSL_CONNECTED); + if (state_ != SSL_CONNECTED) + return false; + + SRTP_PROTECTION_PROFILE *srtp_profile = + SSL_get_selected_srtp_profile(ssl_); + + if (!srtp_profile) + return false; + + for (SrtpCipherMapEntry *entry = SrtpCipherMap; + entry->internal_name; ++entry) { + if (!strcmp(entry->internal_name, srtp_profile->name)) { + *cipher = entry->external_name; + return true; + } + } + + ASSERT(false); // This should never happen + + return false; +#else + return false; +#endif +} + +int OpenSSLStreamAdapter::StartSSLWithServer(const char* server_name) { + ASSERT(server_name != NULL && server_name[0] != '\0'); + ssl_server_name_ = server_name; + return StartSSL(); +} + +int OpenSSLStreamAdapter::StartSSLWithPeer() { + ASSERT(ssl_server_name_.empty()); + // It is permitted to specify peer_certificate_ only later. + return StartSSL(); +} + +void OpenSSLStreamAdapter::SetMode(SSLMode mode) { + ASSERT(state_ == SSL_NONE); + ssl_mode_ = mode; +} + +// +// StreamInterface Implementation +// + +StreamResult OpenSSLStreamAdapter::Write(const void* data, size_t data_len, + size_t* written, int* error) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::Write(" << data_len << ")"; + + switch (state_) { + case SSL_NONE: + // pass-through in clear text + return StreamAdapterInterface::Write(data, data_len, written, error); + + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + case SSL_CLOSED: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + // OpenSSL will return an error if we try to write zero bytes + if (data_len == 0) { + if (written) + *written = 0; + return SR_SUCCESS; + } + + ssl_write_needs_read_ = false; + + int code = SSL_write(ssl_, data, data_len); + int ssl_error = SSL_get_error(ssl_, code); + switch (ssl_error) { + case SSL_ERROR_NONE: + LOG(LS_VERBOSE) << " -- success"; + ASSERT(0 < code && static_cast(code) <= data_len); + if (written) + *written = code; + return SR_SUCCESS; + case SSL_ERROR_WANT_READ: + LOG(LS_VERBOSE) << " -- error want read"; + ssl_write_needs_read_ = true; + return SR_BLOCK; + case SSL_ERROR_WANT_WRITE: + LOG(LS_VERBOSE) << " -- error want write"; + return SR_BLOCK; + + case SSL_ERROR_ZERO_RETURN: + default: + Error("SSL_write", (ssl_error ? ssl_error : -1), false); + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + // not reached +} + +StreamResult OpenSSLStreamAdapter::Read(void* data, size_t data_len, + size_t* read, int* error) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::Read(" << data_len << ")"; + switch (state_) { + case SSL_NONE: + // pass-through in clear text + return StreamAdapterInterface::Read(data, data_len, read, error); + + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_CLOSED: + return SR_EOS; + + case SSL_ERROR: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + // Don't trust OpenSSL with zero byte reads + if (data_len == 0) { + if (read) + *read = 0; + return SR_SUCCESS; + } + + ssl_read_needs_write_ = false; + + int code = SSL_read(ssl_, data, data_len); + int ssl_error = SSL_get_error(ssl_, code); + switch (ssl_error) { + case SSL_ERROR_NONE: + LOG(LS_VERBOSE) << " -- success"; + ASSERT(0 < code && static_cast(code) <= data_len); + if (read) + *read = code; + + if (ssl_mode_ == SSL_MODE_DTLS) { + // Enforce atomic reads -- this is a short read + unsigned int pending = SSL_pending(ssl_); + + if (pending) { + LOG(LS_INFO) << " -- short DTLS read. flushing"; + FlushInput(pending); + if (error) + *error = SSE_MSG_TRUNC; + return SR_ERROR; + } + } + return SR_SUCCESS; + case SSL_ERROR_WANT_READ: + LOG(LS_VERBOSE) << " -- error want read"; + return SR_BLOCK; + case SSL_ERROR_WANT_WRITE: + LOG(LS_VERBOSE) << " -- error want write"; + ssl_read_needs_write_ = true; + return SR_BLOCK; + case SSL_ERROR_ZERO_RETURN: + LOG(LS_VERBOSE) << " -- remote side closed"; + return SR_EOS; + break; + default: + LOG(LS_VERBOSE) << " -- error " << code; + Error("SSL_read", (ssl_error ? ssl_error : -1), false); + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + // not reached +} + +void OpenSSLStreamAdapter::FlushInput(unsigned int left) { + unsigned char buf[2048]; + + while (left) { + // This should always succeed + int toread = (sizeof(buf) < left) ? sizeof(buf) : left; + int code = SSL_read(ssl_, buf, toread); + + int ssl_error = SSL_get_error(ssl_, code); + ASSERT(ssl_error == SSL_ERROR_NONE); + + if (ssl_error != SSL_ERROR_NONE) { + LOG(LS_VERBOSE) << " -- error " << code; + Error("SSL_read", (ssl_error ? ssl_error : -1), false); + return; + } + + LOG(LS_VERBOSE) << " -- flushed " << code << " bytes"; + left -= code; + } +} + +void OpenSSLStreamAdapter::Close() { + Cleanup(); + ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR); + StreamAdapterInterface::Close(); +} + +StreamState OpenSSLStreamAdapter::GetState() const { + switch (state_) { + case SSL_WAIT: + case SSL_CONNECTING: + return SS_OPENING; + case SSL_CONNECTED: + return SS_OPEN; + default: + return SS_CLOSED; + }; + // not reached +} + +void OpenSSLStreamAdapter::OnEvent(StreamInterface* stream, int events, + int err) { + int events_to_signal = 0; + int signal_error = 0; + ASSERT(stream == this->stream()); + if ((events & SE_OPEN)) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent SE_OPEN"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + events_to_signal |= SE_OPEN; + } else { + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, true); + return; + } + } + } + if ((events & (SE_READ|SE_WRITE))) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent" + << ((events & SE_READ) ? " SE_READ" : "") + << ((events & SE_WRITE) ? " SE_WRITE" : ""); + if (state_ == SSL_NONE) { + events_to_signal |= events & (SE_READ|SE_WRITE); + } else if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err, true); + return; + } + } else if (state_ == SSL_CONNECTED) { + if (((events & SE_READ) && ssl_write_needs_read_) || + (events & SE_WRITE)) { + LOG(LS_VERBOSE) << " -- onStreamWriteable"; + events_to_signal |= SE_WRITE; + } + if (((events & SE_WRITE) && ssl_read_needs_write_) || + (events & SE_READ)) { + LOG(LS_VERBOSE) << " -- onStreamReadable"; + events_to_signal |= SE_READ; + } + } + } + if ((events & SE_CLOSE)) { + LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent(SE_CLOSE, " << err << ")"; + Cleanup(); + events_to_signal |= SE_CLOSE; + // SE_CLOSE is the only event that uses the final parameter to OnEvent(). + ASSERT(signal_error == 0); + signal_error = err; + } + if (events_to_signal) + StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error); +} + +int OpenSSLStreamAdapter::StartSSL() { + ASSERT(state_ == SSL_NONE); + + if (StreamAdapterInterface::GetState() != SS_OPEN) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +int OpenSSLStreamAdapter::BeginSSL() { + ASSERT(state_ == SSL_CONNECTING); + // The underlying stream has open. If we are in peer-to-peer mode + // then a peer certificate must have been specified by now. + ASSERT(!ssl_server_name_.empty() || + !peer_certificate_digest_algorithm_.empty()); + LOG(LS_INFO) << "BeginSSL: " + << (!ssl_server_name_.empty() ? ssl_server_name_ : + "with peer"); + + BIO* bio = NULL; + + // First set up the context + ASSERT(ssl_ctx_ == NULL); + ssl_ctx_ = SetupSSLContext(); + if (!ssl_ctx_) + return -1; + + bio = BIO_new_stream(static_cast(stream())); + if (!bio) + return -1; + + ssl_ = SSL_new(ssl_ctx_); + if (!ssl_) { + BIO_free(bio); + return -1; + } + + SSL_set_app_data(ssl_, this); + + SSL_set_bio(ssl_, bio, bio); // the SSL object owns the bio now. + + SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + // Do the connect + return ContinueSSL(); +} + +int OpenSSLStreamAdapter::ContinueSSL() { + LOG(LS_VERBOSE) << "ContinueSSL"; + ASSERT(state_ == SSL_CONNECTING); + + // Clear the DTLS timer + Thread::Current()->Clear(this, MSG_TIMEOUT); + + int code = (role_ == SSL_CLIENT) ? SSL_connect(ssl_) : SSL_accept(ssl_); + int ssl_error; + switch (ssl_error = SSL_get_error(ssl_, code)) { + case SSL_ERROR_NONE: + LOG(LS_VERBOSE) << " -- success"; + + if (!SSLPostConnectionCheck(ssl_, ssl_server_name_.c_str(), NULL, + peer_certificate_digest_algorithm_)) { + LOG(LS_ERROR) << "TLS post connection check failed"; + return -1; + } + + state_ = SSL_CONNECTED; + StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0); + break; + + case SSL_ERROR_WANT_READ: { + LOG(LS_VERBOSE) << " -- error want read"; + struct timeval timeout; + if (DTLSv1_get_timeout(ssl_, &timeout)) { + int delay = timeout.tv_sec * 1000 + timeout.tv_usec/1000; + + Thread::Current()->PostDelayed(delay, this, MSG_TIMEOUT, 0); + } + } + break; + + case SSL_ERROR_WANT_WRITE: + LOG(LS_VERBOSE) << " -- error want write"; + break; + + case SSL_ERROR_ZERO_RETURN: + default: + LOG(LS_VERBOSE) << " -- error " << code; + return (ssl_error != 0) ? ssl_error : -1; + } + + return 0; +} + +void OpenSSLStreamAdapter::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "OpenSSLStreamAdapter::Error(" + << context << ", " << err << ")"; + state_ = SSL_ERROR; + ssl_error_code_ = err; + Cleanup(); + if (signal) + StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err); +} + +void OpenSSLStreamAdapter::Cleanup() { + LOG(LS_INFO) << "Cleanup"; + + if (state_ != SSL_ERROR) { + state_ = SSL_CLOSED; + ssl_error_code_ = 0; + } + + if (ssl_) { + SSL_free(ssl_); + ssl_ = NULL; + } + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + ssl_ctx_ = NULL; + } + identity_.reset(); + peer_certificate_.reset(); + + // Clear the DTLS timer + Thread::Current()->Clear(this, MSG_TIMEOUT); +} + + +void OpenSSLStreamAdapter::OnMessage(Message* msg) { + // Process our own messages and then pass others to the superclass + if (MSG_TIMEOUT == msg->message_id) { + LOG(LS_INFO) << "DTLS timeout expired"; + DTLSv1_handle_timeout(ssl_); + ContinueSSL(); + } else { + StreamInterface::OnMessage(msg); + } +} + +SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() { + SSL_CTX *ctx = NULL; + + if (role_ == SSL_CLIENT) { + ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ? + DTLSv1_client_method() : TLSv1_client_method()); + } else { + ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ? + DTLSv1_server_method() : TLSv1_server_method()); + } + if (ctx == NULL) + return NULL; + + if (identity_ && !identity_->ConfigureIdentity(ctx)) { + SSL_CTX_free(ctx); + return NULL; + } + +#ifdef _DEBUG + SSL_CTX_set_info_callback(ctx, OpenSSLAdapter::SSLInfoCallback); +#endif + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER |SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + SSLVerifyCallback); + SSL_CTX_set_verify_depth(ctx, 4); + SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + +#ifdef HAVE_DTLS_SRTP + if (!srtp_ciphers_.empty()) { + if (SSL_CTX_set_tlsext_use_srtp(ctx, srtp_ciphers_.c_str())) { + SSL_CTX_free(ctx); + return NULL; + } + } +#endif + + return ctx; +} + +int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { + // Get our SSL structure from the store + SSL* ssl = reinterpret_cast(X509_STORE_CTX_get_ex_data( + store, + SSL_get_ex_data_X509_STORE_CTX_idx())); + OpenSSLStreamAdapter* stream = + reinterpret_cast(SSL_get_app_data(ssl)); + + if (stream->peer_certificate_digest_algorithm_.empty()) { + return 0; + } + X509* cert = X509_STORE_CTX_get_current_cert(store); + int depth = X509_STORE_CTX_get_error_depth(store); + + // For now We ignore the parent certificates and verify the leaf against + // the digest. + // + // TODO(jiayl): Verify the chain is a proper chain and report the chain to + // |stream->peer_certificate_|, like what NSS does. + if (depth > 0) { + LOG(LS_INFO) << "Ignored chained certificate at depth " << depth; + return 1; + } + + unsigned char digest[EVP_MAX_MD_SIZE]; + size_t digest_length; + if (!OpenSSLCertificate::ComputeDigest( + cert, + stream->peer_certificate_digest_algorithm_, + digest, sizeof(digest), + &digest_length)) { + LOG(LS_WARNING) << "Failed to compute peer cert digest."; + return 0; + } + + Buffer computed_digest(digest, digest_length); + if (computed_digest != stream->peer_certificate_digest_value_) { + LOG(LS_WARNING) << "Rejected peer certificate due to mismatched digest."; + return 0; + } + // Ignore any verification error if the digest matches, since there is no + // value in checking the validity of a self-signed cert issued by untrusted + // sources. + LOG(LS_INFO) << "Accepted peer certificate."; + + // Record the peer's certificate. + stream->peer_certificate_.reset(new OpenSSLCertificate(cert)); + return 1; +} + +// This code is taken from the "Network Security with OpenSSL" +// sample in chapter 5 +bool OpenSSLStreamAdapter::SSLPostConnectionCheck(SSL* ssl, + const char* server_name, + const X509* peer_cert, + const std::string + &peer_digest) { + ASSERT(server_name != NULL); + bool ok; + if (server_name[0] != '\0') { // traditional mode + ok = OpenSSLAdapter::VerifyServerName(ssl, server_name, ignore_bad_cert()); + + if (ok) { + ok = (SSL_get_verify_result(ssl) == X509_V_OK || + custom_verification_succeeded_); + } + } else { // peer-to-peer mode + ASSERT((peer_cert != NULL) || (!peer_digest.empty())); + // no server name validation + ok = true; + } + + if (!ok && ignore_bad_cert()) { + LOG(LS_ERROR) << "SSL_get_verify_result(ssl) = " + << SSL_get_verify_result(ssl); + LOG(LS_INFO) << "Other TLS post connection checks failed."; + ok = true; + } + + return ok; +} + +bool OpenSSLStreamAdapter::HaveDtls() { + return true; +} + +bool OpenSSLStreamAdapter::HaveDtlsSrtp() { +#ifdef HAVE_DTLS_SRTP + return true; +#else + return false; +#endif +} + +bool OpenSSLStreamAdapter::HaveExporter() { +#ifdef HAVE_DTLS_SRTP + return true; +#else + return false; +#endif +} + +} // namespace rtc + +#endif // HAVE_OPENSSL_SSL_H diff --git a/webrtc/base/opensslstreamadapter.h b/webrtc/base/opensslstreamadapter.h new file mode 100644 index 000000000..9506217b4 --- /dev/null +++ b/webrtc/base/opensslstreamadapter.h @@ -0,0 +1,198 @@ +/* + * Copyright 2004 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_BASE_OPENSSLSTREAMADAPTER_H__ +#define WEBRTC_BASE_OPENSSLSTREAMADAPTER_H__ + +#include +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/opensslidentity.h" + +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct x509_store_ctx_st X509_STORE_CTX; + +namespace rtc { + +// This class was written with OpenSSLAdapter (a socket adapter) as a +// starting point. It has similar structure and functionality, with +// the peer-to-peer mode added. +// +// Static methods to initialize and deinit the SSL library are in +// OpenSSLAdapter. This class also uses +// OpenSSLAdapter::custom_verify_callback_ (a static field). These +// should probably be moved out to a neutral class. +// +// In a few cases I have factored out some OpenSSLAdapter code into +// static methods so it can be reused from this class. Eventually that +// code should probably be moved to a common support +// class. Unfortunately there remain a few duplicated sections of +// code. I have not done more restructuring because I did not want to +// affect existing code that uses OpenSSLAdapter. +// +// This class does not support the SSL connection restart feature +// present in OpenSSLAdapter. I am not entirely sure how the feature +// is useful and I am not convinced that it works properly. +// +// This implementation is careful to disallow data exchange after an +// SSL error, and it has an explicit SSL_CLOSED state. It should not +// be possible to send any data in clear after one of the StartSSL +// methods has been called. + +// Look in sslstreamadapter.h for documentation of the methods. + +class OpenSSLIdentity; + +/////////////////////////////////////////////////////////////////////////////// + +class OpenSSLStreamAdapter : public SSLStreamAdapter { + public: + explicit OpenSSLStreamAdapter(StreamInterface* stream); + virtual ~OpenSSLStreamAdapter(); + + virtual void SetIdentity(SSLIdentity* identity); + + // Default argument is for compatibility + virtual void SetServerRole(SSLRole role = SSL_SERVER); + virtual bool SetPeerCertificateDigest(const std::string& digest_alg, + const unsigned char* digest_val, + size_t digest_len); + + virtual bool GetPeerCertificate(SSLCertificate** cert) const; + + virtual int StartSSLWithServer(const char* server_name); + virtual int StartSSLWithPeer(); + virtual void SetMode(SSLMode mode); + + virtual StreamResult Read(void* data, size_t data_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual StreamState GetState() const; + + // Key Extractor interface + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len); + + + // DTLS-SRTP interface + virtual bool SetDtlsSrtpCiphers(const std::vector& ciphers); + virtual bool GetDtlsSrtpCipher(std::string* cipher); + + // Capabilities interfaces + static bool HaveDtls(); + static bool HaveDtlsSrtp(); + static bool HaveExporter(); + + protected: + virtual void OnEvent(StreamInterface* stream, int events, int err); + + private: + enum SSLState { + // Before calling one of the StartSSL methods, data flows + // in clear text. + SSL_NONE, + SSL_WAIT, // waiting for the stream to open to start SSL negotiation + SSL_CONNECTING, // SSL negotiation in progress + SSL_CONNECTED, // SSL stream successfully established + SSL_ERROR, // some SSL error occurred, stream is closed + SSL_CLOSED // Clean close + }; + + enum { MSG_TIMEOUT = MSG_MAX+1}; + + // The following three methods return 0 on success and a negative + // error code on failure. The error code may be from OpenSSL or -1 + // on some other error cases, so it can't really be interpreted + // unfortunately. + + // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT, + // depending on whether the underlying stream is already open or + // not. + int StartSSL(); + // Prepare SSL library, state is SSL_CONNECTING. + int BeginSSL(); + // Perform SSL negotiation steps. + int ContinueSSL(); + + // Error handler helper. signal is given as true for errors in + // asynchronous contexts (when an error method was not returned + // through some other method), and in that case an SE_CLOSE event is + // raised on the stream with the specified error. + // A 0 error means a graceful close, otherwise there is not really enough + // context to interpret the error code. + void Error(const char* context, int err, bool signal); + void Cleanup(); + + // Override MessageHandler + virtual void OnMessage(Message* msg); + + // Flush the input buffers by reading left bytes (for DTLS) + void FlushInput(unsigned int left); + + // SSL library configuration + SSL_CTX* SetupSSLContext(); + // SSL verification check + bool SSLPostConnectionCheck(SSL* ssl, const char* server_name, + const X509* peer_cert, + const std::string& peer_digest); + // SSL certification verification error handler, called back from + // the openssl library. Returns an int interpreted as a boolean in + // the C style: zero means verification failure, non-zero means + // passed. + static int SSLVerifyCallback(int ok, X509_STORE_CTX* store); + + SSLState state_; + SSLRole role_; + int ssl_error_code_; // valid when state_ == SSL_ERROR or SSL_CLOSED + // Whether the SSL negotiation is blocked on needing to read or + // write to the wrapped stream. + bool ssl_read_needs_write_; + bool ssl_write_needs_read_; + + SSL* ssl_; + SSL_CTX* ssl_ctx_; + + // Our key and certificate, mostly useful in peer-to-peer mode. + scoped_ptr identity_; + // in traditional mode, the server name that the server's certificate + // must specify. Empty in peer-to-peer mode. + std::string ssl_server_name_; + // The certificate that the peer must present or did present. Initially + // null in traditional mode, until the connection is established. + scoped_ptr peer_certificate_; + // In peer-to-peer mode, the digest of the certificate that + // the peer must present. + Buffer peer_certificate_digest_value_; + std::string peer_certificate_digest_algorithm_; + + // OpenSSLAdapter::custom_verify_callback_ result + bool custom_verification_succeeded_; + + // The DtlsSrtp ciphers + std::string srtp_ciphers_; + + // Do DTLS or not + SSLMode ssl_mode_; +}; + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_OPENSSLSTREAMADAPTER_H__ diff --git a/webrtc/base/optionsfile.cc b/webrtc/base/optionsfile.cc new file mode 100644 index 000000000..d84c948e3 --- /dev/null +++ b/webrtc/base/optionsfile.cc @@ -0,0 +1,184 @@ +/* + * Copyright 2008 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 "webrtc/base/optionsfile.h" + +#include + +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +OptionsFile::OptionsFile(const std::string &path) : path_(path) { +} + +bool OptionsFile::Load() { + options_.clear(); + // Open file. + FileStream stream; + int err; + if (!stream.Open(path_, "r", &err)) { + LOG_F(LS_WARNING) << "Could not open file, err=" << err; + // We do not consider this an error because we expect there to be no file + // until the user saves a setting. + return true; + } + // Read in all its data. + std::string line; + StreamResult res; + for (;;) { + res = stream.ReadLine(&line); + if (res != SR_SUCCESS) { + break; + } + size_t equals_pos = line.find('='); + if (equals_pos == std::string::npos) { + // We do not consider this an error. Instead we ignore the line and + // keep going. + LOG_F(LS_WARNING) << "Ignoring malformed line in " << path_; + continue; + } + std::string key(line, 0, equals_pos); + std::string value(line, equals_pos + 1, line.length() - (equals_pos + 1)); + options_[key] = value; + } + if (res != SR_EOS) { + LOG_F(LS_ERROR) << "Error when reading from file"; + return false; + } else { + return true; + } +} + +bool OptionsFile::Save() { + // Open file. + FileStream stream; + int err; + if (!stream.Open(path_, "w", &err)) { + LOG_F(LS_ERROR) << "Could not open file, err=" << err; + return false; + } + // Write out all the data. + StreamResult res = SR_SUCCESS; + size_t written; + int error; + for (OptionsMap::const_iterator i = options_.begin(); i != options_.end(); + ++i) { + res = stream.WriteAll(i->first.c_str(), i->first.length(), &written, + &error); + if (res != SR_SUCCESS) { + break; + } + res = stream.WriteAll("=", 1, &written, &error); + if (res != SR_SUCCESS) { + break; + } + res = stream.WriteAll(i->second.c_str(), i->second.length(), &written, + &error); + if (res != SR_SUCCESS) { + break; + } + res = stream.WriteAll("\n", 1, &written, &error); + if (res != SR_SUCCESS) { + break; + } + } + if (res != SR_SUCCESS) { + LOG_F(LS_ERROR) << "Unable to write to file"; + return false; + } else { + return true; + } +} + +bool OptionsFile::IsLegalName(const std::string &name) { + for (size_t pos = 0; pos < name.length(); ++pos) { + if (name[pos] == '\n' || name[pos] == '\\' || name[pos] == '=') { + // Illegal character. + LOG(LS_WARNING) << "Ignoring operation for illegal option " << name; + return false; + } + } + return true; +} + +bool OptionsFile::IsLegalValue(const std::string &value) { + for (size_t pos = 0; pos < value.length(); ++pos) { + if (value[pos] == '\n' || value[pos] == '\\') { + // Illegal character. + LOG(LS_WARNING) << "Ignoring operation for illegal value " << value; + return false; + } + } + return true; +} + +bool OptionsFile::GetStringValue(const std::string& option, + std::string *out_val) const { + LOG(LS_VERBOSE) << "OptionsFile::GetStringValue " + << option; + if (!IsLegalName(option)) { + return false; + } + OptionsMap::const_iterator i = options_.find(option); + if (i == options_.end()) { + return false; + } + *out_val = i->second; + return true; +} + +bool OptionsFile::GetIntValue(const std::string& option, + int *out_val) const { + LOG(LS_VERBOSE) << "OptionsFile::GetIntValue " + << option; + if (!IsLegalName(option)) { + return false; + } + OptionsMap::const_iterator i = options_.find(option); + if (i == options_.end()) { + return false; + } + return FromString(i->second, out_val); +} + +bool OptionsFile::SetStringValue(const std::string& option, + const std::string& value) { + LOG(LS_VERBOSE) << "OptionsFile::SetStringValue " + << option << ":" << value; + if (!IsLegalName(option) || !IsLegalValue(value)) { + return false; + } + options_[option] = value; + return true; +} + +bool OptionsFile::SetIntValue(const std::string& option, + int value) { + LOG(LS_VERBOSE) << "OptionsFile::SetIntValue " + << option << ":" << value; + if (!IsLegalName(option)) { + return false; + } + return ToString(value, &options_[option]); +} + +bool OptionsFile::RemoveValue(const std::string& option) { + LOG(LS_VERBOSE) << "OptionsFile::RemoveValue " << option; + if (!IsLegalName(option)) { + return false; + } + options_.erase(option); + return true; +} + +} // namespace rtc diff --git a/webrtc/base/optionsfile.h b/webrtc/base/optionsfile.h new file mode 100644 index 000000000..c740ce4fa --- /dev/null +++ b/webrtc/base/optionsfile.h @@ -0,0 +1,49 @@ +/* + * Copyright 2008 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_BASE_OPTIONSFILE_H_ +#define WEBRTC_BASE_OPTIONSFILE_H_ + +#include +#include + +namespace rtc { + +// Implements storage of simple options in a text file on disk. This is +// cross-platform, but it is intended mostly for Linux where there is no +// first-class options storage system. +class OptionsFile { + public: + OptionsFile(const std::string &path); + + // Loads the file from disk, overwriting the in-memory values. + bool Load(); + // Saves the contents in memory, overwriting the on-disk values. + bool Save(); + + bool GetStringValue(const std::string& option, std::string* out_val) const; + bool GetIntValue(const std::string& option, int* out_val) const; + bool SetStringValue(const std::string& option, const std::string& val); + bool SetIntValue(const std::string& option, int val); + bool RemoveValue(const std::string& option); + + private: + typedef std::map OptionsMap; + + static bool IsLegalName(const std::string &name); + static bool IsLegalValue(const std::string &value); + + std::string path_; + OptionsMap options_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_OPTIONSFILE_H_ diff --git a/webrtc/base/optionsfile_unittest.cc b/webrtc/base/optionsfile_unittest.cc new file mode 100644 index 000000000..adddb9521 --- /dev/null +++ b/webrtc/base/optionsfile_unittest.cc @@ -0,0 +1,168 @@ +/* + * Copyright 2008 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 "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/optionsfile.h" +#include "webrtc/base/pathutils.h" + +namespace rtc { + +static const std::string kTestOptionA = "test-option-a"; +static const std::string kTestOptionB = "test-option-b"; +static const std::string kTestString1 = "a string"; +static const std::string kTestString2 = "different string"; +static const std::string kOptionWithEquals = "foo=bar"; +static const std::string kOptionWithNewline = "foo\nbar"; +static const std::string kValueWithEquals = "baz=quux"; +static const std::string kValueWithNewline = "baz\nquux"; +static const std::string kEmptyString = ""; +static const char kOptionWithUtf8[] = {'O', 'p', 't', '\302', '\256', 'i', 'o', + 'n', '\342', '\204', '\242', '\0'}; // Opt(R)io(TM). +static const char kValueWithUtf8[] = {'V', 'a', 'l', '\302', '\256', 'v', 'e', + '\342', '\204', '\242', '\0'}; // Val(R)ue(TM). +static int kTestInt1 = 12345; +static int kTestInt2 = 67890; +static int kNegInt = -634; +static int kZero = 0; + +class OptionsFileTest : public testing::Test { + public: + OptionsFileTest() { + Pathname dir; + ASSERT(Filesystem::GetTemporaryFolder(dir, true, NULL)); + test_file_ = Filesystem::TempFilename(dir, ".testfile"); + OpenStore(); + } + + protected: + void OpenStore() { + store_.reset(new OptionsFile(test_file_)); + } + + rtc::scoped_ptr store_; + + private: + std::string test_file_; +}; + +TEST_F(OptionsFileTest, GetSetString) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + std::string out1, out2; + EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kTestString2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out2)); + EXPECT_EQ(kTestString1, out1); + EXPECT_EQ(kTestString2, out2); + EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); +} + +TEST_F(OptionsFileTest, GetSetInt) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + int out1, out2; + EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kTestInt1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kTestInt2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_EQ(kTestInt1, out1); + EXPECT_EQ(kTestInt2, out2); + EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kNegInt)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_EQ(kNegInt, out1); + EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kZero)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); + EXPECT_EQ(kZero, out1); +} + +TEST_F(OptionsFileTest, Persist) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kNegInt)); + EXPECT_TRUE(store_->Save()); + + // Load the saved contents from above. + OpenStore(); + EXPECT_TRUE(store_->Load()); + std::string out1; + int out2; + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); + EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); + EXPECT_EQ(kTestString1, out1); + EXPECT_EQ(kNegInt, out2); +} + +TEST_F(OptionsFileTest, SpecialCharacters) { + // Clear contents of the file on disk. + EXPECT_TRUE(store_->Save()); + std::string out; + EXPECT_FALSE(store_->SetStringValue(kOptionWithEquals, kTestString1)); + EXPECT_FALSE(store_->GetStringValue(kOptionWithEquals, &out)); + EXPECT_FALSE(store_->SetStringValue(kOptionWithNewline, kTestString1)); + EXPECT_FALSE(store_->GetStringValue(kOptionWithNewline, &out)); + EXPECT_TRUE(store_->SetStringValue(kOptionWithUtf8, kValueWithUtf8)); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); + EXPECT_EQ(kTestString1, out); + EXPECT_TRUE(store_->GetStringValue(kOptionWithUtf8, &out)); + EXPECT_EQ(kValueWithUtf8, out); + EXPECT_FALSE(store_->SetStringValue(kTestOptionA, kValueWithNewline)); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); + EXPECT_EQ(kTestString1, out); + EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kValueWithEquals)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); + EXPECT_EQ(kValueWithEquals, out); + EXPECT_TRUE(store_->SetStringValue(kEmptyString, kTestString2)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kEmptyString, &out)); + EXPECT_EQ(kTestString2, out); + EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kEmptyString)); + EXPECT_TRUE(store_->Save()); + EXPECT_TRUE(store_->Load()); + EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out)); + EXPECT_EQ(kEmptyString, out); +} + +} // namespace rtc diff --git a/webrtc/base/pathutils.cc b/webrtc/base/pathutils.cc new file mode 100644 index 000000000..7671bfc29 --- /dev/null +++ b/webrtc/base/pathutils.cc @@ -0,0 +1,251 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#include +#include +#endif // WEBRTC_WIN + +#include "webrtc/base/common.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/urlencode.h" + +namespace rtc { + +static const char EMPTY_STR[] = ""; + +// EXT_DELIM separates a file basename from extension +const char EXT_DELIM = '.'; + +// FOLDER_DELIMS separate folder segments and the filename +const char* const FOLDER_DELIMS = "/\\"; + +// DEFAULT_FOLDER_DELIM is the preferred delimiter for this platform +#if WEBRTC_WIN +const char DEFAULT_FOLDER_DELIM = '\\'; +#else // !WEBRTC_WIN +const char DEFAULT_FOLDER_DELIM = '/'; +#endif // !WEBRTC_WIN + +/////////////////////////////////////////////////////////////////////////////// +// Pathname - parsing of pathnames into components, and vice versa +/////////////////////////////////////////////////////////////////////////////// + +bool Pathname::IsFolderDelimiter(char ch) { + return (NULL != ::strchr(FOLDER_DELIMS, ch)); +} + +char Pathname::DefaultFolderDelimiter() { + return DEFAULT_FOLDER_DELIM; +} + +Pathname::Pathname() + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { +} + +Pathname::Pathname(const std::string& pathname) + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { + SetPathname(pathname); +} + +Pathname::Pathname(const std::string& folder, const std::string& filename) + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { + SetPathname(folder, filename); +} + +void Pathname::SetFolderDelimiter(char delimiter) { + ASSERT(IsFolderDelimiter(delimiter)); + folder_delimiter_ = delimiter; +} + +void Pathname::Normalize() { + for (size_t i=0; i= 2) { + pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2); + } + if (pos != std::string::npos) { + return folder_.substr(pos + 1); + } else { + return folder_; + } +} + +std::string Pathname::parent_folder() const { + std::string::size_type pos = std::string::npos; + if (folder_.size() >= 2) { + pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2); + } + if (pos != std::string::npos) { + return folder_.substr(0, pos + 1); + } else { + return EMPTY_STR; + } +} + +void Pathname::SetFolder(const std::string& folder) { + folder_.assign(folder); + // Ensure folder ends in a path delimiter + if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) { + folder_.push_back(folder_delimiter_); + } +} + +void Pathname::AppendFolder(const std::string& folder) { + folder_.append(folder); + // Ensure folder ends in a path delimiter + if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) { + folder_.push_back(folder_delimiter_); + } +} + +std::string Pathname::basename() const { + return basename_; +} + +bool Pathname::SetBasename(const std::string& basename) { + if(basename.find_first_of(FOLDER_DELIMS) != std::string::npos) { + return false; + } + basename_.assign(basename); + return true; +} + +std::string Pathname::extension() const { + return extension_; +} + +bool Pathname::SetExtension(const std::string& extension) { + if (extension.find_first_of(FOLDER_DELIMS) != std::string::npos || + extension.find_first_of(EXT_DELIM, 1) != std::string::npos) { + return false; + } + extension_.assign(extension); + // Ensure extension begins with the extension delimiter + if (!extension_.empty() && (extension_[0] != EXT_DELIM)) { + extension_.insert(extension_.begin(), EXT_DELIM); + } + return true; +} + +std::string Pathname::filename() const { + std::string filename(basename_); + filename.append(extension_); + return filename; +} + +bool Pathname::SetFilename(const std::string& filename) { + std::string::size_type pos = filename.rfind(EXT_DELIM); + if ((pos == std::string::npos) || (pos == 0)) { + return SetExtension(EMPTY_STR) && SetBasename(filename); + } else { + return SetExtension(filename.substr(pos)) && SetBasename(filename.substr(0, pos)); + } +} + +#if defined(WEBRTC_WIN) +bool Pathname::GetDrive(char *drive, uint32 bytes) const { + return GetDrive(drive, bytes, folder_); +} + +// static +bool Pathname::GetDrive(char *drive, uint32 bytes, + const std::string& pathname) { + // need at lease 4 bytes to save c: + if (bytes < 4 || pathname.size() < 3) { + return false; + } + + memcpy(drive, pathname.c_str(), 3); + drive[3] = 0; + // sanity checking + return (isalpha(drive[0]) && + drive[1] == ':' && + drive[2] == '\\'); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/pathutils.h b/webrtc/base/pathutils.h new file mode 100644 index 000000000..8f07e1dbc --- /dev/null +++ b/webrtc/base/pathutils.h @@ -0,0 +1,163 @@ +/* + * Copyright 2004 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_BASE_PATHUTILS_H__ +#define WEBRTC_BASE_PATHUTILS_H__ + +#include +// Temporary, until deprecated helpers are removed. +#include "webrtc/base/fileutils.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Pathname - parsing of pathnames into components, and vice versa. +// +// To establish consistent terminology, a filename never contains a folder +// component. A folder never contains a filename. A pathname may include +// a folder and/or filename component. Here are some examples: +// +// pathname() /home/john/example.txt +// folder() /home/john/ +// filename() example.txt +// parent_folder() /home/ +// folder_name() john/ +// basename() example +// extension() .txt +// +// Basename may begin, end, and/or include periods, but no folder delimiters. +// If extension exists, it consists of a period followed by zero or more +// non-period/non-delimiter characters, and basename is non-empty. +/////////////////////////////////////////////////////////////////////////////// + +class Pathname { +public: + // Folder delimiters are slash and backslash + static bool IsFolderDelimiter(char ch); + static char DefaultFolderDelimiter(); + + Pathname(); + Pathname(const std::string& pathname); + Pathname(const std::string& folder, const std::string& filename); + + // Set's the default folder delimiter for this Pathname + char folder_delimiter() const { return folder_delimiter_; } + void SetFolderDelimiter(char delimiter); + + // Normalize changes all folder delimiters to folder_delimiter() + void Normalize(); + + // Reset to the empty pathname + void clear(); + + // Returns true if the pathname is empty. Note: this->pathname().empty() + // is always false. + bool empty() const; + + std::string url() const; + + // Returns the folder and filename components. If the pathname is empty, + // returns a string representing the current directory (as a relative path, + // i.e., "."). + std::string pathname() const; + void SetPathname(const std::string& pathname); + void SetPathname(const std::string& folder, const std::string& filename); + + // Append pathname to the current folder (if any). Any existing filename + // will be discarded. + void AppendPathname(const std::string& pathname); + + std::string folder() const; + std::string folder_name() const; + std::string parent_folder() const; + // SetFolder and AppendFolder will append a folder delimiter, if needed. + void SetFolder(const std::string& folder); + void AppendFolder(const std::string& folder); + + std::string basename() const; + bool SetBasename(const std::string& basename); + + std::string extension() const; + // SetExtension will prefix a period, if needed. + bool SetExtension(const std::string& extension); + + std::string filename() const; + bool SetFilename(const std::string& filename); + +#if defined(WEBRTC_WIN) + bool GetDrive(char *drive, uint32 bytes) const; + static bool GetDrive(char *drive, uint32 bytes,const std::string& pathname); +#endif + +private: + std::string folder_, basename_, extension_; + char folder_delimiter_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Global Helpers (deprecated) +/////////////////////////////////////////////////////////////////////////////// + +inline void SetOrganizationName(const std::string& organization) { + Filesystem::SetOrganizationName(organization); +} +inline void SetApplicationName(const std::string& application) { + Filesystem::SetApplicationName(application); +} +inline void GetOrganizationName(std::string* organization) { + Filesystem::GetOrganizationName(organization); +} +inline void GetApplicationName(std::string* application) { + Filesystem::GetApplicationName(application); +} +inline bool CreateFolder(const Pathname& path) { + return Filesystem::CreateFolder(path); +} +inline bool FinishPath(Pathname& path, bool create, const std::string& append) { + if (!append.empty()) + path.AppendFolder(append); + return !create || CreateFolder(path); +} +// Note: this method uses the convention of / for the temporary +// folder. Filesystem uses /. We will be migrating exclusively +// to // eventually. Since these are temp folders, +// it's probably ok to orphan them during the transition. +inline bool GetTemporaryFolder(Pathname& path, bool create, + const std::string& append) { + std::string application_name; + Filesystem::GetApplicationName(&application_name); + ASSERT(!application_name.empty()); + return Filesystem::GetTemporaryFolder(path, create, &application_name) + && FinishPath(path, create, append); +} +inline bool GetAppDataFolder(Pathname& path, bool create, + const std::string& append) { + ASSERT(!create); // TODO: Support create flag on Filesystem::GetAppDataFolder. + return Filesystem::GetAppDataFolder(&path, true) + && FinishPath(path, create, append); +} +inline bool CleanupTemporaryFolder() { + Pathname path; + if (!GetTemporaryFolder(path, false, "")) + return false; + if (Filesystem::IsAbsent(path)) + return true; + if (!Filesystem::IsTemporaryPath(path)) { + ASSERT(false); + return false; + } + return Filesystem::DeleteFolderContents(path); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_PATHUTILS_H__ diff --git a/webrtc/base/pathutils_unittest.cc b/webrtc/base/pathutils_unittest.cc new file mode 100644 index 000000000..a04effa68 --- /dev/null +++ b/webrtc/base/pathutils_unittest.cc @@ -0,0 +1,48 @@ +/* + * Copyright 2007 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 "webrtc/base/pathutils.h" +#include "webrtc/base/gunit.h" + +TEST(Pathname, ReturnsDotForEmptyPathname) { + const std::string kCWD = + std::string(".") + rtc::Pathname::DefaultFolderDelimiter(); + + rtc::Pathname path("/", ""); + EXPECT_FALSE(path.empty()); + EXPECT_FALSE(path.folder().empty()); + EXPECT_TRUE (path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(std::string("/"), path.pathname()); + + path.SetPathname("", "foo"); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE (path.folder().empty()); + EXPECT_FALSE(path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(std::string("foo"), path.pathname()); + + path.SetPathname("", ""); + EXPECT_TRUE (path.empty()); + EXPECT_TRUE (path.folder().empty()); + EXPECT_TRUE (path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(kCWD, path.pathname()); + + path.SetPathname(kCWD, ""); + EXPECT_FALSE(path.empty()); + EXPECT_FALSE(path.folder().empty()); + EXPECT_TRUE (path.filename().empty()); + EXPECT_FALSE(path.pathname().empty()); + EXPECT_EQ(kCWD, path.pathname()); + + rtc::Pathname path2("c:/foo bar.txt"); + EXPECT_EQ(path2.url(), std::string("file:///c:/foo%20bar.txt")); +} diff --git a/webrtc/base/physicalsocketserver.cc b/webrtc/base/physicalsocketserver.cc new file mode 100644 index 000000000..cff5e4dcb --- /dev/null +++ b/webrtc/base/physicalsocketserver.cc @@ -0,0 +1,1659 @@ +/* + * Copyright 2004 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#undef SetPort +#endif + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/winping.h" +#include "webrtc/base/win32socketinit.h" + +// stm: this will tell us if we are on OSX +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(WEBRTC_POSIX) +#include // for TCP_NODELAY +#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h +typedef void* SockOptArg; +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +typedef char* SockOptArg; +#endif + +namespace rtc { + +#if defined(WEBRTC_WIN) +// Standard MTUs, from RFC 1191 +const uint16 PACKET_MAXIMUMS[] = { + 65535, // Theoretical maximum, Hyperchannel + 32000, // Nothing + 17914, // 16Mb IBM Token Ring + 8166, // IEEE 802.4 + //4464, // IEEE 802.5 (4Mb max) + 4352, // FDDI + //2048, // Wideband Network + 2002, // IEEE 802.5 (4Mb recommended) + //1536, // Expermental Ethernet Networks + //1500, // Ethernet, Point-to-Point (default) + 1492, // IEEE 802.3 + 1006, // SLIP, ARPANET + //576, // X.25 Networks + //544, // DEC IP Portal + //512, // NETBIOS + 508, // IEEE 802/Source-Rt Bridge, ARCNET + 296, // Point-to-Point (low delay) + 68, // Official minimum + 0, // End of list marker +}; + +static const int IP_HEADER_SIZE = 20u; +static const int IPV6_HEADER_SIZE = 40u; +static const int ICMP_HEADER_SIZE = 8u; +static const int ICMP_PING_TIMEOUT_MILLIS = 10000u; +#endif + +class PhysicalSocket : public AsyncSocket, public sigslot::has_slots<> { + public: + PhysicalSocket(PhysicalSocketServer* ss, SOCKET s = INVALID_SOCKET) + : ss_(ss), s_(s), enabled_events_(0), error_(0), + state_((s == INVALID_SOCKET) ? CS_CLOSED : CS_CONNECTED), + resolver_(NULL) { +#if defined(WEBRTC_WIN) + // EnsureWinsockInit() ensures that winsock is initialized. The default + // version of this function doesn't do anything because winsock is + // initialized by constructor of a static object. If neccessary libjingle + // users can link it with a different version of this function by replacing + // win32socketinit.cc. See win32socketinit.cc for more details. + EnsureWinsockInit(); +#endif + if (s_ != INVALID_SOCKET) { + enabled_events_ = DE_READ | DE_WRITE; + + int type = SOCK_STREAM; + socklen_t len = sizeof(type); + VERIFY(0 == getsockopt(s_, SOL_SOCKET, SO_TYPE, (SockOptArg)&type, &len)); + udp_ = (SOCK_DGRAM == type); + } + } + + virtual ~PhysicalSocket() { + Close(); + } + + // Creates the underlying OS socket (same as the "socket" function). + virtual bool Create(int family, int type) { + Close(); + s_ = ::socket(family, type, 0); + udp_ = (SOCK_DGRAM == type); + UpdateLastError(); + if (udp_) + enabled_events_ = DE_READ | DE_WRITE; + return s_ != INVALID_SOCKET; + } + + SocketAddress GetLocalAddress() const { + sockaddr_storage addr_storage = {0}; + socklen_t addrlen = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int result = ::getsockname(s_, addr, &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr_storage, &address); + } else { + LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket=" + << s_; + } + return address; + } + + SocketAddress GetRemoteAddress() const { + sockaddr_storage addr_storage = {0}; + socklen_t addrlen = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int result = ::getpeername(s_, addr, &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr_storage, &address); + } else { + LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket=" + << s_; + } + return address; + } + + int Bind(const SocketAddress& bind_addr) { + sockaddr_storage addr_storage; + size_t len = bind_addr.ToSockAddrStorage(&addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int err = ::bind(s_, addr, static_cast(len)); + UpdateLastError(); +#ifdef _DEBUG + if (0 == err) { + dbg_addr_ = "Bound @ "; + dbg_addr_.append(GetLocalAddress().ToString()); + } +#endif // _DEBUG + return err; + } + + int Connect(const SocketAddress& addr) { + // TODO: Implicit creation is required to reconnect... + // ...but should we make it more explicit? + if (state_ != CS_CLOSED) { + SetError(EALREADY); + return SOCKET_ERROR; + } + if (addr.IsUnresolved()) { + LOG(LS_VERBOSE) << "Resolving addr in PhysicalSocket::Connect"; + resolver_ = new AsyncResolver(); + resolver_->SignalDone.connect(this, &PhysicalSocket::OnResolveResult); + resolver_->Start(addr); + state_ = CS_CONNECTING; + return 0; + } + + return DoConnect(addr); + } + + int DoConnect(const SocketAddress& connect_addr) { + if ((s_ == INVALID_SOCKET) && + !Create(connect_addr.family(), SOCK_STREAM)) { + return SOCKET_ERROR; + } + sockaddr_storage addr_storage; + size_t len = connect_addr.ToSockAddrStorage(&addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int err = ::connect(s_, addr, static_cast(len)); + UpdateLastError(); + if (err == 0) { + state_ = CS_CONNECTED; + } else if (IsBlockingError(GetError())) { + state_ = CS_CONNECTING; + enabled_events_ |= DE_CONNECT; + } else { + return SOCKET_ERROR; + } + + enabled_events_ |= DE_READ | DE_WRITE; + return 0; + } + + int GetError() const { + CritScope cs(&crit_); + return error_; + } + + void SetError(int error) { + CritScope cs(&crit_); + error_ = error; + } + + ConnState GetState() const { + return state_; + } + + int GetOption(Option opt, int* value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + socklen_t optlen = sizeof(*value); + int ret = ::getsockopt(s_, slevel, sopt, (SockOptArg)value, &optlen); + if (ret != -1 && opt == OPT_DONTFRAGMENT) { +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + *value = (*value != IP_PMTUDISC_DONT) ? 1 : 0; +#endif + } + return ret; + } + + int SetOption(Option opt, int value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + if (opt == OPT_DONTFRAGMENT) { +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + value = (value) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT; +#endif + } + return ::setsockopt(s_, slevel, sopt, (SockOptArg)&value, sizeof(value)); + } + + int Send(const void *pv, size_t cb) { + int sent = ::send(s_, reinterpret_cast(pv), (int)cb, +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // Suppress SIGPIPE. Without this, attempting to send on a socket whose + // other end is closed will result in a SIGPIPE signal being raised to + // our process, which by default will terminate the process, which we + // don't want. By specifying this flag, we'll just get the error EPIPE + // instead and can handle the error gracefully. + MSG_NOSIGNAL +#else + 0 +#endif + ); + UpdateLastError(); + MaybeRemapSendError(); + // We have seen minidumps where this may be false. + ASSERT(sent <= static_cast(cb)); + if ((sent < 0) && IsBlockingError(GetError())) { + enabled_events_ |= DE_WRITE; + } + return sent; + } + + int SendTo(const void* buffer, size_t length, const SocketAddress& addr) { + sockaddr_storage saddr; + size_t len = addr.ToSockAddrStorage(&saddr); + int sent = ::sendto( + s_, static_cast(buffer), static_cast(length), +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // Suppress SIGPIPE. See above for explanation. + MSG_NOSIGNAL, +#else + 0, +#endif + reinterpret_cast(&saddr), static_cast(len)); + UpdateLastError(); + MaybeRemapSendError(); + // We have seen minidumps where this may be false. + ASSERT(sent <= static_cast(length)); + if ((sent < 0) && IsBlockingError(GetError())) { + enabled_events_ |= DE_WRITE; + } + return sent; + } + + int Recv(void* buffer, size_t length) { + int received = ::recv(s_, static_cast(buffer), + static_cast(length), 0); + if ((received == 0) && (length != 0)) { + // Note: on graceful shutdown, recv can return 0. In this case, we + // pretend it is blocking, and then signal close, so that simplifying + // assumptions can be made about Recv. + LOG(LS_WARNING) << "EOF from socket; deferring close event"; + // Must turn this back on so that the select() loop will notice the close + // event. + enabled_events_ |= DE_READ; + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + } + UpdateLastError(); + int error = GetError(); + bool success = (received >= 0) || IsBlockingError(error); + if (udp_ || success) { + enabled_events_ |= DE_READ; + } + if (!success) { + LOG_F(LS_VERBOSE) << "Error = " << error; + } + return received; + } + + int RecvFrom(void* buffer, size_t length, SocketAddress *out_addr) { + sockaddr_storage addr_storage; + socklen_t addr_len = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + int received = ::recvfrom(s_, static_cast(buffer), + static_cast(length), 0, addr, &addr_len); + UpdateLastError(); + if ((received >= 0) && (out_addr != NULL)) + SocketAddressFromSockAddrStorage(addr_storage, out_addr); + int error = GetError(); + bool success = (received >= 0) || IsBlockingError(error); + if (udp_ || success) { + enabled_events_ |= DE_READ; + } + if (!success) { + LOG_F(LS_VERBOSE) << "Error = " << error; + } + return received; + } + + int Listen(int backlog) { + int err = ::listen(s_, backlog); + UpdateLastError(); + if (err == 0) { + state_ = CS_CONNECTING; + enabled_events_ |= DE_ACCEPT; +#ifdef _DEBUG + dbg_addr_ = "Listening @ "; + dbg_addr_.append(GetLocalAddress().ToString()); +#endif // _DEBUG + } + return err; + } + + AsyncSocket* Accept(SocketAddress *out_addr) { + sockaddr_storage addr_storage; + socklen_t addr_len = sizeof(addr_storage); + sockaddr* addr = reinterpret_cast(&addr_storage); + SOCKET s = ::accept(s_, addr, &addr_len); + UpdateLastError(); + if (s == INVALID_SOCKET) + return NULL; + enabled_events_ |= DE_ACCEPT; + if (out_addr != NULL) + SocketAddressFromSockAddrStorage(addr_storage, out_addr); + return ss_->WrapSocket(s); + } + + int Close() { + if (s_ == INVALID_SOCKET) + return 0; + int err = ::closesocket(s_); + UpdateLastError(); + s_ = INVALID_SOCKET; + state_ = CS_CLOSED; + enabled_events_ = 0; + if (resolver_) { + resolver_->Destroy(false); + resolver_ = NULL; + } + return err; + } + + int EstimateMTU(uint16* mtu) { + SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + SetError(ENOTCONN); + return -1; + } + +#if defined(WEBRTC_WIN) + // Gets the interface MTU (TTL=1) for the interface used to reach |addr|. + WinPing ping; + if (!ping.IsValid()) { + SetError(EINVAL); // can't think of a better error ID + return -1; + } + int header_size = ICMP_HEADER_SIZE; + if (addr.family() == AF_INET6) { + header_size += IPV6_HEADER_SIZE; + } else if (addr.family() == AF_INET) { + header_size += IP_HEADER_SIZE; + } + + for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) { + int32 size = PACKET_MAXIMUMS[level] - header_size; + WinPing::PingResult result = ping.Ping(addr.ipaddr(), size, + ICMP_PING_TIMEOUT_MILLIS, + 1, false); + if (result == WinPing::PING_FAIL) { + SetError(EINVAL); // can't think of a better error ID + return -1; + } else if (result != WinPing::PING_TOO_LARGE) { + *mtu = PACKET_MAXIMUMS[level]; + return 0; + } + } + + ASSERT(false); + return -1; +#elif defined(WEBRTC_MAC) + // No simple way to do this on Mac OS X. + // SIOCGIFMTU would work if we knew which interface would be used, but + // figuring that out is pretty complicated. For now we'll return an error + // and let the caller pick a default MTU. + SetError(EINVAL); + return -1; +#elif defined(WEBRTC_LINUX) + // Gets the path MTU. + int value; + socklen_t vlen = sizeof(value); + int err = getsockopt(s_, IPPROTO_IP, IP_MTU, &value, &vlen); + if (err < 0) { + UpdateLastError(); + return err; + } + + ASSERT((0 <= value) && (value <= 65536)); + *mtu = value; + return 0; +#elif defined(__native_client__) + // Most socket operations, including this, will fail in NaCl's sandbox. + error_ = EACCES; + return -1; +#endif + } + + SocketServer* socketserver() { return ss_; } + + protected: + void OnResolveResult(AsyncResolverInterface* resolver) { + if (resolver != resolver_) { + return; + } + + int error = resolver_->GetError(); + if (error == 0) { + error = DoConnect(resolver_->address()); + } else { + Close(); + } + + if (error) { + SetError(error); + SignalCloseEvent(this, error); + } + } + + void UpdateLastError() { + SetError(LAST_SYSTEM_ERROR); + } + + void MaybeRemapSendError() { +#if defined(WEBRTC_MAC) + // https://developer.apple.com/library/mac/documentation/Darwin/ + // Reference/ManPages/man2/sendto.2.html + // ENOBUFS - The output queue for a network interface is full. + // This generally indicates that the interface has stopped sending, + // but may be caused by transient congestion. + if (GetError() == ENOBUFS) { + SetError(EWOULDBLOCK); + } +#endif + } + + static int TranslateOption(Option opt, int* slevel, int* sopt) { + switch (opt) { + case OPT_DONTFRAGMENT: +#if defined(WEBRTC_WIN) + *slevel = IPPROTO_IP; + *sopt = IP_DONTFRAGMENT; + break; +#elif defined(WEBRTC_MAC) || defined(BSD) || defined(__native_client__) + LOG(LS_WARNING) << "Socket::OPT_DONTFRAGMENT not supported."; + return -1; +#elif defined(WEBRTC_POSIX) + *slevel = IPPROTO_IP; + *sopt = IP_MTU_DISCOVER; + break; +#endif + case OPT_RCVBUF: + *slevel = SOL_SOCKET; + *sopt = SO_RCVBUF; + break; + case OPT_SNDBUF: + *slevel = SOL_SOCKET; + *sopt = SO_SNDBUF; + break; + case OPT_NODELAY: + *slevel = IPPROTO_TCP; + *sopt = TCP_NODELAY; + break; + case OPT_DSCP: + LOG(LS_WARNING) << "Socket::OPT_DSCP not supported."; + return -1; + case OPT_RTP_SENDTIME_EXTN_ID: + return -1; // No logging is necessary as this not a OS socket option. + default: + ASSERT(false); + return -1; + } + return 0; + } + + PhysicalSocketServer* ss_; + SOCKET s_; + uint8 enabled_events_; + bool udp_; + int error_; + // Protects |error_| that is accessed from different threads. + mutable CriticalSection crit_; + ConnState state_; + AsyncResolver* resolver_; + +#ifdef _DEBUG + std::string dbg_addr_; +#endif // _DEBUG; +}; + +#if defined(WEBRTC_POSIX) +class EventDispatcher : public Dispatcher { + public: + EventDispatcher(PhysicalSocketServer* ss) : ss_(ss), fSignaled_(false) { + if (pipe(afd_) < 0) + LOG(LERROR) << "pipe failed"; + ss_->Add(this); + } + + virtual ~EventDispatcher() { + ss_->Remove(this); + close(afd_[0]); + close(afd_[1]); + } + + virtual void Signal() { + CritScope cs(&crit_); + if (!fSignaled_) { + const uint8 b[1] = { 0 }; + if (VERIFY(1 == write(afd_[1], b, sizeof(b)))) { + fSignaled_ = true; + } + } + } + + virtual uint32 GetRequestedEvents() { + return DE_READ; + } + + virtual void OnPreEvent(uint32 ff) { + // It is not possible to perfectly emulate an auto-resetting event with + // pipes. This simulates it by resetting before the event is handled. + + CritScope cs(&crit_); + if (fSignaled_) { + uint8 b[4]; // Allow for reading more than 1 byte, but expect 1. + VERIFY(1 == read(afd_[0], b, sizeof(b))); + fSignaled_ = false; + } + } + + virtual void OnEvent(uint32 ff, int err) { + ASSERT(false); + } + + virtual int GetDescriptor() { + return afd_[0]; + } + + virtual bool IsDescriptorClosed() { + return false; + } + + private: + PhysicalSocketServer *ss_; + int afd_[2]; + bool fSignaled_; + CriticalSection crit_; +}; + +// These two classes use the self-pipe trick to deliver POSIX signals to our +// select loop. This is the only safe, reliable, cross-platform way to do +// non-trivial things with a POSIX signal in an event-driven program (until +// proper pselect() implementations become ubiquitous). + +class PosixSignalHandler { + public: + // POSIX only specifies 32 signals, but in principle the system might have + // more and the programmer might choose to use them, so we size our array + // for 128. + static const int kNumPosixSignals = 128; + + // There is just a single global instance. (Signal handlers do not get any + // sort of user-defined void * parameter, so they can't access anything that + // isn't global.) + static PosixSignalHandler* Instance() { + LIBJINGLE_DEFINE_STATIC_LOCAL(PosixSignalHandler, instance, ()); + return &instance; + } + + // Returns true if the given signal number is set. + bool IsSignalSet(int signum) const { + ASSERT(signum < ARRAY_SIZE(received_signal_)); + if (signum < ARRAY_SIZE(received_signal_)) { + return received_signal_[signum]; + } else { + return false; + } + } + + // Clears the given signal number. + void ClearSignal(int signum) { + ASSERT(signum < ARRAY_SIZE(received_signal_)); + if (signum < ARRAY_SIZE(received_signal_)) { + received_signal_[signum] = false; + } + } + + // Returns the file descriptor to monitor for signal events. + int GetDescriptor() const { + return afd_[0]; + } + + // This is called directly from our real signal handler, so it must be + // signal-handler-safe. That means it cannot assume anything about the + // user-level state of the process, since the handler could be executed at any + // time on any thread. + void OnPosixSignalReceived(int signum) { + if (signum >= ARRAY_SIZE(received_signal_)) { + // We don't have space in our array for this. + return; + } + // Set a flag saying we've seen this signal. + received_signal_[signum] = true; + // Notify application code that we got a signal. + const uint8 b[1] = { 0 }; + if (-1 == write(afd_[1], b, sizeof(b))) { + // Nothing we can do here. If there's an error somehow then there's + // nothing we can safely do from a signal handler. + // No, we can't even safely log it. + // But, we still have to check the return value here. Otherwise, + // GCC 4.4.1 complains ignoring return value. Even (void) doesn't help. + return; + } + } + + private: + PosixSignalHandler() { + if (pipe(afd_) < 0) { + LOG_ERR(LS_ERROR) << "pipe failed"; + return; + } + if (fcntl(afd_[0], F_SETFL, O_NONBLOCK) < 0) { + LOG_ERR(LS_WARNING) << "fcntl #1 failed"; + } + if (fcntl(afd_[1], F_SETFL, O_NONBLOCK) < 0) { + LOG_ERR(LS_WARNING) << "fcntl #2 failed"; + } + memset(const_cast(static_cast(received_signal_)), + 0, + sizeof(received_signal_)); + } + + ~PosixSignalHandler() { + int fd1 = afd_[0]; + int fd2 = afd_[1]; + // We clobber the stored file descriptor numbers here or else in principle + // a signal that happens to be delivered during application termination + // could erroneously write a zero byte to an unrelated file handle in + // OnPosixSignalReceived() if some other file happens to be opened later + // during shutdown and happens to be given the same file descriptor number + // as our pipe had. Unfortunately even with this precaution there is still a + // race where that could occur if said signal happens to be handled + // concurrently with this code and happens to have already read the value of + // afd_[1] from memory before we clobber it, but that's unlikely. + afd_[0] = -1; + afd_[1] = -1; + close(fd1); + close(fd2); + } + + int afd_[2]; + // These are boolean flags that will be set in our signal handler and read + // and cleared from Wait(). There is a race involved in this, but it is + // benign. The signal handler sets the flag before signaling the pipe, so + // we'll never end up blocking in select() while a flag is still true. + // However, if two of the same signal arrive close to each other then it's + // possible that the second time the handler may set the flag while it's still + // true, meaning that signal will be missed. But the first occurrence of it + // will still be handled, so this isn't a problem. + // Volatile is not necessary here for correctness, but this data _is_ volatile + // so I've marked it as such. + volatile uint8 received_signal_[kNumPosixSignals]; +}; + +class PosixSignalDispatcher : public Dispatcher { + public: + PosixSignalDispatcher(PhysicalSocketServer *owner) : owner_(owner) { + owner_->Add(this); + } + + virtual ~PosixSignalDispatcher() { + owner_->Remove(this); + } + + virtual uint32 GetRequestedEvents() { + return DE_READ; + } + + virtual void OnPreEvent(uint32 ff) { + // Events might get grouped if signals come very fast, so we read out up to + // 16 bytes to make sure we keep the pipe empty. + uint8 b[16]; + ssize_t ret = read(GetDescriptor(), b, sizeof(b)); + if (ret < 0) { + LOG_ERR(LS_WARNING) << "Error in read()"; + } else if (ret == 0) { + LOG(LS_WARNING) << "Should have read at least one byte"; + } + } + + virtual void OnEvent(uint32 ff, int err) { + for (int signum = 0; signum < PosixSignalHandler::kNumPosixSignals; + ++signum) { + if (PosixSignalHandler::Instance()->IsSignalSet(signum)) { + PosixSignalHandler::Instance()->ClearSignal(signum); + HandlerMap::iterator i = handlers_.find(signum); + if (i == handlers_.end()) { + // This can happen if a signal is delivered to our process at around + // the same time as we unset our handler for it. It is not an error + // condition, but it's unusual enough to be worth logging. + LOG(LS_INFO) << "Received signal with no handler: " << signum; + } else { + // Otherwise, execute our handler. + (*i->second)(signum); + } + } + } + } + + virtual int GetDescriptor() { + return PosixSignalHandler::Instance()->GetDescriptor(); + } + + virtual bool IsDescriptorClosed() { + return false; + } + + void SetHandler(int signum, void (*handler)(int)) { + handlers_[signum] = handler; + } + + void ClearHandler(int signum) { + handlers_.erase(signum); + } + + bool HasHandlers() { + return !handlers_.empty(); + } + + private: + typedef std::map HandlerMap; + + HandlerMap handlers_; + // Our owner. + PhysicalSocketServer *owner_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { + public: + explicit SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) { + } + SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) { + } + + virtual ~SocketDispatcher() { + Close(); + } + + bool Initialize() { + ss_->Add(this); + fcntl(s_, F_SETFL, fcntl(s_, F_GETFL, 0) | O_NONBLOCK); + return true; + } + + virtual bool Create(int type) { + return Create(AF_INET, type); + } + + virtual bool Create(int family, int type) { + // Change the socket to be non-blocking. + if (!PhysicalSocket::Create(family, type)) + return false; + + return Initialize(); + } + + virtual int GetDescriptor() { + return s_; + } + + virtual bool IsDescriptorClosed() { + // We don't have a reliable way of distinguishing end-of-stream + // from readability. So test on each readable call. Is this + // inefficient? Probably. + char ch; + ssize_t res = ::recv(s_, &ch, 1, MSG_PEEK); + if (res > 0) { + // Data available, so not closed. + return false; + } else if (res == 0) { + // EOF, so closed. + return true; + } else { // error + switch (errno) { + // Returned if we've already closed s_. + case EBADF: + // Returned during ungraceful peer shutdown. + case ECONNRESET: + return true; + default: + // Assume that all other errors are just blocking errors, meaning the + // connection is still good but we just can't read from it right now. + // This should only happen when connecting (and at most once), because + // in all other cases this function is only called if the file + // descriptor is already known to be in the readable state. However, + // it's not necessary a problem if we spuriously interpret a + // "connection lost"-type error as a blocking error, because typically + // the next recv() will get EOF, so we'll still eventually notice that + // the socket is closed. + LOG_ERR(LS_WARNING) << "Assuming benign blocking error"; + return false; + } + } + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + if ((ff & DE_CONNECT) != 0) + state_ = CS_CONNECTED; + if ((ff & DE_CLOSE) != 0) + state_ = CS_CLOSED; + } + + virtual void OnEvent(uint32 ff, int err) { + // Make sure we deliver connect/accept first. Otherwise, consumers may see + // something like a READ followed by a CONNECT, which would be odd. + if ((ff & DE_CONNECT) != 0) { + enabled_events_ &= ~DE_CONNECT; + SignalConnectEvent(this); + } + if ((ff & DE_ACCEPT) != 0) { + enabled_events_ &= ~DE_ACCEPT; + SignalReadEvent(this); + } + if ((ff & DE_READ) != 0) { + enabled_events_ &= ~DE_READ; + SignalReadEvent(this); + } + if ((ff & DE_WRITE) != 0) { + enabled_events_ &= ~DE_WRITE; + SignalWriteEvent(this); + } + if ((ff & DE_CLOSE) != 0) { + // The socket is now dead to us, so stop checking it. + enabled_events_ = 0; + SignalCloseEvent(this, err); + } + } + + virtual int Close() { + if (s_ == INVALID_SOCKET) + return 0; + + ss_->Remove(this); + return PhysicalSocket::Close(); + } +}; + +class FileDispatcher: public Dispatcher, public AsyncFile { + public: + FileDispatcher(int fd, PhysicalSocketServer *ss) : ss_(ss), fd_(fd) { + set_readable(true); + + ss_->Add(this); + + fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL, 0) | O_NONBLOCK); + } + + virtual ~FileDispatcher() { + ss_->Remove(this); + } + + SocketServer* socketserver() { return ss_; } + + virtual int GetDescriptor() { + return fd_; + } + + virtual bool IsDescriptorClosed() { + return false; + } + + virtual uint32 GetRequestedEvents() { + return flags_; + } + + virtual void OnPreEvent(uint32 ff) { + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & DE_READ) != 0) + SignalReadEvent(this); + if ((ff & DE_WRITE) != 0) + SignalWriteEvent(this); + if ((ff & DE_CLOSE) != 0) + SignalCloseEvent(this, err); + } + + virtual bool readable() { + return (flags_ & DE_READ) != 0; + } + + virtual void set_readable(bool value) { + flags_ = value ? (flags_ | DE_READ) : (flags_ & ~DE_READ); + } + + virtual bool writable() { + return (flags_ & DE_WRITE) != 0; + } + + virtual void set_writable(bool value) { + flags_ = value ? (flags_ | DE_WRITE) : (flags_ & ~DE_WRITE); + } + + private: + PhysicalSocketServer* ss_; + int fd_; + int flags_; +}; + +AsyncFile* PhysicalSocketServer::CreateFile(int fd) { + return new FileDispatcher(fd, this); +} + +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +static uint32 FlagsToEvents(uint32 events) { + uint32 ffFD = FD_CLOSE; + if (events & DE_READ) + ffFD |= FD_READ; + if (events & DE_WRITE) + ffFD |= FD_WRITE; + if (events & DE_CONNECT) + ffFD |= FD_CONNECT; + if (events & DE_ACCEPT) + ffFD |= FD_ACCEPT; + return ffFD; +} + +class EventDispatcher : public Dispatcher { + public: + EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) { + hev_ = WSACreateEvent(); + if (hev_) { + ss_->Add(this); + } + } + + ~EventDispatcher() { + if (hev_ != NULL) { + ss_->Remove(this); + WSACloseEvent(hev_); + hev_ = NULL; + } + } + + virtual void Signal() { + if (hev_ != NULL) + WSASetEvent(hev_); + } + + virtual uint32 GetRequestedEvents() { + return 0; + } + + virtual void OnPreEvent(uint32 ff) { + WSAResetEvent(hev_); + } + + virtual void OnEvent(uint32 ff, int err) { + } + + virtual WSAEVENT GetWSAEvent() { + return hev_; + } + + virtual SOCKET GetSocket() { + return INVALID_SOCKET; + } + + virtual bool CheckSignalClose() { return false; } + +private: + PhysicalSocketServer* ss_; + WSAEVENT hev_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { + public: + static int next_id_; + int id_; + bool signal_close_; + int signal_err_; + + SocketDispatcher(PhysicalSocketServer* ss) + : PhysicalSocket(ss), + id_(0), + signal_close_(false) { + } + + SocketDispatcher(SOCKET s, PhysicalSocketServer* ss) + : PhysicalSocket(ss, s), + id_(0), + signal_close_(false) { + } + + virtual ~SocketDispatcher() { + Close(); + } + + bool Initialize() { + ASSERT(s_ != INVALID_SOCKET); + // Must be a non-blocking + u_long argp = 1; + ioctlsocket(s_, FIONBIO, &argp); + ss_->Add(this); + return true; + } + + virtual bool Create(int type) { + return Create(AF_INET, type); + } + + virtual bool Create(int family, int type) { + // Create socket + if (!PhysicalSocket::Create(family, type)) + return false; + + if (!Initialize()) + return false; + + do { id_ = ++next_id_; } while (id_ == 0); + return true; + } + + virtual int Close() { + if (s_ == INVALID_SOCKET) + return 0; + + id_ = 0; + signal_close_ = false; + ss_->Remove(this); + return PhysicalSocket::Close(); + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + if ((ff & DE_CONNECT) != 0) + state_ = CS_CONNECTED; + // We set CS_CLOSED from CheckSignalClose. + } + + virtual void OnEvent(uint32 ff, int err) { + int cache_id = id_; + // Make sure we deliver connect/accept first. Otherwise, consumers may see + // something like a READ followed by a CONNECT, which would be odd. + if (((ff & DE_CONNECT) != 0) && (id_ == cache_id)) { + if (ff != DE_CONNECT) + LOG(LS_VERBOSE) << "Signalled with DE_CONNECT: " << ff; + enabled_events_ &= ~DE_CONNECT; +#ifdef _DEBUG + dbg_addr_ = "Connected @ "; + dbg_addr_.append(GetRemoteAddress().ToString()); +#endif // _DEBUG + SignalConnectEvent(this); + } + if (((ff & DE_ACCEPT) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~DE_ACCEPT; + SignalReadEvent(this); + } + if ((ff & DE_READ) != 0) { + enabled_events_ &= ~DE_READ; + SignalReadEvent(this); + } + if (((ff & DE_WRITE) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~DE_WRITE; + SignalWriteEvent(this); + } + if (((ff & DE_CLOSE) != 0) && (id_ == cache_id)) { + signal_close_ = true; + signal_err_ = err; + } + } + + virtual WSAEVENT GetWSAEvent() { + return WSA_INVALID_EVENT; + } + + virtual SOCKET GetSocket() { + return s_; + } + + virtual bool CheckSignalClose() { + if (!signal_close_) + return false; + + char ch; + if (recv(s_, &ch, 1, MSG_PEEK) > 0) + return false; + + state_ = CS_CLOSED; + signal_close_ = false; + SignalCloseEvent(this, signal_err_); + return true; + } +}; + +int SocketDispatcher::next_id_ = 0; + +#endif // WEBRTC_WIN + +// Sets the value of a boolean value to false when signaled. +class Signaler : public EventDispatcher { + public: + Signaler(PhysicalSocketServer* ss, bool* pf) + : EventDispatcher(ss), pf_(pf) { + } + virtual ~Signaler() { } + + void OnEvent(uint32 ff, int err) { + if (pf_) + *pf_ = false; + } + + private: + bool *pf_; +}; + +PhysicalSocketServer::PhysicalSocketServer() + : fWait_(false) { + signal_wakeup_ = new Signaler(this, &fWait_); +#if defined(WEBRTC_WIN) + socket_ev_ = WSACreateEvent(); +#endif +} + +PhysicalSocketServer::~PhysicalSocketServer() { +#if defined(WEBRTC_WIN) + WSACloseEvent(socket_ev_); +#endif +#if defined(WEBRTC_POSIX) + signal_dispatcher_.reset(); +#endif + delete signal_wakeup_; + ASSERT(dispatchers_.empty()); +} + +void PhysicalSocketServer::WakeUp() { + signal_wakeup_->Signal(); +} + +Socket* PhysicalSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* PhysicalSocketServer::CreateSocket(int family, int type) { + PhysicalSocket* socket = new PhysicalSocket(this); + if (socket->Create(family, type)) { + return socket; + } else { + delete socket; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int family, int type) { + SocketDispatcher* dispatcher = new SocketDispatcher(this); + if (dispatcher->Create(family, type)) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::WrapSocket(SOCKET s) { + SocketDispatcher* dispatcher = new SocketDispatcher(s, this); + if (dispatcher->Initialize()) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +void PhysicalSocketServer::Add(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + // Prevent duplicates. This can cause dead dispatchers to stick around. + DispatcherList::iterator pos = std::find(dispatchers_.begin(), + dispatchers_.end(), + pdispatcher); + if (pos != dispatchers_.end()) + return; + dispatchers_.push_back(pdispatcher); +} + +void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + DispatcherList::iterator pos = std::find(dispatchers_.begin(), + dispatchers_.end(), + pdispatcher); + // We silently ignore duplicate calls to Add, so we should silently ignore + // the (expected) symmetric calls to Remove. Note that this may still hide + // a real issue, so we at least log a warning about it. + if (pos == dispatchers_.end()) { + LOG(LS_WARNING) << "PhysicalSocketServer asked to remove a unknown " + << "dispatcher, potentially from a duplicate call to Add."; + return; + } + size_t index = pos - dispatchers_.begin(); + dispatchers_.erase(pos); + for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end(); + ++it) { + if (index < **it) { + --**it; + } + } +} + +#if defined(WEBRTC_POSIX) +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) { + // Calculate timing information + + struct timeval *ptvWait = NULL; + struct timeval tvWait; + struct timeval tvStop; + if (cmsWait != kForever) { + // Calculate wait timeval + tvWait.tv_sec = cmsWait / 1000; + tvWait.tv_usec = (cmsWait % 1000) * 1000; + ptvWait = &tvWait; + + // Calculate when to return in a timeval + gettimeofday(&tvStop, NULL); + tvStop.tv_sec += tvWait.tv_sec; + tvStop.tv_usec += tvWait.tv_usec; + if (tvStop.tv_usec >= 1000000) { + tvStop.tv_usec -= 1000000; + tvStop.tv_sec += 1; + } + } + + // Zero all fd_sets. Don't need to do this inside the loop since + // select() zeros the descriptors not signaled + + fd_set fdsRead; + FD_ZERO(&fdsRead); + fd_set fdsWrite; + FD_ZERO(&fdsWrite); + + fWait_ = true; + + while (fWait_) { + int fdmax = -1; + { + CritScope cr(&crit_); + for (size_t i = 0; i < dispatchers_.size(); ++i) { + // Query dispatchers for read and write wait state + Dispatcher *pdispatcher = dispatchers_[i]; + ASSERT(pdispatcher); + if (!process_io && (pdispatcher != signal_wakeup_)) + continue; + int fd = pdispatcher->GetDescriptor(); + if (fd > fdmax) + fdmax = fd; + + uint32 ff = pdispatcher->GetRequestedEvents(); + if (ff & (DE_READ | DE_ACCEPT)) + FD_SET(fd, &fdsRead); + if (ff & (DE_WRITE | DE_CONNECT)) + FD_SET(fd, &fdsWrite); + } + } + + // Wait then call handlers as appropriate + // < 0 means error + // 0 means timeout + // > 0 means count of descriptors ready + int n = select(fdmax + 1, &fdsRead, &fdsWrite, NULL, ptvWait); + + // If error, return error. + if (n < 0) { + if (errno != EINTR) { + LOG_E(LS_ERROR, EN, errno) << "select"; + return false; + } + // Else ignore the error and keep going. If this EINTR was for one of the + // signals managed by this PhysicalSocketServer, the + // PosixSignalDeliveryDispatcher will be in the signaled state in the next + // iteration. + } else if (n == 0) { + // If timeout, return success + return true; + } else { + // We have signaled descriptors + CritScope cr(&crit_); + for (size_t i = 0; i < dispatchers_.size(); ++i) { + Dispatcher *pdispatcher = dispatchers_[i]; + int fd = pdispatcher->GetDescriptor(); + uint32 ff = 0; + int errcode = 0; + + // Reap any error code, which can be signaled through reads or writes. + // TODO: Should we set errcode if getsockopt fails? + if (FD_ISSET(fd, &fdsRead) || FD_ISSET(fd, &fdsWrite)) { + socklen_t len = sizeof(errcode); + ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &len); + } + + // Check readable descriptors. If we're waiting on an accept, signal + // that. Otherwise we're waiting for data, check to see if we're + // readable or really closed. + // TODO: Only peek at TCP descriptors. + if (FD_ISSET(fd, &fdsRead)) { + FD_CLR(fd, &fdsRead); + if (pdispatcher->GetRequestedEvents() & DE_ACCEPT) { + ff |= DE_ACCEPT; + } else if (errcode || pdispatcher->IsDescriptorClosed()) { + ff |= DE_CLOSE; + } else { + ff |= DE_READ; + } + } + + // Check writable descriptors. If we're waiting on a connect, detect + // success versus failure by the reaped error code. + if (FD_ISSET(fd, &fdsWrite)) { + FD_CLR(fd, &fdsWrite); + if (pdispatcher->GetRequestedEvents() & DE_CONNECT) { + if (!errcode) { + ff |= DE_CONNECT; + } else { + ff |= DE_CLOSE; + } + } else { + ff |= DE_WRITE; + } + } + + // Tell the descriptor about the event. + if (ff != 0) { + pdispatcher->OnPreEvent(ff); + pdispatcher->OnEvent(ff, errcode); + } + } + } + + // Recalc the time remaining to wait. Doing it here means it doesn't get + // calced twice the first time through the loop + if (ptvWait) { + ptvWait->tv_sec = 0; + ptvWait->tv_usec = 0; + struct timeval tvT; + gettimeofday(&tvT, NULL); + if ((tvStop.tv_sec > tvT.tv_sec) + || ((tvStop.tv_sec == tvT.tv_sec) + && (tvStop.tv_usec > tvT.tv_usec))) { + ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec; + ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec; + if (ptvWait->tv_usec < 0) { + ASSERT(ptvWait->tv_sec > 0); + ptvWait->tv_usec += 1000000; + ptvWait->tv_sec -= 1; + } + } + } + } + + return true; +} + +static void GlobalSignalHandler(int signum) { + PosixSignalHandler::Instance()->OnPosixSignalReceived(signum); +} + +bool PhysicalSocketServer::SetPosixSignalHandler(int signum, + void (*handler)(int)) { + // If handler is SIG_IGN or SIG_DFL then clear our user-level handler, + // otherwise set one. + if (handler == SIG_IGN || handler == SIG_DFL) { + if (!InstallSignal(signum, handler)) { + return false; + } + if (signal_dispatcher_) { + signal_dispatcher_->ClearHandler(signum); + if (!signal_dispatcher_->HasHandlers()) { + signal_dispatcher_.reset(); + } + } + } else { + if (!signal_dispatcher_) { + signal_dispatcher_.reset(new PosixSignalDispatcher(this)); + } + signal_dispatcher_->SetHandler(signum, handler); + if (!InstallSignal(signum, &GlobalSignalHandler)) { + return false; + } + } + return true; +} + +Dispatcher* PhysicalSocketServer::signal_dispatcher() { + return signal_dispatcher_.get(); +} + +bool PhysicalSocketServer::InstallSignal(int signum, void (*handler)(int)) { + struct sigaction act; + // It doesn't really matter what we set this mask to. + if (sigemptyset(&act.sa_mask) != 0) { + LOG_ERR(LS_ERROR) << "Couldn't set mask"; + return false; + } + act.sa_handler = handler; +#if !defined(__native_client__) + // Use SA_RESTART so that our syscalls don't get EINTR, since we don't need it + // and it's a nuisance. Though some syscalls still return EINTR and there's no + // real standard for which ones. :( + act.sa_flags = SA_RESTART; +#else + act.sa_flags = 0; +#endif + if (sigaction(signum, &act, NULL) != 0) { + LOG_ERR(LS_ERROR) << "Couldn't set sigaction"; + return false; + } + return true; +} +#endif // WEBRTC_POSIX + +#if defined(WEBRTC_WIN) +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) { + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = Time(); + + fWait_ = true; + while (fWait_) { + std::vector events; + std::vector event_owners; + + events.push_back(socket_ev_); + + { + CritScope cr(&crit_); + size_t i = 0; + iterators_.push_back(&i); + // Don't track dispatchers_.size(), because we want to pick up any new + // dispatchers that were added while processing the loop. + while (i < dispatchers_.size()) { + Dispatcher* disp = dispatchers_[i++]; + if (!process_io && (disp != signal_wakeup_)) + continue; + SOCKET s = disp->GetSocket(); + if (disp->CheckSignalClose()) { + // We just signalled close, don't poll this socket + } else if (s != INVALID_SOCKET) { + WSAEventSelect(s, + events[0], + FlagsToEvents(disp->GetRequestedEvents())); + } else { + events.push_back(disp->GetWSAEvent()); + event_owners.push_back(disp); + } + } + ASSERT(iterators_.back() == &i); + iterators_.pop_back(); + } + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == kForever) { + cmsNext = cmsWait; + } else { + cmsNext = _max(0, cmsTotal - cmsElapsed); + } + + // Wait for one of the events to signal + DWORD dw = WSAWaitForMultipleEvents(static_cast(events.size()), + &events[0], + false, + cmsNext, + false); + + if (dw == WSA_WAIT_FAILED) { + // Failed? + // TODO: need a better strategy than this! + WSAGetLastError(); + ASSERT(false); + return false; + } else if (dw == WSA_WAIT_TIMEOUT) { + // Timeout? + return true; + } else { + // Figure out which one it is and call it + CritScope cr(&crit_); + int index = dw - WSA_WAIT_EVENT_0; + if (index > 0) { + --index; // The first event is the socket event + event_owners[index]->OnPreEvent(0); + event_owners[index]->OnEvent(0, 0); + } else if (process_io) { + size_t i = 0, end = dispatchers_.size(); + iterators_.push_back(&i); + iterators_.push_back(&end); // Don't iterate over new dispatchers. + while (i < end) { + Dispatcher* disp = dispatchers_[i++]; + SOCKET s = disp->GetSocket(); + if (s == INVALID_SOCKET) + continue; + + WSANETWORKEVENTS wsaEvents; + int err = WSAEnumNetworkEvents(s, events[0], &wsaEvents); + if (err == 0) { + +#if LOGGING + { + if ((wsaEvents.lNetworkEvents & FD_READ) && + wsaEvents.iErrorCode[FD_READ_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_READ_BIT error " + << wsaEvents.iErrorCode[FD_READ_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_WRITE) && + wsaEvents.iErrorCode[FD_WRITE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_WRITE_BIT error " + << wsaEvents.iErrorCode[FD_WRITE_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CONNECT) && + wsaEvents.iErrorCode[FD_CONNECT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CONNECT_BIT error " + << wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_ACCEPT) && + wsaEvents.iErrorCode[FD_ACCEPT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_ACCEPT_BIT error " + << wsaEvents.iErrorCode[FD_ACCEPT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CLOSE) && + wsaEvents.iErrorCode[FD_CLOSE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CLOSE_BIT error " + << wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + } +#endif + uint32 ff = 0; + int errcode = 0; + if (wsaEvents.lNetworkEvents & FD_READ) + ff |= DE_READ; + if (wsaEvents.lNetworkEvents & FD_WRITE) + ff |= DE_WRITE; + if (wsaEvents.lNetworkEvents & FD_CONNECT) { + if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) { + ff |= DE_CONNECT; + } else { + ff |= DE_CLOSE; + errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + } + if (wsaEvents.lNetworkEvents & FD_ACCEPT) + ff |= DE_ACCEPT; + if (wsaEvents.lNetworkEvents & FD_CLOSE) { + ff |= DE_CLOSE; + errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + if (ff != 0) { + disp->OnPreEvent(ff); + disp->OnEvent(ff, errcode); + } + } + } + ASSERT(iterators_.back() == &end); + iterators_.pop_back(); + ASSERT(iterators_.back() == &i); + iterators_.pop_back(); + } + + // Reset the network event until new activity occurs + WSAResetEvent(socket_ev_); + } + + // Break? + if (!fWait_) + break; + cmsElapsed = TimeSince(msStart); + if ((cmsWait != kForever) && (cmsElapsed >= cmsWait)) { + break; + } + } + + // Done + return true; +} +#endif // WEBRTC_WIN + +} // namespace rtc diff --git a/webrtc/base/physicalsocketserver.h b/webrtc/base/physicalsocketserver.h new file mode 100644 index 000000000..8a289de7e --- /dev/null +++ b/webrtc/base/physicalsocketserver.h @@ -0,0 +1,120 @@ +/* + * Copyright 2004 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_BASE_PHYSICALSOCKETSERVER_H__ +#define WEBRTC_BASE_PHYSICALSOCKETSERVER_H__ + +#include + +#include "webrtc/base/asyncfile.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/criticalsection.h" + +#if defined(WEBRTC_POSIX) +typedef int SOCKET; +#endif // WEBRTC_POSIX + +namespace rtc { + +// Event constants for the Dispatcher class. +enum DispatcherEvent { + DE_READ = 0x0001, + DE_WRITE = 0x0002, + DE_CONNECT = 0x0004, + DE_CLOSE = 0x0008, + DE_ACCEPT = 0x0010, +}; + +class Signaler; +#if defined(WEBRTC_POSIX) +class PosixSignalDispatcher; +#endif + +class Dispatcher { + public: + virtual ~Dispatcher() {} + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; +#if defined(WEBRTC_WIN) + virtual WSAEVENT GetWSAEvent() = 0; + virtual SOCKET GetSocket() = 0; + virtual bool CheckSignalClose() = 0; +#elif defined(WEBRTC_POSIX) + virtual int GetDescriptor() = 0; + virtual bool IsDescriptorClosed() = 0; +#endif +}; + +// A socket server that provides the real sockets of the underlying OS. +class PhysicalSocketServer : public SocketServer { + public: + PhysicalSocketServer(); + virtual ~PhysicalSocketServer(); + + // SocketFactory: + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + // Internal Factory for Accept + AsyncSocket* WrapSocket(SOCKET s); + + // SocketServer: + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + void Add(Dispatcher* dispatcher); + void Remove(Dispatcher* dispatcher); + +#if defined(WEBRTC_POSIX) + AsyncFile* CreateFile(int fd); + + // Sets the function to be executed in response to the specified POSIX signal. + // The function is executed from inside Wait() using the "self-pipe trick"-- + // regardless of which thread receives the signal--and hence can safely + // manipulate user-level data structures. + // "handler" may be SIG_IGN, SIG_DFL, or a user-specified function, just like + // with signal(2). + // Only one PhysicalSocketServer should have user-level signal handlers. + // Dispatching signals on multiple PhysicalSocketServers is not reliable. + // The signal mask is not modified. It is the caller's responsibily to + // maintain it as desired. + virtual bool SetPosixSignalHandler(int signum, void (*handler)(int)); + + protected: + Dispatcher* signal_dispatcher(); +#endif + + private: + typedef std::vector DispatcherList; + typedef std::vector IteratorList; + +#if defined(WEBRTC_POSIX) + static bool InstallSignal(int signum, void (*handler)(int)); + + scoped_ptr signal_dispatcher_; +#endif + DispatcherList dispatchers_; + IteratorList iterators_; + Signaler* signal_wakeup_; + CriticalSection crit_; + bool fWait_; +#if defined(WEBRTC_WIN) + WSAEVENT socket_ev_; +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_PHYSICALSOCKETSERVER_H__ diff --git a/webrtc/base/physicalsocketserver_unittest.cc b/webrtc/base/physicalsocketserver_unittest.cc new file mode 100644 index 000000000..f29c5fc12 --- /dev/null +++ b/webrtc/base/physicalsocketserver_unittest.cc @@ -0,0 +1,274 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socket_unittest.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +class PhysicalSocketTest : public SocketTest { +}; + +TEST_F(PhysicalSocketTest, TestConnectIPv4) { + SocketTest::TestConnectIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectIPv6) { + SocketTest::TestConnectIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv4) { + SocketTest::TestConnectWithDnsLookupIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv6) { + SocketTest::TestConnectWithDnsLookupIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectFailIPv4) { + SocketTest::TestConnectFailIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectFailIPv6) { + SocketTest::TestConnectFailIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv4) { + SocketTest::TestConnectWithDnsLookupFailIPv4(); +} + + +TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv6) { + SocketTest::TestConnectWithDnsLookupFailIPv6(); +} + + +TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv4) { + SocketTest::TestConnectWithClosedSocketIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv6) { + SocketTest::TestConnectWithClosedSocketIPv6(); +} + +TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv4) { + SocketTest::TestConnectWhileNotClosedIPv4(); +} + +TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv6) { + SocketTest::TestConnectWhileNotClosedIPv6(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv4) { + SocketTest::TestServerCloseDuringConnectIPv4(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv6) { + SocketTest::TestServerCloseDuringConnectIPv6(); +} + +TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv4) { + SocketTest::TestClientCloseDuringConnectIPv4(); +} + +TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv6) { + SocketTest::TestClientCloseDuringConnectIPv6(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseIPv4) { + SocketTest::TestServerCloseIPv4(); +} + +TEST_F(PhysicalSocketTest, TestServerCloseIPv6) { + SocketTest::TestServerCloseIPv6(); +} + +TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv4) { + SocketTest::TestCloseInClosedCallbackIPv4(); +} + +TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv6) { + SocketTest::TestCloseInClosedCallbackIPv6(); +} + +TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} + +TEST_F(PhysicalSocketTest, TestTcpIPv4) { + SocketTest::TestTcpIPv4(); +} + +TEST_F(PhysicalSocketTest, TestTcpIPv6) { + SocketTest::TestTcpIPv6(); +} + +TEST_F(PhysicalSocketTest, TestUdpIPv4) { + SocketTest::TestUdpIPv4(); +} + +TEST_F(PhysicalSocketTest, TestUdpIPv6) { + SocketTest::TestUdpIPv6(); +} + +TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv4) { + SocketTest::TestUdpReadyToSendIPv4(); +} + +TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv6) { + SocketTest::TestUdpReadyToSendIPv6(); +} + +TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv4) { + SocketTest::TestGetSetOptionsIPv4(); +} + +TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv6) { + SocketTest::TestGetSetOptionsIPv6(); +} + +#if defined(WEBRTC_POSIX) + +class PosixSignalDeliveryTest : public testing::Test { + public: + static void RecordSignal(int signum) { + signals_received_.push_back(signum); + signaled_thread_ = Thread::Current(); + } + + protected: + void SetUp() { + ss_.reset(new PhysicalSocketServer()); + } + + void TearDown() { + ss_.reset(NULL); + signals_received_.clear(); + signaled_thread_ = NULL; + } + + bool ExpectSignal(int signum) { + if (signals_received_.empty()) { + LOG(LS_ERROR) << "ExpectSignal(): No signal received"; + return false; + } + if (signals_received_[0] != signum) { + LOG(LS_ERROR) << "ExpectSignal(): Received signal " << + signals_received_[0] << ", expected " << signum; + return false; + } + signals_received_.erase(signals_received_.begin()); + return true; + } + + bool ExpectNone() { + bool ret = signals_received_.empty(); + if (!ret) { + LOG(LS_ERROR) << "ExpectNone(): Received signal " << signals_received_[0] + << ", expected none"; + } + return ret; + } + + static std::vector signals_received_; + static Thread *signaled_thread_; + + scoped_ptr ss_; +}; + +std::vector PosixSignalDeliveryTest::signals_received_; +Thread *PosixSignalDeliveryTest::signaled_thread_ = NULL; + +// Test receiving a synchronous signal while not in Wait() and then entering +// Wait() afterwards. +TEST_F(PosixSignalDeliveryTest, RaiseThenWait) { + ASSERT_TRUE(ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal)); + raise(SIGTERM); + EXPECT_TRUE(ss_->Wait(0, true)); + EXPECT_TRUE(ExpectSignal(SIGTERM)); + EXPECT_TRUE(ExpectNone()); +} + +// Test that we can handle getting tons of repeated signals and that we see all +// the different ones. +TEST_F(PosixSignalDeliveryTest, InsanelyManySignals) { + ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal); + ss_->SetPosixSignalHandler(SIGINT, &RecordSignal); + for (int i = 0; i < 10000; ++i) { + raise(SIGTERM); + } + raise(SIGINT); + EXPECT_TRUE(ss_->Wait(0, true)); + // Order will be lowest signal numbers first. + EXPECT_TRUE(ExpectSignal(SIGINT)); + EXPECT_TRUE(ExpectSignal(SIGTERM)); + EXPECT_TRUE(ExpectNone()); +} + +// Test that a signal during a Wait() call is detected. +TEST_F(PosixSignalDeliveryTest, SignalDuringWait) { + ss_->SetPosixSignalHandler(SIGALRM, &RecordSignal); + alarm(1); + EXPECT_TRUE(ss_->Wait(1500, true)); + EXPECT_TRUE(ExpectSignal(SIGALRM)); + EXPECT_TRUE(ExpectNone()); +} + +class RaiseSigTermRunnable : public Runnable { + void Run(Thread *thread) { + thread->socketserver()->Wait(1000, false); + + // Allow SIGTERM. This will be the only thread with it not masked so it will + // be delivered to us. + sigset_t mask; + sigemptyset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); + + // Raise it. + raise(SIGTERM); + } +}; + +// Test that it works no matter what thread the kernel chooses to give the +// signal to (since it's not guaranteed to be the one that Wait() runs on). +TEST_F(PosixSignalDeliveryTest, SignalOnDifferentThread) { + ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal); + // Mask out SIGTERM so that it can't be delivered to this thread. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + EXPECT_EQ(0, pthread_sigmask(SIG_SETMASK, &mask, NULL)); + // Start a new thread that raises it. It will have to be delivered to that + // thread. Our implementation should safely handle it and dispatch + // RecordSignal() on this thread. + scoped_ptr thread(new Thread()); + scoped_ptr runnable(new RaiseSigTermRunnable()); + thread->Start(runnable.get()); + EXPECT_TRUE(ss_->Wait(1500, true)); + EXPECT_TRUE(ExpectSignal(SIGTERM)); + EXPECT_EQ(Thread::Current(), signaled_thread_); + EXPECT_TRUE(ExpectNone()); +} + +#endif + +} // namespace rtc diff --git a/webrtc/base/posix.cc b/webrtc/base/posix.cc new file mode 100644 index 000000000..0eb24ee64 --- /dev/null +++ b/webrtc/base/posix.cc @@ -0,0 +1,131 @@ +/* + * Copyright 2004 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 "webrtc/base/posix.h" + +#include +#include +#include + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include "webrtc/base/linuxfdwalk.h" +#endif +#include "webrtc/base/logging.h" + +namespace rtc { + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +static void closefds(void *close_errors, int fd) { + if (fd <= 2) { + // We leave stdin/out/err open to the browser's terminal, if any. + return; + } + if (close(fd) < 0) { + *static_cast(close_errors) = true; + } +} +#endif + +enum { + EXIT_FLAG_CHDIR_ERRORS = 1 << 0, +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + EXIT_FLAG_FDWALK_ERRORS = 1 << 1, + EXIT_FLAG_CLOSE_ERRORS = 1 << 2, +#endif + EXIT_FLAG_SECOND_FORK_FAILED = 1 << 3, +}; + +bool RunAsDaemon(const char *file, const char *const argv[]) { + // Fork intermediate child to daemonize. + pid_t pid = fork(); + if (pid < 0) { + LOG_ERR(LS_ERROR) << "fork()"; + return false; + } else if (!pid) { + // Child. + + // We try to close all fds and change directory to /, but if that fails we + // keep going because it's not critical. + int exit_code = 0; + if (chdir("/") < 0) { + exit_code |= EXIT_FLAG_CHDIR_ERRORS; + } +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + bool close_errors = false; + if (fdwalk(&closefds, &close_errors) < 0) { + exit_code |= EXIT_FLAG_FDWALK_ERRORS; + } + if (close_errors) { + exit_code |= EXIT_FLAG_CLOSE_ERRORS; + } +#endif + + // Fork again to become a daemon. + pid = fork(); + // It is important that everything here use _exit() and not exit(), because + // exit() would call the destructors of all global variables in the whole + // process, which is both unnecessary and unsafe. + if (pid < 0) { + exit_code |= EXIT_FLAG_SECOND_FORK_FAILED; + _exit(exit_code); // if second fork failed + } else if (!pid) { + // Child. + // Successfully daemonized. Run command. + // WEBRTC_POSIX requires the args to be typed as non-const for historical + // reasons, but it mandates that the actual implementation be const, so + // the cast is safe. + execvp(file, const_cast(argv)); + _exit(255); // if execvp failed + } + + // Parent. + // Successfully spawned process, but report any problems to the parent where + // we can log them. + _exit(exit_code); + } + + // Parent. Reap intermediate child. + int status; + pid_t child = waitpid(pid, &status, 0); + if (child < 0) { + LOG_ERR(LS_ERROR) << "Error in waitpid()"; + return false; + } + if (child != pid) { + // Should never happen (see man page). + LOG(LS_ERROR) << "waitpid() chose wrong child???"; + return false; + } + if (!WIFEXITED(status)) { + LOG(LS_ERROR) << "Intermediate child killed uncleanly"; // Probably crashed + return false; + } + + int exit_code = WEXITSTATUS(status); + if (exit_code & EXIT_FLAG_CHDIR_ERRORS) { + LOG(LS_WARNING) << "Child reported probles calling chdir()"; + } +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + if (exit_code & EXIT_FLAG_FDWALK_ERRORS) { + LOG(LS_WARNING) << "Child reported problems calling fdwalk()"; + } + if (exit_code & EXIT_FLAG_CLOSE_ERRORS) { + LOG(LS_WARNING) << "Child reported problems calling close()"; + } +#endif + if (exit_code & EXIT_FLAG_SECOND_FORK_FAILED) { + LOG(LS_ERROR) << "Failed to daemonize"; + // This means the command was not launched, so failure. + return false; + } + return true; +} + +} // namespace rtc diff --git a/webrtc/base/posix.h b/webrtc/base/posix.h new file mode 100644 index 000000000..8d1c2b11e --- /dev/null +++ b/webrtc/base/posix.h @@ -0,0 +1,25 @@ +/* + * Copyright 2004 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_BASE_POSIX_H_ +#define WEBRTC_BASE_POSIX_H_ + +namespace rtc { + +// Runs the given executable name as a daemon, so that it executes concurrently +// with this process. Upon completion, the daemon process will automatically be +// reaped by init(8), so an error exit status or a failure to start the +// executable are not reported. Returns true if the daemon process was forked +// successfully, else false. +bool RunAsDaemon(const char *file, const char *const argv[]); + +} // namespace rtc + +#endif // WEBRTC_BASE_POSIX_H_ diff --git a/webrtc/base/profiler.cc b/webrtc/base/profiler.cc new file mode 100644 index 000000000..f57344853 --- /dev/null +++ b/webrtc/base/profiler.cc @@ -0,0 +1,186 @@ +/* + * Copyright 2013 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 "webrtc/base/profiler.h" + +#include + +#include "webrtc/base/timeutils.h" + +namespace { + +// When written to an ostream, FormattedTime chooses an appropriate scale and +// suffix for a time value given in seconds. +class FormattedTime { + public: + explicit FormattedTime(double t) : time_(t) {} + double time() const { return time_; } + private: + double time_; +}; + +std::ostream& operator<<(std::ostream& stream, const FormattedTime& time) { + if (time.time() < 1.0) { + stream << (time.time() * 1000.0) << "ms"; + } else { + stream << time.time() << 's'; + } + return stream; +} + +} // namespace + +namespace rtc { + +ProfilerEvent::ProfilerEvent() + : total_time_(0.0), + mean_(0.0), + sum_of_squared_differences_(0.0), + start_count_(0), + event_count_(0) { +} + +void ProfilerEvent::Start() { + if (start_count_ == 0) { + current_start_time_ = TimeNanos(); + } + ++start_count_; +} + +void ProfilerEvent::Stop(uint64 stop_time) { + --start_count_; + ASSERT(start_count_ >= 0); + if (start_count_ == 0) { + double elapsed = static_cast(stop_time - current_start_time_) / + kNumNanosecsPerSec; + total_time_ += elapsed; + if (event_count_ == 0) { + minimum_ = maximum_ = elapsed; + } else { + minimum_ = _min(minimum_, elapsed); + maximum_ = _max(maximum_, elapsed); + } + // Online variance and mean algorithm: http://en.wikipedia.org/wiki/ + // Algorithms_for_calculating_variance#Online_algorithm + ++event_count_; + double delta = elapsed - mean_; + mean_ = mean_ + delta / event_count_; + sum_of_squared_differences_ += delta * (elapsed - mean_); + } +} + +void ProfilerEvent::Stop() { + Stop(TimeNanos()); +} + +double ProfilerEvent::standard_deviation() const { + if (event_count_ <= 1) return 0.0; + return sqrt(sum_of_squared_differences_ / (event_count_ - 1.0)); +} + +Profiler* Profiler::Instance() { + LIBJINGLE_DEFINE_STATIC_LOCAL(Profiler, instance, ()); + return &instance; +} + +void Profiler::StartEvent(const std::string& event_name) { + lock_.LockShared(); + EventMap::iterator it = events_.find(event_name); + bool needs_insert = (it == events_.end()); + lock_.UnlockShared(); + + if (needs_insert) { + // Need an exclusive lock to modify the map. + ExclusiveScope scope(&lock_); + it = events_.insert( + EventMap::value_type(event_name, ProfilerEvent())).first; + } + + it->second.Start(); +} + +void Profiler::StopEvent(const std::string& event_name) { + // Get the time ASAP, then wait for the lock. + uint64 stop_time = TimeNanos(); + SharedScope scope(&lock_); + EventMap::iterator it = events_.find(event_name); + if (it != events_.end()) { + it->second.Stop(stop_time); + } +} + +void Profiler::ReportToLog(const char* file, int line, + LoggingSeverity severity_to_use, + const std::string& event_prefix) { + if (!LogMessage::Loggable(severity_to_use)) { + return; + } + + SharedScope scope(&lock_); + + { // Output first line. + LogMessage msg(file, line, severity_to_use); + msg.stream() << "=== Profile report "; + if (event_prefix.empty()) { + msg.stream() << "(prefix: '" << event_prefix << "') "; + } + msg.stream() << "==="; + } + for (EventMap::const_iterator it = events_.begin(); + it != events_.end(); ++it) { + if (event_prefix.empty() || it->first.find(event_prefix) == 0) { + LogMessage(file, line, severity_to_use).stream() + << it->first << " " << it->second; + } + } + LogMessage(file, line, severity_to_use).stream() + << "=== End profile report ==="; +} + +void Profiler::ReportAllToLog(const char* file, int line, + LoggingSeverity severity_to_use) { + ReportToLog(file, line, severity_to_use, ""); +} + +const ProfilerEvent* Profiler::GetEvent(const std::string& event_name) const { + SharedScope scope(&lock_); + EventMap::const_iterator it = + events_.find(event_name); + return (it == events_.end()) ? NULL : &it->second; +} + +bool Profiler::Clear() { + ExclusiveScope scope(&lock_); + bool result = true; + // Clear all events that aren't started. + EventMap::iterator it = events_.begin(); + while (it != events_.end()) { + if (it->second.is_started()) { + ++it; // Can't clear started events. + result = false; + } else { + events_.erase(it++); + } + } + return result; +} + +std::ostream& operator<<(std::ostream& stream, + const ProfilerEvent& profiler_event) { + stream << "count=" << profiler_event.event_count() + << " total=" << FormattedTime(profiler_event.total_time()) + << " mean=" << FormattedTime(profiler_event.mean()) + << " min=" << FormattedTime(profiler_event.minimum()) + << " max=" << FormattedTime(profiler_event.maximum()) + << " sd=" << profiler_event.standard_deviation(); + return stream; +} + +} // namespace rtc diff --git a/webrtc/base/profiler.h b/webrtc/base/profiler.h new file mode 100644 index 000000000..13b99f7c9 --- /dev/null +++ b/webrtc/base/profiler.h @@ -0,0 +1,161 @@ +/* + * Copyright 2013 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. + */ + +// A simple wall-clock profiler for instrumented code. +// Example: +// void MyLongFunction() { +// PROFILE_F(); // Time the execution of this function. +// // Do something +// { // Time just what is in this scope. +// PROFILE("My event"); +// // Do something else +// } +// } +// Another example: +// void StartAsyncProcess() { +// PROFILE_START("My async event"); +// DoSomethingAsyncAndThenCall(&Callback); +// } +// void Callback() { +// PROFILE_STOP("My async event"); +// // Handle callback. +// } + +#ifndef WEBRTC_BASE_PROFILER_H_ +#define WEBRTC_BASE_PROFILER_H_ + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/sharedexclusivelock.h" + +// Profiling could be switched via a build flag, but for now, it's always on. +#ifndef ENABLE_PROFILING +#define ENABLE_PROFILING +#endif + +#ifdef ENABLE_PROFILING + +#define UV_HELPER2(x) _uv_ ## x +#define UV_HELPER(x) UV_HELPER2(x) +#define UNIQUE_VAR UV_HELPER(__LINE__) + +// Profiles the current scope. +#define PROFILE(msg) rtc::ProfilerScope UNIQUE_VAR(msg) +// When placed at the start of a function, profiles the current function. +#define PROFILE_F() PROFILE(__FUNCTION__) +// Reports current timings to the log at severity |sev|. +#define PROFILE_DUMP_ALL(sev) \ + rtc::Profiler::Instance()->ReportAllToLog(__FILE__, __LINE__, sev) +// Reports current timings for all events whose names are prefixed by |prefix| +// to the log at severity |sev|. Using a unique event name as |prefix| will +// report only that event. +#define PROFILE_DUMP(sev, prefix) \ + rtc::Profiler::Instance()->ReportToLog(__FILE__, __LINE__, sev, prefix) +// Starts and stops a profile event. Useful when an event is not easily +// captured within a scope (eg, an async call with a callback when done). +#define PROFILE_START(msg) rtc::Profiler::Instance()->StartEvent(msg) +#define PROFILE_STOP(msg) rtc::Profiler::Instance()->StopEvent(msg) +// TODO(ryanpetrie): Consider adding PROFILE_DUMP_EVERY(sev, iterations) + +#undef UV_HELPER2 +#undef UV_HELPER +#undef UNIQUE_VAR + +#else // ENABLE_PROFILING + +#define PROFILE(msg) (void)0 +#define PROFILE_F() (void)0 +#define PROFILE_DUMP_ALL(sev) (void)0 +#define PROFILE_DUMP(sev, prefix) (void)0 +#define PROFILE_START(msg) (void)0 +#define PROFILE_STOP(msg) (void)0 + +#endif // ENABLE_PROFILING + +namespace rtc { + +// Tracks information for one profiler event. +class ProfilerEvent { + public: + ProfilerEvent(); + void Start(); + void Stop(); + void Stop(uint64 stop_time); + double standard_deviation() const; + double total_time() const { return total_time_; } + double mean() const { return mean_; } + double minimum() const { return minimum_; } + double maximum() const { return maximum_; } + int event_count() const { return event_count_; } + bool is_started() const { return start_count_ > 0; } + + private: + uint64 current_start_time_; + double total_time_; + double mean_; + double sum_of_squared_differences_; + double minimum_; + double maximum_; + int start_count_; + int event_count_; +}; + +// Singleton that owns ProfilerEvents and reports results. Prefer to use +// macros, defined above, rather than directly calling Profiler methods. +class Profiler { + public: + void StartEvent(const std::string& event_name); + void StopEvent(const std::string& event_name); + void ReportToLog(const char* file, int line, LoggingSeverity severity_to_use, + const std::string& event_prefix); + void ReportAllToLog(const char* file, int line, + LoggingSeverity severity_to_use); + const ProfilerEvent* GetEvent(const std::string& event_name) const; + // Clears all _stopped_ events. Returns true if _all_ events were cleared. + bool Clear(); + + static Profiler* Instance(); + private: + Profiler() {} + + typedef std::map EventMap; + EventMap events_; + mutable SharedExclusiveLock lock_; + + DISALLOW_COPY_AND_ASSIGN(Profiler); +}; + +// Starts an event on construction and stops it on destruction. +// Used by PROFILE macro. +class ProfilerScope { + public: + explicit ProfilerScope(const std::string& event_name) + : event_name_(event_name) { + Profiler::Instance()->StartEvent(event_name_); + } + ~ProfilerScope() { + Profiler::Instance()->StopEvent(event_name_); + } + private: + std::string event_name_; + + DISALLOW_COPY_AND_ASSIGN(ProfilerScope); +}; + +std::ostream& operator<<(std::ostream& stream, + const ProfilerEvent& profiler_event); + +} // namespace rtc + +#endif // WEBRTC_BASE_PROFILER_H_ diff --git a/webrtc/base/profiler_unittest.cc b/webrtc/base/profiler_unittest.cc new file mode 100644 index 000000000..8f4421e9e --- /dev/null +++ b/webrtc/base/profiler_unittest.cc @@ -0,0 +1,113 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/profiler.h" +#include "webrtc/base/thread.h" + +namespace { + +const int kWaitMs = 250; +const double kWaitSec = 0.250; +const double kTolerance = 0.1; + +const char* TestFunc() { + PROFILE_F(); + rtc::Thread::SleepMs(kWaitMs); + return __FUNCTION__; +} + +} // namespace + +namespace rtc { + +TEST(ProfilerTest, TestFunction) { + ASSERT_TRUE(Profiler::Instance()->Clear()); + + // Profile a long-running function. + const char* function_name = TestFunc(); + const ProfilerEvent* event = Profiler::Instance()->GetEvent(function_name); + ASSERT_TRUE(event != NULL); + EXPECT_FALSE(event->is_started()); + EXPECT_EQ(1, event->event_count()); + EXPECT_NEAR(kWaitSec, event->mean(), kTolerance * 3); + + // Run it a second time. + TestFunc(); + EXPECT_FALSE(event->is_started()); + EXPECT_EQ(2, event->event_count()); + EXPECT_NEAR(kWaitSec, event->mean(), kTolerance); + EXPECT_NEAR(kWaitSec * 2, event->total_time(), kTolerance * 2); + EXPECT_DOUBLE_EQ(event->mean(), event->total_time() / event->event_count()); +} + +TEST(ProfilerTest, TestScopedEvents) { + const std::string kEvent1Name = "Event 1"; + const std::string kEvent2Name = "Event 2"; + const int kEvent2WaitMs = 150; + const double kEvent2WaitSec = 0.150; + const ProfilerEvent* event1; + const ProfilerEvent* event2; + ASSERT_TRUE(Profiler::Instance()->Clear()); + { // Profile a scope. + PROFILE(kEvent1Name); + event1 = Profiler::Instance()->GetEvent(kEvent1Name); + ASSERT_TRUE(event1 != NULL); + EXPECT_TRUE(event1->is_started()); + EXPECT_EQ(0, event1->event_count()); + rtc::Thread::SleepMs(kWaitMs); + EXPECT_TRUE(event1->is_started()); + } + // Check the result. + EXPECT_FALSE(event1->is_started()); + EXPECT_EQ(1, event1->event_count()); + EXPECT_NEAR(kWaitSec, event1->mean(), kTolerance); + { // Profile a second event. + PROFILE(kEvent2Name); + event2 = Profiler::Instance()->GetEvent(kEvent2Name); + ASSERT_TRUE(event2 != NULL); + EXPECT_FALSE(event1->is_started()); + EXPECT_TRUE(event2->is_started()); + rtc::Thread::SleepMs(kEvent2WaitMs); + } + // Check the result. + EXPECT_FALSE(event2->is_started()); + EXPECT_EQ(1, event2->event_count()); + + // The difference here can be as much as 0.33, so we need high tolerance. + EXPECT_NEAR(kEvent2WaitSec, event2->mean(), kTolerance * 4); + // Make sure event1 is unchanged. + EXPECT_FALSE(event1->is_started()); + EXPECT_EQ(1, event1->event_count()); + { // Run another event 1. + PROFILE(kEvent1Name); + EXPECT_TRUE(event1->is_started()); + rtc::Thread::SleepMs(kWaitMs); + } + // Check the result. + EXPECT_FALSE(event1->is_started()); + EXPECT_EQ(2, event1->event_count()); + EXPECT_NEAR(kWaitSec, event1->mean(), kTolerance); + EXPECT_NEAR(kWaitSec * 2, event1->total_time(), kTolerance * 2); + EXPECT_DOUBLE_EQ(event1->mean(), + event1->total_time() / event1->event_count()); +} + +TEST(ProfilerTest, Clear) { + ASSERT_TRUE(Profiler::Instance()->Clear()); + PROFILE_START("event"); + EXPECT_FALSE(Profiler::Instance()->Clear()); + EXPECT_TRUE(Profiler::Instance()->GetEvent("event") != NULL); + PROFILE_STOP("event"); + EXPECT_TRUE(Profiler::Instance()->Clear()); + EXPECT_EQ(NULL, Profiler::Instance()->GetEvent("event")); +} + +} // namespace rtc diff --git a/webrtc/base/proxy_unittest.cc b/webrtc/base/proxy_unittest.cc new file mode 100644 index 000000000..d8a523fe1 --- /dev/null +++ b/webrtc/base/proxy_unittest.cc @@ -0,0 +1,135 @@ +/* + * Copyright 2009 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 "webrtc/base/autodetectproxy.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/httpserver.h" +#include "webrtc/base/proxyserver.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testechoserver.h" +#include "webrtc/base/virtualsocketserver.h" + +using rtc::Socket; +using rtc::Thread; +using rtc::SocketAddress; + +static const SocketAddress kSocksProxyIntAddr("1.2.3.4", 1080); +static const SocketAddress kSocksProxyExtAddr("1.2.3.5", 0); +static const SocketAddress kHttpsProxyIntAddr("1.2.3.4", 443); +static const SocketAddress kHttpsProxyExtAddr("1.2.3.5", 0); +static const SocketAddress kBogusProxyIntAddr("1.2.3.4", 999); + +// Used to run a proxy detect on the current thread. Otherwise we would need +// to make both threads share the same VirtualSocketServer. +class AutoDetectProxyRunner : public rtc::AutoDetectProxy { + public: + explicit AutoDetectProxyRunner(const std::string& agent) + : AutoDetectProxy(agent) {} + void Run() { + DoWork(); + Thread::Current()->Restart(); // needed to reset the messagequeue + } +}; + +// Sets up a virtual socket server and HTTPS/SOCKS5 proxy servers. +class ProxyTest : public testing::Test { + public: + ProxyTest() : ss_(new rtc::VirtualSocketServer(NULL)) { + Thread::Current()->set_socketserver(ss_.get()); + socks_.reset(new rtc::SocksProxyServer( + ss_.get(), kSocksProxyIntAddr, ss_.get(), kSocksProxyExtAddr)); + https_.reset(new rtc::HttpListenServer()); + https_->Listen(kHttpsProxyIntAddr); + } + ~ProxyTest() { + Thread::Current()->set_socketserver(NULL); + } + + rtc::SocketServer* ss() { return ss_.get(); } + + rtc::ProxyType DetectProxyType(const SocketAddress& address) { + rtc::ProxyType type; + AutoDetectProxyRunner* detect = new AutoDetectProxyRunner("unittest/1.0"); + detect->set_proxy(address); + detect->Run(); // blocks until done + type = detect->proxy().type; + detect->Destroy(false); + return type; + } + + private: + rtc::scoped_ptr ss_; + rtc::scoped_ptr socks_; + // TODO: Make this a real HTTPS proxy server. + rtc::scoped_ptr https_; +}; + +// Tests whether we can use a SOCKS5 proxy to connect to a server. +TEST_F(ProxyTest, TestSocks5Connect) { + rtc::AsyncSocket* socket = + ss()->CreateAsyncSocket(kSocksProxyIntAddr.family(), SOCK_STREAM); + rtc::AsyncSocksProxySocket* proxy_socket = + new rtc::AsyncSocksProxySocket(socket, kSocksProxyIntAddr, + "", rtc::CryptString()); + // TODO: IPv6-ize these tests when proxy supports IPv6. + + rtc::TestEchoServer server(Thread::Current(), + SocketAddress(INADDR_ANY, 0)); + + rtc::AsyncTCPSocket* packet_socket = rtc::AsyncTCPSocket::Create( + proxy_socket, SocketAddress(INADDR_ANY, 0), server.address()); + EXPECT_TRUE(packet_socket != NULL); + rtc::TestClient client(packet_socket); + + EXPECT_EQ(Socket::CS_CONNECTING, proxy_socket->GetState()); + EXPECT_TRUE(client.CheckConnected()); + EXPECT_EQ(Socket::CS_CONNECTED, proxy_socket->GetState()); + EXPECT_EQ(server.address(), client.remote_address()); + client.Send("foo", 3); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL)); + EXPECT_TRUE(client.CheckNoPacket()); +} + +/* +// Tests whether we can use a HTTPS proxy to connect to a server. +TEST_F(ProxyTest, TestHttpsConnect) { + AsyncSocket* socket = ss()->CreateAsyncSocket(SOCK_STREAM); + AsyncHttpsProxySocket* proxy_socket = new AsyncHttpsProxySocket( + socket, "unittest/1.0", kHttpsProxyIntAddress, "", CryptString()); + TestClient client(new AsyncTCPSocket(proxy_socket)); + TestEchoServer server(Thread::Current(), SocketAddress()); + + EXPECT_TRUE(client.Connect(server.address())); + EXPECT_TRUE(client.CheckConnected()); + EXPECT_EQ(server.address(), client.remote_address()); + client.Send("foo", 3); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL)); + EXPECT_TRUE(client.CheckNoPacket()); +} +*/ + +// Tests whether we can autodetect a SOCKS5 proxy. +TEST_F(ProxyTest, TestAutoDetectSocks5) { + EXPECT_EQ(rtc::PROXY_SOCKS5, DetectProxyType(kSocksProxyIntAddr)); +} + +/* +// Tests whether we can autodetect a HTTPS proxy. +TEST_F(ProxyTest, TestAutoDetectHttps) { + EXPECT_EQ(rtc::PROXY_HTTPS, DetectProxyType(kHttpsProxyIntAddr)); +} +*/ + +// Tests whether we fail properly for no proxy. +TEST_F(ProxyTest, TestAutoDetectBogus) { + EXPECT_EQ(rtc::PROXY_UNKNOWN, DetectProxyType(kBogusProxyIntAddr)); +} diff --git a/webrtc/base/proxydetect.cc b/webrtc/base/proxydetect.cc new file mode 100644 index 000000000..7265f4fd9 --- /dev/null +++ b/webrtc/base/proxydetect.cc @@ -0,0 +1,1246 @@ +/* + * Copyright 2004 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 "webrtc/base/proxydetect.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include +#endif // WEBRTC_WIN + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#include +#include +#include "macconversion.h" +#endif + +#include + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stringutils.h" + +#if defined(WEBRTC_WIN) +#define _TRY_WINHTTP 1 +#define _TRY_JSPROXY 0 +#define _TRY_WM_FINDPROXY 0 +#define _TRY_IE_LAN_SETTINGS 1 +#endif // WEBRTC_WIN + +// For all platforms try Firefox. +#define _TRY_FIREFOX 1 + +// Use profiles.ini to find the correct profile for this user. +// If not set, we'll just look for the default one. +#define USE_FIREFOX_PROFILES_INI 1 + +static const size_t kMaxLineLength = 1024; +static const char kFirefoxPattern[] = "Firefox"; +static const char kInternetExplorerPattern[] = "MSIE"; + +struct StringMap { + public: + void Add(const char * name, const char * value) { map_[name] = value; } + const std::string& Get(const char * name, const char * def = "") const { + std::map::const_iterator it = + map_.find(name); + if (it != map_.end()) + return it->second; + def_ = def; + return def_; + } + bool IsSet(const char * name) const { + return (map_.find(name) != map_.end()); + } + private: + std::map map_; + mutable std::string def_; +}; + +enum UserAgent { + UA_FIREFOX, + UA_INTERNETEXPLORER, + UA_OTHER, + UA_UNKNOWN +}; + +#if _TRY_WINHTTP +//#include +// Note: From winhttp.h + +const char WINHTTP[] = "winhttp"; + +typedef LPVOID HINTERNET; + +typedef struct { + DWORD dwAccessType; // see WINHTTP_ACCESS_* types below + LPWSTR lpszProxy; // proxy server list + LPWSTR lpszProxyBypass; // proxy bypass list +} WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO; + +typedef struct { + DWORD dwFlags; + DWORD dwAutoDetectFlags; + LPCWSTR lpszAutoConfigUrl; + LPVOID lpvReserved; + DWORD dwReserved; + BOOL fAutoLogonIfChallenged; +} WINHTTP_AUTOPROXY_OPTIONS; + +typedef struct { + BOOL fAutoDetect; + LPWSTR lpszAutoConfigUrl; + LPWSTR lpszProxy; + LPWSTR lpszProxyBypass; +} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG; + +extern "C" { + typedef HINTERNET (WINAPI * pfnWinHttpOpen) + ( + IN LPCWSTR pwszUserAgent, + IN DWORD dwAccessType, + IN LPCWSTR pwszProxyName OPTIONAL, + IN LPCWSTR pwszProxyBypass OPTIONAL, + IN DWORD dwFlags + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpCloseHandle) + ( + IN HINTERNET hInternet + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetProxyForUrl) + ( + IN HINTERNET hSession, + IN LPCWSTR lpcwszUrl, + IN WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions, + OUT WINHTTP_PROXY_INFO * pProxyInfo + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetIEProxyConfig) + ( + IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * pProxyConfig + ); + +} // extern "C" + +#define WINHTTP_AUTOPROXY_AUTO_DETECT 0x00000001 +#define WINHTTP_AUTOPROXY_CONFIG_URL 0x00000002 +#define WINHTTP_AUTOPROXY_RUN_INPROCESS 0x00010000 +#define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY 0x00020000 +#define WINHTTP_AUTO_DETECT_TYPE_DHCP 0x00000001 +#define WINHTTP_AUTO_DETECT_TYPE_DNS_A 0x00000002 +#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 +#define WINHTTP_ACCESS_TYPE_NO_PROXY 1 +#define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 +#define WINHTTP_NO_PROXY_NAME NULL +#define WINHTTP_NO_PROXY_BYPASS NULL + +#endif // _TRY_WINHTTP + +#if _TRY_JSPROXY +extern "C" { + typedef BOOL (STDAPICALLTYPE * pfnInternetGetProxyInfo) + ( + LPCSTR lpszUrl, + DWORD dwUrlLength, + LPSTR lpszUrlHostName, + DWORD dwUrlHostNameLength, + LPSTR * lplpszProxyHostName, + LPDWORD lpdwProxyHostNameLength + ); +} // extern "C" +#endif // _TRY_JSPROXY + +#if _TRY_WM_FINDPROXY +#include +#include +#include +#endif // _TRY_WM_FINDPROXY + +#if _TRY_IE_LAN_SETTINGS +#include +#include +#endif // _TRY_IE_LAN_SETTINGS + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Utility Functions +////////////////////////////////////////////////////////////////////// + +#if defined(WEBRTC_WIN) +#ifdef _UNICODE + +typedef std::wstring tstring; +std::string Utf8String(const tstring& str) { return ToUtf8(str); } + +#else // !_UNICODE + +typedef std::string tstring; +std::string Utf8String(const tstring& str) { return str; } + +#endif // !_UNICODE +#endif // WEBRTC_WIN + +bool ProxyItemMatch(const Url& url, char * item, size_t len) { + // hostname:443 + if (char * port = ::strchr(item, ':')) { + *port++ = '\0'; + if (url.port() != atol(port)) { + return false; + } + } + + // A.B.C.D or A.B.C.D/24 + int a, b, c, d, m; + int match = sscanf(item, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &m); + if (match >= 4) { + uint32 ip = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | + (d & 0xFF); + if ((match < 5) || (m > 32)) + m = 32; + else if (m < 0) + m = 0; + uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m); + SocketAddress addr(url.host(), 0); + // TODO: Support IPv6 proxyitems. This code block is IPv4 only anyway. + return !addr.IsUnresolved() && + ((addr.ipaddr().v4AddressAsHostOrderInteger() & mask) == (ip & mask)); + } + + // .foo.com + if (*item == '.') { + size_t hostlen = url.host().length(); + return (hostlen > len) + && (stricmp(url.host().c_str() + (hostlen - len), item) == 0); + } + + // localhost or www.*.com + if (!string_match(url.host().c_str(), item)) + return false; + + return true; +} + +bool ProxyListMatch(const Url& url, const std::string& proxy_list, + char sep) { + const size_t BUFSIZE = 256; + char buffer[BUFSIZE]; + const char* list = proxy_list.c_str(); + while (*list) { + // Remove leading space + if (isspace(*list)) { + ++list; + continue; + } + // Break on separator + size_t len; + const char * start = list; + if (const char * end = ::strchr(list, sep)) { + len = (end - list); + list += len + 1; + } else { + len = strlen(list); + list += len; + } + // Remove trailing space + while ((len > 0) && isspace(start[len-1])) + --len; + // Check for oversized entry + if (len >= BUFSIZE) + continue; + memcpy(buffer, start, len); + buffer[len] = 0; + if (!ProxyItemMatch(url, buffer, len)) + continue; + return true; + } + return false; +} + +bool Better(ProxyType lhs, const ProxyType rhs) { + // PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN + const int PROXY_VALUE[5] = { 0, 2, 3, 1 }; + return (PROXY_VALUE[lhs] > PROXY_VALUE[rhs]); +} + +bool ParseProxy(const std::string& saddress, ProxyInfo* proxy) { + const size_t kMaxAddressLength = 1024; + // Allow semicolon, space, or tab as an address separator + const char* const kAddressSeparator = " ;\t"; + + ProxyType ptype; + std::string host; + uint16 port; + + const char* address = saddress.c_str(); + while (*address) { + size_t len; + const char * start = address; + if (const char * sep = strchr(address, kAddressSeparator)) { + len = (sep - address); + address += len + 1; + while (*address != '\0' && ::strchr(kAddressSeparator, *address)) { + address += 1; + } + } else { + len = strlen(address); + address += len; + } + + if (len > kMaxAddressLength - 1) { + LOG(LS_WARNING) << "Proxy address too long [" << start << "]"; + continue; + } + + char buffer[kMaxAddressLength]; + memcpy(buffer, start, len); + buffer[len] = 0; + + char * colon = ::strchr(buffer, ':'); + if (!colon) { + LOG(LS_WARNING) << "Proxy address without port [" << buffer << "]"; + continue; + } + + *colon = 0; + char * endptr; + port = static_cast(strtol(colon + 1, &endptr, 0)); + if (*endptr != 0) { + LOG(LS_WARNING) << "Proxy address with invalid port [" << buffer << "]"; + continue; + } + + if (char * equals = ::strchr(buffer, '=')) { + *equals = 0; + host = equals + 1; + if (_stricmp(buffer, "socks") == 0) { + ptype = PROXY_SOCKS5; + } else if (_stricmp(buffer, "https") == 0) { + ptype = PROXY_HTTPS; + } else { + LOG(LS_WARNING) << "Proxy address with unknown protocol [" + << buffer << "]"; + ptype = PROXY_UNKNOWN; + } + } else { + host = buffer; + ptype = PROXY_UNKNOWN; + } + + if (Better(ptype, proxy->type)) { + proxy->type = ptype; + proxy->address.SetIP(host); + proxy->address.SetPort(port); + } + } + + return proxy->type != PROXY_NONE; +} + +UserAgent GetAgent(const char* agent) { + if (agent) { + std::string agent_str(agent); + if (agent_str.find(kFirefoxPattern) != std::string::npos) { + return UA_FIREFOX; + } else if (agent_str.find(kInternetExplorerPattern) != std::string::npos) { + return UA_INTERNETEXPLORER; + } else if (agent_str.empty()) { + return UA_UNKNOWN; + } + } + return UA_OTHER; +} + +bool EndsWith(const std::string& a, const std::string& b) { + if (b.size() > a.size()) { + return false; + } + int result = a.compare(a.size() - b.size(), b.size(), b); + return result == 0; +} + +bool GetFirefoxProfilePath(Pathname* path) { +#if defined(WEBRTC_WIN) + wchar_t w_path[MAX_PATH]; + if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, w_path) != + S_OK) { + LOG(LS_ERROR) << "SHGetFolderPath failed"; + return false; + } + path->SetFolder(ToUtf8(w_path, wcslen(w_path))); + path->AppendFolder("Mozilla"); + path->AppendFolder("Firefox"); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + FSRef fr; + if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType, + kCreateFolder, &fr)) { + LOG(LS_ERROR) << "FSFindFolder failed"; + return false; + } + char buffer[NAME_MAX + 1]; + if (0 != FSRefMakePath(&fr, reinterpret_cast(buffer), + ARRAY_SIZE(buffer))) { + LOG(LS_ERROR) << "FSRefMakePath failed"; + return false; + } + path->SetFolder(std::string(buffer)); + path->AppendFolder("Firefox"); +#else + char* user_home = getenv("HOME"); + if (user_home == NULL) { + return false; + } + path->SetFolder(std::string(user_home)); + path->AppendFolder(".mozilla"); + path->AppendFolder("firefox"); +#endif // WEBRTC_WIN + return true; +} + +bool GetDefaultFirefoxProfile(Pathname* profile_path) { + ASSERT(NULL != profile_path); + Pathname path; + if (!GetFirefoxProfilePath(&path)) { + return false; + } + +#if USE_FIREFOX_PROFILES_INI + // [Profile0] + // Name=default + // IsRelative=1 + // Path=Profiles/2de53ejb.default + // Default=1 + + // Note: we are looking for the first entry with "Default=1", or the last + // entry in the file + path.SetFilename("profiles.ini"); + scoped_ptr fs(Filesystem::OpenFile(path, "r")); + if (!fs) { + return false; + } + Pathname candidate; + bool relative = true; + std::string line; + while (fs->ReadLine(&line) == SR_SUCCESS) { + if (line.length() == 0) { + continue; + } + if (line.at(0) == '[') { + relative = true; + candidate.clear(); + } else if (line.find("IsRelative=") == 0 && + line.length() >= 12) { + // TODO: The initial Linux public launch revealed a fairly + // high number of machines where IsRelative= did not have anything after + // it. Perhaps that is legal profiles.ini syntax? + relative = (line.at(11) != '0'); + } else if (line.find("Path=") == 0 && + line.length() >= 6) { + if (relative) { + candidate = path; + } else { + candidate.clear(); + } + candidate.AppendFolder(line.substr(5)); + } else if (line.find("Default=") == 0 && + line.length() >= 9) { + if ((line.at(8) != '0') && !candidate.empty()) { + break; + } + } + } + fs->Close(); + if (candidate.empty()) { + return false; + } + profile_path->SetPathname(candidate.pathname()); + +#else // !USE_FIREFOX_PROFILES_INI + path.AppendFolder("Profiles"); + DirectoryIterator* it = Filesystem::IterateDirectory(); + it->Iterate(path); + std::string extension(".default"); + while (!EndsWith(it->Name(), extension)) { + if (!it->Next()) { + return false; + } + } + + profile_path->SetPathname(path); + profile->AppendFolder("Profiles"); + profile->AppendFolder(it->Name()); + delete it; + +#endif // !USE_FIREFOX_PROFILES_INI + + return true; +} + +bool ReadFirefoxPrefs(const Pathname& filename, + const char * prefix, + StringMap* settings) { + scoped_ptr fs(Filesystem::OpenFile(filename, "r")); + if (!fs) { + LOG(LS_ERROR) << "Failed to open file: " << filename.pathname(); + return false; + } + + std::string line; + while (fs->ReadLine(&line) == SR_SUCCESS) { + size_t prefix_len = strlen(prefix); + + // Skip blank lines and too long lines. + if ((line.length() == 0) || (line.length() > kMaxLineLength) + || (line.at(0) == '#') || line.compare(0, 2, "/*") == 0 + || line.compare(0, 2, " *") == 0) { + continue; + } + + char buffer[kMaxLineLength]; + strcpyn(buffer, sizeof(buffer), line.c_str()); + int nstart = 0, nend = 0, vstart = 0, vend = 0; + sscanf(buffer, "user_pref(\"%n%*[^\"]%n\", %n%*[^)]%n);", + &nstart, &nend, &vstart, &vend); + if (vend > 0) { + char* name = buffer + nstart; + name[nend - nstart] = 0; + if ((vend - vstart >= 2) && (buffer[vstart] == '"')) { + vstart += 1; + vend -= 1; + } + char* value = buffer + vstart; + value[vend - vstart] = 0; + if ((strncmp(name, prefix, prefix_len) == 0) && *value) { + settings->Add(name + prefix_len, value); + } + } else { + LOG_F(LS_WARNING) << "Unparsed pref [" << buffer << "]"; + } + } + fs->Close(); + return true; +} + +bool GetFirefoxProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + Pathname path; + bool success = false; + if (GetDefaultFirefoxProfile(&path)) { + StringMap settings; + path.SetFilename("prefs.js"); + if (ReadFirefoxPrefs(path, "network.proxy.", &settings)) { + success = true; + proxy->bypass_list = + settings.Get("no_proxies_on", "localhost, 127.0.0.1"); + if (settings.Get("type") == "1") { + // User has manually specified a proxy, try to figure out what + // type it is. + if (ProxyListMatch(purl, proxy->bypass_list.c_str(), ',')) { + // Our url is in the list of url's to bypass proxy. + } else if (settings.Get("share_proxy_settings") == "true") { + proxy->type = PROXY_UNKNOWN; + proxy->address.SetIP(settings.Get("http")); + proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); + } else if (settings.IsSet("socks")) { + proxy->type = PROXY_SOCKS5; + proxy->address.SetIP(settings.Get("socks")); + proxy->address.SetPort(atoi(settings.Get("socks_port").c_str())); + } else if (settings.IsSet("ssl")) { + proxy->type = PROXY_HTTPS; + proxy->address.SetIP(settings.Get("ssl")); + proxy->address.SetPort(atoi(settings.Get("ssl_port").c_str())); + } else if (settings.IsSet("http")) { + proxy->type = PROXY_HTTPS; + proxy->address.SetIP(settings.Get("http")); + proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); + } + } else if (settings.Get("type") == "2") { + // Browser is configured to get proxy settings from a given url. + proxy->autoconfig_url = settings.Get("autoconfig_url").c_str(); + } else if (settings.Get("type") == "4") { + // Browser is configured to auto detect proxy config. + proxy->autodetect = true; + } else { + // No proxy set. + } + } + } + return success; +} + +#if defined(WEBRTC_WIN) // Windows specific implementation for reading Internet + // Explorer proxy settings. + +void LogGetProxyFault() { + LOG_GLEM(LERROR, WINHTTP) << "WinHttpGetProxyForUrl faulted!!"; +} + +BOOL MyWinHttpGetProxyForUrl(pfnWinHttpGetProxyForUrl pWHGPFU, + HINTERNET hWinHttp, LPCWSTR url, + WINHTTP_AUTOPROXY_OPTIONS *options, + WINHTTP_PROXY_INFO *info) { + // WinHttpGetProxyForUrl() can call plugins which can crash. + // In the case of McAfee scriptproxy.dll, it does crash in + // older versions. Try to catch crashes here and treat as an + // error. + BOOL success = FALSE; + +#if (_HAS_EXCEPTIONS == 0) + __try { + success = pWHGPFU(hWinHttp, url, options, info); + } __except(EXCEPTION_EXECUTE_HANDLER) { + // This is a separate function to avoid + // Visual C++ error 2712 when compiling with C++ EH + LogGetProxyFault(); + } +#else + success = pWHGPFU(hWinHttp, url, options, info); +#endif // (_HAS_EXCEPTIONS == 0) + + return success; +} + +bool IsDefaultBrowserFirefox() { + HKEY key; + LONG result = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"http\\shell\\open\\command", + 0, KEY_READ, &key); + if (ERROR_SUCCESS != result) + return false; + + DWORD size, type; + bool success = false; + result = RegQueryValueEx(key, L"", 0, &type, NULL, &size); + if (result == ERROR_SUCCESS && type == REG_SZ) { + wchar_t* value = new wchar_t[size+1]; + BYTE* buffer = reinterpret_cast(value); + result = RegQueryValueEx(key, L"", 0, &type, buffer, &size); + if (result == ERROR_SUCCESS) { + // Size returned by RegQueryValueEx is in bytes, convert to number of + // wchar_t's. + size /= sizeof(value[0]); + value[size] = L'\0'; + for (size_t i = 0; i < size; ++i) { + value[i] = tolowercase(value[i]); + } + success = (NULL != strstr(value, L"firefox.exe")); + } + delete[] value; + } + + RegCloseKey(key); + return success; +} + +bool GetWinHttpProxySettings(const char* url, ProxyInfo* proxy) { + HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); + if (winhttp_handle == NULL) { + LOG(LS_ERROR) << "Failed to load winhttp.dll."; + return false; + } + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg; + memset(&iecfg, 0, sizeof(iecfg)); + Url purl(url); + pfnWinHttpGetIEProxyConfig pWHGIEPC = + reinterpret_cast( + GetProcAddress(winhttp_handle, + "WinHttpGetIEProxyConfigForCurrentUser")); + bool success = false; + if (pWHGIEPC && pWHGIEPC(&iecfg)) { + // We were read proxy config successfully. + success = true; + if (iecfg.fAutoDetect) { + proxy->autodetect = true; + } + if (iecfg.lpszAutoConfigUrl) { + proxy->autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl); + GlobalFree(iecfg.lpszAutoConfigUrl); + } + if (iecfg.lpszProxyBypass) { + proxy->bypass_list = ToUtf8(iecfg.lpszProxyBypass); + GlobalFree(iecfg.lpszProxyBypass); + } + if (iecfg.lpszProxy) { + if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { + ParseProxy(ToUtf8(iecfg.lpszProxy), proxy); + } + GlobalFree(iecfg.lpszProxy); + } + } + FreeLibrary(winhttp_handle); + return success; +} + +// Uses the WinHTTP API to auto detect proxy for the given url. Firefox and IE +// have slightly different option dialogs for proxy settings. In Firefox, +// either a location of a proxy configuration file can be specified or auto +// detection can be selected. In IE theese two options can be independently +// selected. For the case where both options are selected (only IE) we try to +// fetch the config file first, and if that fails we'll perform an auto +// detection. +// +// Returns true if we successfully performed an auto detection not depending on +// whether we found a proxy or not. Returns false on error. +bool WinHttpAutoDetectProxyForUrl(const char* agent, const char* url, + ProxyInfo* proxy) { + Url purl(url); + bool success = true; + HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); + if (winhttp_handle == NULL) { + LOG(LS_ERROR) << "Failed to load winhttp.dll."; + return false; + } + pfnWinHttpOpen pWHO = + reinterpret_cast(GetProcAddress(winhttp_handle, + "WinHttpOpen")); + pfnWinHttpCloseHandle pWHCH = + reinterpret_cast( + GetProcAddress(winhttp_handle, "WinHttpCloseHandle")); + pfnWinHttpGetProxyForUrl pWHGPFU = + reinterpret_cast( + GetProcAddress(winhttp_handle, "WinHttpGetProxyForUrl")); + if (pWHO && pWHCH && pWHGPFU) { + if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(), + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0)) { + BOOL result = FALSE; + WINHTTP_PROXY_INFO info; + memset(&info, 0, sizeof(info)); + if (proxy->autodetect) { + // Use DHCP and DNS to try to find any proxy to use. + WINHTTP_AUTOPROXY_OPTIONS options; + memset(&options, 0, sizeof(options)); + options.fAutoLogonIfChallenged = TRUE; + + options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT; + options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP + | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + result = MyWinHttpGetProxyForUrl( + pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); + } + if (!result && !proxy->autoconfig_url.empty()) { + // We have the location of a proxy config file. Download it and + // execute it to find proxy settings for our url. + WINHTTP_AUTOPROXY_OPTIONS options; + memset(&options, 0, sizeof(options)); + memset(&info, 0, sizeof(info)); + options.fAutoLogonIfChallenged = TRUE; + + std::wstring autoconfig_url16((ToUtf16)(proxy->autoconfig_url)); + options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; + options.lpszAutoConfigUrl = autoconfig_url16.c_str(); + + result = MyWinHttpGetProxyForUrl( + pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); + } + if (result) { + // Either the given auto config url was valid or auto + // detection found a proxy on this network. + if (info.lpszProxy) { + // TODO: Does this bypass list differ from the list + // retreived from GetWinHttpProxySettings earlier? + if (info.lpszProxyBypass) { + proxy->bypass_list = ToUtf8(info.lpszProxyBypass); + GlobalFree(info.lpszProxyBypass); + } else { + proxy->bypass_list.clear(); + } + if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { + // Found proxy for this URL. If parsing the address turns + // out ok then we are successful. + success = ParseProxy(ToUtf8(info.lpszProxy), proxy); + } + GlobalFree(info.lpszProxy); + } + } else { + // We could not find any proxy for this url. + LOG(LS_INFO) << "No proxy detected for " << url; + } + pWHCH(hWinHttp); + } + } else { + LOG(LS_ERROR) << "Failed loading WinHTTP functions."; + success = false; + } + FreeLibrary(winhttp_handle); + return success; +} + +#if 0 // Below functions currently not used. + +bool GetJsProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) { + pfnInternetGetProxyInfo pIGPI = + reinterpret_cast( + GetProcAddress(hModJS, "InternetGetProxyInfo")); + if (pIGPI) { + char proxy[256], host[256]; + memset(proxy, 0, sizeof(proxy)); + char * ptr = proxy; + DWORD proxylen = sizeof(proxy); + std::string surl = Utf8String(url); + DWORD hostlen = _snprintf(host, sizeof(host), "http%s://%S", + purl.secure() ? "s" : "", purl.server()); + if (pIGPI(surl.data(), surl.size(), host, hostlen, &ptr, &proxylen)) { + LOG(INFO) << "Proxy: " << proxy; + } else { + LOG_GLE(INFO) << "InternetGetProxyInfo"; + } + } + FreeLibrary(hModJS); + } + return success; +} + +bool GetWmProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + INSNetSourceCreator * nsc = 0; + HRESULT hr = CoCreateInstance(CLSID_ClientNetManager, 0, CLSCTX_ALL, + IID_INSNetSourceCreator, (LPVOID *) &nsc); + if (SUCCEEDED(hr)) { + if (SUCCEEDED(hr = nsc->Initialize())) { + VARIANT dispatch; + VariantInit(&dispatch); + if (SUCCEEDED(hr = nsc->GetNetSourceAdminInterface(L"http", &dispatch))) { + IWMSInternalAdminNetSource * ians = 0; + if (SUCCEEDED(hr = dispatch.pdispVal->QueryInterface( + IID_IWMSInternalAdminNetSource, (LPVOID *) &ians))) { + _bstr_t host(purl.server()); + BSTR proxy = 0; + BOOL bProxyEnabled = FALSE; + DWORD port, context = 0; + if (SUCCEEDED(hr = ians->FindProxyForURL( + L"http", host, &bProxyEnabled, &proxy, &port, &context))) { + success = true; + if (bProxyEnabled) { + _bstr_t sproxy = proxy; + proxy->ptype = PT_HTTPS; + proxy->host = sproxy; + proxy->port = port; + } + } + SysFreeString(proxy); + if (FAILED(hr = ians->ShutdownProxyContext(context))) { + LOG(LS_INFO) << "IWMSInternalAdminNetSource::ShutdownProxyContext" + << "failed: " << hr; + } + ians->Release(); + } + } + VariantClear(&dispatch); + if (FAILED(hr = nsc->Shutdown())) { + LOG(LS_INFO) << "INSNetSourceCreator::Shutdown failed: " << hr; + } + } + nsc->Release(); + } + return success; +} + +bool GetIePerConnectionProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + INTERNET_PER_CONN_OPTION_LIST list; + INTERNET_PER_CONN_OPTION options[3]; + memset(&list, 0, sizeof(list)); + memset(&options, 0, sizeof(options)); + + list.dwSize = sizeof(list); + list.dwOptionCount = 3; + list.pOptions = options; + options[0].dwOption = INTERNET_PER_CONN_FLAGS; + options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER; + options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; + DWORD dwSize = sizeof(list); + + if (!InternetQueryOption(0, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, + &dwSize)) { + LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); + } else if ((options[0].Value.dwValue & PROXY_TYPE_PROXY) != 0) { + success = true; + if (!ProxyListMatch(purl, nonnull(options[2].Value.pszValue), _T(';'))) { + ParseProxy(nonnull(options[1].Value.pszValue), proxy); + } + } else if ((options[0].Value.dwValue & PROXY_TYPE_DIRECT) != 0) { + success = true; + } else { + LOG(LS_INFO) << "unknown internet access type: " + << options[0].Value.dwValue; + } + if (options[1].Value.pszValue) { + GlobalFree(options[1].Value.pszValue); + } + if (options[2].Value.pszValue) { + GlobalFree(options[2].Value.pszValue); + } + return success; +} + +#endif // 0 + +// Uses the InternetQueryOption function to retrieve proxy settings +// from the registry. This will only give us the 'static' settings, +// ie, not any information about auto config etc. +bool GetIeLanProxySettings(const char* url, ProxyInfo* proxy) { + Url purl(url); + bool success = false; + + wchar_t buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + INTERNET_PROXY_INFO * info = reinterpret_cast(buffer); + DWORD dwSize = sizeof(buffer); + + if (!InternetQueryOption(0, INTERNET_OPTION_PROXY, info, &dwSize)) { + LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); + } else if (info->dwAccessType == INTERNET_OPEN_TYPE_DIRECT) { + success = true; + } else if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY) { + success = true; + if (!ProxyListMatch(purl, nonnull(reinterpret_cast( + info->lpszProxyBypass)), ' ')) { + ParseProxy(nonnull(reinterpret_cast(info->lpszProxy)), + proxy); + } + } else { + LOG(LS_INFO) << "unknown internet access type: " << info->dwAccessType; + } + return success; +} + +bool GetIeProxySettings(const char* agent, const char* url, ProxyInfo* proxy) { + bool success = GetWinHttpProxySettings(url, proxy); + if (!success) { + // TODO: Should always call this if no proxy were detected by + // GetWinHttpProxySettings? + // WinHttp failed. Try using the InternetOptionQuery method instead. + return GetIeLanProxySettings(url, proxy); + } + return true; +} + +#endif // WEBRTC_WIN + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) // WEBRTC_MAC && !defined(WEBRTC_IOS) specific implementation for reading system wide + // proxy settings. + +bool p_getProxyInfoForTypeFromDictWithKeys(ProxyInfo* proxy, + ProxyType type, + const CFDictionaryRef proxyDict, + const CFStringRef enabledKey, + const CFStringRef hostKey, + const CFStringRef portKey) { + // whether or not we set up the proxy info. + bool result = false; + + // we use this as a scratch variable for determining if operations + // succeeded. + bool converted = false; + + // the data we need to construct the SocketAddress for the proxy. + std::string hostname; + int port; + + if ((proxyDict != NULL) && + (CFGetTypeID(proxyDict) == CFDictionaryGetTypeID())) { + // CoreFoundation stuff that we'll have to get from + // the dictionaries and interpret or convert into more usable formats. + CFNumberRef enabledCFNum; + CFNumberRef portCFNum; + CFStringRef hostCFStr; + + enabledCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, enabledKey); + + if (p_isCFNumberTrue(enabledCFNum)) { + // let's see if we can get the address and port. + hostCFStr = (CFStringRef)CFDictionaryGetValue(proxyDict, hostKey); + converted = p_convertHostCFStringRefToCPPString(hostCFStr, hostname); + if (converted) { + portCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, portKey); + converted = p_convertCFNumberToInt(portCFNum, &port); + if (converted) { + // we have something enabled, with a hostname and a port. + // That's sufficient to set up the proxy info. + proxy->type = type; + proxy->address.SetIP(hostname); + proxy->address.SetPort(port); + result = true; + } + } + } + } + + return result; +} + +// Looks for proxy information in the given dictionary, +// return true if it found sufficient information to define one, +// false otherwise. This is guaranteed to not change the values in proxy +// unless a full-fledged proxy description was discovered in the dictionary. +// However, at the present time this does not support username or password. +// Checks first for a SOCKS proxy, then for HTTPS, then HTTP. +bool GetMacProxySettingsFromDictionary(ProxyInfo* proxy, + const CFDictionaryRef proxyDict) { + // the function result. + bool gotProxy = false; + + + // first we see if there's a SOCKS proxy in place. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, + PROXY_SOCKS5, + proxyDict, + kSCPropNetProxiesSOCKSEnable, + kSCPropNetProxiesSOCKSProxy, + kSCPropNetProxiesSOCKSPort); + + if (!gotProxy) { + // okay, no SOCKS proxy, let's look for https. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, + PROXY_HTTPS, + proxyDict, + kSCPropNetProxiesHTTPSEnable, + kSCPropNetProxiesHTTPSProxy, + kSCPropNetProxiesHTTPSPort); + if (!gotProxy) { + // Finally, try HTTP proxy. Note that flute doesn't + // differentiate between HTTPS and HTTP, hence we are using the + // same flute type here, ie. PROXY_HTTPS. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys( + proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPEnable, + kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort); + } + } + return gotProxy; +} + +// TODO(hughv) Update keychain functions. They work on 10.8, but are depricated. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +bool p_putPasswordInProxyInfo(ProxyInfo* proxy) { + bool result = true; // by default we assume we're good. + // for all we know there isn't any password. We'll set to false + // if we find a problem. + + // Ask the keychain for an internet password search for the given protocol. + OSStatus oss = 0; + SecKeychainAttributeList attrList; + attrList.count = 3; + SecKeychainAttribute attributes[3]; + attrList.attr = attributes; + + attributes[0].tag = kSecProtocolItemAttr; + attributes[0].length = sizeof(SecProtocolType); + SecProtocolType protocol; + switch (proxy->type) { + case PROXY_HTTPS : + protocol = kSecProtocolTypeHTTPS; + break; + case PROXY_SOCKS5 : + protocol = kSecProtocolTypeSOCKS; + break; + default : + LOG(LS_ERROR) << "asked for proxy password for unknown proxy type."; + result = false; + break; + } + attributes[0].data = &protocol; + + UInt32 port = proxy->address.port(); + attributes[1].tag = kSecPortItemAttr; + attributes[1].length = sizeof(UInt32); + attributes[1].data = &port; + + std::string ip = proxy->address.ipaddr().ToString(); + attributes[2].tag = kSecServerItemAttr; + attributes[2].length = ip.length(); + attributes[2].data = const_cast(ip.c_str()); + + if (result) { + LOG(LS_INFO) << "trying to get proxy username/password"; + SecKeychainSearchRef sref; + oss = SecKeychainSearchCreateFromAttributes(NULL, + kSecInternetPasswordItemClass, + &attrList, &sref); + if (0 == oss) { + LOG(LS_INFO) << "SecKeychainSearchCreateFromAttributes was good"; + // Get the first item, if there is one. + SecKeychainItemRef iref; + oss = SecKeychainSearchCopyNext(sref, &iref); + if (0 == oss) { + LOG(LS_INFO) << "...looks like we have the username/password data"; + // If there is, get the username and the password. + + SecKeychainAttributeInfo attribsToGet; + attribsToGet.count = 1; + UInt32 tag = kSecAccountItemAttr; + UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING; + void *data; + UInt32 length; + SecKeychainAttributeList *localList; + + attribsToGet.tag = &tag; + attribsToGet.format = &format; + OSStatus copyres = SecKeychainItemCopyAttributesAndData(iref, + &attribsToGet, + NULL, + &localList, + &length, + &data); + if (0 == copyres) { + LOG(LS_INFO) << "...and we can pull it out."; + // now, we know from experimentation (sadly not from docs) + // that the username is in the local attribute list, + // and the password in the data, + // both without null termination but with info on their length. + // grab the password from the data. + std::string password; + password.append(static_cast(data), length); + + // make the password into a CryptString + // huh, at the time of writing, you can't. + // so we'll skip that for now and come back to it later. + + // now put the username in the proxy. + if (1 <= localList->attr->length) { + proxy->username.append( + static_cast(localList->attr->data), + localList->attr->length); + LOG(LS_INFO) << "username is " << proxy->username; + } else { + LOG(LS_ERROR) << "got keychain entry with no username"; + result = false; + } + } else { + LOG(LS_ERROR) << "couldn't copy info from keychain."; + result = false; + } + SecKeychainItemFreeAttributesAndData(localList, data); + } else if (errSecItemNotFound == oss) { + LOG(LS_INFO) << "...username/password info not found"; + } else { + // oooh, neither 0 nor itemNotFound. + LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; + result = false; + } + } else if (errSecItemNotFound == oss) { // noop + } else { + // oooh, neither 0 nor itemNotFound. + LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; + result = false; + } + } + + return result; +} + +bool GetMacProxySettings(ProxyInfo* proxy) { + // based on the Apple Technical Q&A QA1234 + // http://developer.apple.com/qa/qa2001/qa1234.html + CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL); + bool result = false; + + if (proxyDict != NULL) { + // sending it off to another function makes it easier to unit test + // since we can make our own dictionary to hand to that function. + result = GetMacProxySettingsFromDictionary(proxy, proxyDict); + + if (result) { + result = p_putPasswordInProxyInfo(proxy); + } + + // We created the dictionary with something that had the + // word 'copy' in it, so we have to release it, according + // to the Carbon memory management standards. + CFRelease(proxyDict); + } else { + LOG(LS_ERROR) << "SCDynamicStoreCopyProxies failed"; + } + + return result; +} +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +bool AutoDetectProxySettings(const char* agent, const char* url, + ProxyInfo* proxy) { +#if defined(WEBRTC_WIN) + return WinHttpAutoDetectProxyForUrl(agent, url, proxy); +#else + LOG(LS_WARNING) << "Proxy auto-detection not implemented for this platform"; + return false; +#endif +} + +bool GetSystemDefaultProxySettings(const char* agent, const char* url, + ProxyInfo* proxy) { +#if defined(WEBRTC_WIN) + return GetIeProxySettings(agent, url, proxy); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + return GetMacProxySettings(proxy); +#else + // TODO: Get System settings if browser is not firefox. + return GetFirefoxProxySettings(url, proxy); +#endif +} + +bool GetProxySettingsForUrl(const char* agent, const char* url, + ProxyInfo* proxy, bool long_operation) { + UserAgent a = GetAgent(agent); + bool result; + switch (a) { + case UA_FIREFOX: { + result = GetFirefoxProxySettings(url, proxy); + break; + } +#if defined(WEBRTC_WIN) + case UA_INTERNETEXPLORER: + result = GetIeProxySettings(agent, url, proxy); + break; + case UA_UNKNOWN: + // Agent not defined, check default browser. + if (IsDefaultBrowserFirefox()) { + result = GetFirefoxProxySettings(url, proxy); + } else { + result = GetIeProxySettings(agent, url, proxy); + } + break; +#endif // WEBRTC_WIN + default: + result = GetSystemDefaultProxySettings(agent, url, proxy); + break; + } + + // TODO: Consider using the 'long_operation' parameter to + // decide whether to do the auto detection. + if (result && (proxy->autodetect || + !proxy->autoconfig_url.empty())) { + // Use WinHTTP to auto detect proxy for us. + result = AutoDetectProxySettings(agent, url, proxy); + if (!result) { + // Either auto detection is not supported or we simply didn't + // find any proxy, reset type. + proxy->type = rtc::PROXY_NONE; + } + } + return result; +} + +} // namespace rtc diff --git a/webrtc/base/proxydetect.h b/webrtc/base/proxydetect.h new file mode 100644 index 000000000..f9bf5f873 --- /dev/null +++ b/webrtc/base/proxydetect.h @@ -0,0 +1,31 @@ +/* + * Copyright 2007 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 _PROXYDETECT_H_ +#define _PROXYDETECT_H_ + +#include "webrtc/base/proxyinfo.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +namespace rtc { +// Auto-detect the proxy server. Returns true if a proxy is configured, +// although hostname may be empty if the proxy is not required for +// the given URL. + +bool GetProxySettingsForUrl(const char* agent, const char* url, + rtc::ProxyInfo* proxy, + bool long_operation = false); + +} // namespace rtc + +#endif // _PROXYDETECT_H_ diff --git a/webrtc/base/proxydetect_unittest.cc b/webrtc/base/proxydetect_unittest.cc new file mode 100644 index 000000000..ca0b428fc --- /dev/null +++ b/webrtc/base/proxydetect_unittest.cc @@ -0,0 +1,164 @@ +/* + * Copyright 2010 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 "webrtc/base/fileutils_mock.h" +#include "webrtc/base/proxydetect.h" + +namespace rtc { + +static const std::string kFirefoxProfilesIni = + "[Profile0]\n" + "Name=default\n" + "IsRelative=1\n" + "Path=Profiles/2de53ejb.default\n" + "Default=1\n"; + +static const std::string kFirefoxHeader = + "# Mozilla User Preferences\n" + "\n" + "/* Some Comments\n" + "*\n" + "*/\n" + "\n"; + +static const std::string kFirefoxCorruptHeader = + "iuahueqe32164"; + +static const std::string kProxyAddress = "proxy.net.com"; + +// Mocking out platform specific path to firefox prefs file. +class FirefoxPrefsFileSystem : public FakeFileSystem { + public: + explicit FirefoxPrefsFileSystem(const std::vector& all_files) : + FakeFileSystem(all_files) { + } + virtual FileStream* OpenFile(const Pathname& filename, + const std::string& mode) { + // TODO: We could have a platform dependent check of paths here. + std::string name = filename.basename(); + name.append(filename.extension()); + EXPECT_TRUE(name.compare("prefs.js") == 0 || + name.compare("profiles.ini") == 0); + FileStream* stream = FakeFileSystem::OpenFile(name, mode); + return stream; + } +}; + +class ProxyDetectTest : public testing::Test { +}; + +bool GetProxyInfo(const std::string prefs, ProxyInfo* info) { + std::vector files; + files.push_back(rtc::FakeFileSystem::File("profiles.ini", + kFirefoxProfilesIni)); + files.push_back(rtc::FakeFileSystem::File("prefs.js", prefs)); + rtc::FilesystemScope fs(new rtc::FirefoxPrefsFileSystem(files)); + return GetProxySettingsForUrl("Firefox", "www.google.com", info, false); +} + +// Verifies that an empty Firefox prefs file results in no proxy detected. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxEmptyPrefs) { + ProxyInfo proxy_info; + EXPECT_TRUE(GetProxyInfo(kFirefoxHeader, &proxy_info)); + EXPECT_EQ(PROXY_NONE, proxy_info.type); +} + +// Verifies that corrupted prefs file results in no proxy detected. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxCorruptedPrefs) { + ProxyInfo proxy_info; + EXPECT_TRUE(GetProxyInfo(kFirefoxCorruptHeader, &proxy_info)); + EXPECT_EQ(PROXY_NONE, proxy_info.type); +} + +// Verifies that SOCKS5 proxy is detected if configured. SOCKS uses a +// handshake protocol to inform the proxy software about the +// connection that the client is trying to make and may be used for +// any form of TCP or UDP socket connection. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySocks) { + ProxyInfo proxy_info; + SocketAddress proxy_address("proxy.socks.com", 6666); + std::string prefs(kFirefoxHeader); + prefs.append("user_pref(\"network.proxy.socks\", \"proxy.socks.com\");\n"); + prefs.append("user_pref(\"network.proxy.socks_port\", 6666);\n"); + prefs.append("user_pref(\"network.proxy.type\", 1);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_SOCKS5, proxy_info.type); + EXPECT_EQ(proxy_address, proxy_info.address); +} + +// Verified that SSL proxy is detected if configured. SSL proxy is an +// extention of a HTTP proxy to support secure connections. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySsl) { + ProxyInfo proxy_info; + SocketAddress proxy_address("proxy.ssl.com", 7777); + std::string prefs(kFirefoxHeader); + + prefs.append("user_pref(\"network.proxy.ssl\", \"proxy.ssl.com\");\n"); + prefs.append("user_pref(\"network.proxy.ssl_port\", 7777);\n"); + prefs.append("user_pref(\"network.proxy.type\", 1);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_HTTPS, proxy_info.type); + EXPECT_EQ(proxy_address, proxy_info.address); +} + +// Verifies that a HTTP proxy is detected if configured. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyHttp) { + ProxyInfo proxy_info; + SocketAddress proxy_address("proxy.http.com", 8888); + std::string prefs(kFirefoxHeader); + + prefs.append("user_pref(\"network.proxy.http\", \"proxy.http.com\");\n"); + prefs.append("user_pref(\"network.proxy.http_port\", 8888);\n"); + prefs.append("user_pref(\"network.proxy.type\", 1);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_HTTPS, proxy_info.type); + EXPECT_EQ(proxy_address, proxy_info.address); +} + +// Verifies detection of automatic proxy detection. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAuto) { + ProxyInfo proxy_info; + std::string prefs(kFirefoxHeader); + + prefs.append("user_pref(\"network.proxy.type\", 4);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_EQ(PROXY_NONE, proxy_info.type); + EXPECT_TRUE(proxy_info.autodetect); + EXPECT_TRUE(proxy_info.autoconfig_url.empty()); +} + +// Verifies detection of automatic proxy detection using a static url +// to config file. +TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAutoUrl) { + ProxyInfo proxy_info; + std::string prefs(kFirefoxHeader); + + prefs.append( + "user_pref(\"network.proxy.autoconfig_url\", \"http://a/b.pac\");\n"); + prefs.append("user_pref(\"network.proxy.type\", 2);\n"); + + EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info)); + + EXPECT_FALSE(proxy_info.autodetect); + EXPECT_EQ(PROXY_NONE, proxy_info.type); + EXPECT_EQ(0, proxy_info.autoconfig_url.compare("http://a/b.pac")); +} + +} // namespace rtc diff --git a/webrtc/base/proxyinfo.cc b/webrtc/base/proxyinfo.cc new file mode 100644 index 000000000..70c3b5584 --- /dev/null +++ b/webrtc/base/proxyinfo.cc @@ -0,0 +1,20 @@ +/* + * Copyright 2004 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 "webrtc/base/proxyinfo.h" + +namespace rtc { + +const char * ProxyToString(ProxyType proxy) { + const char * const PROXY_NAMES[] = { "none", "https", "socks5", "unknown" }; + return PROXY_NAMES[proxy]; +} + +} // namespace rtc diff --git a/webrtc/base/proxyinfo.h b/webrtc/base/proxyinfo.h new file mode 100644 index 000000000..9947f4552 --- /dev/null +++ b/webrtc/base/proxyinfo.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004 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_BASE_PROXYINFO_H__ +#define WEBRTC_BASE_PROXYINFO_H__ + +#include +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/cryptstring.h" + +namespace rtc { + +enum ProxyType { + PROXY_NONE, + PROXY_HTTPS, + PROXY_SOCKS5, + PROXY_UNKNOWN +}; +const char * ProxyToString(ProxyType proxy); + +struct ProxyInfo { + ProxyType type; + SocketAddress address; + std::string autoconfig_url; + bool autodetect; + std::string bypass_list; + std::string username; + CryptString password; + + ProxyInfo() : type(PROXY_NONE), autodetect(false) { } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_PROXYINFO_H__ diff --git a/webrtc/base/proxyserver.cc b/webrtc/base/proxyserver.cc new file mode 100644 index 000000000..548cfbf5b --- /dev/null +++ b/webrtc/base/proxyserver.cc @@ -0,0 +1,144 @@ +/* + * Copyright 2004 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 "webrtc/base/proxyserver.h" + +#include +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +// ProxyServer +ProxyServer::ProxyServer( + SocketFactory* int_factory, const SocketAddress& int_addr, + SocketFactory* ext_factory, const SocketAddress& ext_ip) + : ext_factory_(ext_factory), ext_ip_(ext_ip.ipaddr(), 0), // strip off port + server_socket_(int_factory->CreateAsyncSocket(int_addr.family(), + SOCK_STREAM)) { + ASSERT(server_socket_.get() != NULL); + ASSERT(int_addr.family() == AF_INET || int_addr.family() == AF_INET6); + server_socket_->Bind(int_addr); + server_socket_->Listen(5); + server_socket_->SignalReadEvent.connect(this, &ProxyServer::OnAcceptEvent); +} + +ProxyServer::~ProxyServer() { + for (BindingList::iterator it = bindings_.begin(); + it != bindings_.end(); ++it) { + delete (*it); + } +} + +void ProxyServer::OnAcceptEvent(AsyncSocket* socket) { + ASSERT(socket != NULL && socket == server_socket_.get()); + AsyncSocket* int_socket = socket->Accept(NULL); + AsyncProxyServerSocket* wrapped_socket = WrapSocket(int_socket); + AsyncSocket* ext_socket = ext_factory_->CreateAsyncSocket(ext_ip_.family(), + SOCK_STREAM); + if (ext_socket) { + ext_socket->Bind(ext_ip_); + bindings_.push_back(new ProxyBinding(wrapped_socket, ext_socket)); + } else { + LOG(LS_ERROR) << "Unable to create external socket on proxy accept event"; + } +} + +void ProxyServer::OnBindingDestroyed(ProxyBinding* binding) { + BindingList::iterator it = + std::find(bindings_.begin(), bindings_.end(), binding); + delete (*it); + bindings_.erase(it); +} + +// ProxyBinding +ProxyBinding::ProxyBinding(AsyncProxyServerSocket* int_socket, + AsyncSocket* ext_socket) + : int_socket_(int_socket), ext_socket_(ext_socket), connected_(false), + out_buffer_(kBufferSize), in_buffer_(kBufferSize) { + int_socket_->SignalConnectRequest.connect(this, + &ProxyBinding::OnConnectRequest); + int_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnInternalRead); + int_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnInternalWrite); + int_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnInternalClose); + ext_socket_->SignalConnectEvent.connect(this, + &ProxyBinding::OnExternalConnect); + ext_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnExternalRead); + ext_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnExternalWrite); + ext_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnExternalClose); +} + +void ProxyBinding::OnConnectRequest(AsyncProxyServerSocket* socket, + const SocketAddress& addr) { + ASSERT(!connected_ && ext_socket_.get() != NULL); + ext_socket_->Connect(addr); + // TODO: handle errors here +} + +void ProxyBinding::OnInternalRead(AsyncSocket* socket) { + Read(int_socket_.get(), &out_buffer_); + Write(ext_socket_.get(), &out_buffer_); +} + +void ProxyBinding::OnInternalWrite(AsyncSocket* socket) { + Write(int_socket_.get(), &in_buffer_); +} + +void ProxyBinding::OnInternalClose(AsyncSocket* socket, int err) { + Destroy(); +} + +void ProxyBinding::OnExternalConnect(AsyncSocket* socket) { + ASSERT(socket != NULL); + connected_ = true; + int_socket_->SendConnectResult(0, socket->GetRemoteAddress()); +} + +void ProxyBinding::OnExternalRead(AsyncSocket* socket) { + Read(ext_socket_.get(), &in_buffer_); + Write(int_socket_.get(), &in_buffer_); +} + +void ProxyBinding::OnExternalWrite(AsyncSocket* socket) { + Write(ext_socket_.get(), &out_buffer_); +} + +void ProxyBinding::OnExternalClose(AsyncSocket* socket, int err) { + if (!connected_) { + int_socket_->SendConnectResult(err, SocketAddress()); + } + Destroy(); +} + +void ProxyBinding::Read(AsyncSocket* socket, FifoBuffer* buffer) { + // Only read if the buffer is empty. + ASSERT(socket != NULL); + size_t size; + int read; + if (buffer->GetBuffered(&size) && size == 0) { + void* p = buffer->GetWriteBuffer(&size); + read = socket->Recv(p, size); + buffer->ConsumeWriteBuffer(_max(read, 0)); + } +} + +void ProxyBinding::Write(AsyncSocket* socket, FifoBuffer* buffer) { + ASSERT(socket != NULL); + size_t size; + int written; + const void* p = buffer->GetReadData(&size); + written = socket->Send(p, size); + buffer->ConsumeReadData(_max(written, 0)); +} + +void ProxyBinding::Destroy() { + SignalDestroyed(this); +} + +} // namespace rtc diff --git a/webrtc/base/proxyserver.h b/webrtc/base/proxyserver.h new file mode 100644 index 000000000..80e15d969 --- /dev/null +++ b/webrtc/base/proxyserver.h @@ -0,0 +1,96 @@ +/* + * Copyright 2004 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_BASE_PROXYSERVER_H_ +#define WEBRTC_BASE_PROXYSERVER_H_ + +#include +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +class SocketFactory; + +// ProxyServer is a base class that allows for easy construction of proxy +// servers. With its helper class ProxyBinding, it contains all the necessary +// logic for receiving and bridging connections. The specific client-server +// proxy protocol is implemented by an instance of the AsyncProxyServerSocket +// class; children of ProxyServer implement WrapSocket appropriately to return +// the correct protocol handler. + +class ProxyBinding : public sigslot::has_slots<> { + public: + ProxyBinding(AsyncProxyServerSocket* in_socket, AsyncSocket* out_socket); + sigslot::signal1 SignalDestroyed; + + private: + void OnConnectRequest(AsyncProxyServerSocket* socket, + const SocketAddress& addr); + void OnInternalRead(AsyncSocket* socket); + void OnInternalWrite(AsyncSocket* socket); + void OnInternalClose(AsyncSocket* socket, int err); + void OnExternalConnect(AsyncSocket* socket); + void OnExternalRead(AsyncSocket* socket); + void OnExternalWrite(AsyncSocket* socket); + void OnExternalClose(AsyncSocket* socket, int err); + + static void Read(AsyncSocket* socket, FifoBuffer* buffer); + static void Write(AsyncSocket* socket, FifoBuffer* buffer); + void Destroy(); + + static const int kBufferSize = 4096; + scoped_ptr int_socket_; + scoped_ptr ext_socket_; + bool connected_; + FifoBuffer out_buffer_; + FifoBuffer in_buffer_; + DISALLOW_EVIL_CONSTRUCTORS(ProxyBinding); +}; + +class ProxyServer : public sigslot::has_slots<> { + public: + ProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr, + SocketFactory* ext_factory, const SocketAddress& ext_ip); + virtual ~ProxyServer(); + + protected: + void OnAcceptEvent(AsyncSocket* socket); + virtual AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) = 0; + void OnBindingDestroyed(ProxyBinding* binding); + + private: + typedef std::list BindingList; + SocketFactory* ext_factory_; + SocketAddress ext_ip_; + scoped_ptr server_socket_; + BindingList bindings_; + DISALLOW_EVIL_CONSTRUCTORS(ProxyServer); +}; + +// SocksProxyServer is a simple extension of ProxyServer to implement SOCKS. +class SocksProxyServer : public ProxyServer { + public: + SocksProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr, + SocketFactory* ext_factory, const SocketAddress& ext_ip) + : ProxyServer(int_factory, int_addr, ext_factory, ext_ip) { + } + protected: + AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) { + return new AsyncSocksProxyServerSocket(socket); + } + DISALLOW_EVIL_CONSTRUCTORS(SocksProxyServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_PROXYSERVER_H_ diff --git a/webrtc/base/ratelimiter.cc b/webrtc/base/ratelimiter.cc new file mode 100644 index 000000000..c4a251d14 --- /dev/null +++ b/webrtc/base/ratelimiter.cc @@ -0,0 +1,29 @@ +/* + * Copyright 2012 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 "webrtc/base/ratelimiter.h" + +namespace rtc { + +bool RateLimiter::CanUse(size_t desired, double time) { + return ((time > period_end_ && desired <= max_per_period_) || + (used_in_period_ + desired) <= max_per_period_); +} + +void RateLimiter::Use(size_t used, double time) { + if (time > period_end_) { + period_start_ = time; + period_end_ = time + period_length_; + used_in_period_ = 0; + } + used_in_period_ += used; +} + +} // namespace rtc diff --git a/webrtc/base/ratelimiter.h b/webrtc/base/ratelimiter.h new file mode 100644 index 000000000..cf5d6b05b --- /dev/null +++ b/webrtc/base/ratelimiter.h @@ -0,0 +1,63 @@ +/* + * Copyright 2012 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_BASE_RATELIMITER_H_ +#define WEBRTC_BASE_RATELIMITER_H_ + +#include +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Limits the rate of use to a certain maximum quantity per period of +// time. Use, for example, for simple bandwidth throttling. +// +// It's implemented like a diet plan: You have so many calories per +// day. If you hit the limit, you can't eat any more until the next +// day. +class RateLimiter { + public: + // For example, 100kb per second. + RateLimiter(size_t max, double period) + : max_per_period_(max), + period_length_(period), + used_in_period_(0), + period_start_(0.0), + period_end_(period) { + } + virtual ~RateLimiter() {} + + // Returns true if if the desired quantity is available in the + // current period (< (max - used)). Once the given time passes the + // end of the period, used is set to zero and more use is available. + bool CanUse(size_t desired, double time); + // Increment the quantity used this period. If past the end of a + // period, a new period is started. + void Use(size_t used, double time); + + size_t used_in_period() const { + return used_in_period_; + } + + size_t max_per_period() const { + return max_per_period_; + } + + private: + size_t max_per_period_; + double period_length_; + size_t used_in_period_; + double period_start_; + double period_end_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_RATELIMITER_H_ diff --git a/webrtc/base/ratelimiter_unittest.cc b/webrtc/base/ratelimiter_unittest.cc new file mode 100644 index 000000000..b54a751b7 --- /dev/null +++ b/webrtc/base/ratelimiter_unittest.cc @@ -0,0 +1,59 @@ +/* + * Copyright 2012 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 "webrtc/base/gunit.h" +#include "webrtc/base/ratelimiter.h" + +namespace rtc { + +TEST(RateLimiterTest, TestCanUse) { + // Diet: Can eat 2,000 calories per day. + RateLimiter limiter = RateLimiter(2000, 1.0); + + double monday = 1.0; + double tuesday = 2.0; + double thursday = 4.0; + + EXPECT_TRUE(limiter.CanUse(0, monday)); + EXPECT_TRUE(limiter.CanUse(1000, monday)); + EXPECT_TRUE(limiter.CanUse(1999, monday)); + EXPECT_TRUE(limiter.CanUse(2000, monday)); + EXPECT_FALSE(limiter.CanUse(2001, monday)); + + limiter.Use(1000, monday); + + EXPECT_TRUE(limiter.CanUse(0, monday)); + EXPECT_TRUE(limiter.CanUse(999, monday)); + EXPECT_TRUE(limiter.CanUse(1000, monday)); + EXPECT_FALSE(limiter.CanUse(1001, monday)); + + limiter.Use(1000, monday); + + EXPECT_TRUE(limiter.CanUse(0, monday)); + EXPECT_FALSE(limiter.CanUse(1, monday)); + + EXPECT_TRUE(limiter.CanUse(0, tuesday)); + EXPECT_TRUE(limiter.CanUse(1, tuesday)); + EXPECT_TRUE(limiter.CanUse(1999, tuesday)); + EXPECT_TRUE(limiter.CanUse(2000, tuesday)); + EXPECT_FALSE(limiter.CanUse(2001, tuesday)); + + limiter.Use(1000, tuesday); + + EXPECT_TRUE(limiter.CanUse(1000, tuesday)); + EXPECT_FALSE(limiter.CanUse(1001, tuesday)); + + limiter.Use(1000, thursday); + + EXPECT_TRUE(limiter.CanUse(1000, tuesday)); + EXPECT_FALSE(limiter.CanUse(1001, tuesday)); +} + +} // namespace rtc diff --git a/webrtc/base/ratetracker.cc b/webrtc/base/ratetracker.cc new file mode 100644 index 000000000..31ecd9bbb --- /dev/null +++ b/webrtc/base/ratetracker.cc @@ -0,0 +1,63 @@ +/* + * Copyright 2004 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 "webrtc/base/ratetracker.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +RateTracker::RateTracker() + : total_units_(0), units_second_(0), + last_units_second_time_(static_cast(-1)), + last_units_second_calc_(0) { +} + +size_t RateTracker::total_units() const { + return total_units_; +} + +size_t RateTracker::units_second() { + // Snapshot units / second calculator. Determine how many seconds have + // elapsed since our last reference point. If over 1 second, establish + // a new reference point that is an integer number of seconds since the + // last one, and compute the units over that interval. + uint32 current_time = Time(); + if (last_units_second_time_ != static_cast(-1)) { + int delta = rtc::TimeDiff(current_time, last_units_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds = delta / 1000; + int fraction_units = + static_cast(total_units_ - last_units_second_calc_) * + fraction_time / delta; + // Compute "units received during the interval" / "seconds in interval" + units_second_ = + (total_units_ - last_units_second_calc_ - fraction_units) / seconds; + last_units_second_time_ = current_time - fraction_time; + last_units_second_calc_ = total_units_ - fraction_units; + } + } + if (last_units_second_time_ == static_cast(-1)) { + last_units_second_time_ = current_time; + last_units_second_calc_ = total_units_; + } + + return units_second_; +} + +void RateTracker::Update(size_t units) { + total_units_ += units; +} + +uint32 RateTracker::Time() const { + return rtc::Time(); +} + +} // namespace rtc diff --git a/webrtc/base/ratetracker.h b/webrtc/base/ratetracker.h new file mode 100644 index 000000000..575bff75a --- /dev/null +++ b/webrtc/base/ratetracker.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004 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_BASE_RATETRACKER_H_ +#define WEBRTC_BASE_RATETRACKER_H_ + +#include +#include "webrtc/base/basictypes.h" + +namespace rtc { + +// Computes instantaneous units per second. +class RateTracker { + public: + RateTracker(); + virtual ~RateTracker() {} + + size_t total_units() const; + size_t units_second(); + void Update(size_t units); + + protected: + // overrideable for tests + virtual uint32 Time() const; + + private: + size_t total_units_; + size_t units_second_; + uint32 last_units_second_time_; + size_t last_units_second_calc_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_RATETRACKER_H_ diff --git a/webrtc/base/ratetracker_unittest.cc b/webrtc/base/ratetracker_unittest.cc new file mode 100644 index 000000000..e9fee2b9f --- /dev/null +++ b/webrtc/base/ratetracker_unittest.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/ratetracker.h" + +namespace rtc { + +class RateTrackerForTest : public RateTracker { + public: + RateTrackerForTest() : time_(0) {} + virtual uint32 Time() const { return time_; } + void AdvanceTime(uint32 delta) { time_ += delta; } + + private: + uint32 time_; +}; + +TEST(RateTrackerTest, TestBasics) { + RateTrackerForTest tracker; + EXPECT_EQ(0U, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Add a sample. + tracker.Update(1234); + // Advance the clock by 100 ms. + tracker.AdvanceTime(100); + // total_units should advance, but units_second should stay 0. + EXPECT_EQ(1234U, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Repeat. + tracker.Update(1234); + tracker.AdvanceTime(100); + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Advance the clock by 800 ms, so we've elapsed a full second. + // units_second should now be filled in properly. + tracker.AdvanceTime(800); + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(1234U * 2, tracker.units_second()); + + // Poll the tracker again immediately. The reported rate should stay the same. + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(1234U * 2, tracker.units_second()); + + // Do nothing and advance by a second. We should drop down to zero. + tracker.AdvanceTime(1000); + EXPECT_EQ(1234U * 2, tracker.total_units()); + EXPECT_EQ(0U, tracker.units_second()); + + // Send a bunch of data at a constant rate for 5.5 "seconds". + // We should report the rate properly. + for (int i = 0; i < 5500; i += 100) { + tracker.Update(9876U); + tracker.AdvanceTime(100); + } + EXPECT_EQ(9876U * 10, tracker.units_second()); + + // Advance the clock by 500 ms. Since we sent nothing over this half-second, + // the reported rate should be reduced by half. + tracker.AdvanceTime(500); + EXPECT_EQ(9876U * 5, tracker.units_second()); +} + +} // namespace rtc diff --git a/webrtc/base/refcount.h b/webrtc/base/refcount.h new file mode 100644 index 000000000..7bb6da36a --- /dev/null +++ b/webrtc/base/refcount.h @@ -0,0 +1,78 @@ +/* + * Copyright 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 TALK_APP_BASE_REFCOUNT_H_ +#define TALK_APP_BASE_REFCOUNT_H_ + +#include + +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +// Reference count interface. +class RefCountInterface { + public: + virtual int AddRef() = 0; + virtual int Release() = 0; + protected: + virtual ~RefCountInterface() {} +}; + +template +class RefCountedObject : public T { + public: + RefCountedObject() : ref_count_(0) { + } + + template + explicit RefCountedObject(P p) : T(p), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2) : T(p1, p2), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2, P3 p3) : T(p1, p2, p3), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4) + : T(p1, p2, p3, p4), ref_count_(0) { + } + + template + RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) + : T(p1, p2, p3, p4, p5), ref_count_(0) { + } + + virtual int AddRef() { + return rtc::AtomicOps::Increment(&ref_count_); + } + + virtual int Release() { + int count = rtc::AtomicOps::Decrement(&ref_count_); + if (!count) { + delete this; + } + return count; + } + + protected: + virtual ~RefCountedObject() { + } + + int ref_count_; +}; + +} // namespace rtc + +#endif // TALK_APP_BASE_REFCOUNT_H_ diff --git a/webrtc/base/referencecountedsingletonfactory.h b/webrtc/base/referencecountedsingletonfactory.h new file mode 100644 index 000000000..7138c8c5e --- /dev/null +++ b/webrtc/base/referencecountedsingletonfactory.h @@ -0,0 +1,157 @@ +/* + * Copyright 2004 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_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_ +#define WEBRTC_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_ + +#include "webrtc/base/common.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +template class rcsf_ptr; + +// A ReferenceCountedSingletonFactory is an object which owns another object, +// and doles out the owned object to consumers in a reference-counted manner. +// Thus, the factory owns at most one object of the desired kind, and +// hands consumers a special pointer to it, through which they can access it. +// When the consumers delete the pointer, the reference count goes down, +// and if the reference count hits zero, the factory can throw the object +// away. If a consumer requests the pointer and the factory has none, +// it can create one on the fly and pass it back. +template +class ReferenceCountedSingletonFactory { + friend class rcsf_ptr; + public: + ReferenceCountedSingletonFactory() : ref_count_(0) {} + + virtual ~ReferenceCountedSingletonFactory() { + ASSERT(ref_count_ == 0); + } + + protected: + // Must be implemented in a sub-class. The sub-class may choose whether or not + // to cache the instance across lifetimes by either reset()'ing or not + // reset()'ing the scoped_ptr in CleanupInstance(). + virtual bool SetupInstance() = 0; + virtual void CleanupInstance() = 0; + + scoped_ptr instance_; + + private: + Interface* GetInstance() { + rtc::CritScope cs(&crit_); + if (ref_count_ == 0) { + if (!SetupInstance()) { + LOG(LS_VERBOSE) << "Failed to setup instance"; + return NULL; + } + ASSERT(instance_.get() != NULL); + } + ++ref_count_; + + LOG(LS_VERBOSE) << "Number of references: " << ref_count_; + return instance_.get(); + } + + void ReleaseInstance() { + rtc::CritScope cs(&crit_); + ASSERT(ref_count_ > 0); + ASSERT(instance_.get() != NULL); + --ref_count_; + LOG(LS_VERBOSE) << "Number of references: " << ref_count_; + if (ref_count_ == 0) { + CleanupInstance(); + } + } + + CriticalSection crit_; + int ref_count_; + + DISALLOW_COPY_AND_ASSIGN(ReferenceCountedSingletonFactory); +}; + +template +class rcsf_ptr { + public: + // Create a pointer that uses the factory to get the instance. + // This is lazy - it won't generate the instance until it is requested. + explicit rcsf_ptr(ReferenceCountedSingletonFactory* factory) + : instance_(NULL), + factory_(factory) { + } + + ~rcsf_ptr() { + release(); + } + + Interface& operator*() { + EnsureAcquired(); + return *instance_; + } + + Interface* operator->() { + EnsureAcquired(); + return instance_; + } + + // Gets the pointer, creating the singleton if necessary. May return NULL if + // creation failed. + Interface* get() { + Acquire(); + return instance_; + } + + // Set instance to NULL and tell the factory we aren't using the instance + // anymore. + void release() { + if (instance_) { + instance_ = NULL; + factory_->ReleaseInstance(); + } + } + + // Lets us know whether instance is valid or not right now. + // Even though attempts to use the instance will automatically create it, it + // is advisable to check this because creation can fail. + bool valid() const { + return instance_ != NULL; + } + + // Returns the factory that this pointer is using. + ReferenceCountedSingletonFactory* factory() const { + return factory_; + } + + private: + void EnsureAcquired() { + Acquire(); + ASSERT(instance_ != NULL); + } + + void Acquire() { + // Since we're getting a singleton back, acquire is a noop if instance is + // already populated. + if (!instance_) { + instance_ = factory_->GetInstance(); + } + } + + Interface* instance_; + ReferenceCountedSingletonFactory* factory_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(rcsf_ptr); +}; + +}; // namespace rtc + +#endif // WEBRTC_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_ diff --git a/webrtc/base/referencecountedsingletonfactory_unittest.cc b/webrtc/base/referencecountedsingletonfactory_unittest.cc new file mode 100644 index 000000000..75d97a639 --- /dev/null +++ b/webrtc/base/referencecountedsingletonfactory_unittest.cc @@ -0,0 +1,132 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/referencecountedsingletonfactory.h" + +namespace rtc { + +class MyExistenceWatcher { + public: + MyExistenceWatcher() { create_called_ = true; } + ~MyExistenceWatcher() { delete_called_ = true; } + + static bool create_called_; + static bool delete_called_; +}; + +bool MyExistenceWatcher::create_called_ = false; +bool MyExistenceWatcher::delete_called_ = false; + +class TestReferenceCountedSingletonFactory : + public ReferenceCountedSingletonFactory { + protected: + virtual bool SetupInstance() { + instance_.reset(new MyExistenceWatcher()); + return true; + } + + virtual void CleanupInstance() { + instance_.reset(); + } +}; + +static void DoCreateAndGoOutOfScope( + ReferenceCountedSingletonFactory *factory) { + rcsf_ptr ptr(factory); + ptr.get(); + // and now ptr should go out of scope. +} + +TEST(ReferenceCountedSingletonFactory, ZeroReferenceCountCausesDeletion) { + TestReferenceCountedSingletonFactory factory; + MyExistenceWatcher::delete_called_ = false; + DoCreateAndGoOutOfScope(&factory); + EXPECT_TRUE(MyExistenceWatcher::delete_called_); +} + +TEST(ReferenceCountedSingletonFactory, NonZeroReferenceCountDoesNotDelete) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr ptr(&factory); + ptr.get(); + MyExistenceWatcher::delete_called_ = false; + DoCreateAndGoOutOfScope(&factory); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); +} + +TEST(ReferenceCountedSingletonFactory, ReturnedPointersReferToSameThing) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory), two(&factory); + + EXPECT_EQ(one.get(), two.get()); +} + +TEST(ReferenceCountedSingletonFactory, Release) { + TestReferenceCountedSingletonFactory factory; + + rcsf_ptr one(&factory); + one.get(); + + MyExistenceWatcher::delete_called_ = false; + one.release(); + EXPECT_TRUE(MyExistenceWatcher::delete_called_); +} + +TEST(ReferenceCountedSingletonFactory, GetWithoutRelease) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory); + one.get(); + + MyExistenceWatcher::create_called_ = false; + one.get(); + EXPECT_FALSE(MyExistenceWatcher::create_called_); +} + +TEST(ReferenceCountedSingletonFactory, GetAfterRelease) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory); + + MyExistenceWatcher::create_called_ = false; + one.release(); + one.get(); + EXPECT_TRUE(MyExistenceWatcher::create_called_); +} + +TEST(ReferenceCountedSingletonFactory, MultipleReleases) { + TestReferenceCountedSingletonFactory factory; + rcsf_ptr one(&factory), two(&factory); + + MyExistenceWatcher::create_called_ = false; + MyExistenceWatcher::delete_called_ = false; + one.release(); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); + one.release(); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); + one.release(); + EXPECT_FALSE(MyExistenceWatcher::delete_called_); + one.get(); + EXPECT_TRUE(MyExistenceWatcher::create_called_); +} + +TEST(ReferenceCountedSingletonFactory, Existentialism) { + TestReferenceCountedSingletonFactory factory; + + rcsf_ptr one(&factory); + + MyExistenceWatcher::create_called_ = false; + MyExistenceWatcher::delete_called_ = false; + + one.get(); + EXPECT_TRUE(MyExistenceWatcher::create_called_); + one.release(); + EXPECT_TRUE(MyExistenceWatcher::delete_called_); +} + +} // namespace rtc diff --git a/webrtc/base/rollingaccumulator.h b/webrtc/base/rollingaccumulator.h new file mode 100644 index 000000000..0dce0c3a0 --- /dev/null +++ b/webrtc/base/rollingaccumulator.h @@ -0,0 +1,172 @@ +/* + * Copyright 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_BASE_ROLLINGACCUMULATOR_H_ +#define WEBRTC_BASE_ROLLINGACCUMULATOR_H_ + +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +// RollingAccumulator stores and reports statistics +// over N most recent samples. +// +// T is assumed to be an int, long, double or float. +template +class RollingAccumulator { + public: + explicit RollingAccumulator(size_t max_count) + : samples_(max_count) { + Reset(); + } + ~RollingAccumulator() { + } + + size_t max_count() const { + return samples_.size(); + } + + size_t count() const { + return count_; + } + + void Reset() { + count_ = 0U; + next_index_ = 0U; + sum_ = 0.0; + sum_2_ = 0.0; + max_ = T(); + max_stale_ = false; + min_ = T(); + min_stale_ = false; + } + + void AddSample(T sample) { + if (count_ == max_count()) { + // Remove oldest sample. + T sample_to_remove = samples_[next_index_]; + sum_ -= sample_to_remove; + sum_2_ -= sample_to_remove * sample_to_remove; + if (sample_to_remove >= max_) { + max_stale_ = true; + } + if (sample_to_remove <= min_) { + min_stale_ = true; + } + } else { + // Increase count of samples. + ++count_; + } + // Add new sample. + samples_[next_index_] = sample; + sum_ += sample; + sum_2_ += sample * sample; + if (count_ == 1 || sample >= max_) { + max_ = sample; + max_stale_ = false; + } + if (count_ == 1 || sample <= min_) { + min_ = sample; + min_stale_ = false; + } + // Update next_index_. + next_index_ = (next_index_ + 1) % max_count(); + } + + T ComputeSum() const { + return static_cast(sum_); + } + + double ComputeMean() const { + if (count_ == 0) { + return 0.0; + } + return sum_ / count_; + } + + T ComputeMax() const { + if (max_stale_) { + ASSERT(count_ > 0 && + "It shouldn't be possible for max_stale_ && count_ == 0"); + max_ = samples_[next_index_]; + for (size_t i = 1u; i < count_; i++) { + max_ = _max(max_, samples_[(next_index_ + i) % max_count()]); + } + max_stale_ = false; + } + return max_; + } + + T ComputeMin() const { + if (min_stale_) { + ASSERT(count_ > 0 && + "It shouldn't be possible for min_stale_ && count_ == 0"); + min_ = samples_[next_index_]; + for (size_t i = 1u; i < count_; i++) { + min_ = _min(min_, samples_[(next_index_ + i) % max_count()]); + } + min_stale_ = false; + } + return min_; + } + + // O(n) time complexity. + // Weights nth sample with weight (learning_rate)^n. Learning_rate should be + // between (0.0, 1.0], otherwise the non-weighted mean is returned. + double ComputeWeightedMean(double learning_rate) const { + if (count_ < 1 || learning_rate <= 0.0 || learning_rate >= 1.0) { + return ComputeMean(); + } + double weighted_mean = 0.0; + double current_weight = 1.0; + double weight_sum = 0.0; + const size_t max_size = max_count(); + for (size_t i = 0; i < count_; ++i) { + current_weight *= learning_rate; + weight_sum += current_weight; + // Add max_size to prevent underflow. + size_t index = (next_index_ + max_size - i - 1) % max_size; + weighted_mean += current_weight * samples_[index]; + } + return weighted_mean / weight_sum; + } + + // Compute estimated variance. Estimation is more accurate + // as the number of samples grows. + double ComputeVariance() const { + if (count_ == 0) { + return 0.0; + } + // Var = E[x^2] - (E[x])^2 + double count_inv = 1.0 / count_; + double mean_2 = sum_2_ * count_inv; + double mean = sum_ * count_inv; + return mean_2 - (mean * mean); + } + + private: + size_t count_; + size_t next_index_; + double sum_; // Sum(x) - double to avoid overflow + double sum_2_; // Sum(x*x) - double to avoid overflow + mutable T max_; + mutable bool max_stale_; + mutable T min_; + mutable bool min_stale_; + std::vector samples_; + + DISALLOW_COPY_AND_ASSIGN(RollingAccumulator); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ROLLINGACCUMULATOR_H_ diff --git a/webrtc/base/rollingaccumulator_unittest.cc b/webrtc/base/rollingaccumulator_unittest.cc new file mode 100644 index 000000000..7e3d8cdf0 --- /dev/null +++ b/webrtc/base/rollingaccumulator_unittest.cc @@ -0,0 +1,118 @@ +/* + * Copyright 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 "webrtc/base/gunit.h" +#include "webrtc/base/rollingaccumulator.h" + +namespace rtc { + +namespace { + +const double kLearningRate = 0.5; + +} // namespace + +TEST(RollingAccumulatorTest, ZeroSamples) { + RollingAccumulator accum(10); + + EXPECT_EQ(0U, accum.count()); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeMean()); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeVariance()); + EXPECT_EQ(0, accum.ComputeMin()); + EXPECT_EQ(0, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, SomeSamples) { + RollingAccumulator accum(10); + for (int i = 0; i < 4; ++i) { + accum.AddSample(i); + } + + EXPECT_EQ(4U, accum.count()); + EXPECT_EQ(6, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(1.5, accum.ComputeMean()); + EXPECT_NEAR(2.26666, accum.ComputeWeightedMean(kLearningRate), 0.01); + EXPECT_DOUBLE_EQ(1.25, accum.ComputeVariance()); + EXPECT_EQ(0, accum.ComputeMin()); + EXPECT_EQ(3, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, RollingSamples) { + RollingAccumulator accum(10); + for (int i = 0; i < 12; ++i) { + accum.AddSample(i); + } + + EXPECT_EQ(10U, accum.count()); + EXPECT_EQ(65, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(6.5, accum.ComputeMean()); + EXPECT_NEAR(10.0, accum.ComputeWeightedMean(kLearningRate), 0.01); + EXPECT_NEAR(9.0, accum.ComputeVariance(), 1.0); + EXPECT_EQ(2, accum.ComputeMin()); + EXPECT_EQ(11, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, ResetSamples) { + RollingAccumulator accum(10); + + for (int i = 0; i < 10; ++i) { + accum.AddSample(100); + } + EXPECT_EQ(10U, accum.count()); + EXPECT_DOUBLE_EQ(100.0, accum.ComputeMean()); + EXPECT_EQ(100, accum.ComputeMin()); + EXPECT_EQ(100, accum.ComputeMax()); + + accum.Reset(); + EXPECT_EQ(0U, accum.count()); + + for (int i = 0; i < 5; ++i) { + accum.AddSample(i); + } + + EXPECT_EQ(5U, accum.count()); + EXPECT_EQ(10, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(2.0, accum.ComputeMean()); + EXPECT_EQ(0, accum.ComputeMin()); + EXPECT_EQ(4, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, RollingSamplesDouble) { + RollingAccumulator accum(10); + for (int i = 0; i < 23; ++i) { + accum.AddSample(5 * i); + } + + EXPECT_EQ(10u, accum.count()); + EXPECT_DOUBLE_EQ(875.0, accum.ComputeSum()); + EXPECT_DOUBLE_EQ(87.5, accum.ComputeMean()); + EXPECT_NEAR(105.049, accum.ComputeWeightedMean(kLearningRate), 0.1); + EXPECT_NEAR(229.166667, accum.ComputeVariance(), 25); + EXPECT_DOUBLE_EQ(65.0, accum.ComputeMin()); + EXPECT_DOUBLE_EQ(110.0, accum.ComputeMax()); +} + +TEST(RollingAccumulatorTest, ComputeWeightedMeanCornerCases) { + RollingAccumulator accum(10); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeWeightedMean(kLearningRate)); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeWeightedMean(0.0)); + EXPECT_DOUBLE_EQ(0.0, accum.ComputeWeightedMean(1.1)); + + for (int i = 0; i < 8; ++i) { + accum.AddSample(i); + } + + EXPECT_DOUBLE_EQ(3.5, accum.ComputeMean()); + EXPECT_DOUBLE_EQ(3.5, accum.ComputeWeightedMean(0)); + EXPECT_DOUBLE_EQ(3.5, accum.ComputeWeightedMean(1.1)); + EXPECT_NEAR(6.0, accum.ComputeWeightedMean(kLearningRate), 0.1); +} + +} // namespace rtc diff --git a/webrtc/base/safe_conversions.h b/webrtc/base/safe_conversions.h new file mode 100644 index 000000000..f6cb24e41 --- /dev/null +++ b/webrtc/base/safe_conversions.h @@ -0,0 +1,79 @@ +/* + * Copyright 2014 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. + */ + +// Borrowed from Chromium's src/base/numerics/safe_conversions.h. + +#ifndef WEBRTC_BASE_SAFE_CONVERSIONS_H_ +#define WEBRTC_BASE_SAFE_CONVERSIONS_H_ + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/safe_conversions_impl.h" + +namespace rtc { + +inline void Check(bool condition) { + if (!condition) { + LOG(LS_ERROR) << "CHECK failed."; + Break(); + // The program should have crashed at this point. + } +} + +// Convenience function that returns true if the supplied value is in range +// for the destination type. +template +inline bool IsValueInRangeForNumericType(Src value) { + return internal::RangeCheck(value) == internal::TYPE_VALID; +} + +// checked_cast<> is analogous to static_cast<> for numeric types, +// except that it CHECKs that the specified numeric conversion will not +// overflow or underflow. NaN source will always trigger a CHECK. +template +inline Dst checked_cast(Src value) { + Check(IsValueInRangeForNumericType(value)); + return static_cast(value); +} + +// saturated_cast<> is analogous to static_cast<> for numeric types, except +// that the specified numeric conversion will saturate rather than overflow or +// underflow. NaN assignment to an integral will trigger a CHECK condition. +template +inline Dst saturated_cast(Src value) { + // Optimization for floating point values, which already saturate. + if (std::numeric_limits::is_iec559) + return static_cast(value); + + switch (internal::RangeCheck(value)) { + case internal::TYPE_VALID: + return static_cast(value); + + case internal::TYPE_UNDERFLOW: + return std::numeric_limits::min(); + + case internal::TYPE_OVERFLOW: + return std::numeric_limits::max(); + + // Should fail only on attempting to assign NaN to a saturated integer. + case internal::TYPE_INVALID: + Check(false); + return std::numeric_limits::max(); + } + + Check(false); // NOTREACHED(); + return static_cast(value); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_SAFE_CONVERSIONS_H_ diff --git a/webrtc/base/safe_conversions_impl.h b/webrtc/base/safe_conversions_impl.h new file mode 100644 index 000000000..2950f970c --- /dev/null +++ b/webrtc/base/safe_conversions_impl.h @@ -0,0 +1,188 @@ +/* + * Copyright 2014 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. + */ + +// Borrowed from Chromium's src/base/numerics/safe_conversions_impl.h. + +#ifndef WEBRTC_BASE_SAFE_CONVERSIONS_IMPL_H_ +#define WEBRTC_BASE_SAFE_CONVERSIONS_IMPL_H_ + +#include + +namespace rtc { +namespace internal { + +enum DstSign { + DST_UNSIGNED, + DST_SIGNED +}; + +enum SrcSign { + SRC_UNSIGNED, + SRC_SIGNED +}; + +enum DstRange { + OVERLAPS_RANGE, + CONTAINS_RANGE +}; + +// Helper templates to statically determine if our destination type can contain +// all values represented by the source type. + +template ::is_signed ? + DST_SIGNED : DST_UNSIGNED, + SrcSign IsSrcSigned = std::numeric_limits::is_signed ? + SRC_SIGNED : SRC_UNSIGNED> +struct StaticRangeCheck {}; + +template +struct StaticRangeCheck { + typedef std::numeric_limits DstLimits; + typedef std::numeric_limits SrcLimits; + // Compare based on max_exponent, which we must compute for integrals. + static const size_t kDstMaxExponent = DstLimits::is_iec559 ? + DstLimits::max_exponent : + (sizeof(Dst) * 8 - 1); + static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? + SrcLimits::max_exponent : + (sizeof(Src) * 8 - 1); + static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? + CONTAINS_RANGE : OVERLAPS_RANGE; +}; + +template +struct StaticRangeCheck { + static const DstRange value = sizeof(Dst) >= sizeof(Src) ? + CONTAINS_RANGE : OVERLAPS_RANGE; +}; + +template +struct StaticRangeCheck { + typedef std::numeric_limits DstLimits; + typedef std::numeric_limits SrcLimits; + // Compare based on max_exponent, which we must compute for integrals. + static const size_t kDstMaxExponent = DstLimits::is_iec559 ? + DstLimits::max_exponent : + (sizeof(Dst) * 8 - 1); + static const size_t kSrcMaxExponent = sizeof(Src) * 8; + static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? + CONTAINS_RANGE : OVERLAPS_RANGE; +}; + +template +struct StaticRangeCheck { + static const DstRange value = OVERLAPS_RANGE; +}; + + +enum RangeCheckResult { + TYPE_VALID = 0, // Value can be represented by the destination type. + TYPE_UNDERFLOW = 1, // Value would overflow. + TYPE_OVERFLOW = 2, // Value would underflow. + TYPE_INVALID = 3 // Source value is invalid (i.e. NaN). +}; + +// This macro creates a RangeCheckResult from an upper and lower bound +// check by taking advantage of the fact that only NaN can be out of range in +// both directions at once. +#define BASE_NUMERIC_RANGE_CHECK_RESULT(is_in_upper_bound, is_in_lower_bound) \ + RangeCheckResult(((is_in_upper_bound) ? 0 : TYPE_OVERFLOW) | \ + ((is_in_lower_bound) ? 0 : TYPE_UNDERFLOW)) + +template ::is_signed ? + DST_SIGNED : DST_UNSIGNED, + SrcSign IsSrcSigned = std::numeric_limits::is_signed ? + SRC_SIGNED : SRC_UNSIGNED, + DstRange IsSrcRangeContained = StaticRangeCheck::value> +struct RangeCheckImpl {}; + +// The following templates are for ranges that must be verified at runtime. We +// split it into checks based on signedness to avoid confusing casts and +// compiler warnings on signed an unsigned comparisons. + +// Dst range always contains the result: nothing to check. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + return TYPE_VALID; + } +}; + +// Signed to signed narrowing. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + return DstLimits::is_iec559 ? + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), + value >= static_cast(DstLimits::max() * -1)) : + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), + value >= static_cast(DstLimits::min())); + } +}; + +// Unsigned to unsigned narrowing. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + return BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), true); + } +}; + +// Unsigned to signed. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + return sizeof(Dst) > sizeof(Src) ? TYPE_VALID : + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), true); + } +}; + +// Signed to unsigned. +template +struct RangeCheckImpl { + static RangeCheckResult Check(Src value) { + typedef std::numeric_limits DstLimits; + typedef std::numeric_limits SrcLimits; + // Compare based on max_exponent, which we must compute for integrals. + static const size_t kDstMaxExponent = sizeof(Dst) * 8; + static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? + SrcLimits::max_exponent : + (sizeof(Src) * 8 - 1); + return (kDstMaxExponent >= kSrcMaxExponent) ? + BASE_NUMERIC_RANGE_CHECK_RESULT(true, value >= static_cast(0)) : + BASE_NUMERIC_RANGE_CHECK_RESULT( + value <= static_cast(DstLimits::max()), + value >= static_cast(0)); + } +}; + +template +inline RangeCheckResult RangeCheck(Src value) { + COMPILE_ASSERT(std::numeric_limits::is_specialized, + argument_must_be_numeric); + COMPILE_ASSERT(std::numeric_limits::is_specialized, + result_must_be_numeric); + return RangeCheckImpl::Check(value); +} + +} // namespace internal +} // namespace rtc + +#endif // WEBRTC_BASE_SAFE_CONVERSIONS_IMPL_H_ diff --git a/webrtc/base/schanneladapter.cc b/webrtc/base/schanneladapter.cc new file mode 100644 index 000000000..50c0638fd --- /dev/null +++ b/webrtc/base/schanneladapter.cc @@ -0,0 +1,702 @@ +/* + * Copyright 2004 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 "webrtc/base/win32.h" +#define SECURITY_WIN32 +#include +#include + +#include +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/schanneladapter.h" +#include "webrtc/base/sec_buffer.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +///////////////////////////////////////////////////////////////////////////// +// SChannelAdapter +///////////////////////////////////////////////////////////////////////////// + +extern const ConstantLabel SECURITY_ERRORS[]; + +const ConstantLabel SCHANNEL_BUFFER_TYPES[] = { + KLABEL(SECBUFFER_EMPTY), // 0 + KLABEL(SECBUFFER_DATA), // 1 + KLABEL(SECBUFFER_TOKEN), // 2 + KLABEL(SECBUFFER_PKG_PARAMS), // 3 + KLABEL(SECBUFFER_MISSING), // 4 + KLABEL(SECBUFFER_EXTRA), // 5 + KLABEL(SECBUFFER_STREAM_TRAILER), // 6 + KLABEL(SECBUFFER_STREAM_HEADER), // 7 + KLABEL(SECBUFFER_MECHLIST), // 11 + KLABEL(SECBUFFER_MECHLIST_SIGNATURE), // 12 + KLABEL(SECBUFFER_TARGET), // 13 + KLABEL(SECBUFFER_CHANNEL_BINDINGS), // 14 + LASTLABEL +}; + +void DescribeBuffer(LoggingSeverity severity, const char* prefix, + const SecBuffer& sb) { + LOG_V(severity) + << prefix + << "(" << sb.cbBuffer + << ", " << FindLabel(sb.BufferType & ~SECBUFFER_ATTRMASK, + SCHANNEL_BUFFER_TYPES) + << ", " << sb.pvBuffer << ")"; +} + +void DescribeBuffers(LoggingSeverity severity, const char* prefix, + const SecBufferDesc* sbd) { + if (!LOG_CHECK_LEVEL_V(severity)) + return; + LOG_V(severity) << prefix << "("; + for (size_t i=0; icBuffers; ++i) { + DescribeBuffer(severity, " ", sbd->pBuffers[i]); + } + LOG_V(severity) << ")"; +} + +const ULONG SSL_FLAGS_DEFAULT = ISC_REQ_ALLOCATE_MEMORY + | ISC_REQ_CONFIDENTIALITY + | ISC_REQ_EXTENDED_ERROR + | ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + | ISC_REQ_STREAM; + //| ISC_REQ_USE_SUPPLIED_CREDS; + +typedef std::vector SChannelBuffer; + +struct SChannelAdapter::SSLImpl { + CredHandle cred; + CtxtHandle ctx; + bool cred_init, ctx_init; + SChannelBuffer inbuf, outbuf, readable; + SecPkgContext_StreamSizes sizes; + + SSLImpl() : cred_init(false), ctx_init(false) { } +}; + +SChannelAdapter::SChannelAdapter(AsyncSocket* socket) + : SSLAdapter(socket), state_(SSL_NONE), + restartable_(false), signal_close_(false), message_pending_(false), + impl_(new SSLImpl) { +} + +SChannelAdapter::~SChannelAdapter() { + Cleanup(); +} + +int +SChannelAdapter::StartSSL(const char* hostname, bool restartable) { + if (state_ != SSL_NONE) + return ERROR_ALREADY_INITIALIZED; + + ssl_host_name_ = hostname; + restartable_ = restartable; + + if (socket_->GetState() != Socket::CS_CONNECTED) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +int +SChannelAdapter::BeginSSL() { + LOG(LS_VERBOSE) << "BeginSSL: " << ssl_host_name_; + ASSERT(state_ == SSL_CONNECTING); + + SECURITY_STATUS ret; + + SCHANNEL_CRED sc_cred = { 0 }; + sc_cred.dwVersion = SCHANNEL_CRED_VERSION; + //sc_cred.dwMinimumCipherStrength = 128; // Note: use system default + sc_cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_AUTO_CRED_VALIDATION; + + ret = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, + &sc_cred, NULL, NULL, &impl_->cred, NULL); + if (ret != SEC_E_OK) { + LOG(LS_ERROR) << "AcquireCredentialsHandle error: " + << ErrorName(ret, SECURITY_ERRORS); + return ret; + } + impl_->cred_init = true; + + if (LOG_CHECK_LEVEL(LS_VERBOSE)) { + SecPkgCred_CipherStrengths cipher_strengths = { 0 }; + ret = QueryCredentialsAttributes(&impl_->cred, + SECPKG_ATTR_CIPHER_STRENGTHS, + &cipher_strengths); + if (SUCCEEDED(ret)) { + LOG(LS_VERBOSE) << "SChannel cipher strength: " + << cipher_strengths.dwMinimumCipherStrength << " - " + << cipher_strengths.dwMaximumCipherStrength; + } + + SecPkgCred_SupportedAlgs supported_algs = { 0 }; + ret = QueryCredentialsAttributes(&impl_->cred, + SECPKG_ATTR_SUPPORTED_ALGS, + &supported_algs); + if (SUCCEEDED(ret)) { + LOG(LS_VERBOSE) << "SChannel supported algorithms:"; + for (DWORD i=0; ipwszName : L"Unknown"; + LOG(LS_VERBOSE) << " " << ToUtf8(alg_name) << " (" << alg_id << ")"; + } + CSecBufferBase::FreeSSPI(supported_algs.palgSupportedAlgs); + } + } + + ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0; + if (ignore_bad_cert()) + flags |= ISC_REQ_MANUAL_CRED_VALIDATION; + + CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out; + ret = InitializeSecurityContextA(&impl_->cred, NULL, + const_cast(ssl_host_name_.c_str()), + flags, 0, 0, NULL, 0, + &impl_->ctx, sb_out.desc(), + &ret_flags, NULL); + if (SUCCEEDED(ret)) + impl_->ctx_init = true; + return ProcessContext(ret, NULL, sb_out.desc()); +} + +int +SChannelAdapter::ContinueSSL() { + LOG(LS_VERBOSE) << "ContinueSSL"; + ASSERT(state_ == SSL_CONNECTING); + + SECURITY_STATUS ret; + + CSecBufferBundle<2> sb_in; + sb_in[0].BufferType = SECBUFFER_TOKEN; + sb_in[0].cbBuffer = static_cast(impl_->inbuf.size()); + sb_in[0].pvBuffer = &impl_->inbuf[0]; + //DescribeBuffers(LS_VERBOSE, "Input Buffer ", sb_in.desc()); + + ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0; + if (ignore_bad_cert()) + flags |= ISC_REQ_MANUAL_CRED_VALIDATION; + + CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out; + ret = InitializeSecurityContextA(&impl_->cred, &impl_->ctx, + const_cast(ssl_host_name_.c_str()), + flags, 0, 0, sb_in.desc(), 0, + NULL, sb_out.desc(), + &ret_flags, NULL); + return ProcessContext(ret, sb_in.desc(), sb_out.desc()); +} + +int +SChannelAdapter::ProcessContext(long int status, _SecBufferDesc* sbd_in, + _SecBufferDesc* sbd_out) { + if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED && + status != SEC_E_INCOMPLETE_MESSAGE) { + LOG(LS_ERROR) + << "InitializeSecurityContext error: " + << ErrorName(status, SECURITY_ERRORS); + } + //if (sbd_in) + // DescribeBuffers(LS_VERBOSE, "Input Buffer ", sbd_in); + //if (sbd_out) + // DescribeBuffers(LS_VERBOSE, "Output Buffer ", sbd_out); + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + // Wait for more input from server. + return Flush(); + } + + if (FAILED(status)) { + // We can't continue. Common errors: + // SEC_E_CERT_EXPIRED - Typically, this means the computer clock is wrong. + return status; + } + + // Note: we check both input and output buffers for SECBUFFER_EXTRA. + // Experience shows it appearing in the input, but the documentation claims + // it should appear in the output. + size_t extra = 0; + if (sbd_in) { + for (size_t i=0; icBuffers; ++i) { + SecBuffer& buffer = sbd_in->pBuffers[i]; + if (buffer.BufferType == SECBUFFER_EXTRA) { + extra += buffer.cbBuffer; + } + } + } + if (sbd_out) { + for (size_t i=0; icBuffers; ++i) { + SecBuffer& buffer = sbd_out->pBuffers[i]; + if (buffer.BufferType == SECBUFFER_EXTRA) { + extra += buffer.cbBuffer; + } else if (buffer.BufferType == SECBUFFER_TOKEN) { + impl_->outbuf.insert(impl_->outbuf.end(), + reinterpret_cast(buffer.pvBuffer), + reinterpret_cast(buffer.pvBuffer) + buffer.cbBuffer); + } + } + } + + if (extra) { + ASSERT(extra <= impl_->inbuf.size()); + size_t consumed = impl_->inbuf.size() - extra; + memmove(&impl_->inbuf[0], &impl_->inbuf[consumed], extra); + impl_->inbuf.resize(extra); + } else { + impl_->inbuf.clear(); + } + + if (SEC_I_CONTINUE_NEEDED == status) { + // Send data to server and wait for response. + // Note: ContinueSSL will result in a Flush, anyway. + return impl_->inbuf.empty() ? Flush() : ContinueSSL(); + } + + if (SEC_E_OK == status) { + LOG(LS_VERBOSE) << "QueryContextAttributes"; + status = QueryContextAttributes(&impl_->ctx, SECPKG_ATTR_STREAM_SIZES, + &impl_->sizes); + if (FAILED(status)) { + LOG(LS_ERROR) << "QueryContextAttributes error: " + << ErrorName(status, SECURITY_ERRORS); + return status; + } + + state_ = SSL_CONNECTED; + + if (int err = DecryptData()) { + return err; + } else if (int err = Flush()) { + return err; + } else { + // If we decrypted any data, queue up a notification here + PostEvent(); + // Signal our connectedness + AsyncSocketAdapter::OnConnectEvent(this); + } + return 0; + } + + if (SEC_I_INCOMPLETE_CREDENTIALS == status) { + // We don't support client authentication in schannel. + return status; + } + + // We don't expect any other codes + ASSERT(false); + return status; +} + +int +SChannelAdapter::DecryptData() { + SChannelBuffer& inbuf = impl_->inbuf; + SChannelBuffer& readable = impl_->readable; + + while (!inbuf.empty()) { + CSecBufferBundle<4> in_buf; + in_buf[0].BufferType = SECBUFFER_DATA; + in_buf[0].cbBuffer = static_cast(inbuf.size()); + in_buf[0].pvBuffer = &inbuf[0]; + + //DescribeBuffers(LS_VERBOSE, "Decrypt In ", in_buf.desc()); + SECURITY_STATUS status = DecryptMessage(&impl_->ctx, in_buf.desc(), 0, 0); + //DescribeBuffers(LS_VERBOSE, "Decrypt Out ", in_buf.desc()); + + // Note: We are explicitly treating SEC_E_OK, SEC_I_CONTEXT_EXPIRED, and + // any other successful results as continue. + if (SUCCEEDED(status)) { + size_t data_len = 0, extra_len = 0; + for (size_t i=0; icBuffers; ++i) { + if (in_buf[i].BufferType == SECBUFFER_DATA) { + data_len += in_buf[i].cbBuffer; + readable.insert(readable.end(), + reinterpret_cast(in_buf[i].pvBuffer), + reinterpret_cast(in_buf[i].pvBuffer) + in_buf[i].cbBuffer); + } else if (in_buf[i].BufferType == SECBUFFER_EXTRA) { + extra_len += in_buf[i].cbBuffer; + } + } + // There is a bug on Win2K where SEC_I_CONTEXT_EXPIRED is misclassified. + if ((data_len == 0) && (inbuf[0] == 0x15)) { + status = SEC_I_CONTEXT_EXPIRED; + } + if (extra_len) { + size_t consumed = inbuf.size() - extra_len; + memmove(&inbuf[0], &inbuf[consumed], extra_len); + inbuf.resize(extra_len); + } else { + inbuf.clear(); + } + // TODO: Handle SEC_I_CONTEXT_EXPIRED to do clean shutdown + if (status != SEC_E_OK) { + LOG(LS_INFO) << "DecryptMessage returned continuation code: " + << ErrorName(status, SECURITY_ERRORS); + } + continue; + } + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + break; + } else { + return status; + } + } + + return 0; +} + +void +SChannelAdapter::Cleanup() { + if (impl_->ctx_init) + DeleteSecurityContext(&impl_->ctx); + if (impl_->cred_init) + FreeCredentialsHandle(&impl_->cred); + delete impl_; +} + +void +SChannelAdapter::PostEvent() { + // Check if there's anything notable to signal + if (impl_->readable.empty() && !signal_close_) + return; + + // Only one post in the queue at a time + if (message_pending_) + return; + + if (Thread* thread = Thread::Current()) { + message_pending_ = true; + thread->Post(this); + } else { + LOG(LS_ERROR) << "No thread context available for SChannelAdapter"; + ASSERT(false); + } +} + +void +SChannelAdapter::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "SChannelAdapter::Error(" + << context << ", " + << ErrorName(err, SECURITY_ERRORS) << ")"; + state_ = SSL_ERROR; + SetError(err); + if (signal) + AsyncSocketAdapter::OnCloseEvent(this, err); +} + +int +SChannelAdapter::Read() { + char buffer[4096]; + SChannelBuffer& inbuf = impl_->inbuf; + while (true) { + int ret = AsyncSocketAdapter::Recv(buffer, sizeof(buffer)); + if (ret > 0) { + inbuf.insert(inbuf.end(), buffer, buffer + ret); + } else if (GetError() == EWOULDBLOCK) { + return 0; // Blocking + } else { + return GetError(); + } + } +} + +int +SChannelAdapter::Flush() { + int result = 0; + size_t pos = 0; + SChannelBuffer& outbuf = impl_->outbuf; + while (pos < outbuf.size()) { + int sent = AsyncSocketAdapter::Send(&outbuf[pos], outbuf.size() - pos); + if (sent > 0) { + pos += sent; + } else if (GetError() == EWOULDBLOCK) { + break; // Blocking + } else { + result = GetError(); + break; + } + } + if (int remainder = static_cast(outbuf.size() - pos)) { + memmove(&outbuf[0], &outbuf[pos], remainder); + outbuf.resize(remainder); + } else { + outbuf.clear(); + } + return result; +} + +// +// AsyncSocket Implementation +// + +int +SChannelAdapter::Send(const void* pv, size_t cb) { + switch (state_) { + case SSL_NONE: + return AsyncSocketAdapter::Send(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + size_t written = 0; + SChannelBuffer& outbuf = impl_->outbuf; + while (written < cb) { + const size_t encrypt_len = std::min(cb - written, + impl_->sizes.cbMaximumMessage); + + CSecBufferBundle<4> out_buf; + out_buf[0].BufferType = SECBUFFER_STREAM_HEADER; + out_buf[0].cbBuffer = impl_->sizes.cbHeader; + out_buf[1].BufferType = SECBUFFER_DATA; + out_buf[1].cbBuffer = static_cast(encrypt_len); + out_buf[2].BufferType = SECBUFFER_STREAM_TRAILER; + out_buf[2].cbBuffer = impl_->sizes.cbTrailer; + + size_t packet_len = out_buf[0].cbBuffer + + out_buf[1].cbBuffer + + out_buf[2].cbBuffer; + + SChannelBuffer message; + message.resize(packet_len); + out_buf[0].pvBuffer = &message[0]; + out_buf[1].pvBuffer = &message[out_buf[0].cbBuffer]; + out_buf[2].pvBuffer = &message[out_buf[0].cbBuffer + out_buf[1].cbBuffer]; + + memcpy(out_buf[1].pvBuffer, + static_cast(pv) + written, + encrypt_len); + + //DescribeBuffers(LS_VERBOSE, "Encrypt In ", out_buf.desc()); + SECURITY_STATUS res = EncryptMessage(&impl_->ctx, 0, out_buf.desc(), 0); + //DescribeBuffers(LS_VERBOSE, "Encrypt Out ", out_buf.desc()); + + if (FAILED(res)) { + Error("EncryptMessage", res, false); + return SOCKET_ERROR; + } + + // We assume that the header and data segments do not change length, + // or else encrypting the concatenated packet in-place is wrong. + ASSERT(out_buf[0].cbBuffer == impl_->sizes.cbHeader); + ASSERT(out_buf[1].cbBuffer == static_cast(encrypt_len)); + + // However, the length of the trailer may change due to padding. + ASSERT(out_buf[2].cbBuffer <= impl_->sizes.cbTrailer); + + packet_len = out_buf[0].cbBuffer + + out_buf[1].cbBuffer + + out_buf[2].cbBuffer; + + written += encrypt_len; + outbuf.insert(outbuf.end(), &message[0], &message[packet_len-1]+1); + } + + if (int err = Flush()) { + state_ = SSL_ERROR; + SetError(err); + return SOCKET_ERROR; + } + + return static_cast(written); +} + +int +SChannelAdapter::Recv(void* pv, size_t cb) { + switch (state_) { + case SSL_NONE: + return AsyncSocketAdapter::Recv(pv, cb); + + case SSL_WAIT: + case SSL_CONNECTING: + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + default: + return SOCKET_ERROR; + } + + SChannelBuffer& readable = impl_->readable; + if (readable.empty()) { + SetError(EWOULDBLOCK); + return SOCKET_ERROR; + } + size_t read = _min(cb, readable.size()); + memcpy(pv, &readable[0], read); + if (size_t remaining = readable.size() - read) { + memmove(&readable[0], &readable[read], remaining); + readable.resize(remaining); + } else { + readable.clear(); + } + + PostEvent(); + return static_cast(read); +} + +int +SChannelAdapter::Close() { + if (!impl_->readable.empty()) { + LOG(WARNING) << "SChannelAdapter::Close with readable data"; + // Note: this isn't strictly an error, but we're using it temporarily to + // track bugs. + //ASSERT(false); + } + if (state_ == SSL_CONNECTED) { + DWORD token = SCHANNEL_SHUTDOWN; + CSecBufferBundle<1> sb_in; + sb_in[0].BufferType = SECBUFFER_TOKEN; + sb_in[0].cbBuffer = sizeof(token); + sb_in[0].pvBuffer = &token; + ApplyControlToken(&impl_->ctx, sb_in.desc()); + // TODO: In theory, to do a nice shutdown, we need to begin shutdown + // negotiation with more calls to InitializeSecurityContext. Since the + // socket api doesn't support nice shutdown at this point, we don't bother. + } + Cleanup(); + impl_ = new SSLImpl; + state_ = restartable_ ? SSL_WAIT : SSL_NONE; + signal_close_ = false; + message_pending_ = false; + return AsyncSocketAdapter::Close(); +} + +Socket::ConnState +SChannelAdapter::GetState() const { + if (signal_close_) + return CS_CONNECTED; + ConnState state = socket_->GetState(); + if ((state == CS_CONNECTED) + && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING))) + state = CS_CONNECTING; + return state; +} + +void +SChannelAdapter::OnConnectEvent(AsyncSocket* socket) { + LOG(LS_VERBOSE) << "SChannelAdapter::OnConnectEvent"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + AsyncSocketAdapter::OnConnectEvent(socket); + return; + } + + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err); + } +} + +void +SChannelAdapter::OnReadEvent(AsyncSocket* socket) { + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (int err = Read()) { + Error("Read", err); + return; + } + + if (impl_->inbuf.empty()) + return; + + if (state_ == SSL_CONNECTED) { + if (int err = DecryptData()) { + Error("DecryptData", err); + } else if (!impl_->readable.empty()) { + AsyncSocketAdapter::OnReadEvent(this); + } + } else if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err); + } + } +} + +void +SChannelAdapter::OnWriteEvent(AsyncSocket* socket) { + if (state_ == SSL_NONE) { + AsyncSocketAdapter::OnWriteEvent(socket); + return; + } + + if (int err = Flush()) { + Error("Flush", err); + return; + } + + // See if we have more data to write + if (!impl_->outbuf.empty()) + return; + + // Buffer is empty, submit notification + if (state_ == SSL_CONNECTED) { + AsyncSocketAdapter::OnWriteEvent(socket); + } +} + +void +SChannelAdapter::OnCloseEvent(AsyncSocket* socket, int err) { + if ((state_ == SSL_NONE) || impl_->readable.empty()) { + AsyncSocketAdapter::OnCloseEvent(socket, err); + return; + } + + // If readable is non-empty, then we have a pending Message + // that will allow us to signal close (eventually). + signal_close_ = true; +} + +void +SChannelAdapter::OnMessage(Message* pmsg) { + if (!message_pending_) + return; // This occurs when socket is closed + + message_pending_ = false; + if (!impl_->readable.empty()) { + AsyncSocketAdapter::OnReadEvent(this); + } else if (signal_close_) { + signal_close_ = false; + AsyncSocketAdapter::OnCloseEvent(this, 0); // TODO: cache this error? + } +} + +} // namespace rtc diff --git a/webrtc/base/schanneladapter.h b/webrtc/base/schanneladapter.h new file mode 100644 index 000000000..d174b593f --- /dev/null +++ b/webrtc/base/schanneladapter.h @@ -0,0 +1,77 @@ +/* + * Copyright 2004 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_BASE_SCHANNELADAPTER_H__ +#define WEBRTC_BASE_SCHANNELADAPTER_H__ + +#include +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/messagequeue.h" +struct _SecBufferDesc; + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class SChannelAdapter : public SSLAdapter, public MessageHandler { +public: + SChannelAdapter(AsyncSocket* socket); + virtual ~SChannelAdapter(); + + virtual int StartSSL(const char* hostname, bool restartable); + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + virtual int Close(); + + // Note that the socket returns ST_CONNECTING while SSL is being negotiated. + virtual ConnState GetState() const; + +protected: + enum SSLState { + SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR + }; + struct SSLImpl; + + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void OnReadEvent(AsyncSocket* socket); + virtual void OnWriteEvent(AsyncSocket* socket); + virtual void OnCloseEvent(AsyncSocket* socket, int err); + virtual void OnMessage(Message* pmsg); + + int BeginSSL(); + int ContinueSSL(); + int ProcessContext(long int status, _SecBufferDesc* sbd_in, + _SecBufferDesc* sbd_out); + int DecryptData(); + + int Read(); + int Flush(); + void Error(const char* context, int err, bool signal = true); + void Cleanup(); + + void PostEvent(); + +private: + SSLState state_; + std::string ssl_host_name_; + // If true, socket will retain SSL configuration after Close. + bool restartable_; + // If true, we are delaying signalling close until all data is read. + bool signal_close_; + // If true, we are waiting to be woken up to signal readability or closure. + bool message_pending_; + SSLImpl* impl_; +}; + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SCHANNELADAPTER_H__ diff --git a/webrtc/base/scoped_autorelease_pool.h b/webrtc/base/scoped_autorelease_pool.h new file mode 100644 index 000000000..d9cc3cb36 --- /dev/null +++ b/webrtc/base/scoped_autorelease_pool.h @@ -0,0 +1,59 @@ +/* + * Copyright 2008 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. + */ + +// Automatically initialize and and free an autoreleasepool. Never allocate +// an instance of this class using "new" - that will result in a compile-time +// error. Only use it as a stack object. +// +// Note: NSAutoreleasePool docs say that you should not normally need to +// declare an NSAutoreleasePool as a member of an object - but there's nothing +// that indicates it will be a problem, as long as the stack lifetime of the +// pool exactly matches the stack lifetime of the object. + +#ifndef WEBRTC_BASE_SCOPED_AUTORELEASE_POOL_H__ +#define WEBRTC_BASE_SCOPED_AUTORELEASE_POOL_H__ + +#if defined(WEBRTC_MAC) + +#include "webrtc/base/common.h" + +// This header may be included from Obj-C files or C++ files. +#ifdef __OBJC__ +@class NSAutoreleasePool; +#else +class NSAutoreleasePool; +#endif + +namespace rtc { + +class ScopedAutoreleasePool { + public: + ScopedAutoreleasePool(); + ~ScopedAutoreleasePool(); + + private: + // Declaring private overrides of new and delete here enforces the "only use + // as a stack object" discipline. + // + // Note: new is declared as "throw()" to get around a gcc warning about new + // returning NULL, but this method will never get called and therefore will + // never actually throw any exception. + void* operator new(size_t size) throw() { return NULL; } + void operator delete (void* ptr) {} + + NSAutoreleasePool* pool_; + + DISALLOW_EVIL_CONSTRUCTORS(ScopedAutoreleasePool); +}; + +} // namespace rtc + +#endif // WEBRTC_MAC +#endif // WEBRTC_BASE_SCOPED_AUTORELEASE_POOL_H__ diff --git a/webrtc/base/scoped_autorelease_pool.mm b/webrtc/base/scoped_autorelease_pool.mm new file mode 100644 index 000000000..4176aad0e --- /dev/null +++ b/webrtc/base/scoped_autorelease_pool.mm @@ -0,0 +1,25 @@ +/* + * Copyright 2008 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. + */ + +#import + +#import "webrtc/base/scoped_autorelease_pool.h" + +namespace rtc { + +ScopedAutoreleasePool::ScopedAutoreleasePool() { + pool_ = [[NSAutoreleasePool alloc] init]; +} + +ScopedAutoreleasePool::~ScopedAutoreleasePool() { + [pool_ drain]; +} + +} // namespace rtc diff --git a/webrtc/base/scoped_ptr.h b/webrtc/base/scoped_ptr.h new file mode 100644 index 000000000..0c0a637a2 --- /dev/null +++ b/webrtc/base/scoped_ptr.h @@ -0,0 +1,595 @@ +/* + * Copyright 2012 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. + */ + +// Scopers help you manage ownership of a pointer, helping you easily manage the +// a pointer within a scope, and automatically destroying the pointer at the +// end of a scope. There are two main classes you will use, which correspond +// to the operators new/delete and new[]/delete[]. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo("wee")); +// } // foo goes out of scope, releasing the pointer with it. +// +// { +// scoped_ptr foo; // No pointer managed. +// foo.reset(new Foo("wee")); // Now a pointer is managed. +// foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. +// foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. +// foo->Method(); // Foo::Method() called. +// foo.get()->Method(); // Foo::Method() called. +// SomeFunc(foo.release()); // SomeFunc takes ownership, foo no longer +// // manages a pointer. +// foo.reset(new Foo("wee4")); // foo manages a pointer again. +// foo.reset(); // Foo("wee4") destroyed, foo no longer +// // manages a pointer. +// } // foo wasn't managing a pointer, so nothing was destroyed. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr foo(new Foo[100]); +// foo.get()->Method(); // Foo::Method on the 0th element. +// foo[10].Method(); // Foo::Method on the 10th element. +// } +// +// These scopers also implement part of the functionality of C++11 unique_ptr +// in that they are "movable but not copyable." You can use the scopers in +// the parameter and return types of functions to signify ownership transfer +// in to and out of a function. When calling a function that has a scoper +// as the argument type, it must be called with the result of an analogous +// scoper's Pass() function or another function that generates a temporary; +// passing by copy will NOT work. Here is an example using scoped_ptr: +// +// void TakesOwnership(scoped_ptr arg) { +// // Do something with arg +// } +// scoped_ptr CreateFoo() { +// // No need for calling Pass() because we are constructing a temporary +// // for the return value. +// return scoped_ptr(new Foo("new")); +// } +// scoped_ptr PassThru(scoped_ptr arg) { +// return arg.Pass(); +// } +// +// { +// scoped_ptr ptr(new Foo("yay")); // ptr manages Foo("yay"). +// TakesOwnership(ptr.Pass()); // ptr no longer owns Foo("yay"). +// scoped_ptr ptr2 = CreateFoo(); // ptr2 owns the return Foo. +// scoped_ptr ptr3 = // ptr3 now owns what was in ptr2. +// PassThru(ptr2.Pass()); // ptr2 is correspondingly NULL. +// } +// +// Notice that if you do not call Pass() when returning from PassThru(), or +// when invoking TakesOwnership(), the code will not compile because scopers +// are not copyable; they only implement move semantics which require calling +// the Pass() function to signify a destructive transfer of state. CreateFoo() +// is different though because we are constructing a temporary on the return +// line and thus can avoid needing to call Pass(). +// +// Pass() properly handles upcast in initialization, i.e. you can use a +// scoped_ptr to initialize a scoped_ptr: +// +// scoped_ptr foo(new Foo()); +// scoped_ptr parent(foo.Pass()); +// +// PassAs<>() should be used to upcast return value in return statement: +// +// scoped_ptr CreateFoo() { +// scoped_ptr result(new FooChild()); +// return result.PassAs(); +// } +// +// Note that PassAs<>() is implemented only for scoped_ptr, but not for +// scoped_ptr. This is because casting array pointers may not be safe. + +#ifndef WEBRTC_BASE_SCOPED_PTR_H__ +#define WEBRTC_BASE_SCOPED_PTR_H__ + +#include // for ptrdiff_t +#include // for free() decl + +#include // For std::swap(). + +#include "webrtc/base/common.h" // for ASSERT +#include "webrtc/base/compile_assert.h" // for COMPILE_ASSERT +#include "webrtc/base/move.h" // for TALK_MOVE_ONLY_TYPE_FOR_CPP_03 +#include "webrtc/base/template_util.h" // for is_convertible, is_array + +#ifdef WEBRTC_WIN +namespace std { using ::ptrdiff_t; }; +#endif // WEBRTC_WIN + +namespace rtc { + +// Function object which deletes its parameter, which must be a pointer. +// If C is an array type, invokes 'delete[]' on the parameter; otherwise, +// invokes 'delete'. The default deleter for scoped_ptr. +template +struct DefaultDeleter { + DefaultDeleter() {} + template DefaultDeleter(const DefaultDeleter& other) { + // IMPLEMENTATION NOTE: C++11 20.7.1.1.2p2 only provides this constructor + // if U* is implicitly convertible to T* and U is not an array type. + // + // Correct implementation should use SFINAE to disable this + // constructor. However, since there are no other 1-argument constructors, + // using a COMPILE_ASSERT() based on is_convertible<> and requiring + // complete types is simpler and will cause compile failures for equivalent + // misuses. + // + // Note, the is_convertible check also ensures that U is not an + // array. T is guaranteed to be a non-array, so any U* where U is an array + // cannot convert to T*. + enum { T_must_be_complete = sizeof(T) }; + enum { U_must_be_complete = sizeof(U) }; + COMPILE_ASSERT((rtc::is_convertible::value), + U_ptr_must_implicitly_convert_to_T_ptr); + } + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete ptr; + } +}; + +// Specialization of DefaultDeleter for array types. +template +struct DefaultDeleter { + inline void operator()(T* ptr) const { + enum { type_must_be_complete = sizeof(T) }; + delete[] ptr; + } + + private: + // Disable this operator for any U != T because it is undefined to execute + // an array delete when the static type of the array mismatches the dynamic + // type. + // + // References: + // C++98 [expr.delete]p3 + // http://cplusplus.github.com/LWG/lwg-defects.html#938 + template void operator()(U* array) const; +}; + +template +struct DefaultDeleter { + // Never allow someone to declare something like scoped_ptr. + COMPILE_ASSERT(sizeof(T) == -1, do_not_use_array_with_size_as_type); +}; + +// Function object which invokes 'free' on its parameter, which must be +// a pointer. Can be used to store malloc-allocated pointers in scoped_ptr: +// +// scoped_ptr foo_ptr( +// static_cast(malloc(sizeof(int)))); +struct FreeDeleter { + inline void operator()(void* ptr) const { + free(ptr); + } +}; + +namespace internal { + +// Minimal implementation of the core logic of scoped_ptr, suitable for +// reuse in both scoped_ptr and its specializations. +template +class scoped_ptr_impl { + public: + explicit scoped_ptr_impl(T* p) : data_(p) { } + + // Initializer for deleters that have data parameters. + scoped_ptr_impl(T* p, const D& d) : data_(p, d) {} + + // Templated constructor that destructively takes the value from another + // scoped_ptr_impl. + template + scoped_ptr_impl(scoped_ptr_impl* other) + : data_(other->release(), other->get_deleter()) { + // We do not support move-only deleters. We could modify our move + // emulation to have rtc::subtle::move() and + // rtc::subtle::forward() + // functions that are imperfect emulations of their C++11 equivalents, + // but until there's a requirement, just assume deleters are copyable. + } + + template + void TakeState(scoped_ptr_impl* other) { + // See comment in templated constructor above regarding lack of support + // for move-only deleters. + reset(other->release()); + get_deleter() = other->get_deleter(); + } + + ~scoped_ptr_impl() { + if (data_.ptr != NULL) { + // Not using get_deleter() saves one function call in non-optimized + // builds. + static_cast(data_)(data_.ptr); + } + } + + void reset(T* p) { + // This is a self-reset, which is no longer allowed: http://crbug.com/162971 + if (p != NULL && p == data_.ptr) + abort(); + + // Note that running data_.ptr = p can lead to undefined behavior if + // get_deleter()(get()) deletes this. In order to pevent this, reset() + // should update the stored pointer before deleting its old value. + // + // However, changing reset() to use that behavior may cause current code to + // break in unexpected ways. If the destruction of the owned object + // dereferences the scoped_ptr when it is destroyed by a call to reset(), + // then it will incorrectly dispatch calls to |p| rather than the original + // value of |data_.ptr|. + // + // During the transition period, set the stored pointer to NULL while + // deleting the object. Eventually, this safety check will be removed to + // prevent the scenario initially described from occuring and + // http://crbug.com/176091 can be closed. + T* old = data_.ptr; + data_.ptr = NULL; + if (old != NULL) + static_cast(data_)(old); + data_.ptr = p; + } + + T* get() const { return data_.ptr; } + + D& get_deleter() { return data_; } + const D& get_deleter() const { return data_; } + + void swap(scoped_ptr_impl& p2) { + // Standard swap idiom: 'using std::swap' ensures that std::swap is + // present in the overload set, but we call swap unqualified so that + // any more-specific overloads can be used, if available. + using std::swap; + swap(static_cast(data_), static_cast(p2.data_)); + swap(data_.ptr, p2.data_.ptr); + } + + T* release() { + T* old_ptr = data_.ptr; + data_.ptr = NULL; + return old_ptr; + } + + T** accept() { + reset(NULL); + return &(data_.ptr); + } + + T** use() { + return &(data_.ptr); + } + + private: + // Needed to allow type-converting constructor. + template friend class scoped_ptr_impl; + + // Use the empty base class optimization to allow us to have a D + // member, while avoiding any space overhead for it when D is an + // empty class. See e.g. http://www.cantrip.org/emptyopt.html for a good + // discussion of this technique. + struct Data : public D { + explicit Data(T* ptr_in) : ptr(ptr_in) {} + Data(T* ptr_in, const D& other) : D(other), ptr(ptr_in) {} + T* ptr; + }; + + Data data_; + + DISALLOW_COPY_AND_ASSIGN(scoped_ptr_impl); +}; + +} // namespace internal + +// A scoped_ptr is like a T*, except that the destructor of scoped_ptr +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr owns the T object that it points to. +// Like a T*, a scoped_ptr may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr is thread-compatible, and once you +// dereference it, you get the thread safety guarantees of T. +// +// The size of scoped_ptr is small. On most compilers, when using the +// DefaultDeleter, sizeof(scoped_ptr) == sizeof(T*). Custom deleters will +// increase the size proportional to whatever state they need to have. See +// comments inside scoped_ptr_impl<> for details. +// +// Current implementation targets having a strict subset of C++11's +// unique_ptr<> features. Known deficiencies include not supporting move-only +// deleteres, function pointers as deleters, and deleters with reference +// types. +template > +class scoped_ptr { + TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Takes ownership of p. + explicit scoped_ptr(element_type* p) : impl_(p) { } + + // Constructor. Allows initialization of a stateful deleter. + scoped_ptr(element_type* p, const D& d) : impl_(p, d) { } + + // Constructor. Allows construction from a scoped_ptr rvalue for a + // convertible type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this constructor distinct + // from the normal move constructor. By C++11 20.7.1.2.1.21, this constructor + // has different post-conditions if D is a reference type. Since this + // implementation does not support deleters with reference type, + // we do not need a separate move constructor allowing us to avoid one + // use of SFINAE. You only need to care about this if you modify the + // implementation of scoped_ptr. + template + scoped_ptr(scoped_ptr other) : impl_(&other.impl_) { + COMPILE_ASSERT(!rtc::is_array::value, U_cannot_be_an_array); + } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Allows assignment from a scoped_ptr rvalue for a convertible + // type and deleter. + // + // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this operator= distinct from + // the normal move assignment operator. By C++11 20.7.1.2.3.4, this templated + // form has different requirements on for move-only Deleters. Since this + // implementation does not support move-only Deleters, we do not need a + // separate move assignment operator allowing us to avoid one use of SFINAE. + // You only need to care about this if you modify the implementation of + // scoped_ptr. + template + scoped_ptr& operator=(scoped_ptr rhs) { + COMPILE_ASSERT(!rtc::is_array::value, U_cannot_be_an_array); + impl_.TakeState(&rhs.impl_); + return *this; + } + + // Reset. Deletes the currently owned object, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* p = NULL) { impl_.reset(p); } + + // Accessors to get the owned object. + // operator* and operator-> will assert() if there is no current object. + element_type& operator*() const { + ASSERT(impl_.get() != NULL); + return *impl_.get(); + } + element_type* operator->() const { + ASSERT(impl_.get() != NULL); + return impl_.get(); + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + // + // Note that this trick is only safe when the == and != operators + // are declared explicitly, as otherwise "scoped_ptr1 == + // scoped_ptr2" will compile but do the wrong thing (i.e., convert + // to Testable and then do the comparison). + private: + typedef rtc::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(const element_type* p) const { return impl_.get() == p; } + bool operator!=(const element_type* p) const { return impl_.get() != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + // Delete the currently held pointer and return a pointer + // to allow overwriting of the current pointer address. + element_type** accept() WARN_UNUSED_RESULT { + return impl_.accept(); + } + + // Return a pointer to the current pointer address. + element_type** use() WARN_UNUSED_RESULT { + return impl_.use(); + } + + // C++98 doesn't support functions templates with default parameters which + // makes it hard to write a PassAs() that understands converting the deleter + // while preserving simple calling semantics. + // + // Until there is a use case for PassAs() with custom deleters, just ignore + // the custom deleter. + template + scoped_ptr PassAs() { + return scoped_ptr(Pass()); + } + + private: + // Needed to reach into |impl_| in the constructor. + template friend class scoped_ptr; + rtc::internal::scoped_ptr_impl impl_; + + // Forbidden for API compatibility with std::unique_ptr. + explicit scoped_ptr(int disallow_construction_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +template +class scoped_ptr { + TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + public: + // The element and deleter types. + typedef T element_type; + typedef D deleter_type; + + // Constructor. Defaults to initializing with NULL. + scoped_ptr() : impl_(NULL) { } + + // Constructor. Stores the given array. Note that the argument's type + // must exactly match T*. In particular: + // - it cannot be a pointer to a type derived from T, because it is + // inherently unsafe in the general case to access an array through a + // pointer whose dynamic type does not match its static type (eg., if + // T and the derived types had different sizes access would be + // incorrectly calculated). Deletion is also always undefined + // (C++98 [expr.delete]p3). If you're doing this, fix your code. + // - it cannot be NULL, because NULL is an integral expression, not a + // pointer to T. Use the no-argument version instead of explicitly + // passing NULL. + // - it cannot be const-qualified differently from T per unique_ptr spec + // (http://cplusplus.github.com/LWG/lwg-active.html#2118). Users wanting + // to work around this may use implicit_cast(). + // However, because of the first bullet in this comment, users MUST + // NOT use implicit_cast() to upcast the static type of the array. + explicit scoped_ptr(element_type* array) : impl_(array) { } + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr& operator=(RValue rhs) { + impl_.TakeState(&rhs.object->impl_); + return *this; + } + + // Reset. Deletes the currently owned array, if any. + // Then takes ownership of a new object, if given. + void reset(element_type* array = NULL) { impl_.reset(array); } + + // Accessors to get the owned array. + element_type& operator[](size_t i) const { + ASSERT(impl_.get() != NULL); + return impl_.get()[i]; + } + element_type* get() const { return impl_.get(); } + + // Access to the deleter. + deleter_type& get_deleter() { return impl_.get_deleter(); } + const deleter_type& get_deleter() const { return impl_.get_deleter(); } + + // Allow scoped_ptr to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + typedef rtc::internal::scoped_ptr_impl + scoped_ptr::*Testable; + + public: + operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(element_type* array) const { return impl_.get() == array; } + bool operator!=(element_type* array) const { return impl_.get() != array; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + impl_.swap(p2.impl_); + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + element_type* release() WARN_UNUSED_RESULT { + return impl_.release(); + } + + // Delete the currently held pointer and return a pointer + // to allow overwriting of the current pointer address. + element_type** accept() WARN_UNUSED_RESULT { + return impl_.accept(); + } + + // Return a pointer to the current pointer address. + element_type** use() WARN_UNUSED_RESULT { + return impl_.use(); + } + + private: + // Force element_type to be a complete type. + enum { type_must_be_complete = sizeof(element_type) }; + + // Actually hold the data. + rtc::internal::scoped_ptr_impl impl_; + + // Disable initialization from any type other than element_type*, by + // providing a constructor that matches such an initialization, but is + // private and has no definition. This is disabled because it is not safe to + // call delete[] on an array whose static type does not match its dynamic + // type. + template explicit scoped_ptr(U* array); + explicit scoped_ptr(int disallow_construction_from_null); + + // Disable reset() from any type other than element_type*, for the same + // reasons as the constructor above. + template void reset(U* array); + void reset(int disallow_reset_from_null); + + // Forbid comparison of scoped_ptr types. If U != T, it totally + // doesn't make sense, and if U == T, it still doesn't make sense + // because you should never have the same object owned by two different + // scoped_ptrs. + template bool operator==(scoped_ptr const& p2) const; + template bool operator!=(scoped_ptr const& p2) const; +}; + +} // namespace rtc + +// Free functions +template +void swap(rtc::scoped_ptr& p1, rtc::scoped_ptr& p2) { + p1.swap(p2); +} + +template +bool operator==(T* p1, const rtc::scoped_ptr& p2) { + return p1 == p2.get(); +} + +template +bool operator!=(T* p1, const rtc::scoped_ptr& p2) { + return p1 != p2.get(); +} + +#endif // #ifndef WEBRTC_BASE_SCOPED_PTR_H__ diff --git a/webrtc/base/scoped_ref_ptr.h b/webrtc/base/scoped_ref_ptr.h new file mode 100644 index 000000000..a71c20ae3 --- /dev/null +++ b/webrtc/base/scoped_ref_ptr.h @@ -0,0 +1,147 @@ +/* + * Copyright 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. + */ + +// Originally these classes are from Chromium. +// http://src.chromium.org/viewvc/chrome/trunk/src/base/memory/ref_counted.h?view=markup + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted { +// ... +// }; +// +// void some_function() { +// scoped_refptr foo = new MyFoo(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr foo = new MyFoo(); +// ... +// foo = NULL; // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr acts like a pointer to T. +// Given two scoped_refptr classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b.swap(a); +// // now, |b| references the MyFoo object, and |a| references NULL. +// } +// +// To make both |a| and |b| in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// scoped_refptr a = new MyFoo(); +// scoped_refptr b; +// +// b = a; +// // now, |a| and |b| each own a reference to the same MyFoo object. +// } +// + +#ifndef WEBRTC_BASE_SCOPED_REF_PTR_H_ +#define WEBRTC_BASE_SCOPED_REF_PTR_H_ + +#include + +namespace rtc { + +template +class scoped_refptr { + public: + scoped_refptr() : ptr_(NULL) { + } + + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(const scoped_refptr& r) : ptr_(r.ptr_) { + if (ptr_) + ptr_->AddRef(); + } + + template + scoped_refptr(const scoped_refptr& r) : ptr_(r.get()) { + if (ptr_) + ptr_->AddRef(); + } + + ~scoped_refptr() { + if (ptr_) + ptr_->Release(); + } + + T* get() const { return ptr_; } + operator T*() const { return ptr_; } + T* operator->() const { return ptr_; } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + T* release() { + T* retVal = ptr_; + ptr_ = NULL; + return retVal; + } + + scoped_refptr& operator=(T* p) { + // AddRef first so that self assignment should work + if (p) + p->AddRef(); + if (ptr_ ) + ptr_ ->Release(); + ptr_ = p; + return *this; + } + + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.ptr_; + } + + template + scoped_refptr& operator=(const scoped_refptr& r) { + return *this = r.get(); + } + + void swap(T** pp) { + T* p = ptr_; + ptr_ = *pp; + *pp = p; + } + + void swap(scoped_refptr& r) { + swap(&r.ptr_); + } + + protected: + T* ptr_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SCOPED_REF_PTR_H_ diff --git a/webrtc/base/scopedptrcollection.h b/webrtc/base/scopedptrcollection.h new file mode 100644 index 000000000..47dff6503 --- /dev/null +++ b/webrtc/base/scopedptrcollection.h @@ -0,0 +1,60 @@ +/* + * Copyright 2014 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. + */ + +// Stores a collection of pointers that are deleted when the container is +// destructed. + +#ifndef WEBRTC_BASE_SCOPEDPTRCOLLECTION_H_ +#define WEBRTC_BASE_SCOPEDPTRCOLLECTION_H_ + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +template +class ScopedPtrCollection { + public: + typedef std::vector VectorT; + + ScopedPtrCollection() { } + ~ScopedPtrCollection() { + for (typename VectorT::iterator it = collection_.begin(); + it != collection_.end(); ++it) { + delete *it; + } + } + + const VectorT& collection() const { return collection_; } + void Reserve(size_t size) { + collection_.reserve(size); + } + void PushBack(T* t) { + collection_.push_back(t); + } + + // Remove |t| from the collection without deleting it. + void Remove(T* t) { + collection_.erase(std::remove(collection_.begin(), collection_.end(), t), + collection_.end()); + } + + private: + VectorT collection_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPtrCollection); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SCOPEDPTRCOLLECTION_H_ diff --git a/webrtc/base/scopedptrcollection_unittest.cc b/webrtc/base/scopedptrcollection_unittest.cc new file mode 100644 index 000000000..30b8ed9ed --- /dev/null +++ b/webrtc/base/scopedptrcollection_unittest.cc @@ -0,0 +1,73 @@ +/* + * Copyright 2014 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 "webrtc/base/scopedptrcollection.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +namespace { + +class InstanceCounter { + public: + explicit InstanceCounter(int* num_instances) + : num_instances_(num_instances) { + ++(*num_instances_); + } + ~InstanceCounter() { + --(*num_instances_); + } + + private: + int* num_instances_; + + DISALLOW_COPY_AND_ASSIGN(InstanceCounter); +}; + +} // namespace + +class ScopedPtrCollectionTest : public testing::Test { + protected: + ScopedPtrCollectionTest() + : num_instances_(0), + collection_(new ScopedPtrCollection()) { + } + + int num_instances_; + scoped_ptr > collection_; +}; + +TEST_F(ScopedPtrCollectionTest, PushBack) { + EXPECT_EQ(0u, collection_->collection().size()); + EXPECT_EQ(0, num_instances_); + const int kNum = 100; + for (int i = 0; i < kNum; ++i) { + collection_->PushBack(new InstanceCounter(&num_instances_)); + } + EXPECT_EQ(static_cast(kNum), collection_->collection().size()); + EXPECT_EQ(kNum, num_instances_); + collection_.reset(); + EXPECT_EQ(0, num_instances_); +} + +TEST_F(ScopedPtrCollectionTest, Remove) { + InstanceCounter* ic = new InstanceCounter(&num_instances_); + collection_->PushBack(ic); + EXPECT_EQ(1u, collection_->collection().size()); + collection_->Remove(ic); + EXPECT_EQ(1, num_instances_); + collection_.reset(); + EXPECT_EQ(1, num_instances_); + delete ic; + EXPECT_EQ(0, num_instances_); +} + + +} // namespace rtc diff --git a/webrtc/base/sec_buffer.h b/webrtc/base/sec_buffer.h new file mode 100644 index 000000000..d4cda00d4 --- /dev/null +++ b/webrtc/base/sec_buffer.h @@ -0,0 +1,156 @@ +/* + * Copyright 2004 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. + */ + +// @file Contains utility classes that make it easier to use SecBuffers + +#ifndef WEBRTC_BASE_SEC_BUFFER_H__ +#define WEBRTC_BASE_SEC_BUFFER_H__ + +namespace rtc { + +// A base class for CSecBuffer. Contains +// all implementation that does not require +// template arguments. +class CSecBufferBase : public SecBuffer { + public: + CSecBufferBase() { + Clear(); + } + + // Uses the SSPI to free a pointer, must be + // used for buffers returned from SSPI APIs. + static void FreeSSPI(void *ptr) { + if ( ptr ) { + SECURITY_STATUS status; + status = ::FreeContextBuffer(ptr); + ASSERT(SEC_E_OK == status); // "Freeing context buffer" + } + } + + // Deletes a buffer with operator delete + static void FreeDelete(void *ptr) { + delete [] reinterpret_cast(ptr); + } + + // A noop delete, for buffers over other + // people's memory + static void FreeNone(void *ptr) { + } + + protected: + // Clears the buffer to EMPTY & NULL + void Clear() { + this->BufferType = SECBUFFER_EMPTY; + this->cbBuffer = 0; + this->pvBuffer = NULL; + } +}; + +// Wrapper class for SecBuffer to take care +// of initialization and destruction. +template +class CSecBuffer: public CSecBufferBase { + public: + // Initializes buffer to empty & NULL + CSecBuffer() { + } + + // Frees any allocated memory + ~CSecBuffer() { + Release(); + } + + // Frees the buffer appropriately, and re-nulls + void Release() { + pfnFreeBuffer(this->pvBuffer); + Clear(); + } + + private: + // A placeholder function for compile-time asserts on the class + void CompileAsserts() { + // never invoked... + assert(false); // _T("Notreached") + + // This class must not extend the size of SecBuffer, since + // we use arrays of CSecBuffer in CSecBufferBundle below + cassert(sizeof(CSecBuffer == sizeof(SecBuffer))); + } +}; + +// Contains all generic implementation for the +// SecBufferBundle class +class SecBufferBundleBase { + public: +}; + +// A template class that bundles a SecBufferDesc with +// one or more SecBuffers for convenience. Can take +// care of deallocating buffers appropriately, as indicated +// by pfnFreeBuffer function. +// By default does no deallocation. +template +class CSecBufferBundle : public SecBufferBundleBase { + public: + // Constructs a security buffer bundle with num_buffers + // buffers, all of which are empty and nulled. + CSecBufferBundle() { + desc_.ulVersion = SECBUFFER_VERSION; + desc_.cBuffers = num_buffers; + desc_.pBuffers = buffers_; + } + + // Frees all currently used buffers. + ~CSecBufferBundle() { + Release(); + } + + // Accessor for the descriptor + PSecBufferDesc desc() { + return &desc_; + } + + // Accessor for the descriptor + const PSecBufferDesc desc() const { + return &desc_; + } + + // returns the i-th security buffer + SecBuffer &operator[] (size_t num) { + ASSERT(num < num_buffers); // "Buffer index out of bounds" + return buffers_[num]; + } + + // returns the i-th security buffer + const SecBuffer &operator[] (size_t num) const { + ASSERT(num < num_buffers); // "Buffer index out of bounds" + return buffers_[num]; + } + + // Frees all non-NULL security buffers, + // using the deallocation function + void Release() { + for ( size_t i = 0; i < num_buffers; ++i ) { + buffers_[i].Release(); + } + } + + private: + // Our descriptor + SecBufferDesc desc_; + // Our bundled buffers, each takes care of its own + // initialization and destruction + CSecBuffer buffers_[num_buffers]; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SEC_BUFFER_H__ diff --git a/webrtc/base/sha1.cc b/webrtc/base/sha1.cc new file mode 100644 index 000000000..afc5569fd --- /dev/null +++ b/webrtc/base/sha1.cc @@ -0,0 +1,286 @@ +/* + * SHA-1 in C + * By Steve Reid + * 100% Public Domain + * + * ----------------- + * Modified 7/98 + * By James H. Brown + * Still 100% Public Domain + * + * Corrected a problem which generated improper hash values on 16 bit machines + * Routine SHA1Update changed from + * void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int + * len) + * to + * void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned + * long len) + * + * The 'len' parameter was declared an int which works fine on 32 bit machines. + * However, on 16 bit machines an int is too small for the shifts being done + * against + * it. This caused the hash function to generate incorrect values if len was + * greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + * + * Since the file IO in main() reads 16K at a time, any file 8K or larger would + * be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million + * "a"s). + * + * I also changed the declaration of variables i & j in SHA1Update to + * unsigned long from unsigned int for the same reason. + * + * These changes should make no difference to any 32 bit implementations since + * an + * int and a long are the same size in those environments. + * + * -- + * I also corrected a few compiler warnings generated by Borland C. + * 1. Added #include for exit() prototype + * 2. Removed unused variable 'j' in SHA1Final + * 3. Changed exit(0) to return(0) at end of main. + * + * ALL changes I made can be located by searching for comments containing 'JHB' + * ----------------- + * Modified 8/98 + * By Steve Reid + * Still 100% public domain + * + * 1- Removed #include and used return() instead of exit() + * 2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) + * 3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + * + * ----------------- + * Modified 4/01 + * By Saul Kravitz + * Still 100% PD + * Modified to run on Compaq Alpha hardware. + * + * ----------------- + * Modified 07/2002 + * By Ralph Giles + * Still 100% public domain + * modified for use with stdint types, autoconf + * code cleanup, removed attribution comments + * switched SHA1Final() argument order for consistency + * use SHA1_ prefix for public api + * move public api to sha1.h + * + * ----------------- + * Modified 02/2012 + * By Justin Uberti + * Remove underscore from SHA1 prefix to avoid conflict with OpenSSL + * Remove test code + * Untabify + * + * ----------------- + * Modified 03/2012 + * By Ronghua Wu + * Change the typedef of uint32(8)_t to uint32(8). We need this because in the + * chromium android build, the stdio.h will include stdint.h which already + * defined uint32(8)_t. + * + * ----------------- + * Modified 04/2012 + * By Frank Barchard + * Ported to C++, Google style, change len to size_t, enable SHA1HANDSOFF + * + * Test Vectors (from FIPS PUB 180-1) + * "abc" + * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D + * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 + * A million repetitions of "a" + * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F + */ + +// Enabling SHA1HANDSOFF preserves the caller's data buffer. +// Disabling SHA1HANDSOFF the buffer will be modified (end swapped). +#define SHA1HANDSOFF + +#include "webrtc/base/sha1.h" + +#include +#include + +namespace rtc { + +void SHA1Transform(uint32 state[5], const uint8 buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +// blk0() and blk() perform the initial expand. +// I got the idea of expanding during the round function from SSLeay +// FIXME: can we do this in an endian-proof way? +#ifdef ARCH_CPU_BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \ + (rol(block->l[i], 8) & 0x00FF00FF)) +#endif +#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ \ + block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1)) + +// (R0+R1), R2, R3, R4 are the different operations used in SHA1. +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5);\ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +#ifdef VERBOSE // SAK +void SHAPrintContext(SHA1_CTX *context, char *msg) { + printf("%s (%d,%d) %x %x %x %x %x\n", + msg, + context->count[0], context->count[1], + context->state[0], + context->state[1], + context->state[2], + context->state[3], + context->state[4]); +} +#endif /* VERBOSE */ + +// Hash a single 512-bit block. This is the core of the algorithm. +void SHA1Transform(uint32 state[5], const uint8 buffer[64]) { + union CHAR64LONG16 { + uint8 c[64]; + uint32 l[16]; + }; +#ifdef SHA1HANDSOFF + static uint8 workspace[64]; + memcpy(workspace, buffer, 64); + CHAR64LONG16* block = reinterpret_cast(workspace); +#else + // Note(fbarchard): This option does modify the user's data buffer. + CHAR64LONG16* block = const_cast( + reinterpret_cast(buffer)); +#endif + + // Copy context->state[] to working vars. + uint32 a = state[0]; + uint32 b = state[1]; + uint32 c = state[2]; + uint32 d = state[3]; + uint32 e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + // Note(fbarchard): The following has lint warnings for multiple ; on + // a line and no space after , but is left as-is to be similar to the + // original code. + R0(a,b,c,d,e,0); R0(e,a,b,c,d,1); R0(d,e,a,b,c,2); R0(c,d,e,a,b,3); + R0(b,c,d,e,a,4); R0(a,b,c,d,e,5); R0(e,a,b,c,d,6); R0(d,e,a,b,c,7); + R0(c,d,e,a,b,8); R0(b,c,d,e,a,9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[]. + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} + +// SHA1Init - Initialize new context. +void SHA1Init(SHA1_CTX* context) { + // SHA1 initialization constants. + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +// Run your data through this. +void SHA1Update(SHA1_CTX* context, const uint8* data, size_t input_len) { + size_t i = 0; + +#ifdef VERBOSE + SHAPrintContext(context, "before"); +#endif + + // Compute number of bytes mod 64. + size_t index = (context->count[0] >> 3) & 63; + + // Update number of bits. + // TODO: Use uint64 instead of 2 uint32 for count. + // count[0] has low 29 bits for byte count + 3 pad 0's making 32 bits for + // bit count. + // Add bit count to low uint32 + context->count[0] += static_cast(input_len << 3); + if (context->count[0] < static_cast(input_len << 3)) { + ++context->count[1]; // if overlow (carry), add one to high word + } + context->count[1] += static_cast(input_len >> 29); + if ((index + input_len) > 63) { + i = 64 - index; + memcpy(&context->buffer[index], data, i); + SHA1Transform(context->state, context->buffer); + for (; i + 63 < input_len; i += 64) { + SHA1Transform(context->state, data + i); + } + index = 0; + } + memcpy(&context->buffer[index], &data[i], input_len - i); + +#ifdef VERBOSE + SHAPrintContext(context, "after "); +#endif +} + +// Add padding and return the message digest. +void SHA1Final(SHA1_CTX* context, uint8 digest[SHA1_DIGEST_SIZE]) { + uint8 finalcount[8]; + for (int i = 0; i < 8; ++i) { + // Endian independent + finalcount[i] = static_cast( + (context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8) ) & 255); + } + SHA1Update(context, reinterpret_cast("\200"), 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, reinterpret_cast("\0"), 1); + } + SHA1Update(context, finalcount, 8); // Should cause a SHA1Transform(). + for (int i = 0; i < SHA1_DIGEST_SIZE; ++i) { + digest[i] = static_cast( + (context->state[i >> 2] >> ((3 - (i & 3)) * 8) ) & 255); + } + + // Wipe variables. + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(finalcount, 0, 8); // SWR + +#ifdef SHA1HANDSOFF // Make SHA1Transform overwrite its own static vars. + SHA1Transform(context->state, context->buffer); +#endif +} + +} // namespace rtc diff --git a/webrtc/base/sha1.h b/webrtc/base/sha1.h new file mode 100644 index 000000000..4862a0049 --- /dev/null +++ b/webrtc/base/sha1.h @@ -0,0 +1,32 @@ +/* + * SHA-1 in C + * By Steve Reid + * 100% Public Domain + * +*/ + +// Ported to C++, Google style, under namespace rtc and uses basictypes.h + +#ifndef WEBRTC_BASE_SHA1_H_ +#define WEBRTC_BASE_SHA1_H_ + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +struct SHA1_CTX { + uint32 state[5]; + // TODO: Change bit count to uint64. + uint32 count[2]; // Bit count of input. + uint8 buffer[64]; +}; + +#define SHA1_DIGEST_SIZE 20 + +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const uint8* data, size_t len); +void SHA1Final(SHA1_CTX* context, uint8 digest[SHA1_DIGEST_SIZE]); + +#endif // WEBRTC_BASE_SHA1_H_ + +} // namespace rtc diff --git a/webrtc/base/sha1digest.h b/webrtc/base/sha1digest.h new file mode 100644 index 000000000..fb4c53e6e --- /dev/null +++ b/webrtc/base/sha1digest.h @@ -0,0 +1,47 @@ +/* + * Copyright 2012 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_BASE_SHA1DIGEST_H_ +#define WEBRTC_BASE_SHA1DIGEST_H_ + +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/sha1.h" + +namespace rtc { + +// A simple wrapper for our SHA-1 implementation. +class Sha1Digest : public MessageDigest { + public: + enum { kSize = SHA1_DIGEST_SIZE }; + Sha1Digest() { + SHA1Init(&ctx_); + } + virtual size_t Size() const { + return kSize; + } + virtual void Update(const void* buf, size_t len) { + SHA1Update(&ctx_, static_cast(buf), len); + } + virtual size_t Finish(void* buf, size_t len) { + if (len < kSize) { + return 0; + } + SHA1Final(&ctx_, static_cast(buf)); + SHA1Init(&ctx_); // Reset for next use. + return kSize; + } + + private: + SHA1_CTX ctx_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SHA1DIGEST_H_ diff --git a/webrtc/base/sha1digest_unittest.cc b/webrtc/base/sha1digest_unittest.cc new file mode 100644 index 000000000..d3c204387 --- /dev/null +++ b/webrtc/base/sha1digest_unittest.cc @@ -0,0 +1,82 @@ +/* + * Copyright 2012 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 "webrtc/base/sha1digest.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +std::string Sha1(const std::string& input) { + Sha1Digest sha1; + return ComputeDigest(&sha1, input); +} + +TEST(Sha1DigestTest, TestSize) { + Sha1Digest sha1; + EXPECT_EQ(20, static_cast(Sha1Digest::kSize)); + EXPECT_EQ(20U, sha1.Size()); +} + +TEST(Sha1DigestTest, TestBasic) { + // Test vectors from sha1.c. + EXPECT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709", Sha1("")); + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", Sha1("abc")); + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + Sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")); + std::string a_million_as(1000000, 'a'); + EXPECT_EQ("34aa973cd4c4daa4f61eeb2bdbad27316534016f", Sha1(a_million_as)); +} + +TEST(Sha1DigestTest, TestMultipleUpdates) { + Sha1Digest sha1; + std::string input = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + char output[Sha1Digest::kSize]; + for (size_t i = 0; i < input.size(); ++i) { + sha1.Update(&input[i], 1); + } + EXPECT_EQ(sha1.Size(), sha1.Finish(output, sizeof(output))); + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + hex_encode(output, sizeof(output))); +} + +TEST(Sha1DigestTest, TestReuse) { + Sha1Digest sha1; + std::string input = "abc"; + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", + ComputeDigest(&sha1, input)); + input = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", + ComputeDigest(&sha1, input)); +} + +TEST(Sha1DigestTest, TestBufferTooSmall) { + Sha1Digest sha1; + std::string input = "abcdefghijklmnopqrstuvwxyz"; + char output[Sha1Digest::kSize - 1]; + sha1.Update(input.c_str(), input.size()); + EXPECT_EQ(0U, sha1.Finish(output, sizeof(output))); +} + +TEST(Sha1DigestTest, TestBufferConst) { + Sha1Digest sha1; + const int kLongSize = 1000000; + std::string input(kLongSize, '\0'); + for (int i = 0; i < kLongSize; ++i) { + input[i] = static_cast(i); + } + sha1.Update(input.c_str(), input.size()); + for (int i = 0; i < kLongSize; ++i) { + EXPECT_EQ(static_cast(i), input[i]); + } +} + +} // namespace rtc diff --git a/webrtc/base/sharedexclusivelock.cc b/webrtc/base/sharedexclusivelock.cc new file mode 100644 index 000000000..9facf60ea --- /dev/null +++ b/webrtc/base/sharedexclusivelock.cc @@ -0,0 +1,44 @@ +/* + * Copyright 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 "webrtc/base/sharedexclusivelock.h" + +namespace rtc { + +SharedExclusiveLock::SharedExclusiveLock() + : shared_count_is_zero_(true, true), + shared_count_(0) { +} + +void SharedExclusiveLock::LockExclusive() { + cs_exclusive_.Enter(); + shared_count_is_zero_.Wait(rtc::kForever); +} + +void SharedExclusiveLock::UnlockExclusive() { + cs_exclusive_.Leave(); +} + +void SharedExclusiveLock::LockShared() { + CritScope exclusive_scope(&cs_exclusive_); + CritScope shared_scope(&cs_shared_); + if (++shared_count_ == 1) { + shared_count_is_zero_.Reset(); + } +} + +void SharedExclusiveLock::UnlockShared() { + CritScope shared_scope(&cs_shared_); + if (--shared_count_ == 0) { + shared_count_is_zero_.Set(); + } +} + +} // namespace rtc diff --git a/webrtc/base/sharedexclusivelock.h b/webrtc/base/sharedexclusivelock.h new file mode 100644 index 000000000..f64d7cf50 --- /dev/null +++ b/webrtc/base/sharedexclusivelock.h @@ -0,0 +1,76 @@ +/* + * Copyright 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_BASE_SHAREDEXCLUSIVELOCK_H_ +#define WEBRTC_BASE_SHAREDEXCLUSIVELOCK_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/event.h" + +namespace rtc { + +// This class provides shared-exclusive lock. It can be used in cases like +// multiple-readers/single-writer model. +class SharedExclusiveLock { + public: + SharedExclusiveLock(); + + // Locking/unlocking methods. It is encouraged to use SharedScope or + // ExclusiveScope for protection. + void LockExclusive(); + void UnlockExclusive(); + void LockShared(); + void UnlockShared(); + + private: + rtc::CriticalSection cs_exclusive_; + rtc::CriticalSection cs_shared_; + rtc::Event shared_count_is_zero_; + int shared_count_; + + DISALLOW_COPY_AND_ASSIGN(SharedExclusiveLock); +}; + +class SharedScope { + public: + explicit SharedScope(SharedExclusiveLock* lock) : lock_(lock) { + lock_->LockShared(); + } + + ~SharedScope() { + lock_->UnlockShared(); + } + + private: + SharedExclusiveLock* lock_; + + DISALLOW_COPY_AND_ASSIGN(SharedScope); +}; + +class ExclusiveScope { + public: + explicit ExclusiveScope(SharedExclusiveLock* lock) : lock_(lock) { + lock_->LockExclusive(); + } + + ~ExclusiveScope() { + lock_->UnlockExclusive(); + } + + private: + SharedExclusiveLock* lock_; + + DISALLOW_COPY_AND_ASSIGN(ExclusiveScope); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SHAREDEXCLUSIVELOCK_H_ diff --git a/webrtc/base/sharedexclusivelock_unittest.cc b/webrtc/base/sharedexclusivelock_unittest.cc new file mode 100644 index 000000000..42334af75 --- /dev/null +++ b/webrtc/base/sharedexclusivelock_unittest.cc @@ -0,0 +1,218 @@ +/* + * Copyright 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 "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sharedexclusivelock.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +static const uint32 kMsgRead = 0; +static const uint32 kMsgWrite = 0; +static const int kNoWaitThresholdInMs = 10; +static const int kWaitThresholdInMs = 80; +static const int kProcessTimeInMs = 100; +static const int kProcessTimeoutInMs = 5000; + +class SharedExclusiveTask : public MessageHandler { + public: + SharedExclusiveTask(SharedExclusiveLock* shared_exclusive_lock, + int* value, + bool* done) + : shared_exclusive_lock_(shared_exclusive_lock), + waiting_time_in_ms_(0), + value_(value), + done_(done) { + worker_thread_.reset(new Thread()); + worker_thread_->Start(); + } + + int waiting_time_in_ms() const { return waiting_time_in_ms_; } + + protected: + scoped_ptr worker_thread_; + SharedExclusiveLock* shared_exclusive_lock_; + int waiting_time_in_ms_; + int* value_; + bool* done_; +}; + +class ReadTask : public SharedExclusiveTask { + public: + ReadTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done) + : SharedExclusiveTask(shared_exclusive_lock, value, done) { + } + + void PostRead(int* value) { + worker_thread_->Post(this, kMsgRead, new TypedMessageData(value)); + } + + private: + virtual void OnMessage(Message* message) { + ASSERT(rtc::Thread::Current() == worker_thread_.get()); + ASSERT(message != NULL); + ASSERT(message->message_id == kMsgRead); + + TypedMessageData* message_data = + static_cast*>(message->pdata); + + uint32 start_time = Time(); + { + SharedScope ss(shared_exclusive_lock_); + waiting_time_in_ms_ = TimeDiff(Time(), start_time); + + Thread::SleepMs(kProcessTimeInMs); + *message_data->data() = *value_; + *done_ = true; + } + delete message->pdata; + message->pdata = NULL; + } +}; + +class WriteTask : public SharedExclusiveTask { + public: + WriteTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done) + : SharedExclusiveTask(shared_exclusive_lock, value, done) { + } + + void PostWrite(int value) { + worker_thread_->Post(this, kMsgWrite, new TypedMessageData(value)); + } + + private: + virtual void OnMessage(Message* message) { + ASSERT(rtc::Thread::Current() == worker_thread_.get()); + ASSERT(message != NULL); + ASSERT(message->message_id == kMsgWrite); + + TypedMessageData* message_data = + static_cast*>(message->pdata); + + uint32 start_time = Time(); + { + ExclusiveScope es(shared_exclusive_lock_); + waiting_time_in_ms_ = TimeDiff(Time(), start_time); + + Thread::SleepMs(kProcessTimeInMs); + *value_ = message_data->data(); + *done_ = true; + } + delete message->pdata; + message->pdata = NULL; + } +}; + +// Unit test for SharedExclusiveLock. +class SharedExclusiveLockTest + : public testing::Test { + public: + SharedExclusiveLockTest() : value_(0) { + } + + virtual void SetUp() { + shared_exclusive_lock_.reset(new SharedExclusiveLock()); + } + + protected: + scoped_ptr shared_exclusive_lock_; + int value_; +}; + +// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3318 +TEST_F(SharedExclusiveLockTest, DISABLED_TestSharedShared) { + int value0, value1; + bool done0, done1; + ReadTask reader0(shared_exclusive_lock_.get(), &value_, &done0); + ReadTask reader1(shared_exclusive_lock_.get(), &value_, &done1); + + // Test shared locks can be shared without waiting. + { + SharedScope ss(shared_exclusive_lock_.get()); + value_ = 1; + done0 = false; + done1 = false; + reader0.PostRead(&value0); + reader1.PostRead(&value1); + Thread::SleepMs(kProcessTimeInMs); + } + + EXPECT_TRUE_WAIT(done0, kProcessTimeoutInMs); + EXPECT_EQ(1, value0); + EXPECT_LE(reader0.waiting_time_in_ms(), kNoWaitThresholdInMs); + EXPECT_TRUE_WAIT(done1, kProcessTimeoutInMs); + EXPECT_EQ(1, value1); + EXPECT_LE(reader1.waiting_time_in_ms(), kNoWaitThresholdInMs); +} + +TEST_F(SharedExclusiveLockTest, TestSharedExclusive) { + bool done; + WriteTask writer(shared_exclusive_lock_.get(), &value_, &done); + + // Test exclusive lock needs to wait for shared lock. + { + SharedScope ss(shared_exclusive_lock_.get()); + value_ = 1; + done = false; + writer.PostWrite(2); + Thread::SleepMs(kProcessTimeInMs); + EXPECT_EQ(1, value_); + } + + EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs); + EXPECT_EQ(2, value_); + EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs); +} + +TEST_F(SharedExclusiveLockTest, TestExclusiveShared) { + int value; + bool done; + ReadTask reader(shared_exclusive_lock_.get(), &value_, &done); + + // Test shared lock needs to wait for exclusive lock. + { + ExclusiveScope es(shared_exclusive_lock_.get()); + value_ = 1; + done = false; + reader.PostRead(&value); + Thread::SleepMs(kProcessTimeInMs); + value_ = 2; + } + + EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs); + EXPECT_EQ(2, value); + EXPECT_GE(reader.waiting_time_in_ms(), kWaitThresholdInMs); +} + +TEST_F(SharedExclusiveLockTest, TestExclusiveExclusive) { + bool done; + WriteTask writer(shared_exclusive_lock_.get(), &value_, &done); + + // Test exclusive lock needs to wait for exclusive lock. + { + ExclusiveScope es(shared_exclusive_lock_.get()); + value_ = 1; + done = false; + writer.PostWrite(2); + Thread::SleepMs(kProcessTimeInMs); + EXPECT_EQ(1, value_); + } + + EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs); + EXPECT_EQ(2, value_); + EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs); +} + +} // namespace rtc diff --git a/webrtc/base/signalthread.cc b/webrtc/base/signalthread.cc new file mode 100644 index 000000000..f95cb5fbc --- /dev/null +++ b/webrtc/base/signalthread.cc @@ -0,0 +1,149 @@ +/* + * Copyright 2004 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 "webrtc/base/signalthread.h" + +#include "webrtc/base/common.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// SignalThread +/////////////////////////////////////////////////////////////////////////////// + +SignalThread::SignalThread() + : main_(Thread::Current()), + worker_(this), + state_(kInit), + refcount_(1) { + main_->SignalQueueDestroyed.connect(this, + &SignalThread::OnMainThreadDestroyed); + worker_.SetName("SignalThread", this); +} + +SignalThread::~SignalThread() { + ASSERT(refcount_ == 0); +} + +bool SignalThread::SetName(const std::string& name, const void* obj) { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + ASSERT(kInit == state_); + return worker_.SetName(name, obj); +} + +bool SignalThread::SetPriority(ThreadPriority priority) { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + ASSERT(kInit == state_); + return worker_.SetPriority(priority); +} + +void SignalThread::Start() { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + if (kInit == state_ || kComplete == state_) { + state_ = kRunning; + OnWorkStart(); + worker_.Start(); + } else { + ASSERT(false); + } +} + +void SignalThread::Destroy(bool wait) { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + if ((kInit == state_) || (kComplete == state_)) { + refcount_--; + } else if (kRunning == state_ || kReleasing == state_) { + state_ = kStopping; + // OnWorkStop() must follow Quit(), so that when the thread wakes up due to + // OWS(), ContinueWork() will return false. + worker_.Quit(); + OnWorkStop(); + if (wait) { + // Release the thread's lock so that it can return from ::Run. + cs_.Leave(); + worker_.Stop(); + cs_.Enter(); + refcount_--; + } + } else { + ASSERT(false); + } +} + +void SignalThread::Release() { + EnterExit ee(this); + ASSERT(main_->IsCurrent()); + if (kComplete == state_) { + refcount_--; + } else if (kRunning == state_) { + state_ = kReleasing; + } else { + // if (kInit == state_) use Destroy() + ASSERT(false); + } +} + +bool SignalThread::ContinueWork() { + EnterExit ee(this); + ASSERT(worker_.IsCurrent()); + return worker_.ProcessMessages(0); +} + +void SignalThread::OnMessage(Message *msg) { + EnterExit ee(this); + if (ST_MSG_WORKER_DONE == msg->message_id) { + ASSERT(main_->IsCurrent()); + OnWorkDone(); + bool do_delete = false; + if (kRunning == state_) { + state_ = kComplete; + } else { + do_delete = true; + } + if (kStopping != state_) { + // Before signaling that the work is done, make sure that the worker + // thread actually is done. We got here because DoWork() finished and + // Run() posted the ST_MSG_WORKER_DONE message. This means the worker + // thread is about to go away anyway, but sometimes it doesn't actually + // finish before SignalWorkDone is processed, and for a reusable + // SignalThread this makes an assert in thread.cc fire. + // + // Calling Stop() on the worker ensures that the OS thread that underlies + // the worker will finish, and will be set to NULL, enabling us to call + // Start() again. + worker_.Stop(); + SignalWorkDone(this); + } + if (do_delete) { + refcount_--; + } + } +} + +void SignalThread::Run() { + DoWork(); + { + EnterExit ee(this); + if (main_) { + main_->Post(this, ST_MSG_WORKER_DONE); + } + } +} + +void SignalThread::OnMainThreadDestroyed() { + EnterExit ee(this); + main_ = NULL; +} + +} // namespace rtc diff --git a/webrtc/base/signalthread.h b/webrtc/base/signalthread.h new file mode 100644 index 000000000..a97bda1af --- /dev/null +++ b/webrtc/base/signalthread.h @@ -0,0 +1,156 @@ +/* + * Copyright 2004 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_BASE_SIGNALTHREAD_H_ +#define WEBRTC_BASE_SIGNALTHREAD_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// SignalThread - Base class for worker threads. The main thread should call +// Start() to begin work, and then follow one of these models: +// Normal: Wait for SignalWorkDone, and then call Release to destroy. +// Cancellation: Call Release(true), to abort the worker thread. +// Fire-and-forget: Call Release(false), which allows the thread to run to +// completion, and then self-destruct without further notification. +// Periodic tasks: Wait for SignalWorkDone, then eventually call Start() +// again to repeat the task. When the instance isn't needed anymore, +// call Release. DoWork, OnWorkStart and OnWorkStop are called again, +// on a new thread. +// The subclass should override DoWork() to perform the background task. By +// periodically calling ContinueWork(), it can check for cancellation. +// OnWorkStart and OnWorkDone can be overridden to do pre- or post-work +// tasks in the context of the main thread. +/////////////////////////////////////////////////////////////////////////////// + +class SignalThread + : public sigslot::has_slots<>, + protected MessageHandler { + public: + SignalThread(); + + // Context: Main Thread. Call before Start to change the worker's name. + bool SetName(const std::string& name, const void* obj); + + // Context: Main Thread. Call before Start to change the worker's priority. + bool SetPriority(ThreadPriority priority); + + // Context: Main Thread. Call to begin the worker thread. + void Start(); + + // Context: Main Thread. If the worker thread is not running, deletes the + // object immediately. Otherwise, asks the worker thread to abort processing, + // and schedules the object to be deleted once the worker exits. + // SignalWorkDone will not be signalled. If wait is true, does not return + // until the thread is deleted. + void Destroy(bool wait); + + // Context: Main Thread. If the worker thread is complete, deletes the + // object immediately. Otherwise, schedules the object to be deleted once + // the worker thread completes. SignalWorkDone will be signalled. + void Release(); + + // Context: Main Thread. Signalled when work is complete. + sigslot::signal1 SignalWorkDone; + + enum { ST_MSG_WORKER_DONE, ST_MSG_FIRST_AVAILABLE }; + + protected: + virtual ~SignalThread(); + + Thread* worker() { return &worker_; } + + // Context: Main Thread. Subclass should override to do pre-work setup. + virtual void OnWorkStart() { } + + // Context: Worker Thread. Subclass should override to do work. + virtual void DoWork() = 0; + + // Context: Worker Thread. Subclass should call periodically to + // dispatch messages and determine if the thread should terminate. + bool ContinueWork(); + + // Context: Worker Thread. Subclass should override when extra work is + // needed to abort the worker thread. + virtual void OnWorkStop() { } + + // Context: Main Thread. Subclass should override to do post-work cleanup. + virtual void OnWorkDone() { } + + // Context: Any Thread. If subclass overrides, be sure to call the base + // implementation. Do not use (message_id < ST_MSG_FIRST_AVAILABLE) + virtual void OnMessage(Message *msg); + + private: + enum State { + kInit, // Initialized, but not started + kRunning, // Started and doing work + kReleasing, // Same as running, but to be deleted when work is done + kComplete, // Work is done + kStopping, // Work is being interrupted + }; + + class Worker : public Thread { + public: + explicit Worker(SignalThread* parent) : parent_(parent) {} + virtual ~Worker() { Stop(); } + virtual void Run() { parent_->Run(); } + + private: + SignalThread* parent_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Worker); + }; + + class EnterExit { + public: + explicit EnterExit(SignalThread* t) : t_(t) { + t_->cs_.Enter(); + // If refcount_ is zero then the object has already been deleted and we + // will be double-deleting it in ~EnterExit()! (shouldn't happen) + ASSERT(t_->refcount_ != 0); + ++t_->refcount_; + } + ~EnterExit() { + bool d = (0 == --t_->refcount_); + t_->cs_.Leave(); + if (d) + delete t_; + } + + private: + SignalThread* t_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(EnterExit); + }; + + void Run(); + void OnMainThreadDestroyed(); + + Thread* main_; + Worker worker_; + CriticalSection cs_; + State state_; + int refcount_; + + DISALLOW_COPY_AND_ASSIGN(SignalThread); +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SIGNALTHREAD_H_ diff --git a/webrtc/base/signalthread_unittest.cc b/webrtc/base/signalthread_unittest.cc new file mode 100644 index 000000000..e0ea54eb3 --- /dev/null +++ b/webrtc/base/signalthread_unittest.cc @@ -0,0 +1,198 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/signalthread.h" +#include "webrtc/base/thread.h" + +using namespace rtc; + +class SignalThreadTest : public testing::Test, public sigslot::has_slots<> { + public: + class SlowSignalThread : public SignalThread { + public: + SlowSignalThread(SignalThreadTest* harness) : harness_(harness) { + } + + virtual ~SlowSignalThread() { + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + ++harness_->thread_deleted_; + } + + const SignalThreadTest* harness() { return harness_; } + + protected: + virtual void OnWorkStart() { + ASSERT_TRUE(harness_ != NULL); + ++harness_->thread_started_; + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + EXPECT_FALSE(worker()->RunningForTest()); // not started yet + } + + virtual void OnWorkStop() { + ++harness_->thread_stopped_; + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + EXPECT_TRUE(worker()->RunningForTest()); // not stopped yet + } + + virtual void OnWorkDone() { + ++harness_->thread_done_; + EXPECT_EQ(harness_->main_thread_, Thread::Current()); + EXPECT_TRUE(worker()->RunningForTest()); // not stopped yet + } + + virtual void DoWork() { + EXPECT_NE(harness_->main_thread_, Thread::Current()); + EXPECT_EQ(worker(), Thread::Current()); + Thread::Current()->socketserver()->Wait(250, false); + } + + private: + SignalThreadTest* harness_; + DISALLOW_EVIL_CONSTRUCTORS(SlowSignalThread); + }; + + void OnWorkComplete(rtc::SignalThread* thread) { + SlowSignalThread* t = static_cast(thread); + EXPECT_EQ(t->harness(), this); + EXPECT_EQ(main_thread_, Thread::Current()); + + ++thread_completed_; + if (!called_release_) { + thread->Release(); + } + } + + virtual void SetUp() { + main_thread_ = Thread::Current(); + thread_ = new SlowSignalThread(this); + thread_->SignalWorkDone.connect(this, &SignalThreadTest::OnWorkComplete); + called_release_ = false; + thread_started_ = 0; + thread_done_ = 0; + thread_completed_ = 0; + thread_stopped_ = 0; + thread_deleted_ = 0; + } + + virtual void TearDown() { + } + + Thread* main_thread_; + SlowSignalThread* thread_; + bool called_release_; + + int thread_started_; + int thread_done_; + int thread_completed_; + int thread_stopped_; + int thread_deleted_; +}; + +class OwnerThread : public Thread, public sigslot::has_slots<> { + public: + explicit OwnerThread(SignalThreadTest* harness) + : harness_(harness), + has_run_(false) { + } + + virtual ~OwnerThread() { + Stop(); + } + + virtual void Run() { + SignalThreadTest::SlowSignalThread* signal_thread = + new SignalThreadTest::SlowSignalThread(harness_); + signal_thread->SignalWorkDone.connect(this, &OwnerThread::OnWorkDone); + signal_thread->Start(); + Thread::Current()->socketserver()->Wait(100, false); + signal_thread->Release(); + // Delete |signal_thread|. + signal_thread->Destroy(true); + has_run_ = true; + } + + bool has_run() { return has_run_; } + void OnWorkDone(SignalThread* signal_thread) { + FAIL() << " This shouldn't get called."; + } + + private: + SignalThreadTest* harness_; + bool has_run_; + DISALLOW_EVIL_CONSTRUCTORS(OwnerThread); +}; + +// Test for when the main thread goes away while the +// signal thread is still working. This may happen +// when shutting down the process. +TEST_F(SignalThreadTest, OwnerThreadGoesAway) { + { + scoped_ptr owner(new OwnerThread(this)); + main_thread_ = owner.get(); + owner->Start(); + while (!owner->has_run()) { + Thread::Current()->socketserver()->Wait(10, false); + } + } + // At this point the main thread has gone away. + // Give the SignalThread a little time to do its callback, + // which will crash if the signal thread doesn't handle + // this situation well. + Thread::Current()->socketserver()->Wait(500, false); +} + +#define EXPECT_STATE(started, done, completed, stopped, deleted) \ + EXPECT_EQ(started, thread_started_); \ + EXPECT_EQ(done, thread_done_); \ + EXPECT_EQ(completed, thread_completed_); \ + EXPECT_EQ(stopped, thread_stopped_); \ + EXPECT_EQ(deleted, thread_deleted_); + +TEST_F(SignalThreadTest, ThreadFinishes) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::SleepMs(500); + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 1, 1, 0, 1); +} + +TEST_F(SignalThreadTest, ReleasedThreadFinishes) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + thread_->Release(); + called_release_ = true; + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::SleepMs(500); + EXPECT_STATE(1, 0, 0, 0, 0); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 1, 1, 0, 1); +} + +TEST_F(SignalThreadTest, DestroyedThreadCleansUp) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + thread_->Destroy(true); + EXPECT_STATE(1, 0, 0, 1, 1); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 0, 0, 1, 1); +} + +TEST_F(SignalThreadTest, DeferredDestroyedThreadCleansUp) { + thread_->Start(); + EXPECT_STATE(1, 0, 0, 0, 0); + thread_->Destroy(false); + EXPECT_STATE(1, 0, 0, 1, 0); + Thread::SleepMs(500); + EXPECT_STATE(1, 0, 0, 1, 0); + Thread::Current()->ProcessMessages(0); + EXPECT_STATE(1, 1, 0, 1, 1); +} diff --git a/webrtc/base/sigslot.h b/webrtc/base/sigslot.h new file mode 100644 index 000000000..990d2efb7 --- /dev/null +++ b/webrtc/base/sigslot.h @@ -0,0 +1,2850 @@ +// sigslot.h: Signal/Slot classes +// +// Written by Sarah Thompson (sarah@telergy.com) 2002. +// +// License: Public domain. You are free to use this code however you like, with the proviso that +// the author takes on no responsibility or liability for any use. +// +// QUICK DOCUMENTATION +// +// (see also the full documentation at http://sigslot.sourceforge.net/) +// +// #define switches +// SIGSLOT_PURE_ISO - Define this to force ISO C++ compliance. This also disables +// all of the thread safety support on platforms where it is +// available. +// +// SIGSLOT_USE_POSIX_THREADS - Force use of Posix threads when using a C++ compiler other than +// gcc on a platform that supports Posix threads. (When using gcc, +// this is the default - use SIGSLOT_PURE_ISO to disable this if +// necessary) +// +// SIGSLOT_DEFAULT_MT_POLICY - Where thread support is enabled, this defaults to multi_threaded_global. +// Otherwise, the default is single_threaded. #define this yourself to +// override the default. In pure ISO mode, anything other than +// single_threaded will cause a compiler error. +// +// PLATFORM NOTES +// +// Win32 - On Win32, the WEBRTC_WIN symbol must be #defined. Most mainstream +// compilers do this by default, but you may need to define it +// yourself if your build environment is less standard. This causes +// the Win32 thread support to be compiled in and used automatically. +// +// Unix/Linux/BSD, etc. - If you're using gcc, it is assumed that you have Posix threads +// available, so they are used automatically. You can override this +// (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using +// something other than gcc but still want to use Posix threads, you +// need to #define SIGSLOT_USE_POSIX_THREADS. +// +// ISO C++ - If none of the supported platforms are detected, or if +// SIGSLOT_PURE_ISO is defined, all multithreading support is turned off, +// along with any code that might cause a pure ISO C++ environment to +// complain. Before you ask, gcc -ansi -pedantic won't compile this +// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of +// errors that aren't really there. If you feel like investigating this, +// please contact the author. +// +// +// THREADING MODES +// +// single_threaded - Your program is assumed to be single threaded from the point of view +// of signal/slot usage (i.e. all objects using signals and slots are +// created and destroyed from a single thread). Behaviour if objects are +// destroyed concurrently is undefined (i.e. you'll get the occasional +// segmentation fault/memory exception). +// +// multi_threaded_global - Your program is assumed to be multi threaded. Objects using signals and +// slots can be safely created and destroyed from any thread, even when +// connections exist. In multi_threaded_global mode, this is achieved by a +// single global mutex (actually a critical section on Windows because they +// are faster). This option uses less OS resources, but results in more +// opportunities for contention, possibly resulting in more context switches +// than are strictly necessary. +// +// multi_threaded_local - Behaviour in this mode is essentially the same as multi_threaded_global, +// except that each signal, and each object that inherits has_slots, all +// have their own mutex/critical section. In practice, this means that +// mutex collisions (and hence context switches) only happen if they are +// absolutely essential. However, on some platforms, creating a lot of +// mutexes can slow down the whole OS, so use this option with care. +// +// USING THE LIBRARY +// +// See the full documentation at http://sigslot.sourceforge.net/ +// +// +// Libjingle specific: +// This file has been modified such that has_slots and signalx do not have to be +// using the same threading requirements. E.g. it is possible to connect a +// has_slots and signal0 or +// has_slots and signal0. +// If has_slots is single threaded the user must ensure that it is not trying +// to connect or disconnect to signalx concurrently or data race may occur. +// If signalx is single threaded the user must ensure that disconnect, connect +// or signal is not happening concurrently or data race may occur. + +#ifndef WEBRTC_BASE_SIGSLOT_H__ +#define WEBRTC_BASE_SIGSLOT_H__ + +#include +#include +#include + +// On our copy of sigslot.h, we set single threading as default. +#define SIGSLOT_DEFAULT_MT_POLICY single_threaded + +#if defined(SIGSLOT_PURE_ISO) || (!defined(WEBRTC_WIN) && !defined(__GNUG__) && !defined(SIGSLOT_USE_POSIX_THREADS)) +# define _SIGSLOT_SINGLE_THREADED +#elif defined(WEBRTC_WIN) +# define _SIGSLOT_HAS_WIN32_THREADS +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# include "webrtc/base/win32.h" +#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS) +# define _SIGSLOT_HAS_POSIX_THREADS +# include +#else +# define _SIGSLOT_SINGLE_THREADED +#endif + +#ifndef SIGSLOT_DEFAULT_MT_POLICY +# ifdef _SIGSLOT_SINGLE_THREADED +# define SIGSLOT_DEFAULT_MT_POLICY single_threaded +# else +# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local +# endif +#endif + +// TODO: change this namespace to rtc? +namespace sigslot { + + class single_threaded + { + public: + single_threaded() + { + ; + } + + virtual ~single_threaded() + { + ; + } + + virtual void lock() + { + ; + } + + virtual void unlock() + { + ; + } + }; + +#ifdef _SIGSLOT_HAS_WIN32_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + static bool isinitialised = false; + + if(!isinitialised) + { + InitializeCriticalSection(get_critsec()); + isinitialised = true; + } + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + EnterCriticalSection(get_critsec()); + } + + virtual void unlock() + { + LeaveCriticalSection(get_critsec()); + } + + private: + CRITICAL_SECTION* get_critsec() + { + static CRITICAL_SECTION g_critsec; + return &g_critsec; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + InitializeCriticalSection(&m_critsec); + } + + multi_threaded_local(const multi_threaded_local&) + { + InitializeCriticalSection(&m_critsec); + } + + virtual ~multi_threaded_local() + { + DeleteCriticalSection(&m_critsec); + } + + virtual void lock() + { + EnterCriticalSection(&m_critsec); + } + + virtual void unlock() + { + LeaveCriticalSection(&m_critsec); + } + + private: + CRITICAL_SECTION m_critsec; + }; +#endif // _SIGSLOT_HAS_WIN32_THREADS + +#ifdef _SIGSLOT_HAS_POSIX_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + pthread_mutex_init(get_mutex(), NULL); + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + pthread_mutex_lock(get_mutex()); + } + + virtual void unlock() + { + pthread_mutex_unlock(get_mutex()); + } + + private: + pthread_mutex_t* get_mutex() + { + static pthread_mutex_t g_mutex; + return &g_mutex; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + pthread_mutex_init(&m_mutex, NULL); + } + + multi_threaded_local(const multi_threaded_local&) + { + pthread_mutex_init(&m_mutex, NULL); + } + + virtual ~multi_threaded_local() + { + pthread_mutex_destroy(&m_mutex); + } + + virtual void lock() + { + pthread_mutex_lock(&m_mutex); + } + + virtual void unlock() + { + pthread_mutex_unlock(&m_mutex); + } + + private: + pthread_mutex_t m_mutex; + }; +#endif // _SIGSLOT_HAS_POSIX_THREADS + + template + class lock_block + { + public: + mt_policy *m_mutex; + + lock_block(mt_policy *mtx) + : m_mutex(mtx) + { + m_mutex->lock(); + } + + ~lock_block() + { + m_mutex->unlock(); + } + }; + + class has_slots_interface; + + template + class _connection_base0 + { + public: + virtual ~_connection_base0() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit() = 0; + virtual _connection_base0* clone() = 0; + virtual _connection_base0* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base1 + { + public: + virtual ~_connection_base1() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type) = 0; + virtual _connection_base1* clone() = 0; + virtual _connection_base1* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base2 + { + public: + virtual ~_connection_base2() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type) = 0; + virtual _connection_base2* clone() = 0; + virtual _connection_base2* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base3 + { + public: + virtual ~_connection_base3() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type) = 0; + virtual _connection_base3* clone() = 0; + virtual _connection_base3* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base4 + { + public: + virtual ~_connection_base4() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0; + virtual _connection_base4* clone() = 0; + virtual _connection_base4* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base5 + { + public: + virtual ~_connection_base5() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type) = 0; + virtual _connection_base5* clone() = 0; + virtual _connection_base5* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base6 + { + public: + virtual ~_connection_base6() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type) = 0; + virtual _connection_base6* clone() = 0; + virtual _connection_base6* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base7 + { + public: + virtual ~_connection_base7() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type) = 0; + virtual _connection_base7* clone() = 0; + virtual _connection_base7* duplicate(has_slots_interface* pnewdest) = 0; + }; + + template + class _connection_base8 + { + public: + virtual ~_connection_base8() {} + virtual has_slots_interface* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type, arg8_type) = 0; + virtual _connection_base8* clone() = 0; + virtual _connection_base8* duplicate(has_slots_interface* pnewdest) = 0; + }; + + class _signal_base_interface + { + public: + virtual void slot_disconnect(has_slots_interface* pslot) = 0; + virtual void slot_duplicate(const has_slots_interface* poldslot, has_slots_interface* pnewslot) = 0; + }; + + template + class _signal_base : public _signal_base_interface, public mt_policy + { + }; + + class has_slots_interface + { + public: + has_slots_interface() + { + ; + } + + virtual void signal_connect(_signal_base_interface* sender) = 0; + + virtual void signal_disconnect(_signal_base_interface* sender) = 0; + + virtual ~has_slots_interface() + { + } + + virtual void disconnect_all() = 0; + }; + + template + class has_slots : public has_slots_interface, public mt_policy + { + private: + typedef std::set<_signal_base_interface*> sender_set; + typedef sender_set::const_iterator const_iterator; + + public: + has_slots() + { + ; + } + + has_slots(const has_slots& hs) + { + lock_block lock(this); + const_iterator it = hs.m_senders.begin(); + const_iterator itEnd = hs.m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_duplicate(&hs, this); + m_senders.insert(*it); + ++it; + } + } + + void signal_connect(_signal_base_interface* sender) + { + lock_block lock(this); + m_senders.insert(sender); + } + + void signal_disconnect(_signal_base_interface* sender) + { + lock_block lock(this); + m_senders.erase(sender); + } + + virtual ~has_slots() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_senders.begin(); + const_iterator itEnd = m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_disconnect(this); + ++it; + } + + m_senders.erase(m_senders.begin(), m_senders.end()); + } + + private: + sender_set m_senders; + }; + + template + class _signal_base0 : public _signal_base + { + public: + typedef std::list<_connection_base0 *> connections_list; + + _signal_base0() + { + ; + } + + _signal_base0(const _signal_base0& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + ~_signal_base0() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base1 : public _signal_base + { + public: + typedef std::list<_connection_base1 *> connections_list; + + _signal_base1() + { + ; + } + + _signal_base1(const _signal_base1& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base1() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base2 : public _signal_base + { + public: + typedef std::list<_connection_base2 *> + connections_list; + + _signal_base2() + { + ; + } + + _signal_base2(const _signal_base2& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base2() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base3 : public _signal_base + { + public: + typedef std::list<_connection_base3 *> + connections_list; + + _signal_base3() + { + ; + } + + _signal_base3(const _signal_base3& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base3() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base4 : public _signal_base + { + public: + typedef std::list<_connection_base4 *> connections_list; + + _signal_base4() + { + ; + } + + _signal_base4(const _signal_base4& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base4() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base5 : public _signal_base + { + public: + typedef std::list<_connection_base5 *> connections_list; + + _signal_base5() + { + ; + } + + _signal_base5(const _signal_base5& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base5() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base6 : public _signal_base + { + public: + typedef std::list<_connection_base6 *> connections_list; + + _signal_base6() + { + ; + } + + _signal_base6(const _signal_base6& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base6() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base7 : public _signal_base + { + public: + typedef std::list<_connection_base7 *> connections_list; + + _signal_base7() + { + ; + } + + _signal_base7(const _signal_base7& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base7() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base8 : public _signal_base + { + public: + typedef std::list<_connection_base8 *> + connections_list; + + _signal_base8() + { + ; + } + + _signal_base8(const _signal_base8& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base8() + { + disconnect_all(); + } + + bool is_empty() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + return it == itEnd; + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots_interface* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots_interface* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + delete *it; + m_connected_slots.erase(it); + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + + template + class _connection0 : public _connection_base0 + { + public: + _connection0() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection0(dest_type* pobject, void (dest_type::*pmemfun)()) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection0() + { + } + + virtual _connection_base0* clone() + { + return new _connection0(*this); + } + + virtual _connection_base0* duplicate(has_slots_interface* pnewdest) + { + return new _connection0((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit() + { + (m_pobject->*m_pmemfun)(); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(); + }; + + template + class _connection1 : public _connection_base1 + { + public: + _connection1() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection1() + { + } + + virtual _connection_base1* clone() + { + return new _connection1(*this); + } + + virtual _connection_base1* duplicate(has_slots_interface* pnewdest) + { + return new _connection1((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1) + { + (m_pobject->*m_pmemfun)(a1); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type); + }; + + template + class _connection2 : public _connection_base2 + { + public: + _connection2() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection2() + { + } + + virtual _connection_base2* clone() + { + return new _connection2(*this); + } + + virtual _connection_base2* duplicate(has_slots_interface* pnewdest) + { + return new _connection2((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2) + { + (m_pobject->*m_pmemfun)(a1, a2); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type); + }; + + template + class _connection3 : public _connection_base3 + { + public: + _connection3() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection3() + { + } + + virtual _connection_base3* clone() + { + return new _connection3(*this); + } + + virtual _connection_base3* duplicate(has_slots_interface* pnewdest) + { + return new _connection3((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + (m_pobject->*m_pmemfun)(a1, a2, a3); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type); + }; + + template + class _connection4 : public _connection_base4 + { + public: + _connection4() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection4() + { + } + + virtual _connection_base4* clone() + { + return new _connection4(*this); + } + + virtual _connection_base4* duplicate(has_slots_interface* pnewdest) + { + return new _connection4((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, + arg4_type a4) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, + arg4_type); + }; + + template + class _connection5 : public _connection_base5 + { + public: + _connection5() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection5() + { + } + + virtual _connection_base5* clone() + { + return new _connection5(*this); + } + + virtual _connection_base5* duplicate(has_slots_interface* pnewdest) + { + return new _connection5((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type); + }; + + template + class _connection6 : public _connection_base6 + { + public: + _connection6() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection6() + { + } + + virtual _connection_base6* clone() + { + return new _connection6(*this); + } + + virtual _connection_base6* duplicate(has_slots_interface* pnewdest) + { + return new _connection6((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type); + }; + + template + class _connection7 : public _connection_base7 + { + public: + _connection7() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection7() + { + } + + virtual _connection_base7* clone() + { + return new _connection7(*this); + } + + virtual _connection_base7* duplicate(has_slots_interface* pnewdest) + { + return new _connection7((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type); + }; + + template + class _connection8 : public _connection_base8 + { + public: + _connection8() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual ~_connection8() + { + } + + virtual _connection_base8* clone() + { + return new _connection8(*this); + } + + virtual _connection_base8* duplicate(has_slots_interface* pnewdest) + { + return new _connection8((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + virtual has_slots_interface* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type, arg8_type); + }; + + template + class signal0 : public _signal_base0 + { + public: + typedef _signal_base0 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal0() + { + ; + } + + signal0(const signal0& s) + : _signal_base0(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)()) + { + lock_block lock(this); + _connection0* conn = + new _connection0(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + + void operator()() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + }; + + template + class signal1 : public _signal_base1 + { + public: + typedef _signal_base1 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal1() + { + ; + } + + signal1(const signal1& s) + : _signal_base1(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type)) + { + lock_block lock(this); + _connection1* conn = + new _connection1(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + + void operator()(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + }; + + template + class signal2 : public _signal_base2 + { + public: + typedef _signal_base2 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal2() + { + ; + } + + signal2(const signal2& s) + : _signal_base2(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type)) + { + lock_block lock(this); + _connection2* conn = new + _connection2(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + }; + + template + class signal3 : public _signal_base3 + { + public: + typedef _signal_base3 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal3() + { + ; + } + + signal3(const signal3& s) + : _signal_base3(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + lock_block lock(this); + _connection3* conn = + new _connection3(pclass, + pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + }; + + template + class signal4 : public _signal_base4 + { + public: + typedef _signal_base4 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal4() + { + ; + } + + signal4(const signal4& s) + : _signal_base4(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + lock_block lock(this); + _connection4* + conn = new _connection4(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + }; + + template + class signal5 : public _signal_base5 + { + public: + typedef _signal_base5 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal5() + { + ; + } + + signal5(const signal5& s) + : _signal_base5(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + lock_block lock(this); + _connection5* conn = new _connection5(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + }; + + + template + class signal6 : public _signal_base6 + { + public: + typedef _signal_base6 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal6() + { + ; + } + + signal6(const signal6& s) + : _signal_base6(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + lock_block lock(this); + _connection6* conn = + new _connection6(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + }; + + template + class signal7 : public _signal_base7 + { + public: + typedef _signal_base7 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal7() + { + ; + } + + signal7(const signal7& s) + : _signal_base7(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type)) + { + lock_block lock(this); + _connection7* conn = + new _connection7(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + }; + + template + class signal8 : public _signal_base8 + { + public: + typedef _signal_base8 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal8() + { + ; + } + + signal8(const signal8& s) + : _signal_base8(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + lock_block lock(this); + _connection8* conn = + new _connection8(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + }; + +}; // namespace sigslot + +#endif // WEBRTC_BASE_SIGSLOT_H__ diff --git a/webrtc/base/sigslot_unittest.cc b/webrtc/base/sigslot_unittest.cc new file mode 100644 index 000000000..4d3041d13 --- /dev/null +++ b/webrtc/base/sigslot_unittest.cc @@ -0,0 +1,250 @@ +/* + * Copyright 2012 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 "webrtc/base/sigslot.h" + +#include "webrtc/base/gunit.h" + +// This function, when passed a has_slots or signalx, will break the build if +// its threading requirement is not single threaded +static bool TemplateIsST(const sigslot::single_threaded* p) { + return true; +} +// This function, when passed a has_slots or signalx, will break the build if +// its threading requirement is not multi threaded +static bool TemplateIsMT(const sigslot::multi_threaded_local* p) { + return true; +} + +class SigslotDefault : public testing::Test, public sigslot::has_slots<> { + protected: + sigslot::signal0<> signal_; +}; + +template +class SigslotReceiver : public sigslot::has_slots { + public: + SigslotReceiver() : signal_(NULL), signal_count_(0) { + } + ~SigslotReceiver() { + } + + void Connect(sigslot::signal0* signal) { + if (!signal) return; + Disconnect(); + signal_ = signal; + signal->connect(this, + &SigslotReceiver::OnSignal); + } + void Disconnect() { + if (!signal_) return; + signal_->disconnect(this); + signal_ = NULL; + } + void OnSignal() { + ++signal_count_; + } + int signal_count() { return signal_count_; } + + private: + sigslot::signal0* signal_; + int signal_count_; +}; + +template +class SigslotSlotTest : public testing::Test { + protected: + SigslotSlotTest() { + mt_signal_policy mt_policy; + TemplateIsMT(&mt_policy); + } + + virtual void SetUp() { + Connect(); + } + virtual void TearDown() { + Disconnect(); + } + + void Disconnect() { + st_receiver_.Disconnect(); + mt_receiver_.Disconnect(); + } + + void Connect() { + st_receiver_.Connect(&SignalSTLoopback); + mt_receiver_.Connect(&SignalMTLoopback); + } + + int st_loop_back_count() { return st_receiver_.signal_count(); } + int mt_loop_back_count() { return mt_receiver_.signal_count(); } + + sigslot::signal0<> SignalSTLoopback; + SigslotReceiver st_receiver_; + sigslot::signal0 SignalMTLoopback; + SigslotReceiver mt_receiver_; +}; + +typedef SigslotSlotTest<> SigslotSTSlotTest; +typedef SigslotSlotTest SigslotMTSlotTest; + +class multi_threaded_local_fake : public sigslot::multi_threaded_local { + public: + multi_threaded_local_fake() : lock_count_(0), unlock_count_(0) { + } + + virtual void lock() { + ++lock_count_; + } + virtual void unlock() { + ++unlock_count_; + } + + int lock_count() { return lock_count_; } + + bool InCriticalSection() { return lock_count_ != unlock_count_; } + + protected: + int lock_count_; + int unlock_count_; +}; + +typedef SigslotSlotTest SigslotMTLockBase; + +class SigslotMTLockTest : public SigslotMTLockBase { + protected: + SigslotMTLockTest() {} + + virtual void SetUp() { + EXPECT_EQ(0, SlotLockCount()); + SigslotMTLockBase::SetUp(); + // Connects to two signals (ST and MT). However, + // SlotLockCount() only gets the count for the + // MT signal (there are two separate SigslotReceiver which + // keep track of their own count). + EXPECT_EQ(1, SlotLockCount()); + } + virtual void TearDown() { + const int previous_lock_count = SlotLockCount(); + SigslotMTLockBase::TearDown(); + // Disconnects from two signals. Note analogous to SetUp(). + EXPECT_EQ(previous_lock_count + 1, SlotLockCount()); + } + + int SlotLockCount() { return mt_receiver_.lock_count(); } + void Signal() { SignalMTLoopback(); } + int SignalLockCount() { return SignalMTLoopback.lock_count(); } + int signal_count() { return mt_loop_back_count(); } + bool InCriticalSection() { return SignalMTLoopback.InCriticalSection(); } +}; + +// This test will always succeed. However, if the default template instantiation +// changes from single threaded to multi threaded it will break the build here. +TEST_F(SigslotDefault, DefaultIsST) { + EXPECT_TRUE(TemplateIsST(this)); + EXPECT_TRUE(TemplateIsST(&signal_)); +} + +// ST slot, ST signal +TEST_F(SigslotSTSlotTest, STLoopbackTest) { + SignalSTLoopback(); + EXPECT_EQ(1, st_loop_back_count()); + EXPECT_EQ(0, mt_loop_back_count()); +} + +// ST slot, MT signal +TEST_F(SigslotSTSlotTest, MTLoopbackTest) { + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(0, st_loop_back_count()); +} + +// ST slot, both ST and MT (separate) signal +TEST_F(SigslotSTSlotTest, AllLoopbackTest) { + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(1, st_loop_back_count()); +} + +TEST_F(SigslotSTSlotTest, Reconnect) { + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(1, st_loop_back_count()); + Disconnect(); + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(1, st_loop_back_count()); + Connect(); + SignalSTLoopback(); + SignalMTLoopback(); + EXPECT_EQ(2, mt_loop_back_count()); + EXPECT_EQ(2, st_loop_back_count()); +} + +// MT slot, ST signal +TEST_F(SigslotMTSlotTest, STLoopbackTest) { + SignalSTLoopback(); + EXPECT_EQ(1, st_loop_back_count()); + EXPECT_EQ(0, mt_loop_back_count()); +} + +// MT slot, MT signal +TEST_F(SigslotMTSlotTest, MTLoopbackTest) { + SignalMTLoopback(); + EXPECT_EQ(1, mt_loop_back_count()); + EXPECT_EQ(0, st_loop_back_count()); +} + +// MT slot, both ST and MT (separate) signal +TEST_F(SigslotMTSlotTest, AllLoopbackTest) { + SignalMTLoopback(); + SignalSTLoopback(); + EXPECT_EQ(1, st_loop_back_count()); + EXPECT_EQ(1, mt_loop_back_count()); +} + +// Test that locks are acquired and released correctly. +TEST_F(SigslotMTLockTest, LockSanity) { + const int lock_count = SignalLockCount(); + Signal(); + EXPECT_FALSE(InCriticalSection()); + EXPECT_EQ(lock_count + 1, SignalLockCount()); + EXPECT_EQ(1, signal_count()); +} + +// Destroy signal and slot in different orders. +TEST(DestructionOrder, SignalFirst) { + sigslot::signal0<>* signal = new sigslot::signal0<>; + SigslotReceiver<>* receiver = new SigslotReceiver<>(); + receiver->Connect(signal); + (*signal)(); + EXPECT_EQ(1, receiver->signal_count()); + delete signal; + delete receiver; +} + +TEST(DestructionOrder, SlotFirst) { + sigslot::signal0<>* signal = new sigslot::signal0<>; + SigslotReceiver<>* receiver = new SigslotReceiver<>(); + receiver->Connect(signal); + (*signal)(); + EXPECT_EQ(1, receiver->signal_count()); + + delete receiver; + (*signal)(); + delete signal; +} diff --git a/webrtc/base/sigslotrepeater.h b/webrtc/base/sigslotrepeater.h new file mode 100644 index 000000000..d1c891e02 --- /dev/null +++ b/webrtc/base/sigslotrepeater.h @@ -0,0 +1,94 @@ +/* + * Copyright 2006 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_BASE_SIGSLOTREPEATER_H__ +#define WEBRTC_BASE_SIGSLOTREPEATER_H__ + +// repeaters are both signals and slots, which are designed as intermediate +// pass-throughs for signals and slots which don't know about each other (for +// modularity or encapsulation). This eliminates the need to declare a signal +// handler whose sole purpose is to fire another signal. The repeater connects +// to the originating signal using the 'repeat' method. When the repeated +// signal fires, the repeater will also fire. + +#include "webrtc/base/sigslot.h" + +namespace sigslot { + + template + class repeater0 : public signal0, + public has_slots + { + public: + typedef signal0 base_type; + typedef repeater0 this_type; + + repeater0() { } + repeater0(const this_type& s) : base_type(s) { } + + void reemit() { signal0::emit(); } + void repeat(base_type &s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + + template + class repeater1 : public signal1, + public has_slots + { + public: + typedef signal1 base_type; + typedef repeater1 this_type; + + repeater1() { } + repeater1(const this_type& s) : base_type(s) { } + + void reemit(arg1_type a1) { signal1::emit(a1); } + void repeat(base_type& s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + + template + class repeater2 : public signal2, + public has_slots + { + public: + typedef signal2 base_type; + typedef repeater2 this_type; + + repeater2() { } + repeater2(const this_type& s) : base_type(s) { } + + void reemit(arg1_type a1, arg2_type a2) { signal2::emit(a1,a2); } + void repeat(base_type& s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + + template + class repeater3 : public signal3, + public has_slots + { + public: + typedef signal3 base_type; + typedef repeater3 this_type; + + repeater3() { } + repeater3(const this_type& s) : base_type(s) { } + + void reemit(arg1_type a1, arg2_type a2, arg3_type a3) { + signal3::emit(a1,a2,a3); + } + void repeat(base_type& s) { s.connect(this, &this_type::reemit); } + void stop(base_type &s) { s.disconnect(this); } + }; + +} // namespace sigslot + +#endif // WEBRTC_BASE_SIGSLOTREPEATER_H__ diff --git a/webrtc/base/sigslottester.h b/webrtc/base/sigslottester.h new file mode 100755 index 000000000..ae781a97e --- /dev/null +++ b/webrtc/base/sigslottester.h @@ -0,0 +1,199 @@ +// This file was GENERATED by command: +// pump.py sigslottester.h.pump +// DO NOT EDIT BY HAND!!! + +/* + * Copyright 2014 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_BASE_SIGSLOTTESTER_H_ +#define WEBRTC_BASE_SIGSLOTTESTER_H_ + +// To generate sigslottester.h from sigslottester.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py sigslottester.h.pump + + +// SigslotTester(s) are utility classes to check if signals owned by an +// object are being invoked at the right time and with the right arguments. +// They are meant to be used in tests. Tests must provide "capture" pointers +// (i.e. address of variables) where the arguments from the signal callback +// can be stored. +// +// Example: +// /* Some signal */ +// sigslot::signal1 foo; +// +// /* We want to monitor foo in some test. Note how signal argument is +// const std::string&, but capture-type is std::string. Capture type +// must be type that can be assigned to. */ +// std::string capture; +// SigslotTester1 slot(&foo, &capture); +// foo.emit("hello"); +// EXPECT_EQ(1, slot.callback_count()); +// EXPECT_EQ("hello", capture); +// /* See unit-tests for more examples */ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +// For all the templates below: +// - A1-A5 is the type of the argument i in the callback. Signals may and often +// do use const-references here for efficiency. +// - C1-C5 is the type of the variable to capture argument i. These should be +// non-const value types suitable for use as lvalues. + +template +class SigslotTester1 : public sigslot::has_slots<> { + public: + SigslotTester1(sigslot::signal1* signal, + C1* capture1) + : callback_count_(0), + capture1_(capture1) { + signal->connect(this, &SigslotTester1::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1) { + callback_count_++; + *capture1_ = arg1; + } + + int callback_count_; + C1* capture1_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester1); +}; + +template +class SigslotTester2 : public sigslot::has_slots<> { + public: + SigslotTester2(sigslot::signal2* signal, + C1* capture1, C2* capture2) + : callback_count_(0), + capture1_(capture1), capture2_(capture2) { + signal->connect(this, &SigslotTester2::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester2); +}; + +template +class SigslotTester3 : public sigslot::has_slots<> { + public: + SigslotTester3(sigslot::signal3* signal, + C1* capture1, C2* capture2, C3* capture3) + : callback_count_(0), + capture1_(capture1), capture2_(capture2), capture3_(capture3) { + signal->connect(this, &SigslotTester3::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + *capture3_ = arg3; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + C3* capture3_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester3); +}; + +template +class SigslotTester4 : public sigslot::has_slots<> { + public: + SigslotTester4(sigslot::signal4* signal, + C1* capture1, C2* capture2, C3* capture3, C4* capture4) + : callback_count_(0), + capture1_(capture1), capture2_(capture2), capture3_(capture3), + capture4_(capture4) { + signal->connect(this, &SigslotTester4::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + *capture3_ = arg3; + *capture4_ = arg4; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + C3* capture3_; + C4* capture4_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester4); +}; + +template +class SigslotTester5 : public sigslot::has_slots<> { + public: + SigslotTester5(sigslot::signal5* signal, + C1* capture1, C2* capture2, C3* capture3, C4* capture4, + C5* capture5) + : callback_count_(0), + capture1_(capture1), capture2_(capture2), capture3_(capture3), + capture4_(capture4), capture5_(capture5) { + signal->connect(this, &SigslotTester5::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) { + callback_count_++; + *capture1_ = arg1; + *capture2_ = arg2; + *capture3_ = arg3; + *capture4_ = arg4; + *capture5_ = arg5; + } + + int callback_count_; + C1* capture1_; + C2* capture2_; + C3* capture3_; + C4* capture4_; + C5* capture5_; + + DISALLOW_COPY_AND_ASSIGN(SigslotTester5); +}; +} // namespace rtc + +#endif // WEBRTC_BASE_SIGSLOTTESTER_H_ diff --git a/webrtc/base/sigslottester.h.pump b/webrtc/base/sigslottester.h.pump new file mode 100755 index 000000000..2fd9386a1 --- /dev/null +++ b/webrtc/base/sigslottester.h.pump @@ -0,0 +1,85 @@ +/* + * Copyright 2014 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_BASE_SIGSLOTTESTER_H_ +#define WEBRTC_BASE_SIGSLOTTESTER_H_ + +// To generate sigslottester.h from sigslottester.h.pump, execute: +// /home/build/google3/third_party/gtest/scripts/pump.py sigslottester.h.pump + + +// SigslotTester(s) are utility classes to check if signals owned by an +// object are being invoked at the right time and with the right arguments. +// They are meant to be used in tests. Tests must provide "capture" pointers +// (i.e. address of variables) where the arguments from the signal callback +// can be stored. +// +// Example: +// /* Some signal */ +// sigslot::signal1 foo; +// +// /* We want to monitor foo in some test. Note how signal argument is +// const std::string&, but capture-type is std::string. Capture type +// must be type that can be assigned to. */ +// std::string capture; +// SigslotTester1 slot(&foo, &capture); +// foo.emit("hello"); +// EXPECT_EQ(1, slot.callback_count()); +// EXPECT_EQ("hello", capture); +// /* See unit-tests for more examples */ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +// For all the templates below: +// - A1-A5 is the type of the argument i in the callback. Signals may and often +// do use const-references here for efficiency. +// - C1-C5 is the type of the variable to capture argument i. These should be +// non-const value types suitable for use as lvalues. + +$var n = 5 +$range i 1..n +$for i [[ +$range j 1..i + +template <$for j , [[class A$j]], $for j , [[class C$j]]> +class SigslotTester$i : public sigslot::has_slots<> { + public: + SigslotTester$i(sigslot::signal$i<$for j , [[A$j]]>* signal, + $for j , [[C$j* capture$j]]) + : callback_count_(0), + $for j , [[capture$j[[]]_(capture$j)]] { + signal->connect(this, &SigslotTester$i::OnSignalCallback); + } + + int callback_count() const { return callback_count_; } + + private: + void OnSignalCallback($for j , [[A$j arg$j]]) { + callback_count_++;$for j [[ + + *capture$j[[]]_ = arg$j;]] + + } + + int callback_count_;$for j [[ + + C$j* capture$j[[]]_;]] + + + DISALLOW_COPY_AND_ASSIGN(SigslotTester$i); +}; + +]] +} // namespace rtc + +#endif // WEBRTC_BASE_SIGSLOTTESTER_H_ diff --git a/webrtc/base/sigslottester_unittest.cc b/webrtc/base/sigslottester_unittest.cc new file mode 100755 index 000000000..778e352f4 --- /dev/null +++ b/webrtc/base/sigslottester_unittest.cc @@ -0,0 +1,86 @@ +/* + * Copyright 2014 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 "webrtc/base/sigslottester.h" + +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +TEST(SigslotTester, TestSignal1Arg) { + sigslot::signal1 source1; + int capture1; + SigslotTester1 slot1(&source1, &capture1); + EXPECT_EQ(0, slot1.callback_count()); + + source1.emit(10); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ(10, capture1); + + source1.emit(20); + EXPECT_EQ(2, slot1.callback_count()); + EXPECT_EQ(20, capture1); +} + +TEST(SigslotTester, TestSignal2Args) { + sigslot::signal2 source2; + int capture1; + char capture2; + SigslotTester2 slot2(&source2, &capture1, &capture2); + EXPECT_EQ(0, slot2.callback_count()); + + source2.emit(10, 'x'); + EXPECT_EQ(1, slot2.callback_count()); + EXPECT_EQ(10, capture1); + EXPECT_EQ('x', capture2); + + source2.emit(20, 'y'); + EXPECT_EQ(2, slot2.callback_count()); + EXPECT_EQ(20, capture1); + EXPECT_EQ('y', capture2); +} + +// Since it applies for 1 and 2 args, we assume it will work for up to 5 args. + +TEST(SigslotTester, TestSignalWithConstReferenceArgs) { + sigslot::signal1 source1; + std::string capture1; + SigslotTester1 slot1(&source1, &capture1); + EXPECT_EQ(0, slot1.callback_count()); + source1.emit("hello"); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ("hello", capture1); +} + +TEST(SigslotTester, TestSignalWithPointerToConstArgs) { + sigslot::signal1 source1; + const std::string* capture1; + SigslotTester1 slot1(&source1, + &capture1); + EXPECT_EQ(0, slot1.callback_count()); + source1.emit(NULL); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ(NULL, capture1); +} + +TEST(SigslotTester, TestSignalWithConstPointerArgs) { + sigslot::signal1 source1; + std::string* capture1; + SigslotTester1 slot1(&source1, &capture1); + EXPECT_EQ(0, slot1.callback_count()); + source1.emit(NULL); + EXPECT_EQ(1, slot1.callback_count()); + EXPECT_EQ(NULL, capture1); +} + +} // namespace rtc diff --git a/webrtc/base/socket.h b/webrtc/base/socket.h new file mode 100644 index 000000000..725bd45d1 --- /dev/null +++ b/webrtc/base/socket.h @@ -0,0 +1,188 @@ +/* + * Copyright 2004 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_BASE_SOCKET_H__ +#define WEBRTC_BASE_SOCKET_H__ + +#include + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#include +#define SOCKET_EACCES EACCES +#endif + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/socketaddress.h" + +// Rather than converting errors into a private namespace, +// Reuse the POSIX socket api errors. Note this depends on +// Win32 compatibility. + +#if defined(WEBRTC_WIN) +#undef EWOULDBLOCK // Remove errno.h's definition for each macro below. +#define EWOULDBLOCK WSAEWOULDBLOCK +#undef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#undef EALREADY +#define EALREADY WSAEALREADY +#undef ENOTSOCK +#define ENOTSOCK WSAENOTSOCK +#undef EDESTADDRREQ +#define EDESTADDRREQ WSAEDESTADDRREQ +#undef EMSGSIZE +#define EMSGSIZE WSAEMSGSIZE +#undef EPROTOTYPE +#define EPROTOTYPE WSAEPROTOTYPE +#undef ENOPROTOOPT +#define ENOPROTOOPT WSAENOPROTOOPT +#undef EPROTONOSUPPORT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#undef ESOCKTNOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#undef EOPNOTSUPP +#define EOPNOTSUPP WSAEOPNOTSUPP +#undef EPFNOSUPPORT +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#undef EAFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#undef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE +#undef EADDRNOTAVAIL +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#undef ENETDOWN +#define ENETDOWN WSAENETDOWN +#undef ENETUNREACH +#define ENETUNREACH WSAENETUNREACH +#undef ENETRESET +#define ENETRESET WSAENETRESET +#undef ECONNABORTED +#define ECONNABORTED WSAECONNABORTED +#undef ECONNRESET +#define ECONNRESET WSAECONNRESET +#undef ENOBUFS +#define ENOBUFS WSAENOBUFS +#undef EISCONN +#define EISCONN WSAEISCONN +#undef ENOTCONN +#define ENOTCONN WSAENOTCONN +#undef ESHUTDOWN +#define ESHUTDOWN WSAESHUTDOWN +#undef ETOOMANYREFS +#define ETOOMANYREFS WSAETOOMANYREFS +#undef ETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT +#undef ECONNREFUSED +#define ECONNREFUSED WSAECONNREFUSED +#undef ELOOP +#define ELOOP WSAELOOP +#undef ENAMETOOLONG +#define ENAMETOOLONG WSAENAMETOOLONG +#undef EHOSTDOWN +#define EHOSTDOWN WSAEHOSTDOWN +#undef EHOSTUNREACH +#define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENOTEMPTY +#define ENOTEMPTY WSAENOTEMPTY +#undef EPROCLIM +#define EPROCLIM WSAEPROCLIM +#undef EUSERS +#define EUSERS WSAEUSERS +#undef EDQUOT +#define EDQUOT WSAEDQUOT +#undef ESTALE +#define ESTALE WSAESTALE +#undef EREMOTE +#define EREMOTE WSAEREMOTE +#undef EACCES +#define SOCKET_EACCES WSAEACCES +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) +#define closesocket(s) close(s) +#endif // WEBRTC_POSIX + +namespace rtc { + +inline bool IsBlockingError(int e) { + return (e == EWOULDBLOCK) || (e == EAGAIN) || (e == EINPROGRESS); +} + +// General interface for the socket implementations of various networks. The +// methods match those of normal UNIX sockets very closely. +class Socket { + public: + virtual ~Socket() {} + + // Returns the address to which the socket is bound. If the socket is not + // bound, then the any-address is returned. + virtual SocketAddress GetLocalAddress() const = 0; + + // Returns the address to which the socket is connected. If the socket is + // not connected, then the any-address is returned. + virtual SocketAddress GetRemoteAddress() const = 0; + + virtual int Bind(const SocketAddress& addr) = 0; + virtual int Connect(const SocketAddress& addr) = 0; + virtual int Send(const void *pv, size_t cb) = 0; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0; + virtual int Recv(void *pv, size_t cb) = 0; + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) = 0; + virtual int Listen(int backlog) = 0; + virtual Socket *Accept(SocketAddress *paddr) = 0; + virtual int Close() = 0; + virtual int GetError() const = 0; + virtual void SetError(int error) = 0; + inline bool IsBlocking() const { return IsBlockingError(GetError()); } + + enum ConnState { + CS_CLOSED, + CS_CONNECTING, + CS_CONNECTED + }; + virtual ConnState GetState() const = 0; + + // Fills in the given uint16 with the current estimate of the MTU along the + // path to the address to which this socket is connected. NOTE: This method + // can block for up to 10 seconds on Windows. + virtual int EstimateMTU(uint16* mtu) = 0; + + enum Option { + OPT_DONTFRAGMENT, + OPT_RCVBUF, // receive buffer size + OPT_SNDBUF, // send buffer size + OPT_NODELAY, // whether Nagle algorithm is enabled + OPT_IPV6_V6ONLY, // Whether the socket is IPv6 only. + OPT_DSCP, // DSCP code + OPT_RTP_SENDTIME_EXTN_ID, // This is a non-traditional socket option param. + // This is specific to libjingle and will be used + // if SendTime option is needed at socket level. + }; + virtual int GetOption(Option opt, int* value) = 0; + virtual int SetOption(Option opt, int value) = 0; + + protected: + Socket() {} + + private: + DISALLOW_EVIL_CONSTRUCTORS(Socket); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKET_H__ diff --git a/webrtc/base/socket_unittest.cc b/webrtc/base/socket_unittest.cc new file mode 100644 index 000000000..6104eda4e --- /dev/null +++ b/webrtc/base/socket_unittest.cc @@ -0,0 +1,1012 @@ +/* + * Copyright 2007 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 "webrtc/base/socket_unittest.h" + +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +#define MAYBE_SKIP_IPV6 \ + if (!HasIPv6Enabled()) { \ + LOG(LS_INFO) << "No IPv6... skipping"; \ + return; \ + } + + +void SocketTest::TestConnectIPv4() { + ConnectInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectIPv6() { + MAYBE_SKIP_IPV6; + ConnectInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWithDnsLookupIPv4() { + ConnectWithDnsLookupInternal(kIPv4Loopback, "localhost"); +} + +void SocketTest::TestConnectWithDnsLookupIPv6() { + // TODO: Enable this when DNS resolution supports IPv6. + LOG(LS_INFO) << "Skipping IPv6 DNS test"; + // ConnectWithDnsLookupInternal(kIPv6Loopback, "localhost6"); +} + +void SocketTest::TestConnectFailIPv4() { + ConnectFailInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectFailIPv6() { + MAYBE_SKIP_IPV6; + ConnectFailInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWithDnsLookupFailIPv4() { + ConnectWithDnsLookupFailInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectWithDnsLookupFailIPv6() { + MAYBE_SKIP_IPV6; + ConnectWithDnsLookupFailInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWithClosedSocketIPv4() { + ConnectWithClosedSocketInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectWithClosedSocketIPv6() { + MAYBE_SKIP_IPV6; + ConnectWithClosedSocketInternal(kIPv6Loopback); +} + +void SocketTest::TestConnectWhileNotClosedIPv4() { + ConnectWhileNotClosedInternal(kIPv4Loopback); +} + +void SocketTest::TestConnectWhileNotClosedIPv6() { + MAYBE_SKIP_IPV6; + ConnectWhileNotClosedInternal(kIPv6Loopback); +} + +void SocketTest::TestServerCloseDuringConnectIPv4() { + ServerCloseDuringConnectInternal(kIPv4Loopback); +} + +void SocketTest::TestServerCloseDuringConnectIPv6() { + MAYBE_SKIP_IPV6; + ServerCloseDuringConnectInternal(kIPv6Loopback); +} + +void SocketTest::TestClientCloseDuringConnectIPv4() { + ClientCloseDuringConnectInternal(kIPv4Loopback); +} + +void SocketTest::TestClientCloseDuringConnectIPv6() { + MAYBE_SKIP_IPV6; + ClientCloseDuringConnectInternal(kIPv6Loopback); +} + +void SocketTest::TestServerCloseIPv4() { + ServerCloseInternal(kIPv4Loopback); +} + +void SocketTest::TestServerCloseIPv6() { + MAYBE_SKIP_IPV6; + ServerCloseInternal(kIPv6Loopback); +} + +void SocketTest::TestCloseInClosedCallbackIPv4() { + CloseInClosedCallbackInternal(kIPv4Loopback); +} + +void SocketTest::TestCloseInClosedCallbackIPv6() { + MAYBE_SKIP_IPV6; + CloseInClosedCallbackInternal(kIPv6Loopback); +} + +void SocketTest::TestSocketServerWaitIPv4() { + SocketServerWaitInternal(kIPv4Loopback); +} + +void SocketTest::TestSocketServerWaitIPv6() { + MAYBE_SKIP_IPV6; + SocketServerWaitInternal(kIPv6Loopback); +} + +void SocketTest::TestTcpIPv4() { + TcpInternal(kIPv4Loopback); +} + +void SocketTest::TestTcpIPv6() { + MAYBE_SKIP_IPV6; + TcpInternal(kIPv6Loopback); +} + +void SocketTest::TestSingleFlowControlCallbackIPv4() { + SingleFlowControlCallbackInternal(kIPv4Loopback); +} + +void SocketTest::TestSingleFlowControlCallbackIPv6() { + MAYBE_SKIP_IPV6; + SingleFlowControlCallbackInternal(kIPv6Loopback); +} + +void SocketTest::TestUdpIPv4() { + UdpInternal(kIPv4Loopback); +} + +void SocketTest::TestUdpIPv6() { + MAYBE_SKIP_IPV6; + UdpInternal(kIPv6Loopback); +} + +void SocketTest::TestUdpReadyToSendIPv4() { +#if !defined(WEBRTC_MAC) + // TODO(ronghuawu): Enable this test on mac/ios. + UdpReadyToSend(kIPv4Loopback); +#endif +} + +void SocketTest::TestUdpReadyToSendIPv6() { +#if defined(WEBRTC_WIN) + // TODO(ronghuawu): Enable this test (currently flakey) on mac and linux. + MAYBE_SKIP_IPV6; + UdpReadyToSend(kIPv6Loopback); +#endif +} + +void SocketTest::TestGetSetOptionsIPv4() { + GetSetOptionsInternal(kIPv4Loopback); +} + +void SocketTest::TestGetSetOptionsIPv6() { + MAYBE_SKIP_IPV6; + GetSetOptionsInternal(kIPv6Loopback); +} + +// For unbound sockets, GetLocalAddress / GetRemoteAddress return AF_UNSPEC +// values on Windows, but an empty address of the same family on Linux/MacOS X. +bool IsUnspecOrEmptyIP(const IPAddress& address) { +#if !defined(WEBRTC_WIN) + return IPIsAny(address); +#else + return address.family() == AF_UNSPEC; +#endif +} + +void SocketTest::ConnectInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client(ss_->CreateAsyncSocket(loopback.family(), + SOCK_STREAM)); + sink.Monitor(client.get()); + EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState()); + EXPECT_PRED1(IsUnspecOrEmptyIP, client->GetLocalAddress().ipaddr()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(AsyncSocket::CS_CONNECTING, server->GetState()); + + // Ensure no pending server connections, since we haven't done anything yet. + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_TRUE(accept_addr.IsNil()); + + // Attempt connect to listening socket. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + EXPECT_FALSE(client->GetLocalAddress().IsNil()); + EXPECT_NE(server->GetLocalAddress(), client->GetLocalAddress()); + + // Client is connecting, outcome not yet determined. + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + + // Server has pending connection, accept it. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + EXPECT_FALSE(accept_addr.IsNil()); + EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr); + + // Connected from server perspective, check the addresses are correct. + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + + // Connected from client perspective, check the addresses are correct. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); +} + +void SocketTest::ConnectWithDnsLookupInternal(const IPAddress& loopback, + const std::string& host) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connect to listening socket. + SocketAddress dns_addr(server->GetLocalAddress()); + dns_addr.SetIP(host); + EXPECT_EQ(0, client->Connect(dns_addr)); + // TODO: Bind when doing DNS lookup. + //EXPECT_NE(kEmptyAddr, client->GetLocalAddress()); // Implicit Bind + + // Client is connecting, outcome not yet determined. + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + + // Server has pending connection, accept it. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + EXPECT_FALSE(accept_addr.IsNil()); + EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr); + + // Connected from server perspective, check the addresses are correct. + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + + // Connected from client perspective, check the addresses are correct. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); +} + +void SocketTest::ConnectFailInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server, but don't listen yet. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + + // Attempt connect to a non-existent socket. + // We don't connect to the server socket created above, since on + // MacOS it takes about 75 seconds to get back an error! + SocketAddress bogus_addr(loopback, 65535); + EXPECT_EQ(0, client->Connect(bogus_addr)); + + // Wait for connection to fail (ECONNREFUSED). + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + EXPECT_TRUE(client->GetRemoteAddress().IsNil()); + + // Should be no pending server connections. + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_EQ(IPAddress(), accept_addr.ipaddr()); +} + +void SocketTest::ConnectWithDnsLookupFailInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server, but don't listen yet. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + + // Attempt connect to a non-existent host. + // We don't connect to the server socket created above, since on + // MacOS it takes about 75 seconds to get back an error! + SocketAddress bogus_dns_addr("not-a-real-hostname", 65535); + EXPECT_EQ(0, client->Connect(bogus_dns_addr)); + + // Wait for connection to fail (EHOSTNOTFOUND). + bool dns_lookup_finished = false; + WAIT_(client->GetState() == AsyncSocket::CS_CLOSED, kTimeout, + dns_lookup_finished); + if (!dns_lookup_finished) { + LOG(LS_WARNING) << "Skipping test; DNS resolution took longer than 5 " + << "seconds."; + return; + } + + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + EXPECT_TRUE(client->GetRemoteAddress().IsNil()); + // Should be no pending server connections. + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_TRUE(accept_addr.IsNil()); +} + +void SocketTest::ConnectWithClosedSocketInternal(const IPAddress& loopback) { + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Create a client and put in to CS_CLOSED state. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + EXPECT_EQ(0, client->Close()); + EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState()); + + // Connect() should reinitialize the socket, and put it in to CS_CONNECTING. + EXPECT_EQ(0, client->Connect(SocketAddress(server->GetLocalAddress()))); + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); +} + +void SocketTest::ConnectWhileNotClosedInternal(const IPAddress& loopback) { + // Create server and listen. + testing::StreamSink sink; + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + // Create client, connect. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + EXPECT_EQ(0, client->Connect(SocketAddress(server->GetLocalAddress()))); + EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState()); + // Try to connect again. Should fail, but not interfere with original attempt. + EXPECT_EQ(SOCKET_ERROR, + client->Connect(SocketAddress(server->GetLocalAddress()))); + + // Accept the original connection. + SocketAddress accept_addr; + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + EXPECT_FALSE(accept_addr.IsNil()); + + // Check the states and addresses. + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + + // Try to connect again, to an unresolved hostname. + // Shouldn't break anything. + EXPECT_EQ(SOCKET_ERROR, + client->Connect(SocketAddress("localhost", + server->GetLocalAddress().port()))); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState()); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); +} + +void SocketTest::ServerCloseDuringConnectInternal(const IPAddress& loopback) { + testing::StreamSink sink; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connect to listening socket. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Close down the server while the socket is in the accept queue. + EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout); + server->Close(); + + // This should fail the connection for the client. Clean up. + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + client->Close(); +} + +void SocketTest::ClientCloseDuringConnectInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connect to listening socket. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Close down the client while the socket is in the accept queue. + EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout); + client->Close(); + + // The connection should still be able to be accepted. + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + + // The accepted socket should then close (possibly with err, timing-related) + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, accepted->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(accepted.get(), testing::SSE_CLOSE) || + sink.Check(accepted.get(), testing::SSE_ERROR)); + + // The client should not get a close event. + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); +} + +void SocketTest::ServerCloseInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Send data to the client, and then close the connection. + EXPECT_EQ(1, accepted->Send("a", 1)); + accepted->Close(); + EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState()); + + // Expect that the client is notified, and has not yet closed. + EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState()); + + // Ensure the data can be read. + char buffer[10]; + EXPECT_EQ(1, client->Recv(buffer, sizeof(buffer))); + EXPECT_EQ('a', buffer[0]); + + // Now we should close, but the remote address will remain. + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_FALSE(client->GetRemoteAddress().IsAnyIP()); + + // The closer should not get a close signal. + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_CLOSE)); + EXPECT_TRUE(accepted->GetRemoteAddress().IsNil()); + + // And the closee should only get a single signal. + Thread::Current()->ProcessMessages(0); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + + // Close down the client and ensure all is good. + client->Close(); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_TRUE(client->GetRemoteAddress().IsNil()); +} + +class SocketCloser : public sigslot::has_slots<> { + public: + void OnClose(AsyncSocket* socket, int error) { + socket->Close(); // Deleting here would blow up the vector of handlers + // for the socket's signal. + } +}; + +void SocketTest::CloseInClosedCallbackInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketCloser closer; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + client->SignalCloseEvent.connect(&closer, &SocketCloser::OnClose); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Send data to the client, and then close the connection. + accepted->Close(); + EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState()); + + // Expect that the client is notified, and has not yet closed. + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState()); + + // Now we should be closed and invalidated + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_TRUE(Socket::CS_CLOSED == client->GetState()); +} + +class Sleeper : public MessageHandler { + public: + Sleeper() {} + void OnMessage(Message* msg) { + Thread::Current()->SleepMs(500); + } +}; + +void SocketTest::SocketServerWaitInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create & connect server and client sockets. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState()); + EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress()); + + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + + // Do an i/o operation, triggering an eventual callback. + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ)); + char buf[1024] = {0}; + + EXPECT_EQ(1024, client->Send(buf, 1024)); + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ)); + + // Shouldn't signal when blocked in a thread Send, where process_io is false. + scoped_ptr thread(new Thread()); + thread->Start(); + Sleeper sleeper; + TypedMessageData data(client.get()); + thread->Send(&sleeper, 0, &data); + EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ)); + + // But should signal when process_io is true. + EXPECT_TRUE_WAIT((sink.Check(accepted.get(), testing::SSE_READ)), kTimeout); + EXPECT_LT(0, accepted->Recv(buf, 1024)); +} + +void SocketTest::TcpInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create test data. + const size_t kDataSize = 1024 * 1024; + scoped_ptr send_buffer(new char[kDataSize]); + scoped_ptr recv_buffer(new char[kDataSize]); + size_t send_pos = 0, recv_pos = 0; + for (size_t i = 0; i < kDataSize; ++i) { + send_buffer[i] = static_cast(i % 256); + recv_buffer[i] = 0; + } + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Send and receive a bunch of data. + bool send_waiting_for_writability = false; + bool send_expect_success = true; + bool recv_waiting_for_readability = true; + bool recv_expect_success = false; + int data_in_flight = 0; + while (recv_pos < kDataSize) { + // Send as much as we can if we've been cleared to send. + while (!send_waiting_for_writability && send_pos < kDataSize) { + int tosend = static_cast(kDataSize - send_pos); + int sent = accepted->Send(send_buffer.get() + send_pos, tosend); + if (send_expect_success) { + // The first Send() after connecting or getting writability should + // succeed and send some data. + EXPECT_GT(sent, 0); + send_expect_success = false; + } + if (sent >= 0) { + EXPECT_LE(sent, tosend); + send_pos += sent; + data_in_flight += sent; + } else { + ASSERT_TRUE(accepted->IsBlocking()); + send_waiting_for_writability = true; + } + } + + // Read all the sent data. + while (data_in_flight > 0) { + if (recv_waiting_for_readability) { + // Wait until data is available. + EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout); + recv_waiting_for_readability = false; + recv_expect_success = true; + } + + // Receive as much as we can get in a single recv call. + int rcvd = client->Recv(recv_buffer.get() + recv_pos, + kDataSize - recv_pos); + + if (recv_expect_success) { + // The first Recv() after getting readability should succeed and receive + // some data. + // TODO: The following line is disabled due to flakey pulse + // builds. Re-enable if/when possible. + // EXPECT_GT(rcvd, 0); + recv_expect_success = false; + } + if (rcvd >= 0) { + EXPECT_LE(rcvd, data_in_flight); + recv_pos += rcvd; + data_in_flight -= rcvd; + } else { + ASSERT_TRUE(client->IsBlocking()); + recv_waiting_for_readability = true; + } + } + + // Once all that we've sent has been rcvd, expect to be able to send again. + if (send_waiting_for_writability) { + EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), + kTimeout); + send_waiting_for_writability = false; + send_expect_success = true; + } + } + + // The received data matches the sent data. + EXPECT_EQ(kDataSize, send_pos); + EXPECT_EQ(kDataSize, recv_pos); + EXPECT_EQ(0, memcmp(recv_buffer.get(), send_buffer.get(), kDataSize)); + + // Close down. + accepted->Close(); + EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE)); + client->Close(); +} + +void SocketTest::SingleFlowControlCallbackInternal(const IPAddress& loopback) { + testing::StreamSink sink; + SocketAddress accept_addr; + + // Create client. + scoped_ptr client( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(client.get()); + + // Create server and listen. + scoped_ptr server( + ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0))); + EXPECT_EQ(0, server->Listen(5)); + + // Attempt connection. + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Accept connection. + EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(accepted); + sink.Monitor(accepted.get()); + + // Both sides are now connected. + EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + // Expect a writable callback from the connect. + EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), kTimeout); + + // Fill the socket buffer. + char buf[1024 * 16] = {0}; + int sends = 0; + while (++sends && accepted->Send(&buf, ARRAY_SIZE(buf)) != -1) {} + EXPECT_TRUE(accepted->IsBlocking()); + + // Wait until data is available. + EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout); + + // Pull data. + for (int i = 0; i < sends; ++i) { + client->Recv(buf, ARRAY_SIZE(buf)); + } + + // Expect at least one additional writable callback. + EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), kTimeout); + + // Adding data in response to the writeable callback shouldn't cause infinite + // callbacks. + int extras = 0; + for (int i = 0; i < 100; ++i) { + accepted->Send(&buf, ARRAY_SIZE(buf)); + rtc::Thread::Current()->ProcessMessages(1); + if (sink.Check(accepted.get(), testing::SSE_WRITE)) { + extras++; + } + } + EXPECT_LT(extras, 2); + + // Close down. + accepted->Close(); + client->Close(); +} + +void SocketTest::UdpInternal(const IPAddress& loopback) { + SocketAddress empty = EmptySocketAddressWithFamily(loopback.family()); + // Test basic bind and connect behavior. + AsyncSocket* socket = + ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM); + EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState()); + EXPECT_EQ(0, socket->Bind(SocketAddress(loopback, 0))); + SocketAddress addr1 = socket->GetLocalAddress(); + EXPECT_EQ(0, socket->Connect(addr1)); + EXPECT_EQ(AsyncSocket::CS_CONNECTED, socket->GetState()); + socket->Close(); + EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState()); + delete socket; + + // Test send/receive behavior. + scoped_ptr client1( + new TestClient(AsyncUDPSocket::Create(ss_, addr1))); + scoped_ptr client2( + new TestClient(AsyncUDPSocket::Create(ss_, empty))); + + SocketAddress addr2; + EXPECT_EQ(3, client2->SendTo("foo", 3, addr1)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr2)); + + SocketAddress addr3; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr2)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr3)); + EXPECT_EQ(addr3, addr1); + // TODO: figure out what the intent is here + for (int i = 0; i < 10; ++i) { + client2.reset(new TestClient(AsyncUDPSocket::Create(ss_, empty))); + + SocketAddress addr4; + EXPECT_EQ(3, client2->SendTo("foo", 3, addr1)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr4)); + EXPECT_EQ(addr4.ipaddr(), addr2.ipaddr()); + + SocketAddress addr5; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr4)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr5)); + EXPECT_EQ(addr5, addr1); + + addr2 = addr4; + } +} + +void SocketTest::UdpReadyToSend(const IPAddress& loopback) { + SocketAddress empty = EmptySocketAddressWithFamily(loopback.family()); + // RFC 5737 - The blocks 192.0.2.0/24 (TEST-NET-1) ... are provided for use in + // documentation. + // RFC 3849 - 2001:DB8::/32 as a documentation-only prefix. + std::string dest = (loopback.family() == AF_INET6) ? + "2001:db8::1" : "192.0.2.0"; + SocketAddress test_addr(dest, 2345); + + // Test send + scoped_ptr client( + new TestClient(AsyncUDPSocket::Create(ss_, empty))); + int test_packet_size = 1200; + rtc::scoped_ptr test_packet(new char[test_packet_size]); + // Init the test packet just to avoid memcheck warning. + memset(test_packet.get(), 0, test_packet_size); + // Set the send buffer size to the same size as the test packet to have a + // better chance to get EWOULDBLOCK. + int send_buffer_size = test_packet_size; +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + send_buffer_size /= 2; +#endif + client->SetOption(rtc::Socket::OPT_SNDBUF, send_buffer_size); + + int error = 0; + uint32 start_ms = Time(); + int sent_packet_num = 0; + int expected_error = EWOULDBLOCK; + while (start_ms + kTimeout > Time()) { + int ret = client->SendTo(test_packet.get(), test_packet_size, test_addr); + ++sent_packet_num; + if (ret != test_packet_size) { + error = client->GetError(); + if (error == expected_error) { + LOG(LS_INFO) << "Got expected error code after sending " + << sent_packet_num << " packets."; + break; + } + } + } + EXPECT_EQ(expected_error, error); + EXPECT_FALSE(client->ready_to_send()); + EXPECT_TRUE_WAIT(client->ready_to_send(), kTimeout); + LOG(LS_INFO) << "Got SignalReadyToSend"; +} + +void SocketTest::GetSetOptionsInternal(const IPAddress& loopback) { + rtc::scoped_ptr socket( + ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM)); + socket->Bind(SocketAddress(loopback, 0)); + + // Check SNDBUF/RCVBUF. + const int desired_size = 12345; +#if defined(WEBRTC_LINUX) + // Yes, really. It's in the kernel source. + const int expected_size = desired_size * 2; +#else // !WEBRTC_LINUX + const int expected_size = desired_size; +#endif // !WEBRTC_LINUX + int recv_size = 0; + int send_size = 0; + // get the initial sizes + ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size)); + ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size)); + // set our desired sizes + ASSERT_NE(-1, socket->SetOption(Socket::OPT_RCVBUF, desired_size)); + ASSERT_NE(-1, socket->SetOption(Socket::OPT_SNDBUF, desired_size)); + // get the sizes again + ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size)); + ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size)); + // make sure they are right + ASSERT_EQ(expected_size, recv_size); + ASSERT_EQ(expected_size, send_size); + + // Check that we can't set NODELAY on a UDP socket. + int current_nd, desired_nd = 1; + ASSERT_EQ(-1, socket->GetOption(Socket::OPT_NODELAY, ¤t_nd)); + ASSERT_EQ(-1, socket->SetOption(Socket::OPT_NODELAY, desired_nd)); + + // Skip the esimate MTU test for IPv6 for now. + if (loopback.family() != AF_INET6) { + // Try estimating MTU. + rtc::scoped_ptr + mtu_socket( + ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM)); + mtu_socket->Bind(SocketAddress(loopback, 0)); + uint16 mtu; + // should fail until we connect + ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu)); + mtu_socket->Connect(SocketAddress(loopback, 0)); +#if defined(WEBRTC_WIN) + // now it should succeed + ASSERT_NE(-1, mtu_socket->EstimateMTU(&mtu)); + ASSERT_GE(mtu, 1492); // should be at least the 1492 "plateau" on localhost +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + // except on WEBRTC_MAC && !WEBRTC_IOS, where it's not yet implemented + ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu)); +#else + // and the behavior seems unpredictable on Linux, + // failing on the build machine + // but succeeding on my Ubiquity instance. +#endif + } +} + +} // namespace rtc diff --git a/webrtc/base/socket_unittest.h b/webrtc/base/socket_unittest.h new file mode 100644 index 000000000..d368afb3f --- /dev/null +++ b/webrtc/base/socket_unittest.h @@ -0,0 +1,88 @@ +/* + * Copyright 2009 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_BASE_SOCKET_UNITTEST_H_ +#define WEBRTC_BASE_SOCKET_UNITTEST_H_ + +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// Generic socket tests, to be used when testing individual socketservers. +// Derive your specific test class from SocketTest, install your +// socketserver, and call the SocketTest test methods. +class SocketTest : public testing::Test { + protected: + SocketTest() : ss_(NULL), kIPv4Loopback(INADDR_LOOPBACK), + kIPv6Loopback(in6addr_loopback) {} + virtual void SetUp() { ss_ = Thread::Current()->socketserver(); } + void TestConnectIPv4(); + void TestConnectIPv6(); + void TestConnectWithDnsLookupIPv4(); + void TestConnectWithDnsLookupIPv6(); + void TestConnectFailIPv4(); + void TestConnectFailIPv6(); + void TestConnectWithDnsLookupFailIPv4(); + void TestConnectWithDnsLookupFailIPv6(); + void TestConnectWithClosedSocketIPv4(); + void TestConnectWithClosedSocketIPv6(); + void TestConnectWhileNotClosedIPv4(); + void TestConnectWhileNotClosedIPv6(); + void TestServerCloseDuringConnectIPv4(); + void TestServerCloseDuringConnectIPv6(); + void TestClientCloseDuringConnectIPv4(); + void TestClientCloseDuringConnectIPv6(); + void TestServerCloseIPv4(); + void TestServerCloseIPv6(); + void TestCloseInClosedCallbackIPv4(); + void TestCloseInClosedCallbackIPv6(); + void TestSocketServerWaitIPv4(); + void TestSocketServerWaitIPv6(); + void TestTcpIPv4(); + void TestTcpIPv6(); + void TestSingleFlowControlCallbackIPv4(); + void TestSingleFlowControlCallbackIPv6(); + void TestUdpIPv4(); + void TestUdpIPv6(); + void TestUdpReadyToSendIPv4(); + void TestUdpReadyToSendIPv6(); + void TestGetSetOptionsIPv4(); + void TestGetSetOptionsIPv6(); + + private: + void ConnectInternal(const IPAddress& loopback); + void ConnectWithDnsLookupInternal(const IPAddress& loopback, + const std::string& host); + void ConnectFailInternal(const IPAddress& loopback); + + void ConnectWithDnsLookupFailInternal(const IPAddress& loopback); + void ConnectWithClosedSocketInternal(const IPAddress& loopback); + void ConnectWhileNotClosedInternal(const IPAddress& loopback); + void ServerCloseDuringConnectInternal(const IPAddress& loopback); + void ClientCloseDuringConnectInternal(const IPAddress& loopback); + void ServerCloseInternal(const IPAddress& loopback); + void CloseInClosedCallbackInternal(const IPAddress& loopback); + void SocketServerWaitInternal(const IPAddress& loopback); + void TcpInternal(const IPAddress& loopback); + void SingleFlowControlCallbackInternal(const IPAddress& loopback); + void UdpInternal(const IPAddress& loopback); + void UdpReadyToSend(const IPAddress& loopback); + void GetSetOptionsInternal(const IPAddress& loopback); + + static const int kTimeout = 5000; // ms + SocketServer* ss_; + const IPAddress kIPv4Loopback; + const IPAddress kIPv6Loopback; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKET_UNITTEST_H_ diff --git a/webrtc/base/socketadapters.cc b/webrtc/base/socketadapters.cc new file mode 100644 index 000000000..1cdd1bcbe --- /dev/null +++ b/webrtc/base/socketadapters.cc @@ -0,0 +1,893 @@ +/* + * Copyright 2004 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include +#include + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#define SECURITY_WIN32 +#include +#endif + +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/common.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/sec_buffer.h" +#endif // WEBRTC_WIN + +namespace rtc { + +BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t size) + : AsyncSocketAdapter(socket), buffer_size_(size), + data_len_(0), buffering_(false) { + buffer_ = new char[buffer_size_]; +} + +BufferedReadAdapter::~BufferedReadAdapter() { + delete [] buffer_; +} + +int BufferedReadAdapter::Send(const void *pv, size_t cb) { + if (buffering_) { + // TODO: Spoof error better; Signal Writeable + socket_->SetError(EWOULDBLOCK); + return -1; + } + return AsyncSocketAdapter::Send(pv, cb); +} + +int BufferedReadAdapter::Recv(void *pv, size_t cb) { + if (buffering_) { + socket_->SetError(EWOULDBLOCK); + return -1; + } + + size_t read = 0; + + if (data_len_) { + read = _min(cb, data_len_); + memcpy(pv, buffer_, read); + data_len_ -= read; + if (data_len_ > 0) { + memmove(buffer_, buffer_ + read, data_len_); + } + pv = static_cast(pv) + read; + cb -= read; + } + + // FIX: If cb == 0, we won't generate another read event + + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res < 0) + return res; + + return res + static_cast(read); +} + +void BufferedReadAdapter::BufferInput(bool on) { + buffering_ = on; +} + +void BufferedReadAdapter::OnReadEvent(AsyncSocket * socket) { + ASSERT(socket == socket_); + + if (!buffering_) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (data_len_ >= buffer_size_) { + LOG(INFO) << "Input buffer overflow"; + ASSERT(false); + data_len_ = 0; + } + + int len = socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + LOG_ERR(INFO) << "Recv"; + return; + } + + data_len_ += len; + + ProcessInput(buffer_, &data_len_); +} + +/////////////////////////////////////////////////////////////////////////////// + +// This is a SSL v2 CLIENT_HELLO message. +// TODO: Should this have a session id? The response doesn't have a +// certificate, so the hello should have a session id. +static const uint8 kSslClientHello[] = { + 0x80, 0x46, // msg len + 0x01, // CLIENT_HELLO + 0x03, 0x01, // SSL 3.1 + 0x00, 0x2d, // ciphersuite len + 0x00, 0x00, // session id len + 0x00, 0x10, // challenge len + 0x01, 0x00, 0x80, 0x03, 0x00, 0x80, 0x07, 0x00, 0xc0, // ciphersuites + 0x06, 0x00, 0x40, 0x02, 0x00, 0x80, 0x04, 0x00, 0x80, // + 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x0a, // + 0x00, 0xfe, 0xfe, 0x00, 0x00, 0x09, 0x00, 0x00, 0x64, // + 0x00, 0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, // + 0x1f, 0x17, 0x0c, 0xa6, 0x2f, 0x00, 0x78, 0xfc, // challenge + 0x46, 0x55, 0x2e, 0xb1, 0x83, 0x39, 0xf1, 0xea // +}; + +// This is a TLSv1 SERVER_HELLO message. +static const uint8 kSslServerHello[] = { + 0x16, // handshake message + 0x03, 0x01, // SSL 3.1 + 0x00, 0x4a, // message len + 0x02, // SERVER_HELLO + 0x00, 0x00, 0x46, // handshake len + 0x03, 0x01, // SSL 3.1 + 0x42, 0x85, 0x45, 0xa7, 0x27, 0xa9, 0x5d, 0xa0, // server random + 0xb3, 0xc5, 0xe7, 0x53, 0xda, 0x48, 0x2b, 0x3f, // + 0xc6, 0x5a, 0xca, 0x89, 0xc1, 0x58, 0x52, 0xa1, // + 0x78, 0x3c, 0x5b, 0x17, 0x46, 0x00, 0x85, 0x3f, // + 0x20, // session id len + 0x0e, 0xd3, 0x06, 0x72, 0x5b, 0x5b, 0x1b, 0x5f, // session id + 0x15, 0xac, 0x13, 0xf9, 0x88, 0x53, 0x9d, 0x9b, // + 0xe8, 0x3d, 0x7b, 0x0c, 0x30, 0x32, 0x6e, 0x38, // + 0x4d, 0xa2, 0x75, 0x57, 0x41, 0x6c, 0x34, 0x5c, // + 0x00, 0x04, // RSA/RC4-128/MD5 + 0x00 // null compression +}; + +AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket) + : BufferedReadAdapter(socket, 1024) { +} + +int AsyncSSLSocket::Connect(const SocketAddress& addr) { + // Begin buffering before we connect, so that there isn't a race condition + // between potential senders and receiving the OnConnectEvent signal + BufferInput(true); + return BufferedReadAdapter::Connect(addr); +} + +void AsyncSSLSocket::OnConnectEvent(AsyncSocket * socket) { + ASSERT(socket == socket_); + // TODO: we could buffer output too... + VERIFY(sizeof(kSslClientHello) == + DirectSend(kSslClientHello, sizeof(kSslClientHello))); +} + +void AsyncSSLSocket::ProcessInput(char* data, size_t* len) { + if (*len < sizeof(kSslServerHello)) + return; + + if (memcmp(kSslServerHello, data, sizeof(kSslServerHello)) != 0) { + Close(); + SignalCloseEvent(this, 0); // TODO: error code? + return; + } + + *len -= sizeof(kSslServerHello); + if (*len > 0) { + memmove(data, data + sizeof(kSslServerHello), *len); + } + + bool remainder = (*len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); +} + +AsyncSSLServerSocket::AsyncSSLServerSocket(AsyncSocket* socket) + : BufferedReadAdapter(socket, 1024) { + BufferInput(true); +} + +void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) { + // We only accept client hello messages. + if (*len < sizeof(kSslClientHello)) { + return; + } + + if (memcmp(kSslClientHello, data, sizeof(kSslClientHello)) != 0) { + Close(); + SignalCloseEvent(this, 0); + return; + } + + *len -= sizeof(kSslClientHello); + + // Clients should not send more data until the handshake is completed. + ASSERT(*len == 0); + + // Send a server hello back to the client. + DirectSend(kSslServerHello, sizeof(kSslServerHello)); + + // Handshake completed for us, redirect input to our parent. + BufferInput(false); +} + +/////////////////////////////////////////////////////////////////////////////// + +AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket, + const std::string& user_agent, + const SocketAddress& proxy, + const std::string& username, + const CryptString& password) + : BufferedReadAdapter(socket, 1024), proxy_(proxy), agent_(user_agent), + user_(username), pass_(password), force_connect_(false), state_(PS_ERROR), + context_(0) { +} + +AsyncHttpsProxySocket::~AsyncHttpsProxySocket() { + delete context_; +} + +int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) { + int ret; + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect(" + << proxy_.ToSensitiveString() << ")"; + dest_ = addr; + state_ = PS_INIT; + if (ShouldIssueConnect()) { + BufferInput(true); + } + ret = BufferedReadAdapter::Connect(proxy_); + // TODO: Set state_ appropriately if Connect fails. + return ret; +} + +SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncHttpsProxySocket::Close() { + headers_.clear(); + state_ = PS_ERROR; + dest_.Clear(); + delete context_; + context_ = NULL; + return BufferedReadAdapter::Close(); +} + +Socket::ConnState AsyncHttpsProxySocket::GetState() const { + if (state_ < PS_TUNNEL) { + return CS_CONNECTING; + } else if (state_ == PS_TUNNEL) { + return CS_CONNECTED; + } else { + return CS_CLOSED; + } +} + +void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent"; + if (!ShouldIssueConnect()) { + state_ = PS_TUNNEL; + BufferedReadAdapter::OnConnectEvent(socket); + return; + } + SendRequest(); +} + +void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")"; + if ((state_ == PS_WAIT_CLOSE) && (err == 0)) { + state_ = PS_ERROR; + Connect(dest_); + } else { + BufferedReadAdapter::OnCloseEvent(socket, err); + } +} + +void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) { + size_t start = 0; + for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) { + if (state_ == PS_SKIP_BODY) { + size_t consume = _min(*len - pos, content_length_); + pos += consume; + start = pos; + content_length_ -= consume; + if (content_length_ == 0) { + EndResponse(); + } + continue; + } + + if (data[pos++] != '\n') + continue; + + size_t len = pos - start - 1; + if ((len > 0) && (data[start + len - 1] == '\r')) + --len; + + data[start + len] = 0; + ProcessLine(data + start, len); + start = pos; + } + + *len -= start; + if (*len > 0) { + memmove(data, data + start, *len); + } + + if (state_ != PS_TUNNEL) + return; + + bool remainder = (*len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +bool AsyncHttpsProxySocket::ShouldIssueConnect() const { + // TODO: Think about whether a more sophisticated test + // than dest port == 80 is needed. + return force_connect_ || (dest_.port() != 80); +} + +void AsyncHttpsProxySocket::SendRequest() { + std::stringstream ss; + ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n"; + ss << "User-Agent: " << agent_ << "\r\n"; + ss << "Host: " << dest_.HostAsURIString() << "\r\n"; + ss << "Content-Length: 0\r\n"; + ss << "Proxy-Connection: Keep-Alive\r\n"; + ss << headers_; + ss << "\r\n"; + std::string str = ss.str(); + DirectSend(str.c_str(), str.size()); + state_ = PS_LEADER; + expect_close_ = true; + content_length_ = 0; + headers_.clear(); + + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str; +} + +void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data; + + if (len == 0) { + if (state_ == PS_TUNNEL_HEADERS) { + state_ = PS_TUNNEL; + } else if (state_ == PS_ERROR_HEADERS) { + Error(defer_error_); + return; + } else if (state_ == PS_SKIP_HEADERS) { + if (content_length_) { + state_ = PS_SKIP_BODY; + } else { + EndResponse(); + return; + } + } else { + static bool report = false; + if (!unknown_mechanisms_.empty() && !report) { + report = true; + std::string msg( + "Unable to connect to the Google Talk service due to an incompatibility " + "with your proxy.\r\nPlease help us resolve this issue by submitting the " + "following information to us using our technical issue submission form " + "at:\r\n\r\n" + "http://www.google.com/support/talk/bin/request.py\r\n\r\n" + "We apologize for the inconvenience.\r\n\r\n" + "Information to submit to Google: " + ); + //std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: "); + msg.append(unknown_mechanisms_); +#if defined(WEBRTC_WIN) + MessageBoxA(0, msg.c_str(), "Oops!", MB_OK); +#endif +#if defined(WEBRTC_POSIX) + // TODO: Raise a signal so the UI can be separated. + LOG(LS_ERROR) << "Oops!\n\n" << msg; +#endif + } + // Unexpected end of headers + Error(0); + return; + } + } else if (state_ == PS_LEADER) { + unsigned int code; + if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) { + Error(0); + return; + } + switch (code) { + case 200: + // connection good! + state_ = PS_TUNNEL_HEADERS; + return; +#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407) +#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ +#endif + case 407: // HTTP_STATUS_PROXY_AUTH_REQ + state_ = PS_AUTHENTICATE; + return; + default: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + return; + } + } else if ((state_ == PS_AUTHENTICATE) + && (_strnicmp(data, "Proxy-Authenticate:", 19) == 0)) { + std::string response, auth_method; + switch (HttpAuthenticate(data + 19, len - 19, + proxy_, "CONNECT", "/", + user_, pass_, context_, response, auth_method)) { + case HAR_IGNORE: + LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method; + if (!unknown_mechanisms_.empty()) + unknown_mechanisms_.append(", "); + unknown_mechanisms_.append(auth_method); + break; + case HAR_RESPONSE: + headers_ = "Proxy-Authorization: "; + headers_.append(response); + headers_.append("\r\n"); + state_ = PS_SKIP_HEADERS; + unknown_mechanisms_.clear(); + break; + case HAR_CREDENTIALS: + defer_error_ = SOCKET_EACCES; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + case HAR_ERROR: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + } + } else if (_strnicmp(data, "Content-Length:", 15) == 0) { + content_length_ = strtoul(data + 15, 0, 0); + } else if (_strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) { + expect_close_ = false; + /* + } else if (_strnicmp(data, "Connection: close", 17) == 0) { + expect_close_ = true; + */ + } +} + +void AsyncHttpsProxySocket::EndResponse() { + if (!expect_close_) { + SendRequest(); + return; + } + + // No point in waiting for the server to close... let's close now + // TODO: Refactor out PS_WAIT_CLOSE + state_ = PS_WAIT_CLOSE; + BufferedReadAdapter::Close(); + OnCloseEvent(this, 0); +} + +void AsyncHttpsProxySocket::Error(int error) { + BufferInput(false); + Close(); + SetError(error); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket, + const SocketAddress& proxy, + const std::string& username, + const CryptString& password) + : BufferedReadAdapter(socket, 1024), state_(SS_ERROR), proxy_(proxy), + user_(username), pass_(password) { +} + +int AsyncSocksProxySocket::Connect(const SocketAddress& addr) { + int ret; + dest_ = addr; + state_ = SS_INIT; + BufferInput(true); + ret = BufferedReadAdapter::Connect(proxy_); + // TODO: Set state_ appropriately if Connect fails. + return ret; +} + +SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncSocksProxySocket::Close() { + state_ = SS_ERROR; + dest_.Clear(); + return BufferedReadAdapter::Close(); +} + +Socket::ConnState AsyncSocksProxySocket::GetState() const { + if (state_ < SS_TUNNEL) { + return CS_CONNECTING; + } else if (state_ == SS_TUNNEL) { + return CS_CONNECTED; + } else { + return CS_CLOSED; + } +} + +void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) { + SendHello(); +} + +void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) { + ASSERT(state_ < SS_TUNNEL); + + ByteBuffer response(data, *len); + + if (state_ == SS_HELLO) { + uint8 ver, method; + if (!response.ReadUInt8(&ver) || + !response.ReadUInt8(&method)) + return; + + if (ver != 5) { + Error(0); + return; + } + + if (method == 0) { + SendConnect(); + } else if (method == 2) { + SendAuth(); + } else { + Error(0); + return; + } + } else if (state_ == SS_AUTH) { + uint8 ver, status; + if (!response.ReadUInt8(&ver) || + !response.ReadUInt8(&status)) + return; + + if ((ver != 1) || (status != 0)) { + Error(SOCKET_EACCES); + return; + } + + SendConnect(); + } else if (state_ == SS_CONNECT) { + uint8 ver, rep, rsv, atyp; + if (!response.ReadUInt8(&ver) || + !response.ReadUInt8(&rep) || + !response.ReadUInt8(&rsv) || + !response.ReadUInt8(&atyp)) + return; + + if ((ver != 5) || (rep != 0)) { + Error(0); + return; + } + + uint16 port; + if (atyp == 1) { + uint32 addr; + if (!response.ReadUInt32(&addr) || + !response.ReadUInt16(&port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 3) { + uint8 len; + std::string addr; + if (!response.ReadUInt8(&len) || + !response.ReadString(&addr, len) || + !response.ReadUInt16(&port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 4) { + std::string addr; + if (!response.ReadString(&addr, 16) || + !response.ReadUInt16(&port)) + return; + LOG(LS_VERBOSE) << "Bound on :" << port; + } else { + Error(0); + return; + } + + state_ = SS_TUNNEL; + } + + // Consume parsed data + *len = response.Length(); + memcpy(data, response.Data(), *len); + + if (state_ != SS_TUNNEL) + return; + + bool remainder = (*len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +void AsyncSocksProxySocket::SendHello() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + if (user_.empty()) { + request.WriteUInt8(1); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + } else { + request.WriteUInt8(2); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + request.WriteUInt8(2); // Username/Password + } + DirectSend(request.Data(), request.Length()); + state_ = SS_HELLO; +} + +void AsyncSocksProxySocket::SendAuth() { + ByteBuffer request; + request.WriteUInt8(1); // Negotiation Version + request.WriteUInt8(static_cast(user_.size())); + request.WriteString(user_); // Username + request.WriteUInt8(static_cast(pass_.GetLength())); + size_t len = pass_.GetLength() + 1; + char * sensitive = new char[len]; + pass_.CopyTo(sensitive, true); + request.WriteString(sensitive); // Password + memset(sensitive, 0, len); + delete [] sensitive; + DirectSend(request.Data(), request.Length()); + state_ = SS_AUTH; +} + +void AsyncSocksProxySocket::SendConnect() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + request.WriteUInt8(1); // CONNECT + request.WriteUInt8(0); // Reserved + if (dest_.IsUnresolved()) { + std::string hostname = dest_.hostname(); + request.WriteUInt8(3); // DOMAINNAME + request.WriteUInt8(static_cast(hostname.size())); + request.WriteString(hostname); // Destination Hostname + } else { + request.WriteUInt8(1); // IPV4 + request.WriteUInt32(dest_.ip()); // Destination IP + } + request.WriteUInt16(dest_.port()); // Destination Port + DirectSend(request.Data(), request.Length()); + state_ = SS_CONNECT; +} + +void AsyncSocksProxySocket::Error(int error) { + state_ = SS_ERROR; + BufferInput(false); + Close(); + SetError(SOCKET_EACCES); + SignalCloseEvent(this, error); +} + +AsyncSocksProxyServerSocket::AsyncSocksProxyServerSocket(AsyncSocket* socket) + : AsyncProxyServerSocket(socket, kBufferSize), state_(SS_HELLO) { + BufferInput(true); +} + +void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) { + // TODO: See if the whole message has arrived + ASSERT(state_ < SS_CONNECT_PENDING); + + ByteBuffer response(data, *len); + if (state_ == SS_HELLO) { + HandleHello(&response); + } else if (state_ == SS_AUTH) { + HandleAuth(&response); + } else if (state_ == SS_CONNECT) { + HandleConnect(&response); + } + + // Consume parsed data + *len = response.Length(); + memcpy(data, response.Data(), *len); +} + +void AsyncSocksProxyServerSocket::DirectSend(const ByteBuffer& buf) { + BufferedReadAdapter::DirectSend(buf.Data(), buf.Length()); +} + +void AsyncSocksProxyServerSocket::HandleHello(ByteBuffer* request) { + uint8 ver, num_methods; + if (!request->ReadUInt8(&ver) || + !request->ReadUInt8(&num_methods)) { + Error(0); + return; + } + + if (ver != 5) { + Error(0); + return; + } + + // Handle either no-auth (0) or user/pass auth (2) + uint8 method = 0xFF; + if (num_methods > 0 && !request->ReadUInt8(&method)) { + Error(0); + return; + } + + // TODO: Ask the server which method to use. + SendHelloReply(method); + if (method == 0) { + state_ = SS_CONNECT; + } else if (method == 2) { + state_ = SS_AUTH; + } else { + state_ = SS_ERROR; + } +} + +void AsyncSocksProxyServerSocket::SendHelloReply(int method) { + ByteBuffer response; + response.WriteUInt8(5); // Socks Version + response.WriteUInt8(method); // Auth method + DirectSend(response); +} + +void AsyncSocksProxyServerSocket::HandleAuth(ByteBuffer* request) { + uint8 ver, user_len, pass_len; + std::string user, pass; + if (!request->ReadUInt8(&ver) || + !request->ReadUInt8(&user_len) || + !request->ReadString(&user, user_len) || + !request->ReadUInt8(&pass_len) || + !request->ReadString(&pass, pass_len)) { + Error(0); + return; + } + + // TODO: Allow for checking of credentials. + SendAuthReply(0); + state_ = SS_CONNECT; +} + +void AsyncSocksProxyServerSocket::SendAuthReply(int result) { + ByteBuffer response; + response.WriteUInt8(1); // Negotiation Version + response.WriteUInt8(result); + DirectSend(response); +} + +void AsyncSocksProxyServerSocket::HandleConnect(ByteBuffer* request) { + uint8 ver, command, reserved, addr_type; + uint32 ip; + uint16 port; + if (!request->ReadUInt8(&ver) || + !request->ReadUInt8(&command) || + !request->ReadUInt8(&reserved) || + !request->ReadUInt8(&addr_type) || + !request->ReadUInt32(&ip) || + !request->ReadUInt16(&port)) { + Error(0); + return; + } + + if (ver != 5 || command != 1 || + reserved != 0 || addr_type != 1) { + Error(0); + return; + } + + SignalConnectRequest(this, SocketAddress(ip, port)); + state_ = SS_CONNECT_PENDING; +} + +void AsyncSocksProxyServerSocket::SendConnectResult(int result, + const SocketAddress& addr) { + if (state_ != SS_CONNECT_PENDING) + return; + + ByteBuffer response; + response.WriteUInt8(5); // Socks version + response.WriteUInt8((result != 0)); // 0x01 is generic error + response.WriteUInt8(0); // reserved + response.WriteUInt8(1); // IPv4 address + response.WriteUInt32(addr.ip()); + response.WriteUInt16(addr.port()); + DirectSend(response); + BufferInput(false); + state_ = SS_TUNNEL; +} + +void AsyncSocksProxyServerSocket::Error(int error) { + state_ = SS_ERROR; + BufferInput(false); + Close(); + SetError(SOCKET_EACCES); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +LoggingSocketAdapter::LoggingSocketAdapter(AsyncSocket* socket, + LoggingSeverity level, + const char * label, bool hex_mode) + : AsyncSocketAdapter(socket), level_(level), hex_mode_(hex_mode) { + label_.append("["); + label_.append(label); + label_.append("]"); +} + +int LoggingSocketAdapter::Send(const void *pv, size_t cb) { + int res = AsyncSocketAdapter::Send(pv, cb); + if (res > 0) + LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::SendTo(const void *pv, size_t cb, + const SocketAddress& addr) { + int res = AsyncSocketAdapter::SendTo(pv, cb, addr); + if (res > 0) + LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::Recv(void *pv, size_t cb) { + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res > 0) + LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + if (res > 0) + LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_); + return res; +} + +int LoggingSocketAdapter::Close() { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed locally"; + return socket_->Close(); +} + +void LoggingSocketAdapter::OnConnectEvent(AsyncSocket * socket) { + LOG_V(level_) << label_ << " Connected"; + AsyncSocketAdapter::OnConnectEvent(socket); +} + +void LoggingSocketAdapter::OnCloseEvent(AsyncSocket * socket, int err) { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed with error: " << err; + AsyncSocketAdapter::OnCloseEvent(socket, err); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/socketadapters.h b/webrtc/base/socketadapters.h new file mode 100644 index 000000000..3292df289 --- /dev/null +++ b/webrtc/base/socketadapters.h @@ -0,0 +1,244 @@ +/* + * Copyright 2004 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_BASE_SOCKETADAPTERS_H_ +#define WEBRTC_BASE_SOCKETADAPTERS_H_ + +#include +#include + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +struct HttpAuthContext; +class ByteBuffer; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that can buffer and process data internally, +// as in the case of connecting to a proxy, where you must speak the proxy +// protocol before commencing normal socket behavior. +class BufferedReadAdapter : public AsyncSocketAdapter { + public: + BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size); + virtual ~BufferedReadAdapter(); + + virtual int Send(const void* pv, size_t cb); + virtual int Recv(void* pv, size_t cb); + + protected: + int DirectSend(const void* pv, size_t cb) { + return AsyncSocketAdapter::Send(pv, cb); + } + + void BufferInput(bool on = true); + virtual void ProcessInput(char* data, size_t* len) = 0; + + virtual void OnReadEvent(AsyncSocket * socket); + + private: + char * buffer_; + size_t buffer_size_, data_len_; + bool buffering_; + DISALLOW_EVIL_CONSTRUCTORS(BufferedReadAdapter); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Interface for implementing proxy server sockets. +class AsyncProxyServerSocket : public BufferedReadAdapter { + public: + AsyncProxyServerSocket(AsyncSocket* socket, size_t buffer_size) + : BufferedReadAdapter(socket, buffer_size) {} + sigslot::signal2 SignalConnectRequest; + virtual void SendConnectResult(int err, const SocketAddress& addr) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that performs the client side of a +// fake SSL handshake. Used for "ssltcp" P2P functionality. +class AsyncSSLSocket : public BufferedReadAdapter { + public: + explicit AsyncSSLSocket(AsyncSocket* socket); + + virtual int Connect(const SocketAddress& addr); + + protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void ProcessInput(char* data, size_t* len); + DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLSocket); +}; + +// Implements a socket adapter that performs the server side of a +// fake SSL handshake. Used when implementing a relay server that does "ssltcp". +class AsyncSSLServerSocket : public BufferedReadAdapter { + public: + explicit AsyncSSLServerSocket(AsyncSocket* socket); + + protected: + virtual void ProcessInput(char* data, size_t* len); + DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLServerSocket); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that speaks the HTTP/S proxy protocol. +class AsyncHttpsProxySocket : public BufferedReadAdapter { + public: + AsyncHttpsProxySocket(AsyncSocket* socket, const std::string& user_agent, + const SocketAddress& proxy, + const std::string& username, const CryptString& password); + virtual ~AsyncHttpsProxySocket(); + + // If connect is forced, the adapter will always issue an HTTP CONNECT to the + // target address. Otherwise, it will connect only if the destination port + // is not port 80. + void SetForceConnect(bool force) { force_connect_ = force; } + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + virtual int Close(); + virtual ConnState GetState() const; + + protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void OnCloseEvent(AsyncSocket* socket, int err); + virtual void ProcessInput(char* data, size_t* len); + + bool ShouldIssueConnect() const; + void SendRequest(); + void ProcessLine(char* data, size_t len); + void EndResponse(); + void Error(int error); + + private: + SocketAddress proxy_, dest_; + std::string agent_, user_, headers_; + CryptString pass_; + bool force_connect_; + size_t content_length_; + int defer_error_; + bool expect_close_; + enum ProxyState { + PS_INIT, PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS, + PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR + } state_; + HttpAuthContext * context_; + std::string unknown_mechanisms_; + DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxySocket); +}; + +/* TODO: Implement this. +class AsyncHttpsProxyServerSocket : public AsyncProxyServerSocket { + public: + explicit AsyncHttpsProxyServerSocket(AsyncSocket* socket); + + private: + virtual void ProcessInput(char * data, size_t& len); + void Error(int error); + DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxyServerSocket); +}; +*/ + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that speaks the SOCKS proxy protocol. +class AsyncSocksProxySocket : public BufferedReadAdapter { + public: + AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const CryptString& password); + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + virtual int Close(); + virtual ConnState GetState() const; + + protected: + virtual void OnConnectEvent(AsyncSocket* socket); + virtual void ProcessInput(char* data, size_t* len); + + void SendHello(); + void SendConnect(); + void SendAuth(); + void Error(int error); + + private: + enum State { + SS_INIT, SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR + }; + State state_; + SocketAddress proxy_, dest_; + std::string user_; + CryptString pass_; + DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxySocket); +}; + +// Implements a proxy server socket for the SOCKS protocol. +class AsyncSocksProxyServerSocket : public AsyncProxyServerSocket { + public: + explicit AsyncSocksProxyServerSocket(AsyncSocket* socket); + + private: + virtual void ProcessInput(char* data, size_t* len); + void DirectSend(const ByteBuffer& buf); + + void HandleHello(ByteBuffer* request); + void SendHelloReply(int method); + void HandleAuth(ByteBuffer* request); + void SendAuthReply(int result); + void HandleConnect(ByteBuffer* request); + virtual void SendConnectResult(int result, const SocketAddress& addr); + + void Error(int error); + + static const int kBufferSize = 1024; + enum State { + SS_HELLO, SS_AUTH, SS_CONNECT, SS_CONNECT_PENDING, SS_TUNNEL, SS_ERROR + }; + State state_; + DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxyServerSocket); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Implements a socket adapter that logs everything that it sends and receives. +class LoggingSocketAdapter : public AsyncSocketAdapter { + public: + LoggingSocketAdapter(AsyncSocket* socket, LoggingSeverity level, + const char * label, bool hex_mode = false); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + virtual int Recv(void *pv, size_t cb); + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr); + virtual int Close(); + + protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + + private: + LoggingSeverity level_; + std::string label_; + bool hex_mode_; + LogMultilineState lms_; + DISALLOW_EVIL_CONSTRUCTORS(LoggingSocketAdapter); +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETADAPTERS_H_ diff --git a/webrtc/base/socketaddress.cc b/webrtc/base/socketaddress.cc new file mode 100644 index 000000000..47ddd0400 --- /dev/null +++ b/webrtc/base/socketaddress.cc @@ -0,0 +1,383 @@ +/* + * Copyright 2004 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 "webrtc/base/socketaddress.h" + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#if defined(OPENBSD) +#include +#endif +#if !defined(__native_client__) +#include +#endif +#include +#include +#include +#endif + +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +SocketAddress::SocketAddress() { + Clear(); +} + +SocketAddress::SocketAddress(const std::string& hostname, int port) { + SetIP(hostname); + SetPort(port); +} + +SocketAddress::SocketAddress(uint32 ip_as_host_order_integer, int port) { + SetIP(IPAddress(ip_as_host_order_integer)); + SetPort(port); +} + +SocketAddress::SocketAddress(const IPAddress& ip, int port) { + SetIP(ip); + SetPort(port); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + this->operator=(addr); +} + +void SocketAddress::Clear() { + hostname_.clear(); + literal_ = false; + ip_ = IPAddress(); + port_ = 0; + scope_id_ = 0; +} + +bool SocketAddress::IsNil() const { + return hostname_.empty() && IPIsUnspec(ip_) && 0 == port_; +} + +bool SocketAddress::IsComplete() const { + return (!IPIsAny(ip_)) && (0 != port_); +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& addr) { + hostname_ = addr.hostname_; + ip_ = addr.ip_; + port_ = addr.port_; + literal_ = addr.literal_; + scope_id_ = addr.scope_id_; + return *this; +} + +void SocketAddress::SetIP(uint32 ip_as_host_order_integer) { + hostname_.clear(); + literal_ = false; + ip_ = IPAddress(ip_as_host_order_integer); + scope_id_ = 0; +} + +void SocketAddress::SetIP(const IPAddress& ip) { + hostname_.clear(); + literal_ = false; + ip_ = ip; + scope_id_ = 0; +} + +void SocketAddress::SetIP(const std::string& hostname) { + hostname_ = hostname; + literal_ = IPFromString(hostname, &ip_); + if (!literal_) { + ip_ = IPAddress(); + } + scope_id_ = 0; +} + +void SocketAddress::SetResolvedIP(uint32 ip_as_host_order_integer) { + ip_ = IPAddress(ip_as_host_order_integer); + scope_id_ = 0; +} + +void SocketAddress::SetResolvedIP(const IPAddress& ip) { + ip_ = ip; + scope_id_ = 0; +} + +void SocketAddress::SetPort(int port) { + ASSERT((0 <= port) && (port < 65536)); + port_ = port; +} + +uint32 SocketAddress::ip() const { + return ip_.v4AddressAsHostOrderInteger(); +} + +const IPAddress& SocketAddress::ipaddr() const { + return ip_; +} + +uint16 SocketAddress::port() const { + return port_; +} + +std::string SocketAddress::HostAsURIString() const { + // If the hostname was a literal IP string, it may need to have square + // brackets added (for SocketAddress::ToString()). + if (!literal_ && !hostname_.empty()) + return hostname_; + if (ip_.family() == AF_INET6) { + return "[" + ip_.ToString() + "]"; + } else { + return ip_.ToString(); + } +} + +std::string SocketAddress::HostAsSensitiveURIString() const { + // If the hostname was a literal IP string, it may need to have square + // brackets added (for SocketAddress::ToString()). + if (!literal_ && !hostname_.empty()) + return hostname_; + if (ip_.family() == AF_INET6) { + return "[" + ip_.ToSensitiveString() + "]"; + } else { + return ip_.ToSensitiveString(); + } +} + +std::string SocketAddress::PortAsString() const { + std::ostringstream ost; + ost << port_; + return ost.str(); +} + +std::string SocketAddress::ToString() const { + std::ostringstream ost; + ost << *this; + return ost.str(); +} + +std::string SocketAddress::ToSensitiveString() const { + std::ostringstream ost; + ost << HostAsSensitiveURIString() << ":" << port(); + return ost.str(); +} + +bool SocketAddress::FromString(const std::string& str) { + if (str.at(0) == '[') { + std::string::size_type closebracket = str.rfind(']'); + if (closebracket != std::string::npos) { + std::string::size_type colon = str.find(':', closebracket); + if (colon != std::string::npos && colon > closebracket) { + SetPort(strtoul(str.substr(colon + 1).c_str(), NULL, 10)); + SetIP(str.substr(1, closebracket - 1)); + } else { + return false; + } + } + } else { + std::string::size_type pos = str.find(':'); + if (std::string::npos == pos) + return false; + SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10)); + SetIP(str.substr(0, pos)); + } + return true; +} + +std::ostream& operator<<(std::ostream& os, const SocketAddress& addr) { + os << addr.HostAsURIString() << ":" << addr.port(); + return os; +} + +bool SocketAddress::IsAnyIP() const { + return IPIsAny(ip_); +} + +bool SocketAddress::IsLoopbackIP() const { + return IPIsLoopback(ip_) || (IPIsAny(ip_) && + 0 == strcmp(hostname_.c_str(), "localhost")); +} + +bool SocketAddress::IsPrivateIP() const { + return IPIsPrivate(ip_); +} + +bool SocketAddress::IsUnresolvedIP() const { + return IPIsUnspec(ip_) && !literal_ && !hostname_.empty(); +} + +bool SocketAddress::operator==(const SocketAddress& addr) const { + return EqualIPs(addr) && EqualPorts(addr); +} + +bool SocketAddress::operator<(const SocketAddress& addr) const { + if (ip_ < addr.ip_) + return true; + else if (addr.ip_ < ip_) + return false; + + // We only check hostnames if both IPs are zero. This matches EqualIPs() + if (addr.IsAnyIP()) { + if (hostname_ < addr.hostname_) + return true; + else if (addr.hostname_ < hostname_) + return false; + } + + return port_ < addr.port_; +} + +bool SocketAddress::EqualIPs(const SocketAddress& addr) const { + return (ip_ == addr.ip_) && + ((!IPIsAny(ip_)) || (hostname_ == addr.hostname_)); +} + +bool SocketAddress::EqualPorts(const SocketAddress& addr) const { + return (port_ == addr.port_); +} + +size_t SocketAddress::Hash() const { + size_t h = 0; + h ^= HashIP(ip_); + h ^= port_ | (port_ << 16); + return h; +} + +void SocketAddress::ToSockAddr(sockaddr_in* saddr) const { + memset(saddr, 0, sizeof(*saddr)); + if (ip_.family() != AF_INET) { + saddr->sin_family = AF_UNSPEC; + return; + } + saddr->sin_family = AF_INET; + saddr->sin_port = HostToNetwork16(port_); + if (IPIsAny(ip_)) { + saddr->sin_addr.s_addr = INADDR_ANY; + } else { + saddr->sin_addr = ip_.ipv4_address(); + } +} + +bool SocketAddress::FromSockAddr(const sockaddr_in& saddr) { + if (saddr.sin_family != AF_INET) + return false; + SetIP(NetworkToHost32(saddr.sin_addr.s_addr)); + SetPort(NetworkToHost16(saddr.sin_port)); + literal_ = false; + return true; +} + +static size_t ToSockAddrStorageHelper(sockaddr_storage* addr, + IPAddress ip, int port, int scope_id) { + memset(addr, 0, sizeof(sockaddr_storage)); + addr->ss_family = ip.family(); + if (addr->ss_family == AF_INET6) { + sockaddr_in6* saddr = reinterpret_cast(addr); + saddr->sin6_addr = ip.ipv6_address(); + saddr->sin6_port = HostToNetwork16(port); + saddr->sin6_scope_id = scope_id; + return sizeof(sockaddr_in6); + } else if (addr->ss_family == AF_INET) { + sockaddr_in* saddr = reinterpret_cast(addr); + saddr->sin_addr = ip.ipv4_address(); + saddr->sin_port = HostToNetwork16(port); + return sizeof(sockaddr_in); + } + return 0; +} + +size_t SocketAddress::ToDualStackSockAddrStorage(sockaddr_storage *addr) const { + return ToSockAddrStorageHelper(addr, ip_.AsIPv6Address(), port_, scope_id_); +} + +size_t SocketAddress::ToSockAddrStorage(sockaddr_storage* addr) const { + return ToSockAddrStorageHelper(addr, ip_, port_, scope_id_); +} + +std::string SocketAddress::IPToString(uint32 ip_as_host_order_integer) { + return IPAddress(ip_as_host_order_integer).ToString(); +} + +std::string IPToSensitiveString(uint32 ip_as_host_order_integer) { + return IPAddress(ip_as_host_order_integer).ToSensitiveString(); +} + +bool SocketAddress::StringToIP(const std::string& hostname, uint32* ip) { + in_addr addr; + if (rtc::inet_pton(AF_INET, hostname.c_str(), &addr) == 0) + return false; + *ip = NetworkToHost32(addr.s_addr); + return true; +} + +bool SocketAddress::StringToIP(const std::string& hostname, IPAddress* ip) { + in_addr addr4; + if (rtc::inet_pton(AF_INET, hostname.c_str(), &addr4) > 0) { + if (ip) { + *ip = IPAddress(addr4); + } + return true; + } + + in6_addr addr6; + if (rtc::inet_pton(AF_INET6, hostname.c_str(), &addr6) > 0) { + if (ip) { + *ip = IPAddress(addr6); + } + return true; + } + return false; +} + +uint32 SocketAddress::StringToIP(const std::string& hostname) { + uint32 ip = 0; + StringToIP(hostname, &ip); + return ip; +} + +bool SocketAddressFromSockAddrStorage(const sockaddr_storage& addr, + SocketAddress* out) { + if (!out) { + return false; + } + if (addr.ss_family == AF_INET) { + const sockaddr_in* saddr = reinterpret_cast(&addr); + *out = SocketAddress(IPAddress(saddr->sin_addr), + NetworkToHost16(saddr->sin_port)); + return true; + } else if (addr.ss_family == AF_INET6) { + const sockaddr_in6* saddr = reinterpret_cast(&addr); + *out = SocketAddress(IPAddress(saddr->sin6_addr), + NetworkToHost16(saddr->sin6_port)); + out->SetScopeID(saddr->sin6_scope_id); + return true; + } + return false; +} + +SocketAddress EmptySocketAddressWithFamily(int family) { + if (family == AF_INET) { + return SocketAddress(IPAddress(INADDR_ANY), 0); + } else if (family == AF_INET6) { + return SocketAddress(IPAddress(in6addr_any), 0); + } + return SocketAddress(); +} + +} // namespace rtc diff --git a/webrtc/base/socketaddress.h b/webrtc/base/socketaddress.h new file mode 100644 index 000000000..f8256fc62 --- /dev/null +++ b/webrtc/base/socketaddress.h @@ -0,0 +1,214 @@ +/* + * Copyright 2004 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_BASE_SOCKETADDRESS_H_ +#define WEBRTC_BASE_SOCKETADDRESS_H_ + +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/ipaddress.h" + +#undef SetPort + +struct sockaddr_in; +struct sockaddr_storage; + +namespace rtc { + +// Records an IP address and port. +class SocketAddress { + public: + // Creates a nil address. + SocketAddress(); + + // Creates the address with the given host and port. Host may be a + // literal IP string or a hostname to be resolved later. + SocketAddress(const std::string& hostname, int port); + + // Creates the address with the given IP and port. + // IP is given as an integer in host byte order. V4 only, to be deprecated. + SocketAddress(uint32 ip_as_host_order_integer, int port); + + // Creates the address with the given IP and port. + SocketAddress(const IPAddress& ip, int port); + + // Creates a copy of the given address. + SocketAddress(const SocketAddress& addr); + + // Resets to the nil address. + void Clear(); + + // Determines if this is a nil address (empty hostname, any IP, null port) + bool IsNil() const; + + // Returns true if ip and port are set. + bool IsComplete() const; + + // Replaces our address with the given one. + SocketAddress& operator=(const SocketAddress& addr); + + // Changes the IP of this address to the given one, and clears the hostname + // IP is given as an integer in host byte order. V4 only, to be deprecated.. + void SetIP(uint32 ip_as_host_order_integer); + + // Changes the IP of this address to the given one, and clears the hostname. + void SetIP(const IPAddress& ip); + + // Changes the hostname of this address to the given one. + // Does not resolve the address; use Resolve to do so. + void SetIP(const std::string& hostname); + + // Sets the IP address while retaining the hostname. Useful for bypassing + // DNS for a pre-resolved IP. + // IP is given as an integer in host byte order. V4 only, to be deprecated. + void SetResolvedIP(uint32 ip_as_host_order_integer); + + // Sets the IP address while retaining the hostname. Useful for bypassing + // DNS for a pre-resolved IP. + void SetResolvedIP(const IPAddress& ip); + + // Changes the port of this address to the given one. + void SetPort(int port); + + // Returns the hostname. + const std::string& hostname() const { return hostname_; } + + // Returns the IP address as a host byte order integer. + // Returns 0 for non-v4 addresses. + uint32 ip() const; + + const IPAddress& ipaddr() const; + + int family() const {return ip_.family(); } + + // Returns the port part of this address. + uint16 port() const; + + // Returns the scope ID associated with this address. Scope IDs are a + // necessary addition to IPv6 link-local addresses, with different network + // interfaces having different scope-ids for their link-local addresses. + // IPv4 address do not have scope_ids and sockaddr_in structures do not have + // a field for them. + int scope_id() const {return scope_id_; } + void SetScopeID(int id) { scope_id_ = id; } + + // Returns the 'host' portion of the address (hostname or IP) in a form + // suitable for use in a URI. If both IP and hostname are present, hostname + // is preferred. IPv6 addresses are enclosed in square brackets ('[' and ']'). + std::string HostAsURIString() const; + + // Same as HostAsURIString but anonymizes IP addresses by hiding the last + // part. + std::string HostAsSensitiveURIString() const; + + // Returns the port as a string. + std::string PortAsString() const; + + // Returns hostname:port or [hostname]:port. + std::string ToString() const; + + // Same as ToString but anonymizes it by hiding the last part. + std::string ToSensitiveString() const; + + // Parses hostname:port and [hostname]:port. + bool FromString(const std::string& str); + + friend std::ostream& operator<<(std::ostream& os, const SocketAddress& addr); + + // Determines whether this represents a missing / any IP address. + // That is, 0.0.0.0 or ::. + // Hostname and/or port may be set. + bool IsAnyIP() const; + inline bool IsAny() const { return IsAnyIP(); } // deprecated + + // Determines whether the IP address refers to a loopback address. + // For v4 addresses this means the address is in the range 127.0.0.0/8. + // For v6 addresses this means the address is ::1. + bool IsLoopbackIP() const; + + // Determines whether the IP address is in one of the private ranges: + // For v4: 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12. + // For v6: FE80::/16 and ::1. + bool IsPrivateIP() const; + + // Determines whether the hostname has been resolved to an IP. + bool IsUnresolvedIP() const; + inline bool IsUnresolved() const { return IsUnresolvedIP(); } // deprecated + + // Determines whether this address is identical to the given one. + bool operator ==(const SocketAddress& addr) const; + inline bool operator !=(const SocketAddress& addr) const { + return !this->operator ==(addr); + } + + // Compares based on IP and then port. + bool operator <(const SocketAddress& addr) const; + + // Determines whether this address has the same IP as the one given. + bool EqualIPs(const SocketAddress& addr) const; + + // Determines whether this address has the same port as the one given. + bool EqualPorts(const SocketAddress& addr) const; + + // Hashes this address into a small number. + size_t Hash() const; + + // Write this address to a sockaddr_in. + // If IPv6, will zero out the sockaddr_in and sets family to AF_UNSPEC. + void ToSockAddr(sockaddr_in* saddr) const; + + // Read this address from a sockaddr_in. + bool FromSockAddr(const sockaddr_in& saddr); + + // Read and write the address to/from a sockaddr_storage. + // Dual stack version always sets family to AF_INET6, and maps v4 addresses. + // The other version doesn't map, and outputs an AF_INET address for + // v4 or mapped addresses, and AF_INET6 addresses for others. + // Returns the size of the sockaddr_in or sockaddr_in6 structure that is + // written to the sockaddr_storage, or zero on failure. + size_t ToDualStackSockAddrStorage(sockaddr_storage* saddr) const; + size_t ToSockAddrStorage(sockaddr_storage* saddr) const; + + // Converts the IP address given in 'compact form' into dotted form. + // IP is given as an integer in host byte order. V4 only, to be deprecated. + // TODO: Deprecate this. + static std::string IPToString(uint32 ip_as_host_order_integer); + + // Same as IPToString but anonymizes it by hiding the last part. + // TODO: Deprecate this. + static std::string IPToSensitiveString(uint32 ip_as_host_order_integer); + + // Converts the IP address given in dotted form into compact form. + // Only dotted names (A.B.C.D) are converted. + // Output integer is returned in host byte order. + // TODO: Deprecate, replace wth agnostic versions. + static bool StringToIP(const std::string& str, uint32* ip); + static uint32 StringToIP(const std::string& str); + + // Converts the IP address given in printable form into an IPAddress. + static bool StringToIP(const std::string& str, IPAddress* ip); + + private: + std::string hostname_; + IPAddress ip_; + uint16 port_; + int scope_id_; + bool literal_; // Indicates that 'hostname_' contains a literal IP string. +}; + +bool SocketAddressFromSockAddrStorage(const sockaddr_storage& saddr, + SocketAddress* out); +SocketAddress EmptySocketAddressWithFamily(int family); + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETADDRESS_H_ diff --git a/webrtc/base/socketaddress_unittest.cc b/webrtc/base/socketaddress_unittest.cc new file mode 100644 index 000000000..6166183fe --- /dev/null +++ b/webrtc/base/socketaddress_unittest.cc @@ -0,0 +1,335 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_POSIX) +#include // for sockaddr_in +#endif + +#include "webrtc/base/gunit.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ipaddress.h" + +namespace rtc { + +const in6_addr kTestV6Addr = { { {0x20, 0x01, 0x0d, 0xb8, + 0x10, 0x20, 0x30, 0x40, + 0x50, 0x60, 0x70, 0x80, + 0x90, 0xA0, 0xB0, 0xC0} } }; +const in6_addr kMappedV4Addr = { { {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, + 0x01, 0x02, 0x03, 0x04} } }; +const std::string kTestV6AddrString = "2001:db8:1020:3040:5060:7080:90a0:b0c0"; +const std::string kTestV6AddrAnonymizedString = "2001:db8:1020::"; +const std::string kTestV6AddrFullString = + "[2001:db8:1020:3040:5060:7080:90a0:b0c0]:5678"; +const std::string kTestV6AddrFullAnonymizedString = "[2001:db8:1020::]:5678"; + +TEST(SocketAddressTest, TestDefaultCtor) { + SocketAddress addr; + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(0, addr.port()); + EXPECT_EQ("", addr.hostname()); +} + +TEST(SocketAddressTest, TestIPPortCtor) { + SocketAddress addr(IPAddress(0x01020304), 5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestIPv4StringPortCtor) { + SocketAddress addr("1.2.3.4", 5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestIPv6StringPortCtor) { + SocketAddress addr2(kTestV6AddrString, 1234); + IPAddress tocheck(kTestV6Addr); + + EXPECT_FALSE(addr2.IsUnresolvedIP()); + EXPECT_EQ(tocheck, addr2.ipaddr()); + EXPECT_EQ(1234, addr2.port()); + EXPECT_EQ(kTestV6AddrString, addr2.hostname()); + EXPECT_EQ("[" + kTestV6AddrString + "]:1234", addr2.ToString()); +} + +TEST(SocketAddressTest, TestSpecialStringPortCtor) { + // inet_addr doesn't handle this address properly. + SocketAddress addr("255.255.255.255", 5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0xFFFFFFFFU), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("255.255.255.255", addr.hostname()); + EXPECT_EQ("255.255.255.255:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestHostnamePortCtor) { + SocketAddress addr("a.b.com", 5678); + EXPECT_TRUE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestCopyCtor) { + SocketAddress from("1.2.3.4", 5678); + SocketAddress addr(from); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestAssign) { + SocketAddress from("1.2.3.4", 5678); + SocketAddress addr(IPAddress(0x88888888), 9999); + addr = from; + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestSetIPPort) { + SocketAddress addr(IPAddress(0x88888888), 9999); + addr.SetIP(IPAddress(0x01020304)); + addr.SetPort(5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestSetIPFromString) { + SocketAddress addr(IPAddress(0x88888888), 9999); + addr.SetIP("1.2.3.4"); + addr.SetPort(5678); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestSetIPFromHostname) { + SocketAddress addr(IPAddress(0x88888888), 9999); + addr.SetIP("a.b.com"); + addr.SetPort(5678); + EXPECT_TRUE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); + addr.SetResolvedIP(IPAddress(0x01020304)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestFromIPv4String) { + SocketAddress addr; + EXPECT_TRUE(addr.FromString("1.2.3.4:5678")); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("1.2.3.4", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestFromIPv6String) { + SocketAddress addr; + EXPECT_TRUE(addr.FromString(kTestV6AddrFullString)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ(kTestV6AddrString, addr.hostname()); + EXPECT_EQ(kTestV6AddrFullString, addr.ToString()); +} + +TEST(SocketAddressTest, TestFromHostname) { + SocketAddress addr; + EXPECT_TRUE(addr.FromString("a.b.com:5678")); + EXPECT_TRUE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("a.b.com", addr.hostname()); + EXPECT_EQ("a.b.com:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestToFromSockAddr) { + SocketAddress from("1.2.3.4", 5678), addr; + sockaddr_in addr_in; + from.ToSockAddr(&addr_in); + EXPECT_TRUE(addr.FromSockAddr(addr_in)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); +} + +TEST(SocketAddressTest, TestToFromSockAddrStorage) { + SocketAddress from("1.2.3.4", 5678), addr; + sockaddr_storage addr_storage; + from.ToSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("1.2.3.4:5678", addr.ToString()); + + addr.Clear(); + from.ToDualStackSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(kMappedV4Addr), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ("[::ffff:1.2.3.4]:5678", addr.ToString()); + + addr.Clear(); + memset(&addr_storage, 0, sizeof(sockaddr_storage)); + from = SocketAddress(kTestV6AddrString, 5678); + from.SetScopeID(6); + from.ToSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ(kTestV6AddrFullString, addr.ToString()); + EXPECT_EQ(6, addr.scope_id()); + + addr.Clear(); + from.ToDualStackSockAddrStorage(&addr_storage); + EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_FALSE(addr.IsUnresolvedIP()); + EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr()); + EXPECT_EQ(5678, addr.port()); + EXPECT_EQ("", addr.hostname()); + EXPECT_EQ(kTestV6AddrFullString, addr.ToString()); + EXPECT_EQ(6, addr.scope_id()); + + addr = from; + addr_storage.ss_family = AF_UNSPEC; + EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, &addr)); + EXPECT_EQ(from, addr); + + EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, NULL)); +} + +bool AreEqual(const SocketAddress& addr1, + const SocketAddress& addr2) { + return addr1 == addr2 && addr2 == addr1 && + !(addr1 != addr2) && !(addr2 != addr1); +} + +bool AreUnequal(const SocketAddress& addr1, + const SocketAddress& addr2) { + return !(addr1 == addr2) && !(addr2 == addr1) && + addr1 != addr2 && addr2 != addr1; +} + +TEST(SocketAddressTest, TestEqualityOperators) { + SocketAddress addr1("1.2.3.4", 5678); + SocketAddress addr2("1.2.3.4", 5678); + EXPECT_PRED2(AreEqual, addr1, addr2); + + addr2 = SocketAddress("0.0.0.1", 5678); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr2 = SocketAddress("1.2.3.4", 1234); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr2 = SocketAddress(kTestV6AddrString, 5678); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr1 = SocketAddress(kTestV6AddrString, 5678); + EXPECT_PRED2(AreEqual, addr1, addr2); + + addr2 = SocketAddress(kTestV6AddrString, 1234); + EXPECT_PRED2(AreUnequal, addr1, addr2); + + addr2 = SocketAddress("fe80::1", 5678); + EXPECT_PRED2(AreUnequal, addr1, addr2); +} + +bool IsLessThan(const SocketAddress& addr1, + const SocketAddress& addr2) { + return addr1 < addr2 && + !(addr2 < addr1) && + !(addr1 == addr2); +} + +TEST(SocketAddressTest, TestComparisonOperator) { + SocketAddress addr1("1.2.3.4", 5678); + SocketAddress addr2("1.2.3.4", 5678); + + EXPECT_FALSE(addr1 < addr2); + EXPECT_FALSE(addr2 < addr1); + + addr2 = SocketAddress("1.2.3.4", 5679); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr2 = SocketAddress("2.2.3.4", 49152); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr2 = SocketAddress(kTestV6AddrString, 5678); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr1 = SocketAddress("fe80::1", 5678); + EXPECT_PRED2(IsLessThan, addr2, addr1); + + addr2 = SocketAddress("fe80::1", 5679); + EXPECT_PRED2(IsLessThan, addr1, addr2); + + addr2 = SocketAddress("fe80::1", 5678); + EXPECT_FALSE(addr1 < addr2); + EXPECT_FALSE(addr2 < addr1); +} + +TEST(SocketAddressTest, TestToSensitiveString) { + SocketAddress addr_v4("1.2.3.4", 5678); + EXPECT_EQ("1.2.3.4", addr_v4.HostAsURIString()); + EXPECT_EQ("1.2.3.4:5678", addr_v4.ToString()); + EXPECT_EQ("1.2.3.4", addr_v4.HostAsSensitiveURIString()); + EXPECT_EQ("1.2.3.4:5678", addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ("1.2.3.x", addr_v4.HostAsSensitiveURIString()); + EXPECT_EQ("1.2.3.x:5678", addr_v4.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); + + SocketAddress addr_v6(kTestV6AddrString, 5678); + EXPECT_EQ("[" + kTestV6AddrString + "]", addr_v6.HostAsURIString()); + EXPECT_EQ(kTestV6AddrFullString, addr_v6.ToString()); + EXPECT_EQ("[" + kTestV6AddrString + "]", addr_v6.HostAsSensitiveURIString()); + EXPECT_EQ(kTestV6AddrFullString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(true); + EXPECT_EQ("[" + kTestV6AddrAnonymizedString + "]", + addr_v6.HostAsSensitiveURIString()); + EXPECT_EQ(kTestV6AddrFullAnonymizedString, addr_v6.ToSensitiveString()); + IPAddress::set_strip_sensitive(false); +} + +} // namespace rtc diff --git a/webrtc/base/socketaddresspair.cc b/webrtc/base/socketaddresspair.cc new file mode 100644 index 000000000..dfa8b25a0 --- /dev/null +++ b/webrtc/base/socketaddresspair.cc @@ -0,0 +1,41 @@ +/* + * Copyright 2004 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 "webrtc/base/socketaddresspair.h" + +namespace rtc { + +SocketAddressPair::SocketAddressPair( + const SocketAddress& src, const SocketAddress& dest) + : src_(src), dest_(dest) { +} + + +bool SocketAddressPair::operator ==(const SocketAddressPair& p) const { + return (src_ == p.src_) && (dest_ == p.dest_); +} + +bool SocketAddressPair::operator <(const SocketAddressPair& p) const { + if (src_ < p.src_) + return true; + if (p.src_ < src_) + return false; + if (dest_ < p.dest_) + return true; + if (p.dest_ < dest_) + return false; + return false; +} + +size_t SocketAddressPair::Hash() const { + return src_.Hash() ^ dest_.Hash(); +} + +} // namespace rtc diff --git a/webrtc/base/socketaddresspair.h b/webrtc/base/socketaddresspair.h new file mode 100644 index 000000000..73a627f10 --- /dev/null +++ b/webrtc/base/socketaddresspair.h @@ -0,0 +1,41 @@ +/* + * Copyright 2004 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_BASE_SOCKETADDRESSPAIR_H__ +#define WEBRTC_BASE_SOCKETADDRESSPAIR_H__ + +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +// Records a pair (source,destination) of socket addresses. The two addresses +// identify a connection between two machines. (For UDP, this "connection" is +// not maintained explicitly in a socket.) +class SocketAddressPair { +public: + SocketAddressPair() {} + SocketAddressPair(const SocketAddress& srs, const SocketAddress& dest); + + const SocketAddress& source() const { return src_; } + const SocketAddress& destination() const { return dest_; } + + bool operator ==(const SocketAddressPair& r) const; + bool operator <(const SocketAddressPair& r) const; + + size_t Hash() const; + +private: + SocketAddress src_; + SocketAddress dest_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETADDRESSPAIR_H__ diff --git a/webrtc/base/socketfactory.h b/webrtc/base/socketfactory.h new file mode 100644 index 000000000..fe0f32bdb --- /dev/null +++ b/webrtc/base/socketfactory.h @@ -0,0 +1,38 @@ +/* + * Copyright 2004 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_BASE_SOCKETFACTORY_H__ +#define WEBRTC_BASE_SOCKETFACTORY_H__ + +#include "webrtc/base/socket.h" +#include "webrtc/base/asyncsocket.h" + +namespace rtc { + +class SocketFactory { +public: + virtual ~SocketFactory() {} + + // Returns a new socket for blocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + // TODO: C++ inheritance rules mean that all users must have both + // CreateSocket(int) and CreateSocket(int,int). Will remove CreateSocket(int) + // (and CreateAsyncSocket(int) when all callers are changed. + virtual Socket* CreateSocket(int type) = 0; + virtual Socket* CreateSocket(int family, int type) = 0; + // Returns a new socket for nonblocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual AsyncSocket* CreateAsyncSocket(int type) = 0; + virtual AsyncSocket* CreateAsyncSocket(int family, int type) = 0; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETFACTORY_H__ diff --git a/webrtc/base/socketpool.cc b/webrtc/base/socketpool.cc new file mode 100644 index 000000000..8e61cc313 --- /dev/null +++ b/webrtc/base/socketpool.cc @@ -0,0 +1,280 @@ +/* + * Copyright 2004 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 "webrtc/base/asyncsocket.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socketfactory.h" +#include "webrtc/base/socketpool.h" +#include "webrtc/base/socketstream.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// StreamCache - Caches a set of open streams, defers creation to a separate +// StreamPool. +/////////////////////////////////////////////////////////////////////////////// + +StreamCache::StreamCache(StreamPool* pool) : pool_(pool) { +} + +StreamCache::~StreamCache() { + for (ConnectedList::iterator it = active_.begin(); it != active_.end(); + ++it) { + delete it->second; + } + for (ConnectedList::iterator it = cached_.begin(); it != cached_.end(); + ++it) { + delete it->second; + } +} + +StreamInterface* StreamCache::RequestConnectedStream( + const SocketAddress& remote, int* err) { + LOG_F(LS_VERBOSE) << "(" << remote << ")"; + for (ConnectedList::iterator it = cached_.begin(); it != cached_.end(); + ++it) { + if (remote == it->first) { + it->second->SignalEvent.disconnect(this); + // Move from cached_ to active_ + active_.push_front(*it); + cached_.erase(it); + if (err) + *err = 0; + LOG_F(LS_VERBOSE) << "Providing cached stream"; + return active_.front().second; + } + } + if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) { + // We track active streams so that we can remember their address + active_.push_front(ConnectedStream(remote, stream)); + LOG_F(LS_VERBOSE) << "Providing new stream"; + return active_.front().second; + } + return NULL; +} + +void StreamCache::ReturnConnectedStream(StreamInterface* stream) { + for (ConnectedList::iterator it = active_.begin(); it != active_.end(); + ++it) { + if (stream == it->second) { + LOG_F(LS_VERBOSE) << "(" << it->first << ")"; + if (stream->GetState() == SS_CLOSED) { + // Return closed streams + LOG_F(LS_VERBOSE) << "Returning closed stream"; + pool_->ReturnConnectedStream(it->second); + } else { + // Monitor open streams + stream->SignalEvent.connect(this, &StreamCache::OnStreamEvent); + LOG_F(LS_VERBOSE) << "Caching stream"; + cached_.push_front(*it); + } + active_.erase(it); + return; + } + } + ASSERT(false); +} + +void StreamCache::OnStreamEvent(StreamInterface* stream, int events, int err) { + if ((events & SE_CLOSE) == 0) { + LOG_F(LS_WARNING) << "(" << events << ", " << err + << ") received non-close event"; + return; + } + for (ConnectedList::iterator it = cached_.begin(); it != cached_.end(); + ++it) { + if (stream == it->second) { + LOG_F(LS_VERBOSE) << "(" << it->first << ")"; + // We don't cache closed streams, so return it. + it->second->SignalEvent.disconnect(this); + LOG_F(LS_VERBOSE) << "Returning closed stream"; + pool_->ReturnConnectedStream(it->second); + cached_.erase(it); + return; + } + } + ASSERT(false); +} + +////////////////////////////////////////////////////////////////////// +// NewSocketPool +////////////////////////////////////////////////////////////////////// + +NewSocketPool::NewSocketPool(SocketFactory* factory) : factory_(factory) { +} + +NewSocketPool::~NewSocketPool() { +} + +StreamInterface* +NewSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) { + AsyncSocket* socket = + factory_->CreateAsyncSocket(remote.family(), SOCK_STREAM); + if (!socket) { + if (err) + *err = -1; + return NULL; + } + if ((socket->Connect(remote) != 0) && !socket->IsBlocking()) { + if (err) + *err = socket->GetError(); + delete socket; + return NULL; + } + if (err) + *err = 0; + return new SocketStream(socket); +} + +void +NewSocketPool::ReturnConnectedStream(StreamInterface* stream) { + Thread::Current()->Dispose(stream); +} + +////////////////////////////////////////////////////////////////////// +// ReuseSocketPool +////////////////////////////////////////////////////////////////////// + +ReuseSocketPool::ReuseSocketPool(SocketFactory* factory) +: factory_(factory), stream_(NULL), checked_out_(false) { +} + +ReuseSocketPool::~ReuseSocketPool() { + ASSERT(!checked_out_); + delete stream_; +} + +StreamInterface* +ReuseSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) { + // Only one socket can be used from this "pool" at a time + ASSERT(!checked_out_); + if (!stream_) { + LOG_F(LS_VERBOSE) << "Creating new socket"; + int family = remote.family(); + // TODO: Deal with this when we/I clean up DNS resolution. + if (remote.IsUnresolvedIP()) { + family = AF_INET; + } + AsyncSocket* socket = + factory_->CreateAsyncSocket(family, SOCK_STREAM); + if (!socket) { + if (err) + *err = -1; + return NULL; + } + stream_ = new SocketStream(socket); + } + if ((stream_->GetState() == SS_OPEN) && (remote == remote_)) { + LOG_F(LS_VERBOSE) << "Reusing connection to: " << remote_; + } else { + remote_ = remote; + stream_->Close(); + if ((stream_->GetSocket()->Connect(remote_) != 0) + && !stream_->GetSocket()->IsBlocking()) { + if (err) + *err = stream_->GetSocket()->GetError(); + return NULL; + } else { + LOG_F(LS_VERBOSE) << "Opening connection to: " << remote_; + } + } + stream_->SignalEvent.disconnect(this); + checked_out_ = true; + if (err) + *err = 0; + return stream_; +} + +void +ReuseSocketPool::ReturnConnectedStream(StreamInterface* stream) { + ASSERT(stream == stream_); + ASSERT(checked_out_); + checked_out_ = false; + // Until the socket is reused, monitor it to determine if it closes. + stream_->SignalEvent.connect(this, &ReuseSocketPool::OnStreamEvent); +} + +void +ReuseSocketPool::OnStreamEvent(StreamInterface* stream, int events, int err) { + ASSERT(stream == stream_); + ASSERT(!checked_out_); + + // If the stream was written to and then immediately returned to us then + // we may get a writable notification for it, which we should ignore. + if (events == SE_WRITE) { + LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly writable: ignoring"; + return; + } + + // If the peer sent data, we can't process it, so drop the connection. + // If the socket has closed, clean it up. + // In either case, we'll reconnect it the next time it is used. + ASSERT(0 != (events & (SE_READ|SE_CLOSE))); + if (0 != (events & SE_CLOSE)) { + LOG_F(LS_VERBOSE) << "Connection closed with error: " << err; + } else { + LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly readable: closing"; + } + stream_->Close(); +} + +/////////////////////////////////////////////////////////////////////////////// +// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached +// LoggingAdapters. +/////////////////////////////////////////////////////////////////////////////// + +LoggingPoolAdapter::LoggingPoolAdapter( + StreamPool* pool, LoggingSeverity level, const std::string& label, + bool binary_mode) + : pool_(pool), level_(level), label_(label), binary_mode_(binary_mode) { +} + +LoggingPoolAdapter::~LoggingPoolAdapter() { + for (StreamList::iterator it = recycle_bin_.begin(); + it != recycle_bin_.end(); ++it) { + delete *it; + } +} + +StreamInterface* LoggingPoolAdapter::RequestConnectedStream( + const SocketAddress& remote, int* err) { + if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) { + ASSERT(SS_CLOSED != stream->GetState()); + std::stringstream ss; + ss << label_ << "(0x" << std::setfill('0') << std::hex << std::setw(8) + << stream << ")"; + LOG_V(level_) << ss.str() + << ((SS_OPEN == stream->GetState()) ? " Connected" + : " Connecting") + << " to " << remote; + if (recycle_bin_.empty()) { + return new LoggingAdapter(stream, level_, ss.str(), binary_mode_); + } + LoggingAdapter* logging = recycle_bin_.front(); + recycle_bin_.pop_front(); + logging->set_label(ss.str()); + logging->Attach(stream); + return logging; + } + return NULL; +} + +void LoggingPoolAdapter::ReturnConnectedStream(StreamInterface* stream) { + LoggingAdapter* logging = static_cast(stream); + pool_->ReturnConnectedStream(logging->Detach()); + recycle_bin_.push_back(logging); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/socketpool.h b/webrtc/base/socketpool.h new file mode 100644 index 000000000..7bcaa062d --- /dev/null +++ b/webrtc/base/socketpool.h @@ -0,0 +1,143 @@ +/* + * Copyright 2004 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_BASE_SOCKETPOOL_H_ +#define WEBRTC_BASE_SOCKETPOOL_H_ + +#include +#include +#include "webrtc/base/logging.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +class AsyncSocket; +class LoggingAdapter; +class SocketFactory; +class SocketStream; +class StreamInterface; + +////////////////////////////////////////////////////////////////////// +// StreamPool +////////////////////////////////////////////////////////////////////// + +class StreamPool { +public: + virtual ~StreamPool() { } + + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err) = 0; + virtual void ReturnConnectedStream(StreamInterface* stream) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamCache - Caches a set of open streams, defers creation/destruction to +// the supplied StreamPool. +/////////////////////////////////////////////////////////////////////////////// + +class StreamCache : public StreamPool, public sigslot::has_slots<> { +public: + StreamCache(StreamPool* pool); + virtual ~StreamCache(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + typedef std::pair ConnectedStream; + typedef std::list ConnectedList; + + void OnStreamEvent(StreamInterface* stream, int events, int err); + + // We delegate stream creation and deletion to this pool. + StreamPool* pool_; + // Streams that are in use (returned from RequestConnectedStream). + ConnectedList active_; + // Streams which were returned to us, but are still open. + ConnectedList cached_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// NewSocketPool +// Creates a new stream on every request +/////////////////////////////////////////////////////////////////////////////// + +class NewSocketPool : public StreamPool { +public: + NewSocketPool(SocketFactory* factory); + virtual ~NewSocketPool(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + SocketFactory* factory_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ReuseSocketPool +// Maintains a single socket at a time, and will reuse it without closing if +// the destination address is the same. +/////////////////////////////////////////////////////////////////////////////// + +class ReuseSocketPool : public StreamPool, public sigslot::has_slots<> { +public: + ReuseSocketPool(SocketFactory* factory); + virtual ~ReuseSocketPool(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + void OnStreamEvent(StreamInterface* stream, int events, int err); + + SocketFactory* factory_; + SocketStream* stream_; + SocketAddress remote_; + bool checked_out_; // Whether the stream is currently checked out +}; + +/////////////////////////////////////////////////////////////////////////////// +// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached +// LoggingAdapters. +/////////////////////////////////////////////////////////////////////////////// + +class LoggingPoolAdapter : public StreamPool { +public: + LoggingPoolAdapter(StreamPool* pool, LoggingSeverity level, + const std::string& label, bool binary_mode); + virtual ~LoggingPoolAdapter(); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + StreamPool* pool_; + LoggingSeverity level_; + std::string label_; + bool binary_mode_; + typedef std::deque StreamList; + StreamList recycle_bin_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETPOOL_H_ diff --git a/webrtc/base/socketserver.h b/webrtc/base/socketserver.h new file mode 100644 index 000000000..467105a6a --- /dev/null +++ b/webrtc/base/socketserver.h @@ -0,0 +1,44 @@ +/* + * Copyright 2004 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_BASE_SOCKETSERVER_H_ +#define WEBRTC_BASE_SOCKETSERVER_H_ + +#include "webrtc/base/socketfactory.h" + +namespace rtc { + +class MessageQueue; + +// Provides the ability to wait for activity on a set of sockets. The Thread +// class provides a nice wrapper on a socket server. +// +// The server is also a socket factory. The sockets it creates will be +// notified of asynchronous I/O from this server's Wait method. +class SocketServer : public SocketFactory { + public: + // When the socket server is installed into a Thread, this function is + // called to allow the socket server to use the thread's message queue for + // any messaging that it might need to perform. + virtual void SetMessageQueue(MessageQueue* queue) {} + + // Sleeps until: + // 1) cms milliseconds have elapsed (unless cms == kForever) + // 2) WakeUp() is called + // While sleeping, I/O is performed if process_io is true. + virtual bool Wait(int cms, bool process_io) = 0; + + // Causes the current wait (if one is in progress) to wake up. + virtual void WakeUp() = 0; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETSERVER_H_ diff --git a/webrtc/base/socketstream.cc b/webrtc/base/socketstream.cc new file mode 100644 index 000000000..b0acf94c5 --- /dev/null +++ b/webrtc/base/socketstream.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2010 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 "webrtc/base/socketstream.h" + +namespace rtc { + +SocketStream::SocketStream(AsyncSocket* socket) : socket_(NULL) { + Attach(socket); +} + +SocketStream::~SocketStream() { + delete socket_; +} + +void SocketStream::Attach(AsyncSocket* socket) { + if (socket_) + delete socket_; + socket_ = socket; + if (socket_) { + socket_->SignalConnectEvent.connect(this, &SocketStream::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &SocketStream::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &SocketStream::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &SocketStream::OnCloseEvent); + } +} + +AsyncSocket* SocketStream::Detach() { + AsyncSocket* socket = socket_; + if (socket_) { + socket_->SignalConnectEvent.disconnect(this); + socket_->SignalReadEvent.disconnect(this); + socket_->SignalWriteEvent.disconnect(this); + socket_->SignalCloseEvent.disconnect(this); + socket_ = NULL; + } + return socket; +} + +StreamState SocketStream::GetState() const { + ASSERT(socket_ != NULL); + switch (socket_->GetState()) { + case Socket::CS_CONNECTED: + return SS_OPEN; + case Socket::CS_CONNECTING: + return SS_OPENING; + case Socket::CS_CLOSED: + default: + return SS_CLOSED; + } +} + +StreamResult SocketStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + ASSERT(socket_ != NULL); + int result = socket_->Recv(buffer, buffer_len); + if (result < 0) { + if (socket_->IsBlocking()) + return SR_BLOCK; + if (error) + *error = socket_->GetError(); + return SR_ERROR; + } + if ((result > 0) || (buffer_len == 0)) { + if (read) + *read = result; + return SR_SUCCESS; + } + return SR_EOS; +} + +StreamResult SocketStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + ASSERT(socket_ != NULL); + int result = socket_->Send(data, data_len); + if (result < 0) { + if (socket_->IsBlocking()) + return SR_BLOCK; + if (error) + *error = socket_->GetError(); + return SR_ERROR; + } + if (written) + *written = result; + return SR_SUCCESS; +} + +void SocketStream::Close() { + ASSERT(socket_ != NULL); + socket_->Close(); +} + +void SocketStream::OnConnectEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_OPEN | SE_READ | SE_WRITE, 0); +} + +void SocketStream::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_READ, 0); +} + +void SocketStream::OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_WRITE, 0); +} + +void SocketStream::OnCloseEvent(AsyncSocket* socket, int err) { + ASSERT(socket == socket_); + SignalEvent(this, SE_CLOSE, err); +} + + +} // namespace rtc diff --git a/webrtc/base/socketstream.h b/webrtc/base/socketstream.h new file mode 100644 index 000000000..ce9939b0f --- /dev/null +++ b/webrtc/base/socketstream.h @@ -0,0 +1,57 @@ +/* + * Copyright 2005 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_BASE_SOCKETSTREAM_H_ +#define WEBRTC_BASE_SOCKETSTREAM_H_ + +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class SocketStream : public StreamInterface, public sigslot::has_slots<> { + public: + explicit SocketStream(AsyncSocket* socket); + virtual ~SocketStream(); + + void Attach(AsyncSocket* socket); + AsyncSocket* Detach(); + + AsyncSocket* GetSocket() { return socket_; } + + virtual StreamState GetState() const; + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + virtual void Close(); + + private: + void OnConnectEvent(AsyncSocket* socket); + void OnReadEvent(AsyncSocket* socket); + void OnWriteEvent(AsyncSocket* socket); + void OnCloseEvent(AsyncSocket* socket, int err); + + AsyncSocket* socket_; + + DISALLOW_EVIL_CONSTRUCTORS(SocketStream); +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SOCKETSTREAM_H_ diff --git a/webrtc/base/ssladapter.cc b/webrtc/base/ssladapter.cc new file mode 100644 index 000000000..d83a2779e --- /dev/null +++ b/webrtc/base/ssladapter.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2004 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. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/ssladapter.h" + +#include "webrtc/base/sslconfig.h" + +#if SSL_USE_SCHANNEL + +#include "schanneladapter.h" + +#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL + +#include "openssladapter.h" + +#elif SSL_USE_NSS // && !SSL_USE_CHANNEL && !SSL_USE_OPENSSL + +#include "nssstreamadapter.h" + +#endif // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +SSLAdapter* +SSLAdapter::Create(AsyncSocket* socket) { +#if SSL_USE_SCHANNEL + return new SChannelAdapter(socket); +#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL + return new OpenSSLAdapter(socket); +#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL + delete socket; + return NULL; +#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL +} + +/////////////////////////////////////////////////////////////////////////////// + +#if SSL_USE_OPENSSL + +bool InitializeSSL(VerificationCallback callback) { + return OpenSSLAdapter::InitializeSSL(callback); +} + +bool InitializeSSLThread() { + return OpenSSLAdapter::InitializeSSLThread(); +} + +bool CleanupSSL() { + return OpenSSLAdapter::CleanupSSL(); +} + +#elif SSL_USE_NSS // !SSL_USE_OPENSSL + +bool InitializeSSL(VerificationCallback callback) { + return NSSContext::InitializeSSL(callback); +} + +bool InitializeSSLThread() { + return NSSContext::InitializeSSLThread(); +} + +bool CleanupSSL() { + return NSSContext::CleanupSSL(); +} + +#else // !SSL_USE_OPENSSL && !SSL_USE_NSS + +bool InitializeSSL(VerificationCallback callback) { + return true; +} + +bool InitializeSSLThread() { + return true; +} + +bool CleanupSSL() { + return true; +} + +#endif // !SSL_USE_OPENSSL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/ssladapter.h b/webrtc/base/ssladapter.h new file mode 100644 index 000000000..87b993ffb --- /dev/null +++ b/webrtc/base/ssladapter.h @@ -0,0 +1,61 @@ +/* + * Copyright 2004 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_BASE_SSLADAPTER_H_ +#define WEBRTC_BASE_SSLADAPTER_H_ + +#include "webrtc/base/asyncsocket.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +class SSLAdapter : public AsyncSocketAdapter { + public: + explicit SSLAdapter(AsyncSocket* socket) + : AsyncSocketAdapter(socket), ignore_bad_cert_(false) { } + + bool ignore_bad_cert() const { return ignore_bad_cert_; } + void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; } + + // StartSSL returns 0 if successful. + // If StartSSL is called while the socket is closed or connecting, the SSL + // negotiation will begin as soon as the socket connects. + virtual int StartSSL(const char* hostname, bool restartable) = 0; + + // Create the default SSL adapter for this platform. On failure, returns NULL + // and deletes |socket|. Otherwise, the returned SSLAdapter takes ownership + // of |socket|. + static SSLAdapter* Create(AsyncSocket* socket); + + private: + // If true, the server certificate need not match the configured hostname. + bool ignore_bad_cert_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +typedef bool (*VerificationCallback)(void* cert); + +// Call this on the main thread, before using SSL. +// Call CleanupSSLThread when finished with SSL. +bool InitializeSSL(VerificationCallback callback = NULL); + +// Call to initialize additional threads. +bool InitializeSSLThread(); + +// Call to cleanup additional threads, and also the main thread. +bool CleanupSSL(); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLADAPTER_H_ diff --git a/webrtc/base/sslconfig.h b/webrtc/base/sslconfig.h new file mode 100644 index 000000000..d824ab062 --- /dev/null +++ b/webrtc/base/sslconfig.h @@ -0,0 +1,33 @@ +/* + * Copyright 2012 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_BASE_SSLCONFIG_H_ +#define WEBRTC_BASE_SSLCONFIG_H_ + +// If no preference has been indicated, default to SChannel on Windows and +// OpenSSL everywhere else, if it is available. +#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL) && \ + !defined(SSL_USE_NSS) +#if defined(WEBRTC_WIN) + +#define SSL_USE_SCHANNEL 1 + +#else // defined(WEBRTC_WIN) + +#if defined(HAVE_OPENSSL_SSL_H) +#define SSL_USE_OPENSSL 1 +#elif defined(HAVE_NSS_SSL_H) +#define SSL_USE_NSS 1 +#endif + +#endif // !defined(WEBRTC_WIN) +#endif + +#endif // WEBRTC_BASE_SSLCONFIG_H_ diff --git a/webrtc/base/sslfingerprint.cc b/webrtc/base/sslfingerprint.cc new file mode 100644 index 000000000..1419243c8 --- /dev/null +++ b/webrtc/base/sslfingerprint.cc @@ -0,0 +1,96 @@ +/* + * Copyright 2012 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 "webrtc/base/sslfingerprint.h" + +#include +#include + +#include "webrtc/base/helpers.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/stringencode.h" + +namespace rtc { + +SSLFingerprint* SSLFingerprint::Create( + const std::string& algorithm, const rtc::SSLIdentity* identity) { + if (!identity) { + return NULL; + } + + return Create(algorithm, &(identity->certificate())); +} + +SSLFingerprint* SSLFingerprint::Create( + const std::string& algorithm, const rtc::SSLCertificate* cert) { + uint8 digest_val[64]; + size_t digest_len; + bool ret = cert->ComputeDigest( + algorithm, digest_val, sizeof(digest_val), &digest_len); + if (!ret) { + return NULL; + } + + return new SSLFingerprint(algorithm, digest_val, digest_len); +} + +SSLFingerprint* SSLFingerprint::CreateFromRfc4572( + const std::string& algorithm, const std::string& fingerprint) { + if (algorithm.empty() || !rtc::IsFips180DigestAlgorithm(algorithm)) + return NULL; + + if (fingerprint.empty()) + return NULL; + + size_t value_len; + char value[rtc::MessageDigest::kMaxSize]; + value_len = rtc::hex_decode_with_delimiter(value, sizeof(value), + fingerprint.c_str(), + fingerprint.length(), + ':'); + if (!value_len) + return NULL; + + return new SSLFingerprint(algorithm, + reinterpret_cast(value), + value_len); +} + +SSLFingerprint::SSLFingerprint( + const std::string& algorithm, const uint8* digest_in, size_t digest_len) + : algorithm(algorithm) { + digest.SetData(digest_in, digest_len); +} + +SSLFingerprint::SSLFingerprint(const SSLFingerprint& from) + : algorithm(from.algorithm), digest(from.digest) {} + +bool SSLFingerprint::operator==(const SSLFingerprint& other) const { + return algorithm == other.algorithm && + digest == other.digest; +} + +std::string SSLFingerprint::GetRfc4572Fingerprint() const { + std::string fingerprint = + rtc::hex_encode_with_delimiter( + digest.data(), digest.length(), ':'); + std::transform(fingerprint.begin(), fingerprint.end(), + fingerprint.begin(), ::toupper); + return fingerprint; +} + +std::string SSLFingerprint::ToString() { + std::string fp_str = algorithm; + fp_str.append(" "); + fp_str.append(GetRfc4572Fingerprint()); + return fp_str; +} + +} // namespace rtc diff --git a/webrtc/base/sslfingerprint.h b/webrtc/base/sslfingerprint.h new file mode 100644 index 000000000..a63b3dd87 --- /dev/null +++ b/webrtc/base/sslfingerprint.h @@ -0,0 +1,50 @@ +/* + * Copyright 2012 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_BASE_SSLFINGERPRINT_H_ +#define WEBRTC_BASE_SSLFINGERPRINT_H_ + +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +class SSLCertificate; + +struct SSLFingerprint { + static SSLFingerprint* Create(const std::string& algorithm, + const rtc::SSLIdentity* identity); + + static SSLFingerprint* Create(const std::string& algorithm, + const rtc::SSLCertificate* cert); + + static SSLFingerprint* CreateFromRfc4572(const std::string& algorithm, + const std::string& fingerprint); + + SSLFingerprint(const std::string& algorithm, const uint8* digest_in, + size_t digest_len); + + SSLFingerprint(const SSLFingerprint& from); + + bool operator==(const SSLFingerprint& other) const; + + std::string GetRfc4572Fingerprint() const; + + std::string ToString(); + + std::string algorithm; + rtc::Buffer digest; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLFINGERPRINT_H_ diff --git a/webrtc/base/sslidentity.cc b/webrtc/base/sslidentity.cc new file mode 100644 index 000000000..00085740d --- /dev/null +++ b/webrtc/base/sslidentity.cc @@ -0,0 +1,154 @@ +/* + * Copyright 2004 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. + */ + +// Handling of certificates and keypairs for SSLStreamAdapter's peer mode. +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/sslidentity.h" + +#include + +#include "webrtc/base/base64.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/sslconfig.h" + +#if SSL_USE_SCHANNEL + +#elif SSL_USE_OPENSSL // !SSL_USE_SCHANNEL + +#include "webrtc/base/opensslidentity.h" + +#elif SSL_USE_NSS // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + +#include "webrtc/base/nssidentity.h" + +#endif // SSL_USE_SCHANNEL + +namespace rtc { + +const char kPemTypeCertificate[] = "CERTIFICATE"; +const char kPemTypeRsaPrivateKey[] = "RSA PRIVATE KEY"; + +bool SSLIdentity::PemToDer(const std::string& pem_type, + const std::string& pem_string, + std::string* der) { + // Find the inner body. We need this to fulfill the contract of + // returning pem_length. + size_t header = pem_string.find("-----BEGIN " + pem_type + "-----"); + if (header == std::string::npos) + return false; + + size_t body = pem_string.find("\n", header); + if (body == std::string::npos) + return false; + + size_t trailer = pem_string.find("-----END " + pem_type + "-----"); + if (trailer == std::string::npos) + return false; + + std::string inner = pem_string.substr(body + 1, trailer - (body + 1)); + + *der = Base64::Decode(inner, Base64::DO_PARSE_WHITE | + Base64::DO_PAD_ANY | + Base64::DO_TERM_BUFFER); + return true; +} + +std::string SSLIdentity::DerToPem(const std::string& pem_type, + const unsigned char* data, + size_t length) { + std::stringstream result; + + result << "-----BEGIN " << pem_type << "-----\n"; + + std::string b64_encoded; + Base64::EncodeFromArray(data, length, &b64_encoded); + + // Divide the Base-64 encoded data into 64-character chunks, as per + // 4.3.2.4 of RFC 1421. + static const size_t kChunkSize = 64; + size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize; + for (size_t i = 0, chunk_offset = 0; i < chunks; + ++i, chunk_offset += kChunkSize) { + result << b64_encoded.substr(chunk_offset, kChunkSize); + result << "\n"; + } + + result << "-----END " << pem_type << "-----\n"; + + return result.str(); +} + +#if SSL_USE_SCHANNEL + +SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) { + return NULL; +} + +SSLIdentity* SSLIdentity::Generate(const std::string& common_name) { + return NULL; +} + +SSLIdentity* GenerateForTest(const SSLIdentityParams& params) { + return NULL; +} + +SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + return NULL; +} + +#elif SSL_USE_OPENSSL // !SSL_USE_SCHANNEL + +SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) { + return OpenSSLCertificate::FromPEMString(pem_string); +} + +SSLIdentity* SSLIdentity::Generate(const std::string& common_name) { + return OpenSSLIdentity::Generate(common_name); +} + +SSLIdentity* SSLIdentity::GenerateForTest(const SSLIdentityParams& params) { + return OpenSSLIdentity::GenerateForTest(params); +} + +SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + return OpenSSLIdentity::FromPEMStrings(private_key, certificate); +} + +#elif SSL_USE_NSS // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL + +SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) { + return NSSCertificate::FromPEMString(pem_string); +} + +SSLIdentity* SSLIdentity::Generate(const std::string& common_name) { + return NSSIdentity::Generate(common_name); +} + +SSLIdentity* SSLIdentity::GenerateForTest(const SSLIdentityParams& params) { + return NSSIdentity::GenerateForTest(params); +} + +SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + return NSSIdentity::FromPEMStrings(private_key, certificate); +} + +#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS + +#error "No SSL implementation" + +#endif // SSL_USE_SCHANNEL + +} // namespace rtc diff --git a/webrtc/base/sslidentity.h b/webrtc/base/sslidentity.h new file mode 100644 index 000000000..a0f32fd3b --- /dev/null +++ b/webrtc/base/sslidentity.h @@ -0,0 +1,172 @@ +/* + * Copyright 2004 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. + */ + +// Handling of certificates and keypairs for SSLStreamAdapter's peer mode. + +#ifndef WEBRTC_BASE_SSLIDENTITY_H_ +#define WEBRTC_BASE_SSLIDENTITY_H_ + +#include +#include +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/messagedigest.h" + +namespace rtc { + +// Forward declaration due to circular dependency with SSLCertificate. +class SSLCertChain; + +// Abstract interface overridden by SSL library specific +// implementations. + +// A somewhat opaque type used to encapsulate a certificate. +// Wraps the SSL library's notion of a certificate, with reference counting. +// The SSLCertificate object is pretty much immutable once created. +// (The OpenSSL implementation only does reference counting and +// possibly caching of intermediate results.) +class SSLCertificate { + public: + // Parses and build a certificate from a PEM encoded string. + // Returns NULL on failure. + // The length of the string representation of the certificate is + // stored in *pem_length if it is non-NULL, and only if + // parsing was successful. + // Caller is responsible for freeing the returned object. + static SSLCertificate* FromPEMString(const std::string& pem_string); + virtual ~SSLCertificate() {} + + // Returns a new SSLCertificate object instance wrapping the same + // underlying certificate, including its chain if present. + // Caller is responsible for freeing the returned object. + virtual SSLCertificate* GetReference() const = 0; + + // Provides the cert chain, or returns false. The caller owns the chain. + // The chain includes a copy of each certificate, excluding the leaf. + virtual bool GetChain(SSLCertChain** chain) const = 0; + + // Returns a PEM encoded string representation of the certificate. + virtual std::string ToPEMString() const = 0; + + // Provides a DER encoded binary representation of the certificate. + virtual void ToDER(Buffer* der_buffer) const = 0; + + // Gets the name of the digest algorithm that was used to compute this + // certificate's signature. + virtual bool GetSignatureDigestAlgorithm(std::string* algorithm) const = 0; + + // Compute the digest of the certificate given algorithm + virtual bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const = 0; +}; + +// SSLCertChain is a simple wrapper for a vector of SSLCertificates. It serves +// primarily to ensure proper memory management (especially deletion) of the +// SSLCertificate pointers. +class SSLCertChain { + public: + // These constructors copy the provided SSLCertificate(s), so the caller + // retains ownership. + explicit SSLCertChain(const std::vector& certs) { + ASSERT(!certs.empty()); + certs_.resize(certs.size()); + std::transform(certs.begin(), certs.end(), certs_.begin(), DupCert); + } + explicit SSLCertChain(const SSLCertificate* cert) { + certs_.push_back(cert->GetReference()); + } + + ~SSLCertChain() { + std::for_each(certs_.begin(), certs_.end(), DeleteCert); + } + + // Vector access methods. + size_t GetSize() const { return certs_.size(); } + + // Returns a temporary reference, only valid until the chain is destroyed. + const SSLCertificate& Get(size_t pos) const { return *(certs_[pos]); } + + // Returns a new SSLCertChain object instance wrapping the same underlying + // certificate chain. Caller is responsible for freeing the returned object. + SSLCertChain* Copy() const { + return new SSLCertChain(certs_); + } + + private: + // Helper function for duplicating a vector of certificates. + static SSLCertificate* DupCert(const SSLCertificate* cert) { + return cert->GetReference(); + } + + // Helper function for deleting a vector of certificates. + static void DeleteCert(SSLCertificate* cert) { delete cert; } + + std::vector certs_; + + DISALLOW_COPY_AND_ASSIGN(SSLCertChain); +}; + +// Parameters for generating an identity for testing. If common_name is +// non-empty, it will be used for the certificate's subject and issuer name, +// otherwise a random string will be used. |not_before| and |not_after| are +// offsets to the current time in number of seconds. +struct SSLIdentityParams { + std::string common_name; + int not_before; // in seconds. + int not_after; // in seconds. +}; + +// Our identity in an SSL negotiation: a keypair and certificate (both +// with the same public key). +// This too is pretty much immutable once created. +class SSLIdentity { + public: + // Generates an identity (keypair and self-signed certificate). If + // common_name is non-empty, it will be used for the certificate's + // subject and issuer name, otherwise a random string will be used. + // Returns NULL on failure. + // Caller is responsible for freeing the returned object. + static SSLIdentity* Generate(const std::string& common_name); + + // Generates an identity with the specified validity period. + static SSLIdentity* GenerateForTest(const SSLIdentityParams& params); + + // Construct an identity from a private key and a certificate. + static SSLIdentity* FromPEMStrings(const std::string& private_key, + const std::string& certificate); + + virtual ~SSLIdentity() {} + + // Returns a new SSLIdentity object instance wrapping the same + // identity information. + // Caller is responsible for freeing the returned object. + virtual SSLIdentity* GetReference() const = 0; + + // Returns a temporary reference to the certificate. + virtual const SSLCertificate& certificate() const = 0; + + // Helpers for parsing converting between PEM and DER format. + static bool PemToDer(const std::string& pem_type, + const std::string& pem_string, + std::string* der); + static std::string DerToPem(const std::string& pem_type, + const unsigned char* data, + size_t length); +}; + +extern const char kPemTypeCertificate[]; +extern const char kPemTypeRsaPrivateKey[]; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLIDENTITY_H_ diff --git a/webrtc/base/sslidentity_unittest.cc b/webrtc/base/sslidentity_unittest.cc new file mode 100644 index 000000000..1486bebb5 --- /dev/null +++ b/webrtc/base/sslidentity_unittest.cc @@ -0,0 +1,208 @@ +/* + * Copyright 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 "webrtc/base/gunit.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslidentity.h" + +using rtc::SSLIdentity; + +const char kTestCertificate[] = "-----BEGIN CERTIFICATE-----\n" + "MIIB6TCCAVICAQYwDQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCQVUxEzARBgNV\n" + "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRswGQYD\n" + "VQQDExJUZXN0IENBICgxMDI0IGJpdCkwHhcNMDAxMDE2MjIzMTAzWhcNMDMwMTE0\n" + "MjIzMTAzWjBjMQswCQYDVQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFuZDEaMBgG\n" + "A1UEChMRQ3J5cHRTb2Z0IFB0eSBMdGQxIzAhBgNVBAMTGlNlcnZlciB0ZXN0IGNl\n" + "cnQgKDUxMiBiaXQpMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ+zw4Qnlf8SMVIP\n" + "Fe9GEcStgOY2Ww/dgNdhjeD8ckUJNP5VZkVDTGiXav6ooKXfX3j/7tdkuD8Ey2//\n" + "Kv7+ue0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCT0grFQeZaqYb5EYfk20XixZV4\n" + "GmyAbXMftG1Eo7qGiMhYzRwGNWxEYojf5PZkYZXvSqZ/ZXHXa4g59jK/rJNnaVGM\n" + "k+xIX8mxQvlV0n5O9PIha5BX5teZnkHKgL8aKKLKW1BK7YTngsfSzzaeame5iKfz\n" + "itAE+OjGF+PFKbwX8Q==\n" + "-----END CERTIFICATE-----\n"; + +const unsigned char kTestCertSha1[] = {0xA6, 0xC8, 0x59, 0xEA, + 0xC3, 0x7E, 0x6D, 0x33, + 0xCF, 0xE2, 0x69, 0x9D, + 0x74, 0xE6, 0xF6, 0x8A, + 0x9E, 0x47, 0xA7, 0xCA}; + +class SSLIdentityTest : public testing::Test { + public: + SSLIdentityTest() : + identity1_(), identity2_() { + } + + ~SSLIdentityTest() { + } + + static void SetUpTestCase() { + rtc::InitializeSSL(); + } + + static void TearDownTestCase() { + rtc::CleanupSSL(); + } + + virtual void SetUp() { + identity1_.reset(SSLIdentity::Generate("test1")); + identity2_.reset(SSLIdentity::Generate("test2")); + + ASSERT_TRUE(identity1_); + ASSERT_TRUE(identity2_); + + test_cert_.reset( + rtc::SSLCertificate::FromPEMString(kTestCertificate)); + ASSERT_TRUE(test_cert_); + } + + void TestGetSignatureDigestAlgorithm() { + std::string digest_algorithm; + // Both NSSIdentity::Generate and OpenSSLIdentity::Generate are + // hard-coded to generate RSA-SHA1 certificates. + ASSERT_TRUE(identity1_->certificate().GetSignatureDigestAlgorithm( + &digest_algorithm)); + ASSERT_EQ(rtc::DIGEST_SHA_1, digest_algorithm); + ASSERT_TRUE(identity2_->certificate().GetSignatureDigestAlgorithm( + &digest_algorithm)); + ASSERT_EQ(rtc::DIGEST_SHA_1, digest_algorithm); + + // The test certificate has an MD5-based signature. + ASSERT_TRUE(test_cert_->GetSignatureDigestAlgorithm(&digest_algorithm)); + ASSERT_EQ(rtc::DIGEST_MD5, digest_algorithm); + } + + void TestDigest(const std::string &algorithm, size_t expected_len, + const unsigned char *expected_digest = NULL) { + unsigned char digest1[64]; + unsigned char digest1b[64]; + unsigned char digest2[64]; + size_t digest1_len; + size_t digest1b_len; + size_t digest2_len; + bool rv; + + rv = identity1_->certificate().ComputeDigest(algorithm, + digest1, sizeof(digest1), + &digest1_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest1_len); + + rv = identity1_->certificate().ComputeDigest(algorithm, + digest1b, sizeof(digest1b), + &digest1b_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest1b_len); + EXPECT_EQ(0, memcmp(digest1, digest1b, expected_len)); + + + rv = identity2_->certificate().ComputeDigest(algorithm, + digest2, sizeof(digest2), + &digest2_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest2_len); + EXPECT_NE(0, memcmp(digest1, digest2, expected_len)); + + // If we have an expected hash for the test cert, check it. + if (expected_digest) { + unsigned char digest3[64]; + size_t digest3_len; + + rv = test_cert_->ComputeDigest(algorithm, digest3, sizeof(digest3), + &digest3_len); + EXPECT_TRUE(rv); + EXPECT_EQ(expected_len, digest3_len); + EXPECT_EQ(0, memcmp(digest3, expected_digest, expected_len)); + } + } + + private: + rtc::scoped_ptr identity1_; + rtc::scoped_ptr identity2_; + rtc::scoped_ptr test_cert_; +}; + +TEST_F(SSLIdentityTest, DigestSHA1) { + TestDigest(rtc::DIGEST_SHA_1, 20, kTestCertSha1); +} + +// HASH_AlgSHA224 is not supported in the chromium linux build. +#if SSL_USE_NSS +TEST_F(SSLIdentityTest, DISABLED_DigestSHA224) { +#else +TEST_F(SSLIdentityTest, DigestSHA224) { +#endif + TestDigest(rtc::DIGEST_SHA_224, 28); +} + +TEST_F(SSLIdentityTest, DigestSHA256) { + TestDigest(rtc::DIGEST_SHA_256, 32); +} + +TEST_F(SSLIdentityTest, DigestSHA384) { + TestDigest(rtc::DIGEST_SHA_384, 48); +} + +TEST_F(SSLIdentityTest, DigestSHA512) { + TestDigest(rtc::DIGEST_SHA_512, 64); +} + +TEST_F(SSLIdentityTest, FromPEMStrings) { + static const char kRSA_PRIVATE_KEY_PEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAECgYAvgOs4FJcgvp+TuREx7YtiYVsH\n" + "mwQPTum2z/8VzWGwR8BBHBvIpVe1MbD/Y4seyI2aco/7UaisatSgJhsU46/9Y4fq\n" + "2TwXH9QANf4at4d9n/R6rzwpAJOpgwZgKvdQjkfrKTtgLV+/dawvpxUYkRH4JZM1\n" + "CVGukMfKNrSVH4Ap4QJBAOJmGV1ASPnB4r4nc99at7JuIJmd7fmuVUwUgYi4XgaR\n" + "WhScBsgYwZ/JoywdyZJgnbcrTDuVcWG56B3vXbhdpMsCQQDf9zeJrjnPZ3Cqm79y\n" + "kdqANep0uwZciiNiWxsQrCHztywOvbFhdp8iYVFG9EK8DMY41Y5TxUwsHD+67zao\n" + "ZNqJAkEA1suLUP/GvL8IwuRneQd2tWDqqRQ/Td3qq03hP7e77XtF/buya3Ghclo5\n" + "54czUR89QyVfJEC6278nzA7n2h1uVQJAcG6mztNL6ja/dKZjYZye2CY44QjSlLo0\n" + "MTgTSjdfg/28fFn2Jjtqf9Pi/X+50LWI/RcYMC2no606wRk9kyOuIQJBAK6VSAim\n" + "1pOEjsYQn0X5KEIrz1G3bfCbB848Ime3U2/FWlCHMr6ch8kCZ5d1WUeJD3LbwMNG\n" + "UCXiYxSsu20QNVw=\n" + "-----END RSA PRIVATE KEY-----\n"; + + static const char kCERT_PEM[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIBmTCCAQKgAwIBAgIEbzBSAjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZX\n" + "ZWJSVEMwHhcNMTQwMTAyMTgyNDQ3WhcNMTQwMjAxMTgyNDQ3WjARMQ8wDQYDVQQD\n" + "EwZXZWJSVEMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAUflI\n" + "VUe5Krqf5RVa5C3u/UTAOAUJBiDS3VANTCLBxjuMsvqOG0WvaYWP3HYPgrz0jXK2\n" + "LJE/mGw3MyFHEqi81jh95J+ypl6xKW6Rm8jKLR87gUvCaVYn/Z4/P3AqcQTB7wOv\n" + "UD0A8qfhfDM+LK6rPAnCsVN0NRDY3jvd6rzix9M=\n" + "-----END CERTIFICATE-----\n"; + + rtc::scoped_ptr identity( + SSLIdentity::FromPEMStrings(kRSA_PRIVATE_KEY_PEM, kCERT_PEM)); + EXPECT_TRUE(identity); + EXPECT_EQ(kCERT_PEM, identity->certificate().ToPEMString()); +} + +TEST_F(SSLIdentityTest, PemDerConversion) { + std::string der; + EXPECT_TRUE(SSLIdentity::PemToDer("CERTIFICATE", kTestCertificate, &der)); + + EXPECT_EQ(kTestCertificate, SSLIdentity::DerToPem( + "CERTIFICATE", + reinterpret_cast(der.data()), der.length())); +} + +TEST_F(SSLIdentityTest, GetSignatureDigestAlgorithm) { + TestGetSignatureDigestAlgorithm(); +} diff --git a/webrtc/base/sslroots.h b/webrtc/base/sslroots.h new file mode 100644 index 000000000..31d601c16 --- /dev/null +++ b/webrtc/base/sslroots.h @@ -0,0 +1,4932 @@ +// This file is the root certificates in C form that are needed to connect to +// Google. + +// It was generated with the following command line: +// > python //depot/googleclient/talk/tools/generate_sslroots.py +// //depot/google3/security/cacerts/for_connecting_to_google/roots.pem + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root */ + +namespace rtc { + +const unsigned char AddTrust_External_Root_certificate[1082]={ +0x30,0x82,0x04,0x36,0x30,0x82,0x03,0x1E,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,0x13,0x1D,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C, +0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x22,0x30,0x20, +0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20, +0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74, +0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30,0x31,0x30,0x34,0x38,0x33,0x38, +0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31,0x30,0x34,0x38,0x33,0x38,0x5A, +0x30,0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31, +0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75, +0x73,0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,0x13,0x1D, +0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61, +0x6C,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x22,0x30, +0x20,0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74, +0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F, +0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01, +0x01,0x00,0xB7,0xF7,0x1A,0x33,0xE6,0xF2,0x00,0x04,0x2D,0x39,0xE0,0x4E,0x5B,0xED, +0x1F,0xBC,0x6C,0x0F,0xCD,0xB5,0xFA,0x23,0xB6,0xCE,0xDE,0x9B,0x11,0x33,0x97,0xA4, +0x29,0x4C,0x7D,0x93,0x9F,0xBD,0x4A,0xBC,0x93,0xED,0x03,0x1A,0xE3,0x8F,0xCF,0xE5, +0x6D,0x50,0x5A,0xD6,0x97,0x29,0x94,0x5A,0x80,0xB0,0x49,0x7A,0xDB,0x2E,0x95,0xFD, +0xB8,0xCA,0xBF,0x37,0x38,0x2D,0x1E,0x3E,0x91,0x41,0xAD,0x70,0x56,0xC7,0xF0,0x4F, +0x3F,0xE8,0x32,0x9E,0x74,0xCA,0xC8,0x90,0x54,0xE9,0xC6,0x5F,0x0F,0x78,0x9D,0x9A, +0x40,0x3C,0x0E,0xAC,0x61,0xAA,0x5E,0x14,0x8F,0x9E,0x87,0xA1,0x6A,0x50,0xDC,0xD7, +0x9A,0x4E,0xAF,0x05,0xB3,0xA6,0x71,0x94,0x9C,0x71,0xB3,0x50,0x60,0x0A,0xC7,0x13, +0x9D,0x38,0x07,0x86,0x02,0xA8,0xE9,0xA8,0x69,0x26,0x18,0x90,0xAB,0x4C,0xB0,0x4F, +0x23,0xAB,0x3A,0x4F,0x84,0xD8,0xDF,0xCE,0x9F,0xE1,0x69,0x6F,0xBB,0xD7,0x42,0xD7, +0x6B,0x44,0xE4,0xC7,0xAD,0xEE,0x6D,0x41,0x5F,0x72,0x5A,0x71,0x08,0x37,0xB3,0x79, +0x65,0xA4,0x59,0xA0,0x94,0x37,0xF7,0x00,0x2F,0x0D,0xC2,0x92,0x72,0xDA,0xD0,0x38, +0x72,0xDB,0x14,0xA8,0x45,0xC4,0x5D,0x2A,0x7D,0xB7,0xB4,0xD6,0xC4,0xEE,0xAC,0xCD, +0x13,0x44,0xB7,0xC9,0x2B,0xDD,0x43,0x00,0x25,0xFA,0x61,0xB9,0x69,0x6A,0x58,0x23, +0x11,0xB7,0xA7,0x33,0x8F,0x56,0x75,0x59,0xF5,0xCD,0x29,0xD7,0x46,0xB7,0x0A,0x2B, +0x65,0xB6,0xD3,0x42,0x6F,0x15,0xB2,0xB8,0x7B,0xFB,0xEF,0xE9,0x5D,0x53,0xD5,0x34, +0x5A,0x27,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xDC,0x30,0x81,0xD9,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xAD,0xBD,0x98,0x7A,0x34,0xB4,0x26,0xF7, +0xFA,0xC4,0x26,0x54,0xEF,0x03,0xBD,0xE0,0x24,0xCB,0x54,0x1A,0x30,0x0B,0x06,0x03, +0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13, +0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x99,0x06,0x03,0x55, +0x1D,0x23,0x04,0x81,0x91,0x30,0x81,0x8E,0x80,0x14,0xAD,0xBD,0x98,0x7A,0x34,0xB4, +0x26,0xF7,0xFA,0xC4,0x26,0x54,0xEF,0x03,0xBD,0xE0,0x24,0xCB,0x54,0x1A,0xA1,0x73, +0xA4,0x71,0x30,0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53, +0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54, +0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B, +0x13,0x1D,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72, +0x6E,0x61,0x6C,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31, +0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75, +0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52, +0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xB0,0x9B,0xE0,0x85,0x25,0xC2, +0xD6,0x23,0xE2,0x0F,0x96,0x06,0x92,0x9D,0x41,0x98,0x9C,0xD9,0x84,0x79,0x81,0xD9, +0x1E,0x5B,0x14,0x07,0x23,0x36,0x65,0x8F,0xB0,0xD8,0x77,0xBB,0xAC,0x41,0x6C,0x47, +0x60,0x83,0x51,0xB0,0xF9,0x32,0x3D,0xE7,0xFC,0xF6,0x26,0x13,0xC7,0x80,0x16,0xA5, +0xBF,0x5A,0xFC,0x87,0xCF,0x78,0x79,0x89,0x21,0x9A,0xE2,0x4C,0x07,0x0A,0x86,0x35, +0xBC,0xF2,0xDE,0x51,0xC4,0xD2,0x96,0xB7,0xDC,0x7E,0x4E,0xEE,0x70,0xFD,0x1C,0x39, +0xEB,0x0C,0x02,0x51,0x14,0x2D,0x8E,0xBD,0x16,0xE0,0xC1,0xDF,0x46,0x75,0xE7,0x24, +0xAD,0xEC,0xF4,0x42,0xB4,0x85,0x93,0x70,0x10,0x67,0xBA,0x9D,0x06,0x35,0x4A,0x18, +0xD3,0x2B,0x7A,0xCC,0x51,0x42,0xA1,0x7A,0x63,0xD1,0xE6,0xBB,0xA1,0xC5,0x2B,0xC2, +0x36,0xBE,0x13,0x0D,0xE6,0xBD,0x63,0x7E,0x79,0x7B,0xA7,0x09,0x0D,0x40,0xAB,0x6A, +0xDD,0x8F,0x8A,0xC3,0xF6,0xF6,0x8C,0x1A,0x42,0x05,0x51,0xD4,0x45,0xF5,0x9F,0xA7, +0x62,0x21,0x68,0x15,0x20,0x43,0x3C,0x99,0xE7,0x7C,0xBD,0x24,0xD8,0xA9,0x91,0x17, +0x73,0x88,0x3F,0x56,0x1B,0x31,0x38,0x18,0xB4,0x71,0x0F,0x9A,0xCD,0xC8,0x0E,0x9E, +0x8E,0x2E,0x1B,0xE1,0x8C,0x98,0x83,0xCB,0x1F,0x31,0xF1,0x44,0x4C,0xC6,0x04,0x73, +0x49,0x76,0x60,0x0F,0xC7,0xF8,0xBD,0x17,0x80,0x6B,0x2E,0xE9,0xCC,0x4C,0x0E,0x5A, +0x9A,0x79,0x0F,0x20,0x0A,0x2E,0xD5,0x9E,0x63,0x26,0x1E,0x55,0x92,0x94,0xD8,0x82, +0x17,0x5A,0x7B,0xD0,0xBC,0xC7,0x8F,0x4E,0x86,0x04, +}; + + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Class 1 CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Class 1 CA Root */ + + +const unsigned char AddTrust_Low_Value_Services_Root_certificate[1052]={ +0x30,0x82,0x04,0x18,0x30,0x82,0x03,0x00,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x43, +0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30, +0x31,0x30,0x33,0x38,0x33,0x31,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31, +0x30,0x33,0x38,0x33,0x31,0x5A,0x30,0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B, +0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06, +0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54, +0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03, +0x55,0x04,0x03,0x13,0x18,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x31,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01, +0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0x96,0x96, +0xD4,0x21,0x49,0x60,0xE2,0x6B,0xE8,0x41,0x07,0x0C,0xDE,0xC4,0xE0,0xDC,0x13,0x23, +0xCD,0xC1,0x35,0xC7,0xFB,0xD6,0x4E,0x11,0x0A,0x67,0x5E,0xF5,0x06,0x5B,0x6B,0xA5, +0x08,0x3B,0x5B,0x29,0x16,0x3A,0xE7,0x87,0xB2,0x34,0x06,0xC5,0xBC,0x05,0xA5,0x03, +0x7C,0x82,0xCB,0x29,0x10,0xAE,0xE1,0x88,0x81,0xBD,0xD6,0x9E,0xD3,0xFE,0x2D,0x56, +0xC1,0x15,0xCE,0xE3,0x26,0x9D,0x15,0x2E,0x10,0xFB,0x06,0x8F,0x30,0x04,0xDE,0xA7, +0xB4,0x63,0xB4,0xFF,0xB1,0x9C,0xAE,0x3C,0xAF,0x77,0xB6,0x56,0xC5,0xB5,0xAB,0xA2, +0xE9,0x69,0x3A,0x3D,0x0E,0x33,0x79,0x32,0x3F,0x70,0x82,0x92,0x99,0x61,0x6D,0x8D, +0x30,0x08,0x8F,0x71,0x3F,0xA6,0x48,0x57,0x19,0xF8,0x25,0xDC,0x4B,0x66,0x5C,0xA5, +0x74,0x8F,0x98,0xAE,0xC8,0xF9,0xC0,0x06,0x22,0xE7,0xAC,0x73,0xDF,0xA5,0x2E,0xFB, +0x52,0xDC,0xB1,0x15,0x65,0x20,0xFA,0x35,0x66,0x69,0xDE,0xDF,0x2C,0xF1,0x6E,0xBC, +0x30,0xDB,0x2C,0x24,0x12,0xDB,0xEB,0x35,0x35,0x68,0x90,0xCB,0x00,0xB0,0x97,0x21, +0x3D,0x74,0x21,0x23,0x65,0x34,0x2B,0xBB,0x78,0x59,0xA3,0xD6,0xE1,0x76,0x39,0x9A, +0xA4,0x49,0x8E,0x8C,0x74,0xAF,0x6E,0xA4,0x9A,0xA3,0xD9,0x9B,0xD2,0x38,0x5C,0x9B, +0xA2,0x18,0xCC,0x75,0x23,0x84,0xBE,0xEB,0xE2,0x4D,0x33,0x71,0x8E,0x1A,0xF0,0xC2, +0xF8,0xC7,0x1D,0xA2,0xAD,0x03,0x97,0x2C,0xF8,0xCF,0x25,0xC6,0xF6,0xB8,0x24,0x31, +0xB1,0x63,0x5D,0x92,0x7F,0x63,0xF0,0x25,0xC9,0x53,0x2E,0x1F,0xBF,0x4D,0x02,0x03, +0x01,0x00,0x01,0xA3,0x81,0xD2,0x30,0x81,0xCF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E, +0x04,0x16,0x04,0x14,0x95,0xB1,0xB4,0xF0,0x94,0xB6,0xBD,0xC7,0xDA,0xD1,0x11,0x09, +0x21,0xBE,0xC1,0xAF,0x49,0xFD,0x10,0x7B,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04, +0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x8F,0x06,0x03,0x55,0x1D,0x23,0x04,0x81, +0x87,0x30,0x81,0x84,0x80,0x14,0x95,0xB1,0xB4,0xF0,0x94,0xB6,0xBD,0xC7,0xDA,0xD1, +0x11,0x09,0x21,0xBE,0xC1,0xAF,0x49,0xFD,0x10,0x7B,0xA1,0x69,0xA4,0x67,0x30,0x65, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30, +0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74, +0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F, +0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x41,0x64,0x64, +0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x43,0x41, +0x20,0x52,0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x2C,0x6D,0x64,0x1B, +0x1F,0xCD,0x0D,0xDD,0xB9,0x01,0xFA,0x96,0x63,0x34,0x32,0x48,0x47,0x99,0xAE,0x97, +0xED,0xFD,0x72,0x16,0xA6,0x73,0x47,0x5A,0xF4,0xEB,0xDD,0xE9,0xF5,0xD6,0xFB,0x45, +0xCC,0x29,0x89,0x44,0x5D,0xBF,0x46,0x39,0x3D,0xE8,0xEE,0xBC,0x4D,0x54,0x86,0x1E, +0x1D,0x6C,0xE3,0x17,0x27,0x43,0xE1,0x89,0x56,0x2B,0xA9,0x6F,0x72,0x4E,0x49,0x33, +0xE3,0x72,0x7C,0x2A,0x23,0x9A,0xBC,0x3E,0xFF,0x28,0x2A,0xED,0xA3,0xFF,0x1C,0x23, +0xBA,0x43,0x57,0x09,0x67,0x4D,0x4B,0x62,0x06,0x2D,0xF8,0xFF,0x6C,0x9D,0x60,0x1E, +0xD8,0x1C,0x4B,0x7D,0xB5,0x31,0x2F,0xD9,0xD0,0x7C,0x5D,0xF8,0xDE,0x6B,0x83,0x18, +0x78,0x37,0x57,0x2F,0xE8,0x33,0x07,0x67,0xDF,0x1E,0xC7,0x6B,0x2A,0x95,0x76,0xAE, +0x8F,0x57,0xA3,0xF0,0xF4,0x52,0xB4,0xA9,0x53,0x08,0xCF,0xE0,0x4F,0xD3,0x7A,0x53, +0x8B,0xFD,0xBB,0x1C,0x56,0x36,0xF2,0xFE,0xB2,0xB6,0xE5,0x76,0xBB,0xD5,0x22,0x65, +0xA7,0x3F,0xFE,0xD1,0x66,0xAD,0x0B,0xBC,0x6B,0x99,0x86,0xEF,0x3F,0x7D,0xF3,0x18, +0x32,0xCA,0x7B,0xC6,0xE3,0xAB,0x64,0x46,0x95,0xF8,0x26,0x69,0xD9,0x55,0x83,0x7B, +0x2C,0x96,0x07,0xFF,0x59,0x2C,0x44,0xA3,0xC6,0xE5,0xE9,0xA9,0xDC,0xA1,0x63,0x80, +0x5A,0x21,0x5E,0x21,0xCF,0x53,0x54,0xF0,0xBA,0x6F,0x89,0xDB,0xA8,0xAA,0x95,0xCF, +0x8B,0xE3,0x71,0xCC,0x1E,0x1B,0x20,0x44,0x08,0xC0,0x7A,0xB6,0x40,0xFD,0xC4,0xE4, +0x35,0xE1,0x1D,0x16,0x1C,0xD0,0xBC,0x2B,0x8E,0xD6,0x71,0xD9, +}; + + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Public CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Public CA Root */ + + +const unsigned char AddTrust_Public_Services_Root_certificate[1049]={ +0x30,0x82,0x04,0x15,0x30,0x82,0x02,0xFD,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x64,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x43,0x41, +0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30,0x31, +0x30,0x34,0x31,0x35,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31,0x30, +0x34,0x31,0x35,0x30,0x5A,0x30,0x64,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03, +0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54, +0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x20,0x30,0x1E,0x06,0x03,0x55, +0x04,0x03,0x13,0x17,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x75,0x62, +0x6C,0x69,0x63,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xE9,0x1A,0x30,0x8F, +0x83,0x88,0x14,0xC1,0x20,0xD8,0x3C,0x9B,0x8F,0x1B,0x7E,0x03,0x74,0xBB,0xDA,0x69, +0xD3,0x46,0xA5,0xF8,0x8E,0xC2,0x0C,0x11,0x90,0x51,0xA5,0x2F,0x66,0x54,0x40,0x55, +0xEA,0xDB,0x1F,0x4A,0x56,0xEE,0x9F,0x23,0x6E,0xF4,0x39,0xCB,0xA1,0xB9,0x6F,0xF2, +0x7E,0xF9,0x5D,0x87,0x26,0x61,0x9E,0x1C,0xF8,0xE2,0xEC,0xA6,0x81,0xF8,0x21,0xC5, +0x24,0xCC,0x11,0x0C,0x3F,0xDB,0x26,0x72,0x7A,0xC7,0x01,0x97,0x07,0x17,0xF9,0xD7, +0x18,0x2C,0x30,0x7D,0x0E,0x7A,0x1E,0x62,0x1E,0xC6,0x4B,0xC0,0xFD,0x7D,0x62,0x77, +0xD3,0x44,0x1E,0x27,0xF6,0x3F,0x4B,0x44,0xB3,0xB7,0x38,0xD9,0x39,0x1F,0x60,0xD5, +0x51,0x92,0x73,0x03,0xB4,0x00,0x69,0xE3,0xF3,0x14,0x4E,0xEE,0xD1,0xDC,0x09,0xCF, +0x77,0x34,0x46,0x50,0xB0,0xF8,0x11,0xF2,0xFE,0x38,0x79,0xF7,0x07,0x39,0xFE,0x51, +0x92,0x97,0x0B,0x5B,0x08,0x5F,0x34,0x86,0x01,0xAD,0x88,0x97,0xEB,0x66,0xCD,0x5E, +0xD1,0xFF,0xDC,0x7D,0xF2,0x84,0xDA,0xBA,0x77,0xAD,0xDC,0x80,0x08,0xC7,0xA7,0x87, +0xD6,0x55,0x9F,0x97,0x6A,0xE8,0xC8,0x11,0x64,0xBA,0xE7,0x19,0x29,0x3F,0x11,0xB3, +0x78,0x90,0x84,0x20,0x52,0x5B,0x11,0xEF,0x78,0xD0,0x83,0xF6,0xD5,0x48,0x90,0xD0, +0x30,0x1C,0xCF,0x80,0xF9,0x60,0xFE,0x79,0xE4,0x88,0xF2,0xDD,0x00,0xEB,0x94,0x45, +0xEB,0x65,0x94,0x69,0x40,0xBA,0xC0,0xD5,0xB4,0xB8,0xBA,0x7D,0x04,0x11,0xA8,0xEB, +0x31,0x05,0x96,0x94,0x4E,0x58,0x21,0x8E,0x9F,0xD0,0x60,0xFD,0x02,0x03,0x01,0x00, +0x01,0xA3,0x81,0xD1,0x30,0x81,0xCE,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0x81,0x3E,0x37,0xD8,0x92,0xB0,0x1F,0x77,0x9F,0x5C,0xB4,0xAB,0x73,0xAA, +0xE7,0xF6,0x34,0x60,0x2F,0xFA,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03, +0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x81,0x8E,0x06,0x03,0x55,0x1D,0x23,0x04,0x81,0x86,0x30, +0x81,0x83,0x80,0x14,0x81,0x3E,0x37,0xD8,0x92,0xB0,0x1F,0x77,0x9F,0x5C,0xB4,0xAB, +0x73,0xAA,0xE7,0xF6,0x34,0x60,0x2F,0xFA,0xA1,0x68,0xA4,0x66,0x30,0x64,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41, +0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54, +0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B, +0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x41,0x64,0x64,0x54,0x72, +0x75,0x73,0x74,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x43,0x41,0x20,0x52,0x6F, +0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x03,0xF7,0x15,0x4A,0xF8,0x24,0xDA, +0x23,0x56,0x16,0x93,0x76,0xDD,0x36,0x28,0xB9,0xAE,0x1B,0xB8,0xC3,0xF1,0x64,0xBA, +0x20,0x18,0x78,0x95,0x29,0x27,0x57,0x05,0xBC,0x7C,0x2A,0xF4,0xB9,0x51,0x55,0xDA, +0x87,0x02,0xDE,0x0F,0x16,0x17,0x31,0xF8,0xAA,0x79,0x2E,0x09,0x13,0xBB,0xAF,0xB2, +0x20,0x19,0x12,0xE5,0x93,0xF9,0x4B,0xF9,0x83,0xE8,0x44,0xD5,0xB2,0x41,0x25,0xBF, +0x88,0x75,0x6F,0xFF,0x10,0xFC,0x4A,0x54,0xD0,0x5F,0xF0,0xFA,0xEF,0x36,0x73,0x7D, +0x1B,0x36,0x45,0xC6,0x21,0x6D,0xB4,0x15,0xB8,0x4E,0xCF,0x9C,0x5C,0xA5,0x3D,0x5A, +0x00,0x8E,0x06,0xE3,0x3C,0x6B,0x32,0x7B,0xF2,0x9F,0xF0,0xB6,0xFD,0xDF,0xF0,0x28, +0x18,0x48,0xF0,0xC6,0xBC,0xD0,0xBF,0x34,0x80,0x96,0xC2,0x4A,0xB1,0x6D,0x8E,0xC7, +0x90,0x45,0xDE,0x2F,0x67,0xAC,0x45,0x04,0xA3,0x7A,0xDC,0x55,0x92,0xC9,0x47,0x66, +0xD8,0x1A,0x8C,0xC7,0xED,0x9C,0x4E,0x9A,0xE0,0x12,0xBB,0xB5,0x6A,0x4C,0x84,0xE1, +0xE1,0x22,0x0D,0x87,0x00,0x64,0xFE,0x8C,0x7D,0x62,0x39,0x65,0xA6,0xEF,0x42,0xB6, +0x80,0x25,0x12,0x61,0x01,0xA8,0x24,0x13,0x70,0x00,0x11,0x26,0x5F,0xFA,0x35,0x50, +0xC5,0x48,0xCC,0x06,0x47,0xE8,0x27,0xD8,0x70,0x8D,0x5F,0x64,0xE6,0xA1,0x44,0x26, +0x5E,0x22,0xEC,0x92,0xCD,0xFF,0x42,0x9A,0x44,0x21,0x6D,0x5C,0xC5,0xE3,0x22,0x1D, +0x5F,0x47,0x12,0xE7,0xCE,0x5F,0x5D,0xFA,0xD8,0xAA,0xB1,0x33,0x2D,0xD9,0x76,0xF2, +0x4E,0x3A,0x33,0x0C,0x2B,0xB3,0x2D,0x90,0x06, +}; + + +/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Qualified CA Root */ +/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Qualified CA Root */ + + +const unsigned char AddTrust_Qualified_Certificates_Root_certificate[1058]={ +0x30,0x82,0x04,0x1E,0x30,0x82,0x03,0x06,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x67,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14, +0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73, +0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41, +0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x41,0x64, +0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x51,0x75,0x61,0x6C,0x69,0x66,0x69,0x65,0x64, +0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35, +0x33,0x30,0x31,0x30,0x34,0x34,0x35,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33, +0x30,0x31,0x30,0x34,0x34,0x35,0x30,0x5A,0x30,0x67,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30, +0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74, +0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x23,0x30,0x21, +0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20, +0x51,0x75,0x61,0x6C,0x69,0x66,0x69,0x65,0x64,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F, +0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01, +0x01,0x00,0xE4,0x1E,0x9A,0xFE,0xDC,0x09,0x5A,0x87,0xA4,0x9F,0x47,0xBE,0x11,0x5F, +0xAF,0x84,0x34,0xDB,0x62,0x3C,0x79,0x78,0xB7,0xE9,0x30,0xB5,0xEC,0x0C,0x1C,0x2A, +0xC4,0x16,0xFF,0xE0,0xEC,0x71,0xEB,0x8A,0xF5,0x11,0x6E,0xED,0x4F,0x0D,0x91,0xD2, +0x12,0x18,0x2D,0x49,0x15,0x01,0xC2,0xA4,0x22,0x13,0xC7,0x11,0x64,0xFF,0x22,0x12, +0x9A,0xB9,0x8E,0x5C,0x2F,0x08,0xCF,0x71,0x6A,0xB3,0x67,0x01,0x59,0xF1,0x5D,0x46, +0xF3,0xB0,0x78,0xA5,0xF6,0x0E,0x42,0x7A,0xE3,0x7F,0x1B,0xCC,0xD0,0xF0,0xB7,0x28, +0xFD,0x2A,0xEA,0x9E,0xB3,0xB0,0xB9,0x04,0xAA,0xFD,0xF6,0xC7,0xB4,0xB1,0xB8,0x2A, +0xA0,0xFB,0x58,0xF1,0x19,0xA0,0x6F,0x70,0x25,0x7E,0x3E,0x69,0x4A,0x7F,0x0F,0x22, +0xD8,0xEF,0xAD,0x08,0x11,0x9A,0x29,0x99,0xE1,0xAA,0x44,0x45,0x9A,0x12,0x5E,0x3E, +0x9D,0x6D,0x52,0xFC,0xE7,0xA0,0x3D,0x68,0x2F,0xF0,0x4B,0x70,0x7C,0x13,0x38,0xAD, +0xBC,0x15,0x25,0xF1,0xD6,0xCE,0xAB,0xA2,0xC0,0x31,0xD6,0x2F,0x9F,0xE0,0xFF,0x14, +0x59,0xFC,0x84,0x93,0xD9,0x87,0x7C,0x4C,0x54,0x13,0xEB,0x9F,0xD1,0x2D,0x11,0xF8, +0x18,0x3A,0x3A,0xDE,0x25,0xD9,0xF7,0xD3,0x40,0xED,0xA4,0x06,0x12,0xC4,0x3B,0xE1, +0x91,0xC1,0x56,0x35,0xF0,0x14,0xDC,0x65,0x36,0x09,0x6E,0xAB,0xA4,0x07,0xC7,0x35, +0xD1,0xC2,0x03,0x33,0x36,0x5B,0x75,0x26,0x6D,0x42,0xF1,0x12,0x6B,0x43,0x6F,0x4B, +0x71,0x94,0xFA,0x34,0x1D,0xED,0x13,0x6E,0xCA,0x80,0x7F,0x98,0x2F,0x6C,0xB9,0x65, +0xD8,0xE9,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xD4,0x30,0x81,0xD1,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x39,0x95,0x8B,0x62,0x8B,0x5C,0xC9,0xD4, +0x80,0xBA,0x58,0x0F,0x97,0x3F,0x15,0x08,0x43,0xCC,0x98,0xA7,0x30,0x0B,0x06,0x03, +0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13, +0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x91,0x06,0x03,0x55, +0x1D,0x23,0x04,0x81,0x89,0x30,0x81,0x86,0x80,0x14,0x39,0x95,0x8B,0x62,0x8B,0x5C, +0xC9,0xD4,0x80,0xBA,0x58,0x0F,0x97,0x3F,0x15,0x08,0x43,0xCC,0x98,0xA7,0xA1,0x6B, +0xA4,0x69,0x30,0x67,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53, +0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54, +0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B, +0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E, +0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13, +0x1A,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x51,0x75,0x61,0x6C,0x69,0x66, +0x69,0x65,0x64,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01, +0x01,0x00,0x19,0xAB,0x75,0xEA,0xF8,0x8B,0x65,0x61,0x95,0x13,0xBA,0x69,0x04,0xEF, +0x86,0xCA,0x13,0xA0,0xC7,0xAA,0x4F,0x64,0x1B,0x3F,0x18,0xF6,0xA8,0x2D,0x2C,0x55, +0x8F,0x05,0xB7,0x30,0xEA,0x42,0x6A,0x1D,0xC0,0x25,0x51,0x2D,0xA7,0xBF,0x0C,0xB3, +0xED,0xEF,0x08,0x7F,0x6C,0x3C,0x46,0x1A,0xEA,0x18,0x43,0xDF,0x76,0xCC,0xF9,0x66, +0x86,0x9C,0x2C,0x68,0xF5,0xE9,0x17,0xF8,0x31,0xB3,0x18,0xC4,0xD6,0x48,0x7D,0x23, +0x4C,0x68,0xC1,0x7E,0xBB,0x01,0x14,0x6F,0xC5,0xD9,0x6E,0xDE,0xBB,0x04,0x42,0x6A, +0xF8,0xF6,0x5C,0x7D,0xE5,0xDA,0xFA,0x87,0xEB,0x0D,0x35,0x52,0x67,0xD0,0x9E,0x97, +0x76,0x05,0x93,0x3F,0x95,0xC7,0x01,0xE6,0x69,0x55,0x38,0x7F,0x10,0x61,0x99,0xC9, +0xE3,0x5F,0xA6,0xCA,0x3E,0x82,0x63,0x48,0xAA,0xE2,0x08,0x48,0x3E,0xAA,0xF2,0xB2, +0x85,0x62,0xA6,0xB4,0xA7,0xD9,0xBD,0x37,0x9C,0x68,0xB5,0x2D,0x56,0x7D,0xB0,0xB7, +0x3F,0xA0,0xB1,0x07,0xD6,0xE9,0x4F,0xDC,0xDE,0x45,0x71,0x30,0x32,0x7F,0x1B,0x2E, +0x09,0xF9,0xBF,0x52,0xA1,0xEE,0xC2,0x80,0x3E,0x06,0x5C,0x2E,0x55,0x40,0xC1,0x1B, +0xF5,0x70,0x45,0xB0,0xDC,0x5D,0xFA,0xF6,0x72,0x5A,0x77,0xD2,0x63,0xCD,0xCF,0x58, +0x89,0x00,0x42,0x63,0x3F,0x79,0x39,0xD0,0x44,0xB0,0x82,0x6E,0x41,0x19,0xE8,0xDD, +0xE0,0xC1,0x88,0x5A,0xD1,0x1E,0x71,0x93,0x1F,0x24,0x30,0x74,0xE5,0x1E,0xA8,0xDE, +0x3C,0x27,0x37,0x7F,0x83,0xAE,0x9E,0x77,0xCF,0xF0,0x30,0xB1,0xFF,0x4B,0x99,0xE8, +0xC6,0xA1, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Commercial */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Commercial */ + + +const unsigned char AffirmTrust_Commercial_certificate[848]={ +0x30,0x82,0x03,0x4C,0x30,0x82,0x02,0x34,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x77, +0x77,0x06,0x27,0x26,0xA9,0xB1,0x7C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B, +0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1F,0x30,0x1D,0x06, +0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69,0x61,0x6C,0x30,0x1E,0x17,0x0D, +0x31,0x30,0x30,0x31,0x32,0x39,0x31,0x34,0x30,0x36,0x30,0x36,0x5A,0x17,0x0D,0x33, +0x30,0x31,0x32,0x33,0x31,0x31,0x34,0x30,0x36,0x30,0x36,0x5A,0x30,0x44,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06, +0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69, +0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69, +0x61,0x6C,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xF6,0x1B,0x4F,0x67,0x07,0x2B,0xA1,0x15,0xF5,0x06,0x22,0xCB,0x1F, +0x01,0xB2,0xE3,0x73,0x45,0x06,0x44,0x49,0x2C,0xBB,0x49,0x25,0x14,0xD6,0xCE,0xC3, +0xB7,0xAB,0x2C,0x4F,0xC6,0x41,0x32,0x94,0x57,0xFA,0x12,0xA7,0x5B,0x0E,0xE2,0x8F, +0x1F,0x1E,0x86,0x19,0xA7,0xAA,0xB5,0x2D,0xB9,0x5F,0x0D,0x8A,0xC2,0xAF,0x85,0x35, +0x79,0x32,0x2D,0xBB,0x1C,0x62,0x37,0xF2,0xB1,0x5B,0x4A,0x3D,0xCA,0xCD,0x71,0x5F, +0xE9,0x42,0xBE,0x94,0xE8,0xC8,0xDE,0xF9,0x22,0x48,0x64,0xC6,0xE5,0xAB,0xC6,0x2B, +0x6D,0xAD,0x05,0xF0,0xFA,0xD5,0x0B,0xCF,0x9A,0xE5,0xF0,0x50,0xA4,0x8B,0x3B,0x47, +0xA5,0x23,0x5B,0x7A,0x7A,0xF8,0x33,0x3F,0xB8,0xEF,0x99,0x97,0xE3,0x20,0xC1,0xD6, +0x28,0x89,0xCF,0x94,0xFB,0xB9,0x45,0xED,0xE3,0x40,0x17,0x11,0xD4,0x74,0xF0,0x0B, +0x31,0xE2,0x2B,0x26,0x6A,0x9B,0x4C,0x57,0xAE,0xAC,0x20,0x3E,0xBA,0x45,0x7A,0x05, +0xF3,0xBD,0x9B,0x69,0x15,0xAE,0x7D,0x4E,0x20,0x63,0xC4,0x35,0x76,0x3A,0x07,0x02, +0xC9,0x37,0xFD,0xC7,0x47,0xEE,0xE8,0xF1,0x76,0x1D,0x73,0x15,0xF2,0x97,0xA4,0xB5, +0xC8,0x7A,0x79,0xD9,0x42,0xAA,0x2B,0x7F,0x5C,0xFE,0xCE,0x26,0x4F,0xA3,0x66,0x81, +0x35,0xAF,0x44,0xBA,0x54,0x1E,0x1C,0x30,0x32,0x65,0x9D,0xE6,0x3C,0x93,0x5E,0x50, +0x4E,0x7A,0xE3,0x3A,0xD4,0x6E,0xCC,0x1A,0xFB,0xF9,0xD2,0x37,0xAE,0x24,0x2A,0xAB, +0x57,0x03,0x22,0x28,0x0D,0x49,0x75,0x7F,0xB7,0x28,0xDA,0x75,0xBF,0x8E,0xE3,0xDC, +0x0E,0x79,0x31,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9D,0x93,0xC6,0x53,0x8B,0x5E,0xCA,0xAF,0x3F, +0x9F,0x1E,0x0F,0xE5,0x99,0x95,0xBC,0x24,0xF6,0x94,0x8F,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x58,0xAC,0xF4,0x04,0x0E,0xCD,0xC0,0x0D,0xFF,0x0A,0xFD,0xD4,0xBA,0x16,0x5F,0x29, +0xBD,0x7B,0x68,0x99,0x58,0x49,0xD2,0xB4,0x1D,0x37,0x4D,0x7F,0x27,0x7D,0x46,0x06, +0x5D,0x43,0xC6,0x86,0x2E,0x3E,0x73,0xB2,0x26,0x7D,0x4F,0x93,0xA9,0xB6,0xC4,0x2A, +0x9A,0xAB,0x21,0x97,0x14,0xB1,0xDE,0x8C,0xD3,0xAB,0x89,0x15,0xD8,0x6B,0x24,0xD4, +0xF1,0x16,0xAE,0xD8,0xA4,0x5C,0xD4,0x7F,0x51,0x8E,0xED,0x18,0x01,0xB1,0x93,0x63, +0xBD,0xBC,0xF8,0x61,0x80,0x9A,0x9E,0xB1,0xCE,0x42,0x70,0xE2,0xA9,0x7D,0x06,0x25, +0x7D,0x27,0xA1,0xFE,0x6F,0xEC,0xB3,0x1E,0x24,0xDA,0xE3,0x4B,0x55,0x1A,0x00,0x3B, +0x35,0xB4,0x3B,0xD9,0xD7,0x5D,0x30,0xFD,0x81,0x13,0x89,0xF2,0xC2,0x06,0x2B,0xED, +0x67,0xC4,0x8E,0xC9,0x43,0xB2,0x5C,0x6B,0x15,0x89,0x02,0xBC,0x62,0xFC,0x4E,0xF2, +0xB5,0x33,0xAA,0xB2,0x6F,0xD3,0x0A,0xA2,0x50,0xE3,0xF6,0x3B,0xE8,0x2E,0x44,0xC2, +0xDB,0x66,0x38,0xA9,0x33,0x56,0x48,0xF1,0x6D,0x1B,0x33,0x8D,0x0D,0x8C,0x3F,0x60, +0x37,0x9D,0xD3,0xCA,0x6D,0x7E,0x34,0x7E,0x0D,0x9F,0x72,0x76,0x8B,0x1B,0x9F,0x72, +0xFD,0x52,0x35,0x41,0x45,0x02,0x96,0x2F,0x1C,0xB2,0x9A,0x73,0x49,0x21,0xB1,0x49, +0x47,0x45,0x47,0xB4,0xEF,0x6A,0x34,0x11,0xC9,0x4D,0x9A,0xCC,0x59,0xB7,0xD6,0x02, +0x9E,0x5A,0x4E,0x65,0xB5,0x94,0xAE,0x1B,0xDF,0x29,0xB0,0x16,0xF1,0xBF,0x00,0x9E, +0x07,0x3A,0x17,0x64,0xB5,0x04,0xB5,0x23,0x21,0x99,0x0A,0x95,0x3B,0x97,0x7C,0xEF, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Networking */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Networking */ + + +const unsigned char AffirmTrust_Networking_certificate[848]={ +0x30,0x82,0x03,0x4C,0x30,0x82,0x02,0x34,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x7C, +0x4F,0x04,0x39,0x1C,0xD4,0x99,0x2D,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B, +0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1F,0x30,0x1D,0x06, +0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x69,0x6E,0x67,0x30,0x1E,0x17,0x0D, +0x31,0x30,0x30,0x31,0x32,0x39,0x31,0x34,0x30,0x38,0x32,0x34,0x5A,0x17,0x0D,0x33, +0x30,0x31,0x32,0x33,0x31,0x31,0x34,0x30,0x38,0x32,0x34,0x5A,0x30,0x44,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06, +0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69, +0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x69, +0x6E,0x67,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xB4,0x84,0xCC,0x33,0x17,0x2E,0x6B,0x94,0x6C,0x6B,0x61,0x52,0xA0, +0xEB,0xA3,0xCF,0x79,0x94,0x4C,0xE5,0x94,0x80,0x99,0xCB,0x55,0x64,0x44,0x65,0x8F, +0x67,0x64,0xE2,0x06,0xE3,0x5C,0x37,0x49,0xF6,0x2F,0x9B,0x84,0x84,0x1E,0x2D,0xF2, +0x60,0x9D,0x30,0x4E,0xCC,0x84,0x85,0xE2,0x2C,0xCF,0x1E,0x9E,0xFE,0x36,0xAB,0x33, +0x77,0x35,0x44,0xD8,0x35,0x96,0x1A,0x3D,0x36,0xE8,0x7A,0x0E,0xD8,0xD5,0x47,0xA1, +0x6A,0x69,0x8B,0xD9,0xFC,0xBB,0x3A,0xAE,0x79,0x5A,0xD5,0xF4,0xD6,0x71,0xBB,0x9A, +0x90,0x23,0x6B,0x9A,0xB7,0x88,0x74,0x87,0x0C,0x1E,0x5F,0xB9,0x9E,0x2D,0xFA,0xAB, +0x53,0x2B,0xDC,0xBB,0x76,0x3E,0x93,0x4C,0x08,0x08,0x8C,0x1E,0xA2,0x23,0x1C,0xD4, +0x6A,0xAD,0x22,0xBA,0x99,0x01,0x2E,0x6D,0x65,0xCB,0xBE,0x24,0x66,0x55,0x24,0x4B, +0x40,0x44,0xB1,0x1B,0xD7,0xE1,0xC2,0x85,0xC0,0xDE,0x10,0x3F,0x3D,0xED,0xB8,0xFC, +0xF1,0xF1,0x23,0x53,0xDC,0xBF,0x65,0x97,0x6F,0xD9,0xF9,0x40,0x71,0x8D,0x7D,0xBD, +0x95,0xD4,0xCE,0xBE,0xA0,0x5E,0x27,0x23,0xDE,0xFD,0xA6,0xD0,0x26,0x0E,0x00,0x29, +0xEB,0x3C,0x46,0xF0,0x3D,0x60,0xBF,0x3F,0x50,0xD2,0xDC,0x26,0x41,0x51,0x9E,0x14, +0x37,0x42,0x04,0xA3,0x70,0x57,0xA8,0x1B,0x87,0xED,0x2D,0xFA,0x7B,0xEE,0x8C,0x0A, +0xE3,0xA9,0x66,0x89,0x19,0xCB,0x41,0xF9,0xDD,0x44,0x36,0x61,0xCF,0xE2,0x77,0x46, +0xC8,0x7D,0xF6,0xF4,0x92,0x81,0x36,0xFD,0xDB,0x34,0xF1,0x72,0x7E,0xF3,0x0C,0x16, +0xBD,0xB4,0x15,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x07,0x1F,0xD2,0xE7,0x9C,0xDA,0xC2,0x6E,0xA2, +0x40,0xB4,0xB0,0x7A,0x50,0x10,0x50,0x74,0xC4,0xC8,0xBD,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x89,0x57,0xB2,0x16,0x7A,0xA8,0xC2,0xFD,0xD6,0xD9,0x9B,0x9B,0x34,0xC2,0x9C,0xB4, +0x32,0x14,0x4D,0xA7,0xA4,0xDF,0xEC,0xBE,0xA7,0xBE,0xF8,0x43,0xDB,0x91,0x37,0xCE, +0xB4,0x32,0x2E,0x50,0x55,0x1A,0x35,0x4E,0x76,0x43,0x71,0x20,0xEF,0x93,0x77,0x4E, +0x15,0x70,0x2E,0x87,0xC3,0xC1,0x1D,0x6D,0xDC,0xCB,0xB5,0x27,0xD4,0x2C,0x56,0xD1, +0x52,0x53,0x3A,0x44,0xD2,0x73,0xC8,0xC4,0x1B,0x05,0x65,0x5A,0x62,0x92,0x9C,0xEE, +0x41,0x8D,0x31,0xDB,0xE7,0x34,0xEA,0x59,0x21,0xD5,0x01,0x7A,0xD7,0x64,0xB8,0x64, +0x39,0xCD,0xC9,0xED,0xAF,0xED,0x4B,0x03,0x48,0xA7,0xA0,0x99,0x01,0x80,0xDC,0x65, +0xA3,0x36,0xAE,0x65,0x59,0x48,0x4F,0x82,0x4B,0xC8,0x65,0xF1,0x57,0x1D,0xE5,0x59, +0x2E,0x0A,0x3F,0x6C,0xD8,0xD1,0xF5,0xE5,0x09,0xB4,0x6C,0x54,0x00,0x0A,0xE0,0x15, +0x4D,0x87,0x75,0x6D,0xB7,0x58,0x96,0x5A,0xDD,0x6D,0xD2,0x00,0xA0,0xF4,0x9B,0x48, +0xBE,0xC3,0x37,0xA4,0xBA,0x36,0xE0,0x7C,0x87,0x85,0x97,0x1A,0x15,0xA2,0xDE,0x2E, +0xA2,0x5B,0xBD,0xAF,0x18,0xF9,0x90,0x50,0xCD,0x70,0x59,0xF8,0x27,0x67,0x47,0xCB, +0xC7,0xA0,0x07,0x3A,0x7D,0xD1,0x2C,0x5D,0x6C,0x19,0x3A,0x66,0xB5,0x7D,0xFD,0x91, +0x6F,0x82,0xB1,0xBE,0x08,0x93,0xDB,0x14,0x47,0xF1,0xA2,0x37,0xC7,0x45,0x9E,0x3C, +0xC7,0x77,0xAF,0x64,0xA8,0x93,0xDF,0xF6,0x69,0x83,0x82,0x60,0xF2,0x49,0x42,0x34, +0xED,0x5A,0x00,0x54,0x85,0x1C,0x16,0x36,0x92,0x0C,0x5C,0xFA,0xA6,0xAD,0xBF,0xDB, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Premium */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Premium */ + + +const unsigned char AffirmTrust_Premium_certificate[1354]={ +0x30,0x82,0x05,0x46,0x30,0x82,0x03,0x2E,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x6D, +0x8C,0x14,0x46,0xB1,0xA6,0x0A,0xEE,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x0C,0x05,0x00,0x30,0x41,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B, +0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x03,0x0C,0x13,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73, +0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x30,0x1E,0x17,0x0D,0x31,0x30,0x30, +0x31,0x32,0x39,0x31,0x34,0x31,0x30,0x33,0x36,0x5A,0x17,0x0D,0x34,0x30,0x31,0x32, +0x33,0x31,0x31,0x34,0x31,0x30,0x33,0x36,0x5A,0x30,0x41,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04, +0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x03,0x0C,0x13,0x41,0x66,0x66,0x69,0x72,0x6D,0x54, +0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x30,0x82,0x02,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xC4,0x12,0xDF, +0xA9,0x5F,0xFE,0x41,0xDD,0xDD,0xF5,0x9F,0x8A,0xE3,0xF6,0xAC,0xE1,0x3C,0x78,0x9A, +0xBC,0xD8,0xF0,0x7F,0x7A,0xA0,0x33,0x2A,0xDC,0x8D,0x20,0x5B,0xAE,0x2D,0x6F,0xE7, +0x93,0xD9,0x36,0x70,0x6A,0x68,0xCF,0x8E,0x51,0xA3,0x85,0x5B,0x67,0x04,0xA0,0x10, +0x24,0x6F,0x5D,0x28,0x82,0xC1,0x97,0x57,0xD8,0x48,0x29,0x13,0xB6,0xE1,0xBE,0x91, +0x4D,0xDF,0x85,0x0C,0x53,0x18,0x9A,0x1E,0x24,0xA2,0x4F,0x8F,0xF0,0xA2,0x85,0x0B, +0xCB,0xF4,0x29,0x7F,0xD2,0xA4,0x58,0xEE,0x26,0x4D,0xC9,0xAA,0xA8,0x7B,0x9A,0xD9, +0xFA,0x38,0xDE,0x44,0x57,0x15,0xE5,0xF8,0x8C,0xC8,0xD9,0x48,0xE2,0x0D,0x16,0x27, +0x1D,0x1E,0xC8,0x83,0x85,0x25,0xB7,0xBA,0xAA,0x55,0x41,0xCC,0x03,0x22,0x4B,0x2D, +0x91,0x8D,0x8B,0xE6,0x89,0xAF,0x66,0xC7,0xE9,0xFF,0x2B,0xE9,0x3C,0xAC,0xDA,0xD2, +0xB3,0xC3,0xE1,0x68,0x9C,0x89,0xF8,0x7A,0x00,0x56,0xDE,0xF4,0x55,0x95,0x6C,0xFB, +0xBA,0x64,0xDD,0x62,0x8B,0xDF,0x0B,0x77,0x32,0xEB,0x62,0xCC,0x26,0x9A,0x9B,0xBB, +0xAA,0x62,0x83,0x4C,0xB4,0x06,0x7A,0x30,0xC8,0x29,0xBF,0xED,0x06,0x4D,0x97,0xB9, +0x1C,0xC4,0x31,0x2B,0xD5,0x5F,0xBC,0x53,0x12,0x17,0x9C,0x99,0x57,0x29,0x66,0x77, +0x61,0x21,0x31,0x07,0x2E,0x25,0x49,0x9D,0x18,0xF2,0xEE,0xF3,0x2B,0x71,0x8C,0xB5, +0xBA,0x39,0x07,0x49,0x77,0xFC,0xEF,0x2E,0x92,0x90,0x05,0x8D,0x2D,0x2F,0x77,0x7B, +0xEF,0x43,0xBF,0x35,0xBB,0x9A,0xD8,0xF9,0x73,0xA7,0x2C,0xF2,0xD0,0x57,0xEE,0x28, +0x4E,0x26,0x5F,0x8F,0x90,0x68,0x09,0x2F,0xB8,0xF8,0xDC,0x06,0xE9,0x2E,0x9A,0x3E, +0x51,0xA7,0xD1,0x22,0xC4,0x0A,0xA7,0x38,0x48,0x6C,0xB3,0xF9,0xFF,0x7D,0xAB,0x86, +0x57,0xE3,0xBA,0xD6,0x85,0x78,0x77,0xBA,0x43,0xEA,0x48,0x7F,0xF6,0xD8,0xBE,0x23, +0x6D,0x1E,0xBF,0xD1,0x36,0x6C,0x58,0x5C,0xF1,0xEE,0xA4,0x19,0x54,0x1A,0xF5,0x03, +0xD2,0x76,0xE6,0xE1,0x8C,0xBD,0x3C,0xB3,0xD3,0x48,0x4B,0xE2,0xC8,0xF8,0x7F,0x92, +0xA8,0x76,0x46,0x9C,0x42,0x65,0x3E,0xA4,0x1E,0xC1,0x07,0x03,0x5A,0x46,0x2D,0xB8, +0x97,0xF3,0xB7,0xD5,0xB2,0x55,0x21,0xEF,0xBA,0xDC,0x4C,0x00,0x97,0xFB,0x14,0x95, +0x27,0x33,0xBF,0xE8,0x43,0x47,0x46,0xD2,0x08,0x99,0x16,0x60,0x3B,0x9A,0x7E,0xD2, +0xE6,0xED,0x38,0xEA,0xEC,0x01,0x1E,0x3C,0x48,0x56,0x49,0x09,0xC7,0x4C,0x37,0x00, +0x9E,0x88,0x0E,0xC0,0x73,0xE1,0x6F,0x66,0xE9,0x72,0x47,0x30,0x3E,0x10,0xE5,0x0B, +0x03,0xC9,0x9A,0x42,0x00,0x6C,0xC5,0x94,0x7E,0x61,0xC4,0x8A,0xDF,0x7F,0x82,0x1A, +0x0B,0x59,0xC4,0x59,0x32,0x77,0xB3,0xBC,0x60,0x69,0x56,0x39,0xFD,0xB4,0x06,0x7B, +0x2C,0xD6,0x64,0x36,0xD9,0xBD,0x48,0xED,0x84,0x1F,0x7E,0xA5,0x22,0x8F,0x2A,0xB8, +0x42,0xF4,0x82,0xB7,0xD4,0x53,0x90,0x78,0x4E,0x2D,0x1A,0xFD,0x81,0x6F,0x44,0xD7, +0x3B,0x01,0x74,0x96,0x42,0xE0,0x00,0xE2,0x2E,0x6B,0xEA,0xC5,0xEE,0x72,0xAC,0xBB, +0xBF,0xFE,0xEA,0xAA,0xA8,0xF8,0xDC,0xF6,0xB2,0x79,0x8A,0xB6,0x67,0x02,0x03,0x01, +0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x9D,0xC0,0x67,0xA6,0x0C,0x22,0xD9,0x26,0xF5,0x45,0xAB,0xA6,0x65,0x52,0x11, +0x27,0xD8,0x45,0xAC,0x63,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x0C,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0xB3,0x57,0x4D,0x10,0x62,0x4E, +0x3A,0xE4,0xAC,0xEA,0xB8,0x1C,0xAF,0x32,0x23,0xC8,0xB3,0x49,0x5A,0x51,0x9C,0x76, +0x28,0x8D,0x79,0xAA,0x57,0x46,0x17,0xD5,0xF5,0x52,0xF6,0xB7,0x44,0xE8,0x08,0x44, +0xBF,0x18,0x84,0xD2,0x0B,0x80,0xCD,0xC5,0x12,0xFD,0x00,0x55,0x05,0x61,0x87,0x41, +0xDC,0xB5,0x24,0x9E,0x3C,0xC4,0xD8,0xC8,0xFB,0x70,0x9E,0x2F,0x78,0x96,0x83,0x20, +0x36,0xDE,0x7C,0x0F,0x69,0x13,0x88,0xA5,0x75,0x36,0x98,0x08,0xA6,0xC6,0xDF,0xAC, +0xCE,0xE3,0x58,0xD6,0xB7,0x3E,0xDE,0xBA,0xF3,0xEB,0x34,0x40,0xD8,0xA2,0x81,0xF5, +0x78,0x3F,0x2F,0xD5,0xA5,0xFC,0xD9,0xA2,0xD4,0x5E,0x04,0x0E,0x17,0xAD,0xFE,0x41, +0xF0,0xE5,0xB2,0x72,0xFA,0x44,0x82,0x33,0x42,0xE8,0x2D,0x58,0xF7,0x56,0x8C,0x62, +0x3F,0xBA,0x42,0xB0,0x9C,0x0C,0x5C,0x7E,0x2E,0x65,0x26,0x5C,0x53,0x4F,0x00,0xB2, +0x78,0x7E,0xA1,0x0D,0x99,0x2D,0x8D,0xB8,0x1D,0x8E,0xA2,0xC4,0xB0,0xFD,0x60,0xD0, +0x30,0xA4,0x8E,0xC8,0x04,0x62,0xA9,0xC4,0xED,0x35,0xDE,0x7A,0x97,0xED,0x0E,0x38, +0x5E,0x92,0x2F,0x93,0x70,0xA5,0xA9,0x9C,0x6F,0xA7,0x7D,0x13,0x1D,0x7E,0xC6,0x08, +0x48,0xB1,0x5E,0x67,0xEB,0x51,0x08,0x25,0xE9,0xE6,0x25,0x6B,0x52,0x29,0x91,0x9C, +0xD2,0x39,0x73,0x08,0x57,0xDE,0x99,0x06,0xB4,0x5B,0x9D,0x10,0x06,0xE1,0xC2,0x00, +0xA8,0xB8,0x1C,0x4A,0x02,0x0A,0x14,0xD0,0xC1,0x41,0xCA,0xFB,0x8C,0x35,0x21,0x7D, +0x82,0x38,0xF2,0xA9,0x54,0x91,0x19,0x35,0x93,0x94,0x6D,0x6A,0x3A,0xC5,0xB2,0xD0, +0xBB,0x89,0x86,0x93,0xE8,0x9B,0xC9,0x0F,0x3A,0xA7,0x7A,0xB8,0xA1,0xF0,0x78,0x46, +0xFA,0xFC,0x37,0x2F,0xE5,0x8A,0x84,0xF3,0xDF,0xFE,0x04,0xD9,0xA1,0x68,0xA0,0x2F, +0x24,0xE2,0x09,0x95,0x06,0xD5,0x95,0xCA,0xE1,0x24,0x96,0xEB,0x7C,0xF6,0x93,0x05, +0xBB,0xED,0x73,0xE9,0x2D,0xD1,0x75,0x39,0xD7,0xE7,0x24,0xDB,0xD8,0x4E,0x5F,0x43, +0x8F,0x9E,0xD0,0x14,0x39,0xBF,0x55,0x70,0x48,0x99,0x57,0x31,0xB4,0x9C,0xEE,0x4A, +0x98,0x03,0x96,0x30,0x1F,0x60,0x06,0xEE,0x1B,0x23,0xFE,0x81,0x60,0x23,0x1A,0x47, +0x62,0x85,0xA5,0xCC,0x19,0x34,0x80,0x6F,0xB3,0xAC,0x1A,0xE3,0x9F,0xF0,0x7B,0x48, +0xAD,0xD5,0x01,0xD9,0x67,0xB6,0xA9,0x72,0x93,0xEA,0x2D,0x66,0xB5,0xB2,0xB8,0xE4, +0x3D,0x3C,0xB2,0xEF,0x4C,0x8C,0xEA,0xEB,0x07,0xBF,0xAB,0x35,0x9A,0x55,0x86,0xBC, +0x18,0xA6,0xB5,0xA8,0x5E,0xB4,0x83,0x6C,0x6B,0x69,0x40,0xD3,0x9F,0xDC,0xF1,0xC3, +0x69,0x6B,0xB9,0xE1,0x6D,0x09,0xF4,0xF1,0xAA,0x50,0x76,0x0A,0x7A,0x7D,0x7A,0x17, +0xA1,0x55,0x96,0x42,0x99,0x31,0x09,0xDD,0x60,0x11,0x8D,0x05,0x30,0x7E,0xE6,0x8E, +0x46,0xD1,0x9D,0x14,0xDA,0xC7,0x17,0xE4,0x05,0x96,0x8C,0xC4,0x24,0xB5,0x1B,0xCF, +0x14,0x07,0xB2,0x40,0xF8,0xA3,0x9E,0x41,0x86,0xBC,0x04,0xD0,0x6B,0x96,0xC8,0x2A, +0x80,0x34,0xFD,0xBF,0xEF,0x06,0xA3,0xDD,0x58,0xC5,0x85,0x3D,0x3E,0x8F,0xFE,0x9E, +0x29,0xE0,0xB6,0xB8,0x09,0x68,0x19,0x1C,0x18,0x43, +}; + + +/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Premium ECC */ +/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Premium ECC */ + + +const unsigned char AffirmTrust_Premium_ECC_certificate[514]={ +0x30,0x82,0x01,0xFE,0x30,0x82,0x01,0x85,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x74, +0x97,0x25,0x8A,0xC7,0x3F,0x7A,0x54,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D, +0x04,0x03,0x03,0x30,0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66, +0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04, +0x03,0x0C,0x17,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x50, +0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x45,0x43,0x43,0x30,0x1E,0x17,0x0D,0x31,0x30, +0x30,0x31,0x32,0x39,0x31,0x34,0x32,0x30,0x32,0x34,0x5A,0x17,0x0D,0x34,0x30,0x31, +0x32,0x33,0x31,0x31,0x34,0x32,0x30,0x32,0x34,0x5A,0x30,0x45,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55, +0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31, +0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x0C,0x17,0x41,0x66,0x66,0x69,0x72,0x6D, +0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x45,0x43, +0x43,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05, +0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0x0D,0x30,0x5E,0x1B,0x15,0x9D,0x03, +0xD0,0xA1,0x79,0x35,0xB7,0x3A,0x3C,0x92,0x7A,0xCA,0x15,0x1C,0xCD,0x62,0xF3,0x9C, +0x26,0x5C,0x07,0x3D,0xE5,0x54,0xFA,0xA3,0xD6,0xCC,0x12,0xEA,0xF4,0x14,0x5F,0xE8, +0x8E,0x19,0xAB,0x2F,0x2E,0x48,0xE6,0xAC,0x18,0x43,0x78,0xAC,0xD0,0x37,0xC3,0xBD, +0xB2,0xCD,0x2C,0xE6,0x47,0xE2,0x1A,0xE6,0x63,0xB8,0x3D,0x2E,0x2F,0x78,0xC4,0x4F, +0xDB,0xF4,0x0F,0xA4,0x68,0x4C,0x55,0x72,0x6B,0x95,0x1D,0x4E,0x18,0x42,0x95,0x78, +0xCC,0x37,0x3C,0x91,0xE2,0x9B,0x65,0x2B,0x29,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9A,0xAF,0x29,0x7A,0xC0,0x11,0x35,0x35, +0x26,0x51,0x30,0x00,0xC3,0x6A,0xFE,0x40,0xD5,0xAE,0xD6,0x3C,0x30,0x0F,0x06,0x03, +0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06, +0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0A,0x06, +0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x67,0x00,0x30,0x64,0x02,0x30, +0x17,0x09,0xF3,0x87,0x88,0x50,0x5A,0xAF,0xC8,0xC0,0x42,0xBF,0x47,0x5F,0xF5,0x6C, +0x6A,0x86,0xE0,0xC4,0x27,0x74,0xE4,0x38,0x53,0xD7,0x05,0x7F,0x1B,0x34,0xE3,0xC6, +0x2F,0xB3,0xCA,0x09,0x3C,0x37,0x9D,0xD7,0xE7,0xB8,0x46,0xF1,0xFD,0xA1,0xE2,0x71, +0x02,0x30,0x42,0x59,0x87,0x43,0xD4,0x51,0xDF,0xBA,0xD3,0x09,0x32,0x5A,0xCE,0x88, +0x7E,0x57,0x3D,0x9C,0x5F,0x42,0x6B,0xF5,0x07,0x2D,0xB5,0xF0,0x82,0x93,0xF9,0x59, +0x6F,0xAE,0x64,0xFA,0x58,0xE5,0x8B,0x1E,0xE3,0x63,0xBE,0xB5,0x81,0xCD,0x6F,0x02, +0x8C,0x79, +}; + + +/* subject:/C=US/O=America Online Inc./CN=America Online Root Certification Authority 1 */ +/* issuer :/C=US/O=America Online Inc./CN=America Online Root Certification Authority 1 */ + + +const unsigned char America_Online_Root_Certification_Authority_1_certificate[936]={ +0x30,0x82,0x03,0xA4,0x30,0x82,0x02,0x8C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,0x65,0x72,0x69,0x63,0x61, +0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x36,0x30,0x34, +0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,0x20,0x4F, +0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x31,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x38,0x30,0x36, +0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x31,0x31,0x39,0x32,0x30,0x34, +0x33,0x30,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D, +0x65,0x72,0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72, +0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75, +0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x31,0x30,0x82,0x01,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F, +0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xA8,0x2F,0xE8,0xA4,0x69,0x06, +0x03,0x47,0xC3,0xE9,0x2A,0x98,0xFF,0x19,0xA2,0x70,0x9A,0xC6,0x50,0xB2,0x7E,0xA5, +0xDF,0x68,0x4D,0x1B,0x7C,0x0F,0xB6,0x97,0x68,0x7D,0x2D,0xA6,0x8B,0x97,0xE9,0x64, +0x86,0xC9,0xA3,0xEF,0xA0,0x86,0xBF,0x60,0x65,0x9C,0x4B,0x54,0x88,0xC2,0x48,0xC5, +0x4A,0x39,0xBF,0x14,0xE3,0x59,0x55,0xE5,0x19,0xB4,0x74,0xC8,0xB4,0x05,0x39,0x5C, +0x16,0xA5,0xE2,0x95,0x05,0xE0,0x12,0xAE,0x59,0x8B,0xA2,0x33,0x68,0x58,0x1C,0xA6, +0xD4,0x15,0xB7,0xD8,0x9F,0xD7,0xDC,0x71,0xAB,0x7E,0x9A,0xBF,0x9B,0x8E,0x33,0x0F, +0x22,0xFD,0x1F,0x2E,0xE7,0x07,0x36,0xEF,0x62,0x39,0xC5,0xDD,0xCB,0xBA,0x25,0x14, +0x23,0xDE,0x0C,0xC6,0x3D,0x3C,0xCE,0x82,0x08,0xE6,0x66,0x3E,0xDA,0x51,0x3B,0x16, +0x3A,0xA3,0x05,0x7F,0xA0,0xDC,0x87,0xD5,0x9C,0xFC,0x72,0xA9,0xA0,0x7D,0x78,0xE4, +0xB7,0x31,0x55,0x1E,0x65,0xBB,0xD4,0x61,0xB0,0x21,0x60,0xED,0x10,0x32,0x72,0xC5, +0x92,0x25,0x1E,0xF8,0x90,0x4A,0x18,0x78,0x47,0xDF,0x7E,0x30,0x37,0x3E,0x50,0x1B, +0xDB,0x1C,0xD3,0x6B,0x9A,0x86,0x53,0x07,0xB0,0xEF,0xAC,0x06,0x78,0xF8,0x84,0x99, +0xFE,0x21,0x8D,0x4C,0x80,0xB6,0x0C,0x82,0xF6,0x66,0x70,0x79,0x1A,0xD3,0x4F,0xA3, +0xCF,0xF1,0xCF,0x46,0xB0,0x4B,0x0F,0x3E,0xDD,0x88,0x62,0xB8,0x8C,0xA9,0x09,0x28, +0x3B,0x7A,0xC7,0x97,0xE1,0x1E,0xE5,0xF4,0x9F,0xC0,0xC0,0xAE,0x24,0xA0,0xC8,0xA1, +0xD9,0x0F,0xD6,0x7B,0x26,0x82,0x69,0x32,0x3D,0xA7,0x02,0x03,0x01,0x00,0x01,0xA3, +0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x00, +0xAD,0xD9,0xA3,0xF6,0x79,0xF6,0x6E,0x74,0xA9,0x7F,0x33,0x3D,0x81,0x17,0xD7,0x4C, +0xCF,0x33,0xDE,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0x00,0xAD,0xD9,0xA3,0xF6,0x79,0xF6,0x6E,0x74,0xA9,0x7F,0x33,0x3D,0x81,0x17,0xD7, +0x4C,0xCF,0x33,0xDE,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x7C,0x8A,0xD1,0x1F,0x18,0x37,0x82,0xE0, +0xB8,0xB0,0xA3,0xED,0x56,0x95,0xC8,0x62,0x61,0x9C,0x05,0xA2,0xCD,0xC2,0x62,0x26, +0x61,0xCD,0x10,0x16,0xD7,0xCC,0xB4,0x65,0x34,0xD0,0x11,0x8A,0xAD,0xA8,0xA9,0x05, +0x66,0xEF,0x74,0xF3,0x6D,0x5F,0x9D,0x99,0xAF,0xF6,0x8B,0xFB,0xEB,0x52,0xB2,0x05, +0x98,0xA2,0x6F,0x2A,0xC5,0x54,0xBD,0x25,0xBD,0x5F,0xAE,0xC8,0x86,0xEA,0x46,0x2C, +0xC1,0xB3,0xBD,0xC1,0xE9,0x49,0x70,0x18,0x16,0x97,0x08,0x13,0x8C,0x20,0xE0,0x1B, +0x2E,0x3A,0x47,0xCB,0x1E,0xE4,0x00,0x30,0x95,0x5B,0xF4,0x45,0xA3,0xC0,0x1A,0xB0, +0x01,0x4E,0xAB,0xBD,0xC0,0x23,0x6E,0x63,0x3F,0x80,0x4A,0xC5,0x07,0xED,0xDC,0xE2, +0x6F,0xC7,0xC1,0x62,0xF1,0xE3,0x72,0xD6,0x04,0xC8,0x74,0x67,0x0B,0xFA,0x88,0xAB, +0xA1,0x01,0xC8,0x6F,0xF0,0x14,0xAF,0xD2,0x99,0xCD,0x51,0x93,0x7E,0xED,0x2E,0x38, +0xC7,0xBD,0xCE,0x46,0x50,0x3D,0x72,0xE3,0x79,0x25,0x9D,0x9B,0x88,0x2B,0x10,0x20, +0xDD,0xA5,0xB8,0x32,0x9F,0x8D,0xE0,0x29,0xDF,0x21,0x74,0x86,0x82,0xDB,0x2F,0x82, +0x30,0xC6,0xC7,0x35,0x86,0xB3,0xF9,0x96,0x5F,0x46,0xDB,0x0C,0x45,0xFD,0xF3,0x50, +0xC3,0x6F,0xC6,0xC3,0x48,0xAD,0x46,0xA6,0xE1,0x27,0x47,0x0A,0x1D,0x0E,0x9B,0xB6, +0xC2,0x77,0x7F,0x63,0xF2,0xE0,0x7D,0x1A,0xBE,0xFC,0xE0,0xDF,0xD7,0xC7,0xA7,0x6C, +0xB0,0xF9,0xAE,0xBA,0x3C,0xFD,0x74,0xB4,0x11,0xE8,0x58,0x0D,0x80,0xBC,0xD3,0xA8, +0x80,0x3A,0x99,0xED,0x75,0xCC,0x46,0x7B, +}; + + +/* subject:/C=US/O=America Online Inc./CN=America Online Root Certification Authority 2 */ +/* issuer :/C=US/O=America Online Inc./CN=America Online Root Certification Authority 2 */ + + +const unsigned char America_Online_Root_Certification_Authority_2_certificate[1448]={ +0x30,0x82,0x05,0xA4,0x30,0x82,0x03,0x8C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,0x65,0x72,0x69,0x63,0x61, +0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x36,0x30,0x34, +0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,0x20,0x4F, +0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x38,0x30,0x36, +0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x30,0x39,0x32,0x39,0x31,0x34,0x30, +0x38,0x30,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D, +0x65,0x72,0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72, +0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75, +0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F, +0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xCC,0x41,0x45,0x1D,0xE9,0x3D, +0x4D,0x10,0xF6,0x8C,0xB1,0x41,0xC9,0xE0,0x5E,0xCB,0x0D,0xB7,0xBF,0x47,0x73,0xD3, +0xF0,0x55,0x4D,0xDD,0xC6,0x0C,0xFA,0xB1,0x66,0x05,0x6A,0xCD,0x78,0xB4,0xDC,0x02, +0xDB,0x4E,0x81,0xF3,0xD7,0xA7,0x7C,0x71,0xBC,0x75,0x63,0xA0,0x5D,0xE3,0x07,0x0C, +0x48,0xEC,0x25,0xC4,0x03,0x20,0xF4,0xFF,0x0E,0x3B,0x12,0xFF,0x9B,0x8D,0xE1,0xC6, +0xD5,0x1B,0xB4,0x6D,0x22,0xE3,0xB1,0xDB,0x7F,0x21,0x64,0xAF,0x86,0xBC,0x57,0x22, +0x2A,0xD6,0x47,0x81,0x57,0x44,0x82,0x56,0x53,0xBD,0x86,0x14,0x01,0x0B,0xFC,0x7F, +0x74,0xA4,0x5A,0xAE,0xF1,0xBA,0x11,0xB5,0x9B,0x58,0x5A,0x80,0xB4,0x37,0x78,0x09, +0x33,0x7C,0x32,0x47,0x03,0x5C,0xC4,0xA5,0x83,0x48,0xF4,0x57,0x56,0x6E,0x81,0x36, +0x27,0x18,0x4F,0xEC,0x9B,0x28,0xC2,0xD4,0xB4,0xD7,0x7C,0x0C,0x3E,0x0C,0x2B,0xDF, +0xCA,0x04,0xD7,0xC6,0x8E,0xEA,0x58,0x4E,0xA8,0xA4,0xA5,0x18,0x1C,0x6C,0x45,0x98, +0xA3,0x41,0xD1,0x2D,0xD2,0xC7,0x6D,0x8D,0x19,0xF1,0xAD,0x79,0xB7,0x81,0x3F,0xBD, +0x06,0x82,0x27,0x2D,0x10,0x58,0x05,0xB5,0x78,0x05,0xB9,0x2F,0xDB,0x0C,0x6B,0x90, +0x90,0x7E,0x14,0x59,0x38,0xBB,0x94,0x24,0x13,0xE5,0xD1,0x9D,0x14,0xDF,0xD3,0x82, +0x4D,0x46,0xF0,0x80,0x39,0x52,0x32,0x0F,0xE3,0x84,0xB2,0x7A,0x43,0xF2,0x5E,0xDE, +0x5F,0x3F,0x1D,0xDD,0xE3,0xB2,0x1B,0xA0,0xA1,0x2A,0x23,0x03,0x6E,0x2E,0x01,0x15, +0x87,0x5C,0xA6,0x75,0x75,0xC7,0x97,0x61,0xBE,0xDE,0x86,0xDC,0xD4,0x48,0xDB,0xBD, +0x2A,0xBF,0x4A,0x55,0xDA,0xE8,0x7D,0x50,0xFB,0xB4,0x80,0x17,0xB8,0x94,0xBF,0x01, +0x3D,0xEA,0xDA,0xBA,0x7C,0xE0,0x58,0x67,0x17,0xB9,0x58,0xE0,0x88,0x86,0x46,0x67, +0x6C,0x9D,0x10,0x47,0x58,0x32,0xD0,0x35,0x7C,0x79,0x2A,0x90,0xA2,0x5A,0x10,0x11, +0x23,0x35,0xAD,0x2F,0xCC,0xE4,0x4A,0x5B,0xA7,0xC8,0x27,0xF2,0x83,0xDE,0x5E,0xBB, +0x5E,0x77,0xE7,0xE8,0xA5,0x6E,0x63,0xC2,0x0D,0x5D,0x61,0xD0,0x8C,0xD2,0x6C,0x5A, +0x21,0x0E,0xCA,0x28,0xA3,0xCE,0x2A,0xE9,0x95,0xC7,0x48,0xCF,0x96,0x6F,0x1D,0x92, +0x25,0xC8,0xC6,0xC6,0xC1,0xC1,0x0C,0x05,0xAC,0x26,0xC4,0xD2,0x75,0xD2,0xE1,0x2A, +0x67,0xC0,0x3D,0x5B,0xA5,0x9A,0xEB,0xCF,0x7B,0x1A,0xA8,0x9D,0x14,0x45,0xE5,0x0F, +0xA0,0x9A,0x65,0xDE,0x2F,0x28,0xBD,0xCE,0x6F,0x94,0x66,0x83,0x48,0x29,0xD8,0xEA, +0x65,0x8C,0xAF,0x93,0xD9,0x64,0x9F,0x55,0x57,0x26,0xBF,0x6F,0xCB,0x37,0x31,0x99, +0xA3,0x60,0xBB,0x1C,0xAD,0x89,0x34,0x32,0x62,0xB8,0x43,0x21,0x06,0x72,0x0C,0xA1, +0x5C,0x6D,0x46,0xC5,0xFA,0x29,0xCF,0x30,0xDE,0x89,0xDC,0x71,0x5B,0xDD,0xB6,0x37, +0x3E,0xDF,0x50,0xF5,0xB8,0x07,0x25,0x26,0xE5,0xBC,0xB5,0xFE,0x3C,0x02,0xB3,0xB7, +0xF8,0xBE,0x43,0xC1,0x87,0x11,0x94,0x9E,0x23,0x6C,0x17,0x8A,0xB8,0x8A,0x27,0x0C, +0x54,0x47,0xF0,0xA9,0xB3,0xC0,0x80,0x8C,0xA0,0x27,0xEB,0x1D,0x19,0xE3,0x07,0x8E, +0x77,0x70,0xCA,0x2B,0xF4,0x7D,0x76,0xE0,0x78,0x67,0x02,0x03,0x01,0x00,0x01,0xA3, +0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4D, +0x45,0xC1,0x68,0x38,0xBB,0x73,0xA9,0x69,0xA1,0x20,0xE7,0xED,0xF5,0x22,0xA1,0x23, +0x14,0xD7,0x9E,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0x4D,0x45,0xC1,0x68,0x38,0xBB,0x73,0xA9,0x69,0xA1,0x20,0xE7,0xED,0xF5,0x22,0xA1, +0x23,0x14,0xD7,0x9E,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x67,0x6B,0x06,0xB9,0x5F,0x45,0x3B,0x2A, +0x4B,0x33,0xB3,0xE6,0x1B,0x6B,0x59,0x4E,0x22,0xCC,0xB9,0xB7,0xA4,0x25,0xC9,0xA7, +0xC4,0xF0,0x54,0x96,0x0B,0x64,0xF3,0xB1,0x58,0x4F,0x5E,0x51,0xFC,0xB2,0x97,0x7B, +0x27,0x65,0xC2,0xE5,0xCA,0xE7,0x0D,0x0C,0x25,0x7B,0x62,0xE3,0xFA,0x9F,0xB4,0x87, +0xB7,0x45,0x46,0xAF,0x83,0xA5,0x97,0x48,0x8C,0xA5,0xBD,0xF1,0x16,0x2B,0x9B,0x76, +0x2C,0x7A,0x35,0x60,0x6C,0x11,0x80,0x97,0xCC,0xA9,0x92,0x52,0xE6,0x2B,0xE6,0x69, +0xED,0xA9,0xF8,0x36,0x2D,0x2C,0x77,0xBF,0x61,0x48,0xD1,0x63,0x0B,0xB9,0x5B,0x52, +0xED,0x18,0xB0,0x43,0x42,0x22,0xA6,0xB1,0x77,0xAE,0xDE,0x69,0xC5,0xCD,0xC7,0x1C, +0xA1,0xB1,0xA5,0x1C,0x10,0xFB,0x18,0xBE,0x1A,0x70,0xDD,0xC1,0x92,0x4B,0xBE,0x29, +0x5A,0x9D,0x3F,0x35,0xBE,0xE5,0x7D,0x51,0xF8,0x55,0xE0,0x25,0x75,0x23,0x87,0x1E, +0x5C,0xDC,0xBA,0x9D,0xB0,0xAC,0xB3,0x69,0xDB,0x17,0x83,0xC9,0xF7,0xDE,0x0C,0xBC, +0x08,0xDC,0x91,0x9E,0xA8,0xD0,0xD7,0x15,0x37,0x73,0xA5,0x35,0xB8,0xFC,0x7E,0xC5, +0x44,0x40,0x06,0xC3,0xEB,0xF8,0x22,0x80,0x5C,0x47,0xCE,0x02,0xE3,0x11,0x9F,0x44, +0xFF,0xFD,0x9A,0x32,0xCC,0x7D,0x64,0x51,0x0E,0xEB,0x57,0x26,0x76,0x3A,0xE3,0x1E, +0x22,0x3C,0xC2,0xA6,0x36,0xDD,0x19,0xEF,0xA7,0xFC,0x12,0xF3,0x26,0xC0,0x59,0x31, +0x85,0x4C,0x9C,0xD8,0xCF,0xDF,0xA4,0xCC,0xCC,0x29,0x93,0xFF,0x94,0x6D,0x76,0x5C, +0x13,0x08,0x97,0xF2,0xED,0xA5,0x0B,0x4D,0xDD,0xE8,0xC9,0x68,0x0E,0x66,0xD3,0x00, +0x0E,0x33,0x12,0x5B,0xBC,0x95,0xE5,0x32,0x90,0xA8,0xB3,0xC6,0x6C,0x83,0xAD,0x77, +0xEE,0x8B,0x7E,0x7E,0xB1,0xA9,0xAB,0xD3,0xE1,0xF1,0xB6,0xC0,0xB1,0xEA,0x88,0xC0, +0xE7,0xD3,0x90,0xE9,0x28,0x92,0x94,0x7B,0x68,0x7B,0x97,0x2A,0x0A,0x67,0x2D,0x85, +0x02,0x38,0x10,0xE4,0x03,0x61,0xD4,0xDA,0x25,0x36,0xC7,0x08,0x58,0x2D,0xA1,0xA7, +0x51,0xAF,0x30,0x0A,0x49,0xF5,0xA6,0x69,0x87,0x07,0x2D,0x44,0x46,0x76,0x8E,0x2A, +0xE5,0x9A,0x3B,0xD7,0x18,0xA2,0xFC,0x9C,0x38,0x10,0xCC,0xC6,0x3B,0xD2,0xB5,0x17, +0x3A,0x6F,0xFD,0xAE,0x25,0xBD,0xF5,0x72,0x59,0x64,0xB1,0x74,0x2A,0x38,0x5F,0x18, +0x4C,0xDF,0xCF,0x71,0x04,0x5A,0x36,0xD4,0xBF,0x2F,0x99,0x9C,0xE8,0xD9,0xBA,0xB1, +0x95,0xE6,0x02,0x4B,0x21,0xA1,0x5B,0xD5,0xC1,0x4F,0x8F,0xAE,0x69,0x6D,0x53,0xDB, +0x01,0x93,0xB5,0x5C,0x1E,0x18,0xDD,0x64,0x5A,0xCA,0x18,0x28,0x3E,0x63,0x04,0x11, +0xFD,0x1C,0x8D,0x00,0x0F,0xB8,0x37,0xDF,0x67,0x8A,0x9D,0x66,0xA9,0x02,0x6A,0x91, +0xFF,0x13,0xCA,0x2F,0x5D,0x83,0xBC,0x87,0x93,0x6C,0xDC,0x24,0x51,0x16,0x04,0x25, +0x66,0xFA,0xB3,0xD9,0xC2,0xBA,0x29,0xBE,0x9A,0x48,0x38,0x82,0x99,0xF4,0xBF,0x3B, +0x4A,0x31,0x19,0xF9,0xBF,0x8E,0x21,0x33,0x14,0xCA,0x4F,0x54,0x5F,0xFB,0xCE,0xFB, +0x8F,0x71,0x7F,0xFD,0x5E,0x19,0xA0,0x0F,0x4B,0x91,0xB8,0xC4,0x54,0xBC,0x06,0xB0, +0x45,0x8F,0x26,0x91,0xA2,0x8E,0xFE,0xA9, +}; + + +/* subject:/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root */ +/* issuer :/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root */ + + +const unsigned char Baltimore_CyberTrust_Root_certificate[891]={ +0x30,0x82,0x03,0x77,0x30,0x82,0x02,0x5F,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x02, +0x00,0x00,0xB9,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49, +0x45,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x0A,0x13,0x09,0x42,0x61,0x6C,0x74, +0x69,0x6D,0x6F,0x72,0x65,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0B,0x13,0x0A, +0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x31,0x22,0x30,0x20,0x06,0x03, +0x55,0x04,0x03,0x13,0x19,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72,0x65,0x20,0x43, +0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E, +0x17,0x0D,0x30,0x30,0x30,0x35,0x31,0x32,0x31,0x38,0x34,0x36,0x30,0x30,0x5A,0x17, +0x0D,0x32,0x35,0x30,0x35,0x31,0x32,0x32,0x33,0x35,0x39,0x30,0x30,0x5A,0x30,0x5A, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x45,0x31,0x12,0x30, +0x10,0x06,0x03,0x55,0x04,0x0A,0x13,0x09,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72, +0x65,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0B,0x13,0x0A,0x43,0x79,0x62,0x65, +0x72,0x54,0x72,0x75,0x73,0x74,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x03,0x13, +0x19,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72,0x65,0x20,0x43,0x79,0x62,0x65,0x72, +0x54,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01, +0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xA3,0x04,0xBB,0x22,0xAB, +0x98,0x3D,0x57,0xE8,0x26,0x72,0x9A,0xB5,0x79,0xD4,0x29,0xE2,0xE1,0xE8,0x95,0x80, +0xB1,0xB0,0xE3,0x5B,0x8E,0x2B,0x29,0x9A,0x64,0xDF,0xA1,0x5D,0xED,0xB0,0x09,0x05, +0x6D,0xDB,0x28,0x2E,0xCE,0x62,0xA2,0x62,0xFE,0xB4,0x88,0xDA,0x12,0xEB,0x38,0xEB, +0x21,0x9D,0xC0,0x41,0x2B,0x01,0x52,0x7B,0x88,0x77,0xD3,0x1C,0x8F,0xC7,0xBA,0xB9, +0x88,0xB5,0x6A,0x09,0xE7,0x73,0xE8,0x11,0x40,0xA7,0xD1,0xCC,0xCA,0x62,0x8D,0x2D, +0xE5,0x8F,0x0B,0xA6,0x50,0xD2,0xA8,0x50,0xC3,0x28,0xEA,0xF5,0xAB,0x25,0x87,0x8A, +0x9A,0x96,0x1C,0xA9,0x67,0xB8,0x3F,0x0C,0xD5,0xF7,0xF9,0x52,0x13,0x2F,0xC2,0x1B, +0xD5,0x70,0x70,0xF0,0x8F,0xC0,0x12,0xCA,0x06,0xCB,0x9A,0xE1,0xD9,0xCA,0x33,0x7A, +0x77,0xD6,0xF8,0xEC,0xB9,0xF1,0x68,0x44,0x42,0x48,0x13,0xD2,0xC0,0xC2,0xA4,0xAE, +0x5E,0x60,0xFE,0xB6,0xA6,0x05,0xFC,0xB4,0xDD,0x07,0x59,0x02,0xD4,0x59,0x18,0x98, +0x63,0xF5,0xA5,0x63,0xE0,0x90,0x0C,0x7D,0x5D,0xB2,0x06,0x7A,0xF3,0x85,0xEA,0xEB, +0xD4,0x03,0xAE,0x5E,0x84,0x3E,0x5F,0xFF,0x15,0xED,0x69,0xBC,0xF9,0x39,0x36,0x72, +0x75,0xCF,0x77,0x52,0x4D,0xF3,0xC9,0x90,0x2C,0xB9,0x3D,0xE5,0xC9,0x23,0x53,0x3F, +0x1F,0x24,0x98,0x21,0x5C,0x07,0x99,0x29,0xBD,0xC6,0x3A,0xEC,0xE7,0x6E,0x86,0x3A, +0x6B,0x97,0x74,0x63,0x33,0xBD,0x68,0x18,0x31,0xF0,0x78,0x8D,0x76,0xBF,0xFC,0x9E, +0x8E,0x5D,0x2A,0x86,0xA7,0x4D,0x90,0xDC,0x27,0x1A,0x39,0x02,0x03,0x01,0x00,0x01, +0xA3,0x45,0x30,0x43,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xE5, +0x9D,0x59,0x30,0x82,0x47,0x58,0xCC,0xAC,0xFA,0x08,0x54,0x36,0x86,0x7B,0x3A,0xB5, +0x04,0x4D,0xF0,0x30,0x12,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x08,0x30, +0x06,0x01,0x01,0xFF,0x02,0x01,0x03,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01, +0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x85,0x0C,0x5D,0x8E,0xE4, +0x6F,0x51,0x68,0x42,0x05,0xA0,0xDD,0xBB,0x4F,0x27,0x25,0x84,0x03,0xBD,0xF7,0x64, +0xFD,0x2D,0xD7,0x30,0xE3,0xA4,0x10,0x17,0xEB,0xDA,0x29,0x29,0xB6,0x79,0x3F,0x76, +0xF6,0x19,0x13,0x23,0xB8,0x10,0x0A,0xF9,0x58,0xA4,0xD4,0x61,0x70,0xBD,0x04,0x61, +0x6A,0x12,0x8A,0x17,0xD5,0x0A,0xBD,0xC5,0xBC,0x30,0x7C,0xD6,0xE9,0x0C,0x25,0x8D, +0x86,0x40,0x4F,0xEC,0xCC,0xA3,0x7E,0x38,0xC6,0x37,0x11,0x4F,0xED,0xDD,0x68,0x31, +0x8E,0x4C,0xD2,0xB3,0x01,0x74,0xEE,0xBE,0x75,0x5E,0x07,0x48,0x1A,0x7F,0x70,0xFF, +0x16,0x5C,0x84,0xC0,0x79,0x85,0xB8,0x05,0xFD,0x7F,0xBE,0x65,0x11,0xA3,0x0F,0xC0, +0x02,0xB4,0xF8,0x52,0x37,0x39,0x04,0xD5,0xA9,0x31,0x7A,0x18,0xBF,0xA0,0x2A,0xF4, +0x12,0x99,0xF7,0xA3,0x45,0x82,0xE3,0x3C,0x5E,0xF5,0x9D,0x9E,0xB5,0xC8,0x9E,0x7C, +0x2E,0xC8,0xA4,0x9E,0x4E,0x08,0x14,0x4B,0x6D,0xFD,0x70,0x6D,0x6B,0x1A,0x63,0xBD, +0x64,0xE6,0x1F,0xB7,0xCE,0xF0,0xF2,0x9F,0x2E,0xBB,0x1B,0xB7,0xF2,0x50,0x88,0x73, +0x92,0xC2,0xE2,0xE3,0x16,0x8D,0x9A,0x32,0x02,0xAB,0x8E,0x18,0xDD,0xE9,0x10,0x11, +0xEE,0x7E,0x35,0xAB,0x90,0xAF,0x3E,0x30,0x94,0x7A,0xD0,0x33,0x3D,0xA7,0x65,0x0F, +0xF5,0xFC,0x8E,0x9E,0x62,0xCF,0x47,0x44,0x2C,0x01,0x5D,0xBB,0x1D,0xB5,0x32,0xD2, +0x47,0xD2,0x38,0x2E,0xD0,0xFE,0x81,0xDC,0x32,0x6A,0x1E,0xB5,0xEE,0x3C,0xD5,0xFC, +0xE7,0x81,0x1D,0x19,0xC3,0x24,0x42,0xEA,0x63,0x39,0xA9, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=AAA Certificate Services */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=AAA Certificate Services */ + + +const unsigned char Comodo_AAA_Services_root_certificate[1078]={ +0x30,0x82,0x04,0x32,0x30,0x82,0x03,0x1A,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x7B,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x21,0x30,0x1F,0x06,0x03,0x55, +0x04,0x03,0x0C,0x18,0x41,0x41,0x41,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,0x1E,0x17,0x0D, +0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32, +0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x7B,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06, +0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61, +0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04, +0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03, +0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43,0x41,0x20,0x4C, +0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x0C, +0x18,0x41,0x41,0x41,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65, +0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,0x82,0x01,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F, +0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xBE,0x40,0x9D,0xF4,0x6E,0xE1, +0xEA,0x76,0x87,0x1C,0x4D,0x45,0x44,0x8E,0xBE,0x46,0xC8,0x83,0x06,0x9D,0xC1,0x2A, +0xFE,0x18,0x1F,0x8E,0xE4,0x02,0xFA,0xF3,0xAB,0x5D,0x50,0x8A,0x16,0x31,0x0B,0x9A, +0x06,0xD0,0xC5,0x70,0x22,0xCD,0x49,0x2D,0x54,0x63,0xCC,0xB6,0x6E,0x68,0x46,0x0B, +0x53,0xEA,0xCB,0x4C,0x24,0xC0,0xBC,0x72,0x4E,0xEA,0xF1,0x15,0xAE,0xF4,0x54,0x9A, +0x12,0x0A,0xC3,0x7A,0xB2,0x33,0x60,0xE2,0xDA,0x89,0x55,0xF3,0x22,0x58,0xF3,0xDE, +0xDC,0xCF,0xEF,0x83,0x86,0xA2,0x8C,0x94,0x4F,0x9F,0x68,0xF2,0x98,0x90,0x46,0x84, +0x27,0xC7,0x76,0xBF,0xE3,0xCC,0x35,0x2C,0x8B,0x5E,0x07,0x64,0x65,0x82,0xC0,0x48, +0xB0,0xA8,0x91,0xF9,0x61,0x9F,0x76,0x20,0x50,0xA8,0x91,0xC7,0x66,0xB5,0xEB,0x78, +0x62,0x03,0x56,0xF0,0x8A,0x1A,0x13,0xEA,0x31,0xA3,0x1E,0xA0,0x99,0xFD,0x38,0xF6, +0xF6,0x27,0x32,0x58,0x6F,0x07,0xF5,0x6B,0xB8,0xFB,0x14,0x2B,0xAF,0xB7,0xAA,0xCC, +0xD6,0x63,0x5F,0x73,0x8C,0xDA,0x05,0x99,0xA8,0x38,0xA8,0xCB,0x17,0x78,0x36,0x51, +0xAC,0xE9,0x9E,0xF4,0x78,0x3A,0x8D,0xCF,0x0F,0xD9,0x42,0xE2,0x98,0x0C,0xAB,0x2F, +0x9F,0x0E,0x01,0xDE,0xEF,0x9F,0x99,0x49,0xF1,0x2D,0xDF,0xAC,0x74,0x4D,0x1B,0x98, +0xB5,0x47,0xC5,0xE5,0x29,0xD1,0xF9,0x90,0x18,0xC7,0x62,0x9C,0xBE,0x83,0xC7,0x26, +0x7B,0x3E,0x8A,0x25,0xC7,0xC0,0xDD,0x9D,0xE6,0x35,0x68,0x10,0x20,0x9D,0x8F,0xD8, +0xDE,0xD2,0xC3,0x84,0x9C,0x0D,0x5E,0xE8,0x2F,0xC9,0x02,0x03,0x01,0x00,0x01,0xA3, +0x81,0xC0,0x30,0x81,0xBD,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14, +0xA0,0x11,0x0A,0x23,0x3E,0x96,0xF1,0x07,0xEC,0xE2,0xAF,0x29,0xEF,0x82,0xA5,0x7F, +0xD0,0x30,0xA4,0xB4,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x7B,0x06,0x03,0x55,0x1D,0x1F,0x04,0x74,0x30,0x72, +0x30,0x38,0xA0,0x36,0xA0,0x34,0x86,0x32,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63, +0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F, +0x41,0x41,0x41,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65, +0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30,0x36,0xA0,0x34,0xA0,0x32, +0x86,0x30,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D, +0x6F,0x64,0x6F,0x2E,0x6E,0x65,0x74,0x2F,0x41,0x41,0x41,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63, +0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05, +0x00,0x03,0x82,0x01,0x01,0x00,0x08,0x56,0xFC,0x02,0xF0,0x9B,0xE8,0xFF,0xA4,0xFA, +0xD6,0x7B,0xC6,0x44,0x80,0xCE,0x4F,0xC4,0xC5,0xF6,0x00,0x58,0xCC,0xA6,0xB6,0xBC, +0x14,0x49,0x68,0x04,0x76,0xE8,0xE6,0xEE,0x5D,0xEC,0x02,0x0F,0x60,0xD6,0x8D,0x50, +0x18,0x4F,0x26,0x4E,0x01,0xE3,0xE6,0xB0,0xA5,0xEE,0xBF,0xBC,0x74,0x54,0x41,0xBF, +0xFD,0xFC,0x12,0xB8,0xC7,0x4F,0x5A,0xF4,0x89,0x60,0x05,0x7F,0x60,0xB7,0x05,0x4A, +0xF3,0xF6,0xF1,0xC2,0xBF,0xC4,0xB9,0x74,0x86,0xB6,0x2D,0x7D,0x6B,0xCC,0xD2,0xF3, +0x46,0xDD,0x2F,0xC6,0xE0,0x6A,0xC3,0xC3,0x34,0x03,0x2C,0x7D,0x96,0xDD,0x5A,0xC2, +0x0E,0xA7,0x0A,0x99,0xC1,0x05,0x8B,0xAB,0x0C,0x2F,0xF3,0x5C,0x3A,0xCF,0x6C,0x37, +0x55,0x09,0x87,0xDE,0x53,0x40,0x6C,0x58,0xEF,0xFC,0xB6,0xAB,0x65,0x6E,0x04,0xF6, +0x1B,0xDC,0x3C,0xE0,0x5A,0x15,0xC6,0x9E,0xD9,0xF1,0x59,0x48,0x30,0x21,0x65,0x03, +0x6C,0xEC,0xE9,0x21,0x73,0xEC,0x9B,0x03,0xA1,0xE0,0x37,0xAD,0xA0,0x15,0x18,0x8F, +0xFA,0xBA,0x02,0xCE,0xA7,0x2C,0xA9,0x10,0x13,0x2C,0xD4,0xE5,0x08,0x26,0xAB,0x22, +0x97,0x60,0xF8,0x90,0x5E,0x74,0xD4,0xA2,0x9A,0x53,0xBD,0xF2,0xA9,0x68,0xE0,0xA2, +0x6E,0xC2,0xD7,0x6C,0xB1,0xA3,0x0F,0x9E,0xBF,0xEB,0x68,0xE7,0x56,0xF2,0xAE,0xF2, +0xE3,0x2B,0x38,0x3A,0x09,0x81,0xB5,0x6B,0x85,0xD7,0xBE,0x2D,0xED,0x3F,0x1A,0xB7, +0xB2,0x63,0xE2,0xF5,0x62,0x2C,0x82,0xD4,0x6A,0x00,0x41,0x50,0xF1,0x39,0x83,0x9F, +0x95,0xE9,0x36,0x96,0x98,0x6E, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority */ + + +const unsigned char COMODO_Certification_Authority_certificate[1057]={ +0x30,0x82,0x04,0x1D,0x30,0x82,0x03,0x05,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x4E, +0x81,0x2D,0x8A,0x82,0x65,0xE0,0x0B,0x02,0xEE,0x3E,0x35,0x02,0x46,0xE5,0x3D,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x81,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x27,0x30,0x25,0x06,0x03,0x55, +0x04,0x03,0x13,0x1E,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x30,0x31,0x30,0x30,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35, +0x39,0x5A,0x30,0x81,0x81,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x47,0x42,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65, +0x61,0x74,0x65,0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72, +0x64,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F, +0x44,0x4F,0x20,0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x27,0x30, +0x25,0x06,0x03,0x55,0x04,0x03,0x13,0x1E,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xD0,0x40,0x8B,0x8B,0x72,0xE3,0x91,0x1B,0xF7, +0x51,0xC1,0x1B,0x54,0x04,0x98,0xD3,0xA9,0xBF,0xC1,0xE6,0x8A,0x5D,0x3B,0x87,0xFB, +0xBB,0x88,0xCE,0x0D,0xE3,0x2F,0x3F,0x06,0x96,0xF0,0xA2,0x29,0x50,0x99,0xAE,0xDB, +0x3B,0xA1,0x57,0xB0,0x74,0x51,0x71,0xCD,0xED,0x42,0x91,0x4D,0x41,0xFE,0xA9,0xC8, +0xD8,0x6A,0x86,0x77,0x44,0xBB,0x59,0x66,0x97,0x50,0x5E,0xB4,0xD4,0x2C,0x70,0x44, +0xCF,0xDA,0x37,0x95,0x42,0x69,0x3C,0x30,0xC4,0x71,0xB3,0x52,0xF0,0x21,0x4D,0xA1, +0xD8,0xBA,0x39,0x7C,0x1C,0x9E,0xA3,0x24,0x9D,0xF2,0x83,0x16,0x98,0xAA,0x16,0x7C, +0x43,0x9B,0x15,0x5B,0xB7,0xAE,0x34,0x91,0xFE,0xD4,0x62,0x26,0x18,0x46,0x9A,0x3F, +0xEB,0xC1,0xF9,0xF1,0x90,0x57,0xEB,0xAC,0x7A,0x0D,0x8B,0xDB,0x72,0x30,0x6A,0x66, +0xD5,0xE0,0x46,0xA3,0x70,0xDC,0x68,0xD9,0xFF,0x04,0x48,0x89,0x77,0xDE,0xB5,0xE9, +0xFB,0x67,0x6D,0x41,0xE9,0xBC,0x39,0xBD,0x32,0xD9,0x62,0x02,0xF1,0xB1,0xA8,0x3D, +0x6E,0x37,0x9C,0xE2,0x2F,0xE2,0xD3,0xA2,0x26,0x8B,0xC6,0xB8,0x55,0x43,0x88,0xE1, +0x23,0x3E,0xA5,0xD2,0x24,0x39,0x6A,0x47,0xAB,0x00,0xD4,0xA1,0xB3,0xA9,0x25,0xFE, +0x0D,0x3F,0xA7,0x1D,0xBA,0xD3,0x51,0xC1,0x0B,0xA4,0xDA,0xAC,0x38,0xEF,0x55,0x50, +0x24,0x05,0x65,0x46,0x93,0x34,0x4F,0x2D,0x8D,0xAD,0xC6,0xD4,0x21,0x19,0xD2,0x8E, +0xCA,0x05,0x61,0x71,0x07,0x73,0x47,0xE5,0x8A,0x19,0x12,0xBD,0x04,0x4D,0xCE,0x4E, +0x9C,0xA5,0x48,0xAC,0xBB,0x26,0xF7,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x8E,0x30, +0x81,0x8B,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x0B,0x58,0xE5, +0x8B,0xC6,0x4C,0x15,0x37,0xA4,0x40,0xA9,0x30,0xA9,0x21,0xBE,0x47,0x36,0x5A,0x56, +0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x49,0x06,0x03,0x55,0x1D,0x1F,0x04,0x42,0x30,0x40,0x30,0x3E,0xA0, +0x3C,0xA0,0x3A,0x86,0x38,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E, +0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x43,0x4F,0x4D, +0x4F,0x44,0x4F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x2E,0x63,0x72,0x6C,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01, +0x00,0x3E,0x98,0x9E,0x9B,0xF6,0x1B,0xE9,0xD7,0x39,0xB7,0x78,0xAE,0x1D,0x72,0x18, +0x49,0xD3,0x87,0xE4,0x43,0x82,0xEB,0x3F,0xC9,0xAA,0xF5,0xA8,0xB5,0xEF,0x55,0x7C, +0x21,0x52,0x65,0xF9,0xD5,0x0D,0xE1,0x6C,0xF4,0x3E,0x8C,0x93,0x73,0x91,0x2E,0x02, +0xC4,0x4E,0x07,0x71,0x6F,0xC0,0x8F,0x38,0x61,0x08,0xA8,0x1E,0x81,0x0A,0xC0,0x2F, +0x20,0x2F,0x41,0x8B,0x91,0xDC,0x48,0x45,0xBC,0xF1,0xC6,0xDE,0xBA,0x76,0x6B,0x33, +0xC8,0x00,0x2D,0x31,0x46,0x4C,0xED,0xE7,0x9D,0xCF,0x88,0x94,0xFF,0x33,0xC0,0x56, +0xE8,0x24,0x86,0x26,0xB8,0xD8,0x38,0x38,0xDF,0x2A,0x6B,0xDD,0x12,0xCC,0xC7,0x3F, +0x47,0x17,0x4C,0xA2,0xC2,0x06,0x96,0x09,0xD6,0xDB,0xFE,0x3F,0x3C,0x46,0x41,0xDF, +0x58,0xE2,0x56,0x0F,0x3C,0x3B,0xC1,0x1C,0x93,0x35,0xD9,0x38,0x52,0xAC,0xEE,0xC8, +0xEC,0x2E,0x30,0x4E,0x94,0x35,0xB4,0x24,0x1F,0x4B,0x78,0x69,0xDA,0xF2,0x02,0x38, +0xCC,0x95,0x52,0x93,0xF0,0x70,0x25,0x59,0x9C,0x20,0x67,0xC4,0xEE,0xF9,0x8B,0x57, +0x61,0xF4,0x92,0x76,0x7D,0x3F,0x84,0x8D,0x55,0xB7,0xE8,0xE5,0xAC,0xD5,0xF1,0xF5, +0x19,0x56,0xA6,0x5A,0xFB,0x90,0x1C,0xAF,0x93,0xEB,0xE5,0x1C,0xD4,0x67,0x97,0x5D, +0x04,0x0E,0xBE,0x0B,0x83,0xA6,0x17,0x83,0xB9,0x30,0x12,0xA0,0xC5,0x33,0x15,0x05, +0xB9,0x0D,0xFB,0xC7,0x05,0x76,0xE3,0xD8,0x4A,0x8D,0xFC,0x34,0x17,0xA3,0xC6,0x21, +0x28,0xBE,0x30,0x45,0x31,0x1E,0xC7,0x78,0xBE,0x58,0x61,0x38,0xAC,0x3B,0xE2,0x01, +0x65, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO ECC Certification Authority */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO ECC Certification Authority */ + + +const unsigned char COMODO_ECC_Certification_Authority_certificate[653]={ +0x30,0x82,0x02,0x89,0x30,0x82,0x02,0x0F,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x1F, +0x47,0xAF,0xAA,0x62,0x00,0x70,0x50,0x54,0x4C,0x01,0x9E,0x9B,0x63,0x99,0x2A,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x85,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06, +0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61, +0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04, +0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03, +0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,0x41,0x20,0x4C, +0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13, +0x22,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x45,0x43,0x43,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x38,0x30,0x33,0x30,0x36,0x30,0x30,0x30, +0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32,0x33,0x35,0x39, +0x35,0x39,0x5A,0x30,0x81,0x85,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72, +0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72, +0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F, +0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D, +0x4F,0x44,0x4F,0x20,0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x2B, +0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20, +0x45,0x43,0x43,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x76,0x30,0x10,0x06, +0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03, +0x62,0x00,0x04,0x03,0x47,0x7B,0x2F,0x75,0xC9,0x82,0x15,0x85,0xFB,0x75,0xE4,0x91, +0x16,0xD4,0xAB,0x62,0x99,0xF5,0x3E,0x52,0x0B,0x06,0xCE,0x41,0x00,0x7F,0x97,0xE1, +0x0A,0x24,0x3C,0x1D,0x01,0x04,0xEE,0x3D,0xD2,0x8D,0x09,0x97,0x0C,0xE0,0x75,0xE4, +0xFA,0xFB,0x77,0x8A,0x2A,0xF5,0x03,0x60,0x4B,0x36,0x8B,0x16,0x23,0x16,0xAD,0x09, +0x71,0xF4,0x4A,0xF4,0x28,0x50,0xB4,0xFE,0x88,0x1C,0x6E,0x3F,0x6C,0x2F,0x2F,0x09, +0x59,0x5B,0xA5,0x5B,0x0B,0x33,0x99,0xE2,0xC3,0x3D,0x89,0xF9,0x6A,0x2C,0xEF,0xB2, +0xD3,0x06,0xE9,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0x75,0x71,0xA7,0x19,0x48,0x19,0xBC,0x9D,0x9D,0xEA,0x41,0x47,0xDF,0x94, +0xC4,0x48,0x77,0x99,0xD3,0x79,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF, +0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D, +0x04,0x03,0x03,0x03,0x68,0x00,0x30,0x65,0x02,0x31,0x00,0xEF,0x03,0x5B,0x7A,0xAC, +0xB7,0x78,0x0A,0x72,0xB7,0x88,0xDF,0xFF,0xB5,0x46,0x14,0x09,0x0A,0xFA,0xA0,0xE6, +0x7D,0x08,0xC6,0x1A,0x87,0xBD,0x18,0xA8,0x73,0xBD,0x26,0xCA,0x60,0x0C,0x9D,0xCE, +0x99,0x9F,0xCF,0x5C,0x0F,0x30,0xE1,0xBE,0x14,0x31,0xEA,0x02,0x30,0x14,0xF4,0x93, +0x3C,0x49,0xA7,0x33,0x7A,0x90,0x46,0x47,0xB3,0x63,0x7D,0x13,0x9B,0x4E,0xB7,0x6F, +0x18,0x37,0x80,0x53,0xFE,0xDD,0x20,0xE0,0x35,0x9A,0x36,0xD1,0xC7,0x01,0xB9,0xE6, +0xDC,0xDD,0xF3,0xFF,0x1D,0x2C,0x3A,0x16,0x57,0xD9,0x92,0x39,0xD6, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Secure Certificate Services */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Secure Certificate Services */ + + +const unsigned char Comodo_Secure_Services_root_certificate[1091]={ +0x30,0x82,0x04,0x3F,0x30,0x82,0x03,0x27,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x7E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x03,0x0C,0x1B,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30, +0x1E,0x17,0x0D,0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A, +0x17,0x0D,0x32,0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30, +0x7E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x03,0x0C,0x1B,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30, +0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01, +0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00, +0xC0,0x71,0x33,0x82,0x8A,0xD0,0x70,0xEB,0x73,0x87,0x82,0x40,0xD5,0x1D,0xE4,0xCB, +0xC9,0x0E,0x42,0x90,0xF9,0xDE,0x34,0xB9,0xA1,0xBA,0x11,0xF4,0x25,0x85,0xF3,0xCC, +0x72,0x6D,0xF2,0x7B,0x97,0x6B,0xB3,0x07,0xF1,0x77,0x24,0x91,0x5F,0x25,0x8F,0xF6, +0x74,0x3D,0xE4,0x80,0xC2,0xF8,0x3C,0x0D,0xF3,0xBF,0x40,0xEA,0xF7,0xC8,0x52,0xD1, +0x72,0x6F,0xEF,0xC8,0xAB,0x41,0xB8,0x6E,0x2E,0x17,0x2A,0x95,0x69,0x0C,0xCD,0xD2, +0x1E,0x94,0x7B,0x2D,0x94,0x1D,0xAA,0x75,0xD7,0xB3,0x98,0xCB,0xAC,0xBC,0x64,0x53, +0x40,0xBC,0x8F,0xAC,0xAC,0x36,0xCB,0x5C,0xAD,0xBB,0xDD,0xE0,0x94,0x17,0xEC,0xD1, +0x5C,0xD0,0xBF,0xEF,0xA5,0x95,0xC9,0x90,0xC5,0xB0,0xAC,0xFB,0x1B,0x43,0xDF,0x7A, +0x08,0x5D,0xB7,0xB8,0xF2,0x40,0x1B,0x2B,0x27,0x9E,0x50,0xCE,0x5E,0x65,0x82,0x88, +0x8C,0x5E,0xD3,0x4E,0x0C,0x7A,0xEA,0x08,0x91,0xB6,0x36,0xAA,0x2B,0x42,0xFB,0xEA, +0xC2,0xA3,0x39,0xE5,0xDB,0x26,0x38,0xAD,0x8B,0x0A,0xEE,0x19,0x63,0xC7,0x1C,0x24, +0xDF,0x03,0x78,0xDA,0xE6,0xEA,0xC1,0x47,0x1A,0x0B,0x0B,0x46,0x09,0xDD,0x02,0xFC, +0xDE,0xCB,0x87,0x5F,0xD7,0x30,0x63,0x68,0xA1,0xAE,0xDC,0x32,0xA1,0xBA,0xBE,0xFE, +0x44,0xAB,0x68,0xB6,0xA5,0x17,0x15,0xFD,0xBD,0xD5,0xA7,0xA7,0x9A,0xE4,0x44,0x33, +0xE9,0x88,0x8E,0xFC,0xED,0x51,0xEB,0x93,0x71,0x4E,0xAD,0x01,0xE7,0x44,0x8E,0xAB, +0x2D,0xCB,0xA8,0xFE,0x01,0x49,0x48,0xF0,0xC0,0xDD,0xC7,0x68,0xD8,0x92,0xFE,0x3D, +0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xC7,0x30,0x81,0xC4,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0x3C,0xD8,0x93,0x88,0xC2,0xC0,0x82,0x09,0xCC,0x01, +0x99,0x06,0x93,0x20,0xE9,0x9E,0x70,0x09,0x63,0x4F,0x30,0x0E,0x06,0x03,0x55,0x1D, +0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x81,0x06,0x03, +0x55,0x1D,0x1F,0x04,0x7A,0x30,0x78,0x30,0x3B,0xA0,0x39,0xA0,0x37,0x86,0x35,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F, +0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x53,0x65,0x63,0x75,0x72,0x65,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73, +0x2E,0x63,0x72,0x6C,0x30,0x39,0xA0,0x37,0xA0,0x35,0x86,0x33,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x2E,0x6E,0x65, +0x74,0x2F,0x53,0x65,0x63,0x75,0x72,0x65,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x87,0x01,0x6D,0x23,0x1D,0x7E,0x5B,0x17,0x7D,0xC1,0x61,0x32,0xCF, +0x8F,0xE7,0xF3,0x8A,0x94,0x59,0x66,0xE0,0x9E,0x28,0xA8,0x5E,0xD3,0xB7,0xF4,0x34, +0xE6,0xAA,0x39,0xB2,0x97,0x16,0xC5,0x82,0x6F,0x32,0xA4,0xE9,0x8C,0xE7,0xAF,0xFD, +0xEF,0xC2,0xE8,0xB9,0x4B,0xAA,0xA3,0xF4,0xE6,0xDA,0x8D,0x65,0x21,0xFB,0xBA,0x80, +0xEB,0x26,0x28,0x85,0x1A,0xFE,0x39,0x8C,0xDE,0x5B,0x04,0x04,0xB4,0x54,0xF9,0xA3, +0x67,0x9E,0x41,0xFA,0x09,0x52,0xCC,0x05,0x48,0xA8,0xC9,0x3F,0x21,0x04,0x1E,0xCE, +0x48,0x6B,0xFC,0x85,0xE8,0xC2,0x7B,0xAF,0x7F,0xB7,0xCC,0xF8,0x5F,0x3A,0xFD,0x35, +0xC6,0x0D,0xEF,0x97,0xDC,0x4C,0xAB,0x11,0xE1,0x6B,0xCB,0x31,0xD1,0x6C,0xFB,0x48, +0x80,0xAB,0xDC,0x9C,0x37,0xB8,0x21,0x14,0x4B,0x0D,0x71,0x3D,0xEC,0x83,0x33,0x6E, +0xD1,0x6E,0x32,0x16,0xEC,0x98,0xC7,0x16,0x8B,0x59,0xA6,0x34,0xAB,0x05,0x57,0x2D, +0x93,0xF7,0xAA,0x13,0xCB,0xD2,0x13,0xE2,0xB7,0x2E,0x3B,0xCD,0x6B,0x50,0x17,0x09, +0x68,0x3E,0xB5,0x26,0x57,0xEE,0xB6,0xE0,0xB6,0xDD,0xB9,0x29,0x80,0x79,0x7D,0x8F, +0xA3,0xF0,0xA4,0x28,0xA4,0x15,0xC4,0x85,0xF4,0x27,0xD4,0x6B,0xBF,0xE5,0x5C,0xE4, +0x65,0x02,0x76,0x54,0xB4,0xE3,0x37,0x66,0x24,0xD3,0x19,0x61,0xC8,0x52,0x10,0xE5, +0x8B,0x37,0x9A,0xB9,0xA9,0xF9,0x1D,0xBF,0xEA,0x99,0x92,0x61,0x96,0xFF,0x01,0xCD, +0xA1,0x5F,0x0D,0xBC,0x71,0xBC,0x0E,0xAC,0x0B,0x1D,0x47,0x45,0x1D,0xC1,0xEC,0x7C, +0xEC,0xFD,0x29, +}; + + +/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Trusted Certificate Services */ +/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Trusted Certificate Services */ + + +const unsigned char Comodo_Trusted_Services_root_certificate[1095]={ +0x30,0x82,0x04,0x43,0x30,0x82,0x03,0x2B,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x7F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72, +0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06, +0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30, +0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43, +0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x25,0x30,0x23,0x06,0x03,0x55, +0x04,0x03,0x0C,0x1C,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73, +0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30, +0x5A,0x17,0x0D,0x32,0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A, +0x30,0x7F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31, +0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65, +0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E, +0x06,0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A, +0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20, +0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x25,0x30,0x23,0x06,0x03, +0x55,0x04,0x03,0x0C,0x1C,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65, +0x73,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01, +0x01,0x00,0xDF,0x71,0x6F,0x36,0x58,0x53,0x5A,0xF2,0x36,0x54,0x57,0x80,0xC4,0x74, +0x08,0x20,0xED,0x18,0x7F,0x2A,0x1D,0xE6,0x35,0x9A,0x1E,0x25,0xAC,0x9C,0xE5,0x96, +0x7E,0x72,0x52,0xA0,0x15,0x42,0xDB,0x59,0xDD,0x64,0x7A,0x1A,0xD0,0xB8,0x7B,0xDD, +0x39,0x15,0xBC,0x55,0x48,0xC4,0xED,0x3A,0x00,0xEA,0x31,0x11,0xBA,0xF2,0x71,0x74, +0x1A,0x67,0xB8,0xCF,0x33,0xCC,0xA8,0x31,0xAF,0xA3,0xE3,0xD7,0x7F,0xBF,0x33,0x2D, +0x4C,0x6A,0x3C,0xEC,0x8B,0xC3,0x92,0xD2,0x53,0x77,0x24,0x74,0x9C,0x07,0x6E,0x70, +0xFC,0xBD,0x0B,0x5B,0x76,0xBA,0x5F,0xF2,0xFF,0xD7,0x37,0x4B,0x4A,0x60,0x78,0xF7, +0xF0,0xFA,0xCA,0x70,0xB4,0xEA,0x59,0xAA,0xA3,0xCE,0x48,0x2F,0xA9,0xC3,0xB2,0x0B, +0x7E,0x17,0x72,0x16,0x0C,0xA6,0x07,0x0C,0x1B,0x38,0xCF,0xC9,0x62,0xB7,0x3F,0xA0, +0x93,0xA5,0x87,0x41,0xF2,0xB7,0x70,0x40,0x77,0xD8,0xBE,0x14,0x7C,0xE3,0xA8,0xC0, +0x7A,0x8E,0xE9,0x63,0x6A,0xD1,0x0F,0x9A,0xC6,0xD2,0xF4,0x8B,0x3A,0x14,0x04,0x56, +0xD4,0xED,0xB8,0xCC,0x6E,0xF5,0xFB,0xE2,0x2C,0x58,0xBD,0x7F,0x4F,0x6B,0x2B,0xF7, +0x60,0x24,0x58,0x24,0xCE,0x26,0xEF,0x34,0x91,0x3A,0xD5,0xE3,0x81,0xD0,0xB2,0xF0, +0x04,0x02,0xD7,0x5B,0xB7,0x3E,0x92,0xAC,0x6B,0x12,0x8A,0xF9,0xE4,0x05,0xB0,0x3B, +0x91,0x49,0x5C,0xB2,0xEB,0x53,0xEA,0xF8,0x9F,0x47,0x86,0xEE,0xBF,0x95,0xC0,0xC0, +0x06,0x9F,0xD2,0x5B,0x5E,0x11,0x1B,0xF4,0xC7,0x04,0x35,0x29,0xD2,0x55,0x5C,0xE4, +0xED,0xEB,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xC9,0x30,0x81,0xC6,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC5,0x7B,0x58,0xBD,0xED,0xDA,0x25,0x69, +0xD2,0xF7,0x59,0x16,0xA8,0xB3,0x32,0xC0,0x7B,0x27,0x5B,0xF4,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03, +0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x83, +0x06,0x03,0x55,0x1D,0x1F,0x04,0x7C,0x30,0x7A,0x30,0x3C,0xA0,0x3A,0xA0,0x38,0x86, +0x36,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F, +0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x54,0x72,0x75,0x73,0x74,0x65,0x64, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69, +0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30,0x3A,0xA0,0x38,0xA0,0x36,0x86,0x34,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F, +0x2E,0x6E,0x65,0x74,0x2F,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E, +0x63,0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xC8,0x93,0x81,0x3B,0x89,0xB4,0xAF,0xB8,0x84, +0x12,0x4C,0x8D,0xD2,0xF0,0xDB,0x70,0xBA,0x57,0x86,0x15,0x34,0x10,0xB9,0x2F,0x7F, +0x1E,0xB0,0xA8,0x89,0x60,0xA1,0x8A,0xC2,0x77,0x0C,0x50,0x4A,0x9B,0x00,0x8B,0xD8, +0x8B,0xF4,0x41,0xE2,0xD0,0x83,0x8A,0x4A,0x1C,0x14,0x06,0xB0,0xA3,0x68,0x05,0x70, +0x31,0x30,0xA7,0x53,0x9B,0x0E,0xE9,0x4A,0xA0,0x58,0x69,0x67,0x0E,0xAE,0x9D,0xF6, +0xA5,0x2C,0x41,0xBF,0x3C,0x06,0x6B,0xE4,0x59,0xCC,0x6D,0x10,0xF1,0x96,0x6F,0x1F, +0xDF,0xF4,0x04,0x02,0xA4,0x9F,0x45,0x3E,0xC8,0xD8,0xFA,0x36,0x46,0x44,0x50,0x3F, +0x82,0x97,0x91,0x1F,0x28,0xDB,0x18,0x11,0x8C,0x2A,0xE4,0x65,0x83,0x57,0x12,0x12, +0x8C,0x17,0x3F,0x94,0x36,0xFE,0x5D,0xB0,0xC0,0x04,0x77,0x13,0xB8,0xF4,0x15,0xD5, +0x3F,0x38,0xCC,0x94,0x3A,0x55,0xD0,0xAC,0x98,0xF5,0xBA,0x00,0x5F,0xE0,0x86,0x19, +0x81,0x78,0x2F,0x28,0xC0,0x7E,0xD3,0xCC,0x42,0x0A,0xF5,0xAE,0x50,0xA0,0xD1,0x3E, +0xC6,0xA1,0x71,0xEC,0x3F,0xA0,0x20,0x8C,0x66,0x3A,0x89,0xB4,0x8E,0xD4,0xD8,0xB1, +0x4D,0x25,0x47,0xEE,0x2F,0x88,0xC8,0xB5,0xE1,0x05,0x45,0xC0,0xBE,0x14,0x71,0xDE, +0x7A,0xFD,0x8E,0x7B,0x7D,0x4D,0x08,0x96,0xA5,0x12,0x73,0xF0,0x2D,0xCA,0x37,0x27, +0x74,0x12,0x27,0x4C,0xCB,0xB6,0x97,0xE9,0xD9,0xAE,0x08,0x6D,0x5A,0x39,0x40,0xDD, +0x05,0x47,0x75,0x6A,0x5A,0x21,0xB3,0xA3,0x18,0xCF,0x4E,0xF7,0x2E,0x57,0xB7,0x98, +0x70,0x5E,0xC8,0xC4,0x78,0xB0,0x62, +}; + + +/* subject:/O=Cybertrust, Inc/CN=Cybertrust Global Root */ +/* issuer :/O=Cybertrust, Inc/CN=Cybertrust Global Root */ + + +const unsigned char Cybertrust_Global_Root_certificate[933]={ +0x30,0x82,0x03,0xA1,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x0F,0x85,0xAA,0x2D,0x48,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x3B,0x31,0x18,0x30,0x16,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0F,0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74, +0x2C,0x20,0x49,0x6E,0x63,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16, +0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61, +0x6C,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x31,0x35, +0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x31,0x31,0x32,0x31,0x35,0x30, +0x38,0x30,0x30,0x30,0x30,0x5A,0x30,0x3B,0x31,0x18,0x30,0x16,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0F,0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2C,0x20,0x49, +0x6E,0x63,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,0x43,0x79,0x62, +0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52, +0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02, +0x82,0x01,0x01,0x00,0xF8,0xC8,0xBC,0xBD,0x14,0x50,0x66,0x13,0xFF,0xF0,0xD3,0x79, +0xEC,0x23,0xF2,0xB7,0x1A,0xC7,0x8E,0x85,0xF1,0x12,0x73,0xA6,0x19,0xAA,0x10,0xDB, +0x9C,0xA2,0x65,0x74,0x5A,0x77,0x3E,0x51,0x7D,0x56,0xF6,0xDC,0x23,0xB6,0xD4,0xED, +0x5F,0x58,0xB1,0x37,0x4D,0xD5,0x49,0x0E,0x6E,0xF5,0x6A,0x87,0xD6,0xD2,0x8C,0xD2, +0x27,0xC6,0xE2,0xFF,0x36,0x9F,0x98,0x65,0xA0,0x13,0x4E,0xC6,0x2A,0x64,0x9B,0xD5, +0x90,0x12,0xCF,0x14,0x06,0xF4,0x3B,0xE3,0xD4,0x28,0xBE,0xE8,0x0E,0xF8,0xAB,0x4E, +0x48,0x94,0x6D,0x8E,0x95,0x31,0x10,0x5C,0xED,0xA2,0x2D,0xBD,0xD5,0x3A,0x6D,0xB2, +0x1C,0xBB,0x60,0xC0,0x46,0x4B,0x01,0xF5,0x49,0xAE,0x7E,0x46,0x8A,0xD0,0x74,0x8D, +0xA1,0x0C,0x02,0xCE,0xEE,0xFC,0xE7,0x8F,0xB8,0x6B,0x66,0xF3,0x7F,0x44,0x00,0xBF, +0x66,0x25,0x14,0x2B,0xDD,0x10,0x30,0x1D,0x07,0x96,0x3F,0x4D,0xF6,0x6B,0xB8,0x8F, +0xB7,0x7B,0x0C,0xA5,0x38,0xEB,0xDE,0x47,0xDB,0xD5,0x5D,0x39,0xFC,0x88,0xA7,0xF3, +0xD7,0x2A,0x74,0xF1,0xE8,0x5A,0xA2,0x3B,0x9F,0x50,0xBA,0xA6,0x8C,0x45,0x35,0xC2, +0x50,0x65,0x95,0xDC,0x63,0x82,0xEF,0xDD,0xBF,0x77,0x4D,0x9C,0x62,0xC9,0x63,0x73, +0x16,0xD0,0x29,0x0F,0x49,0xA9,0x48,0xF0,0xB3,0xAA,0xB7,0x6C,0xC5,0xA7,0x30,0x39, +0x40,0x5D,0xAE,0xC4,0xE2,0x5D,0x26,0x53,0xF0,0xCE,0x1C,0x23,0x08,0x61,0xA8,0x94, +0x19,0xBA,0x04,0x62,0x40,0xEC,0x1F,0x38,0x70,0x77,0x12,0x06,0x71,0xA7,0x30,0x18, +0x5D,0x25,0x27,0xA5,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xA5,0x30,0x81,0xA2,0x30, +0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30, +0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xB6,0x08,0x7B,0x0D,0x7A, +0xCC,0xAC,0x20,0x4C,0x86,0x56,0x32,0x5E,0xCF,0xAB,0x6E,0x85,0x2D,0x70,0x57,0x30, +0x3F,0x06,0x03,0x55,0x1D,0x1F,0x04,0x38,0x30,0x36,0x30,0x34,0xA0,0x32,0xA0,0x30, +0x86,0x2E,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x32,0x2E,0x70,0x75, +0x62,0x6C,0x69,0x63,0x2D,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x63, +0x72,0x6C,0x2F,0x63,0x74,0x2F,0x63,0x74,0x72,0x6F,0x6F,0x74,0x2E,0x63,0x72,0x6C, +0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xB6,0x08,0x7B, +0x0D,0x7A,0xCC,0xAC,0x20,0x4C,0x86,0x56,0x32,0x5E,0xCF,0xAB,0x6E,0x85,0x2D,0x70, +0x57,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x82,0x01,0x01,0x00,0x56,0xEF,0x0A,0x23,0xA0,0x54,0x4E,0x95,0x97,0xC9,0xF8, +0x89,0xDA,0x45,0xC1,0xD4,0xA3,0x00,0x25,0xF4,0x1F,0x13,0xAB,0xB7,0xA3,0x85,0x58, +0x69,0xC2,0x30,0xAD,0xD8,0x15,0x8A,0x2D,0xE3,0xC9,0xCD,0x81,0x5A,0xF8,0x73,0x23, +0x5A,0xA7,0x7C,0x05,0xF3,0xFD,0x22,0x3B,0x0E,0xD1,0x06,0xC4,0xDB,0x36,0x4C,0x73, +0x04,0x8E,0xE5,0xB0,0x22,0xE4,0xC5,0xF3,0x2E,0xA5,0xD9,0x23,0xE3,0xB8,0x4E,0x4A, +0x20,0xA7,0x6E,0x02,0x24,0x9F,0x22,0x60,0x67,0x7B,0x8B,0x1D,0x72,0x09,0xC5,0x31, +0x5C,0xE9,0x79,0x9F,0x80,0x47,0x3D,0xAD,0xA1,0x0B,0x07,0x14,0x3D,0x47,0xFF,0x03, +0x69,0x1A,0x0C,0x0B,0x44,0xE7,0x63,0x25,0xA7,0x7F,0xB2,0xC9,0xB8,0x76,0x84,0xED, +0x23,0xF6,0x7D,0x07,0xAB,0x45,0x7E,0xD3,0xDF,0xB3,0xBF,0xE9,0x8A,0xB6,0xCD,0xA8, +0xA2,0x67,0x2B,0x52,0xD5,0xB7,0x65,0xF0,0x39,0x4C,0x63,0xA0,0x91,0x79,0x93,0x52, +0x0F,0x54,0xDD,0x83,0xBB,0x9F,0xD1,0x8F,0xA7,0x53,0x73,0xC3,0xCB,0xFF,0x30,0xEC, +0x7C,0x04,0xB8,0xD8,0x44,0x1F,0x93,0x5F,0x71,0x09,0x22,0xB7,0x6E,0x3E,0xEA,0x1C, +0x03,0x4E,0x9D,0x1A,0x20,0x61,0xFB,0x81,0x37,0xEC,0x5E,0xFC,0x0A,0x45,0xAB,0xD7, +0xE7,0x17,0x55,0xD0,0xA0,0xEA,0x60,0x9B,0xA6,0xF6,0xE3,0x8C,0x5B,0x29,0xC2,0x06, +0x60,0x14,0x9D,0x2D,0x97,0x4C,0xA9,0x93,0x15,0x9D,0x61,0xC4,0x01,0x5F,0x48,0xD6, +0x58,0xBD,0x56,0x31,0x12,0x4E,0x11,0xC8,0x21,0xE0,0xB3,0x11,0x91,0x65,0xDB,0xB4, +0xA6,0x88,0x38,0xCE,0x55, +}; + + +/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA */ +/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA */ + + +const unsigned char DigiCert_Assured_ID_Root_CA_certificate[955]={ +0x30,0x82,0x03,0xB7,0x30,0x82,0x02,0x9F,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x0C, +0xE7,0xE0,0xE5,0x17,0xD8,0x46,0xFE,0x8F,0xE5,0x60,0xFC,0x1B,0xF0,0x30,0x39,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x65, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30, +0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77, +0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31, +0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x44,0x69,0x67,0x69,0x43,0x65, +0x72,0x74,0x20,0x41,0x73,0x73,0x75,0x72,0x65,0x64,0x20,0x49,0x44,0x20,0x52,0x6F, +0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x31,0x30,0x30, +0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x31,0x31,0x31,0x31,0x30,0x30,0x30, +0x30,0x30,0x30,0x30,0x5A,0x30,0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44, +0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06, +0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13, +0x1B,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x41,0x73,0x73,0x75,0x72,0x65, +0x64,0x20,0x49,0x44,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAD,0x0E,0x15, +0xCE,0xE4,0x43,0x80,0x5C,0xB1,0x87,0xF3,0xB7,0x60,0xF9,0x71,0x12,0xA5,0xAE,0xDC, +0x26,0x94,0x88,0xAA,0xF4,0xCE,0xF5,0x20,0x39,0x28,0x58,0x60,0x0C,0xF8,0x80,0xDA, +0xA9,0x15,0x95,0x32,0x61,0x3C,0xB5,0xB1,0x28,0x84,0x8A,0x8A,0xDC,0x9F,0x0A,0x0C, +0x83,0x17,0x7A,0x8F,0x90,0xAC,0x8A,0xE7,0x79,0x53,0x5C,0x31,0x84,0x2A,0xF6,0x0F, +0x98,0x32,0x36,0x76,0xCC,0xDE,0xDD,0x3C,0xA8,0xA2,0xEF,0x6A,0xFB,0x21,0xF2,0x52, +0x61,0xDF,0x9F,0x20,0xD7,0x1F,0xE2,0xB1,0xD9,0xFE,0x18,0x64,0xD2,0x12,0x5B,0x5F, +0xF9,0x58,0x18,0x35,0xBC,0x47,0xCD,0xA1,0x36,0xF9,0x6B,0x7F,0xD4,0xB0,0x38,0x3E, +0xC1,0x1B,0xC3,0x8C,0x33,0xD9,0xD8,0x2F,0x18,0xFE,0x28,0x0F,0xB3,0xA7,0x83,0xD6, +0xC3,0x6E,0x44,0xC0,0x61,0x35,0x96,0x16,0xFE,0x59,0x9C,0x8B,0x76,0x6D,0xD7,0xF1, +0xA2,0x4B,0x0D,0x2B,0xFF,0x0B,0x72,0xDA,0x9E,0x60,0xD0,0x8E,0x90,0x35,0xC6,0x78, +0x55,0x87,0x20,0xA1,0xCF,0xE5,0x6D,0x0A,0xC8,0x49,0x7C,0x31,0x98,0x33,0x6C,0x22, +0xE9,0x87,0xD0,0x32,0x5A,0xA2,0xBA,0x13,0x82,0x11,0xED,0x39,0x17,0x9D,0x99,0x3A, +0x72,0xA1,0xE6,0xFA,0xA4,0xD9,0xD5,0x17,0x31,0x75,0xAE,0x85,0x7D,0x22,0xAE,0x3F, +0x01,0x46,0x86,0xF6,0x28,0x79,0xC8,0xB1,0xDA,0xE4,0x57,0x17,0xC4,0x7E,0x1C,0x0E, +0xB0,0xB4,0x92,0xA6,0x56,0xB3,0xBD,0xB2,0x97,0xED,0xAA,0xA7,0xF0,0xB7,0xC5,0xA8, +0x3F,0x95,0x16,0xD0,0xFF,0xA1,0x96,0xEB,0x08,0x5F,0x18,0x77,0x4F,0x02,0x03,0x01, +0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF, +0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0x45,0xEB,0xA2,0xAF,0xF4,0x92,0xCB,0x82,0x31,0x2D,0x51,0x8B,0xA7,0xA7, +0x21,0x9D,0xF3,0x6D,0xC8,0x0F,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30, +0x16,0x80,0x14,0x45,0xEB,0xA2,0xAF,0xF4,0x92,0xCB,0x82,0x31,0x2D,0x51,0x8B,0xA7, +0xA7,0x21,0x9D,0xF3,0x6D,0xC8,0x0F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xA2,0x0E,0xBC,0xDF,0xE2, +0xED,0xF0,0xE3,0x72,0x73,0x7A,0x64,0x94,0xBF,0xF7,0x72,0x66,0xD8,0x32,0xE4,0x42, +0x75,0x62,0xAE,0x87,0xEB,0xF2,0xD5,0xD9,0xDE,0x56,0xB3,0x9F,0xCC,0xCE,0x14,0x28, +0xB9,0x0D,0x97,0x60,0x5C,0x12,0x4C,0x58,0xE4,0xD3,0x3D,0x83,0x49,0x45,0x58,0x97, +0x35,0x69,0x1A,0xA8,0x47,0xEA,0x56,0xC6,0x79,0xAB,0x12,0xD8,0x67,0x81,0x84,0xDF, +0x7F,0x09,0x3C,0x94,0xE6,0xB8,0x26,0x2C,0x20,0xBD,0x3D,0xB3,0x28,0x89,0xF7,0x5F, +0xFF,0x22,0xE2,0x97,0x84,0x1F,0xE9,0x65,0xEF,0x87,0xE0,0xDF,0xC1,0x67,0x49,0xB3, +0x5D,0xEB,0xB2,0x09,0x2A,0xEB,0x26,0xED,0x78,0xBE,0x7D,0x3F,0x2B,0xF3,0xB7,0x26, +0x35,0x6D,0x5F,0x89,0x01,0xB6,0x49,0x5B,0x9F,0x01,0x05,0x9B,0xAB,0x3D,0x25,0xC1, +0xCC,0xB6,0x7F,0xC2,0xF1,0x6F,0x86,0xC6,0xFA,0x64,0x68,0xEB,0x81,0x2D,0x94,0xEB, +0x42,0xB7,0xFA,0x8C,0x1E,0xDD,0x62,0xF1,0xBE,0x50,0x67,0xB7,0x6C,0xBD,0xF3,0xF1, +0x1F,0x6B,0x0C,0x36,0x07,0x16,0x7F,0x37,0x7C,0xA9,0x5B,0x6D,0x7A,0xF1,0x12,0x46, +0x60,0x83,0xD7,0x27,0x04,0xBE,0x4B,0xCE,0x97,0xBE,0xC3,0x67,0x2A,0x68,0x11,0xDF, +0x80,0xE7,0x0C,0x33,0x66,0xBF,0x13,0x0D,0x14,0x6E,0xF3,0x7F,0x1F,0x63,0x10,0x1E, +0xFA,0x8D,0x1B,0x25,0x6D,0x6C,0x8F,0xA5,0xB7,0x61,0x01,0xB1,0xD2,0xA3,0x26,0xA1, +0x10,0x71,0x9D,0xAD,0xE2,0xC3,0xF9,0xC3,0x99,0x51,0xB7,0x2B,0x07,0x08,0xCE,0x2E, +0xE6,0x50,0xB2,0xA7,0xFA,0x0A,0x45,0x2F,0xA2,0xF0,0xF2, +}; + + +/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA */ +/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA */ + + +const unsigned char DigiCert_Global_Root_CA_certificate[947]={ +0x30,0x82,0x03,0xAF,0x30,0x82,0x02,0x97,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x08, +0x3B,0xE0,0x56,0x90,0x42,0x46,0xB1,0xA1,0x75,0x6A,0xC9,0x59,0x91,0xC7,0x4A,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x61, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30, +0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77, +0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31, +0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x44,0x69,0x67,0x69,0x43,0x65, +0x72,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43, +0x41,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30, +0x30,0x5A,0x17,0x0D,0x33,0x31,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30, +0x5A,0x30,0x61,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53, +0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43, +0x65,0x72,0x74,0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B, +0x13,0x10,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63, +0x6F,0x6D,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x44,0x69,0x67, +0x69,0x43,0x65,0x72,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F, +0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xE2,0x3B,0xE1,0x11,0x72,0xDE,0xA8,0xA4,0xD3,0xA3,0x57, +0xAA,0x50,0xA2,0x8F,0x0B,0x77,0x90,0xC9,0xA2,0xA5,0xEE,0x12,0xCE,0x96,0x5B,0x01, +0x09,0x20,0xCC,0x01,0x93,0xA7,0x4E,0x30,0xB7,0x53,0xF7,0x43,0xC4,0x69,0x00,0x57, +0x9D,0xE2,0x8D,0x22,0xDD,0x87,0x06,0x40,0x00,0x81,0x09,0xCE,0xCE,0x1B,0x83,0xBF, +0xDF,0xCD,0x3B,0x71,0x46,0xE2,0xD6,0x66,0xC7,0x05,0xB3,0x76,0x27,0x16,0x8F,0x7B, +0x9E,0x1E,0x95,0x7D,0xEE,0xB7,0x48,0xA3,0x08,0xDA,0xD6,0xAF,0x7A,0x0C,0x39,0x06, +0x65,0x7F,0x4A,0x5D,0x1F,0xBC,0x17,0xF8,0xAB,0xBE,0xEE,0x28,0xD7,0x74,0x7F,0x7A, +0x78,0x99,0x59,0x85,0x68,0x6E,0x5C,0x23,0x32,0x4B,0xBF,0x4E,0xC0,0xE8,0x5A,0x6D, +0xE3,0x70,0xBF,0x77,0x10,0xBF,0xFC,0x01,0xF6,0x85,0xD9,0xA8,0x44,0x10,0x58,0x32, +0xA9,0x75,0x18,0xD5,0xD1,0xA2,0xBE,0x47,0xE2,0x27,0x6A,0xF4,0x9A,0x33,0xF8,0x49, +0x08,0x60,0x8B,0xD4,0x5F,0xB4,0x3A,0x84,0xBF,0xA1,0xAA,0x4A,0x4C,0x7D,0x3E,0xCF, +0x4F,0x5F,0x6C,0x76,0x5E,0xA0,0x4B,0x37,0x91,0x9E,0xDC,0x22,0xE6,0x6D,0xCE,0x14, +0x1A,0x8E,0x6A,0xCB,0xFE,0xCD,0xB3,0x14,0x64,0x17,0xC7,0x5B,0x29,0x9E,0x32,0xBF, +0xF2,0xEE,0xFA,0xD3,0x0B,0x42,0xD4,0xAB,0xB7,0x41,0x32,0xDA,0x0C,0xD4,0xEF,0xF8, +0x81,0xD5,0xBB,0x8D,0x58,0x3F,0xB5,0x1B,0xE8,0x49,0x28,0xA2,0x70,0xDA,0x31,0x04, +0xDD,0xF7,0xB2,0x16,0xF2,0x4C,0x0A,0x4E,0x07,0xA8,0xED,0x4A,0x3D,0x5E,0xB5,0x7F, +0xA3,0x90,0xC3,0xAF,0x27,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F, +0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30, +0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x03,0xDE,0x50,0x35,0x56,0xD1, +0x4C,0xBB,0x66,0xF0,0xA3,0xE2,0x1B,0x1B,0xC3,0x97,0xB2,0x3D,0xD1,0x55,0x30,0x1F, +0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x03,0xDE,0x50,0x35,0x56, +0xD1,0x4C,0xBB,0x66,0xF0,0xA3,0xE2,0x1B,0x1B,0xC3,0x97,0xB2,0x3D,0xD1,0x55,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0xCB,0x9C,0x37,0xAA,0x48,0x13,0x12,0x0A,0xFA,0xDD,0x44,0x9C,0x4F, +0x52,0xB0,0xF4,0xDF,0xAE,0x04,0xF5,0x79,0x79,0x08,0xA3,0x24,0x18,0xFC,0x4B,0x2B, +0x84,0xC0,0x2D,0xB9,0xD5,0xC7,0xFE,0xF4,0xC1,0x1F,0x58,0xCB,0xB8,0x6D,0x9C,0x7A, +0x74,0xE7,0x98,0x29,0xAB,0x11,0xB5,0xE3,0x70,0xA0,0xA1,0xCD,0x4C,0x88,0x99,0x93, +0x8C,0x91,0x70,0xE2,0xAB,0x0F,0x1C,0xBE,0x93,0xA9,0xFF,0x63,0xD5,0xE4,0x07,0x60, +0xD3,0xA3,0xBF,0x9D,0x5B,0x09,0xF1,0xD5,0x8E,0xE3,0x53,0xF4,0x8E,0x63,0xFA,0x3F, +0xA7,0xDB,0xB4,0x66,0xDF,0x62,0x66,0xD6,0xD1,0x6E,0x41,0x8D,0xF2,0x2D,0xB5,0xEA, +0x77,0x4A,0x9F,0x9D,0x58,0xE2,0x2B,0x59,0xC0,0x40,0x23,0xED,0x2D,0x28,0x82,0x45, +0x3E,0x79,0x54,0x92,0x26,0x98,0xE0,0x80,0x48,0xA8,0x37,0xEF,0xF0,0xD6,0x79,0x60, +0x16,0xDE,0xAC,0xE8,0x0E,0xCD,0x6E,0xAC,0x44,0x17,0x38,0x2F,0x49,0xDA,0xE1,0x45, +0x3E,0x2A,0xB9,0x36,0x53,0xCF,0x3A,0x50,0x06,0xF7,0x2E,0xE8,0xC4,0x57,0x49,0x6C, +0x61,0x21,0x18,0xD5,0x04,0xAD,0x78,0x3C,0x2C,0x3A,0x80,0x6B,0xA7,0xEB,0xAF,0x15, +0x14,0xE9,0xD8,0x89,0xC1,0xB9,0x38,0x6C,0xE2,0x91,0x6C,0x8A,0xFF,0x64,0xB9,0x77, +0x25,0x57,0x30,0xC0,0x1B,0x24,0xA3,0xE1,0xDC,0xE9,0xDF,0x47,0x7C,0xB5,0xB4,0x24, +0x08,0x05,0x30,0xEC,0x2D,0xBD,0x0B,0xBF,0x45,0xBF,0x50,0xB9,0xA9,0xF3,0xEB,0x98, +0x01,0x12,0xAD,0xC8,0x88,0xC6,0x98,0x34,0x5F,0x8D,0x0A,0x3C,0xC6,0xE9,0xD5,0x95, +0x95,0x6D,0xDE, +}; + + +/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA */ +/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA */ + + +const unsigned char DigiCert_High_Assurance_EV_Root_CA_certificate[969]={ +0x30,0x82,0x03,0xC5,0x30,0x82,0x02,0xAD,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x02, +0xAC,0x5C,0x26,0x6A,0x0B,0x40,0x9B,0x8F,0x0B,0x79,0xF2,0xAE,0x46,0x25,0x77,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x6C, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30, +0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77, +0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31, +0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65, +0x72,0x74,0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63, +0x65,0x20,0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D, +0x30,0x36,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33, +0x31,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x30,0x6C,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49, +0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77, +0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x2B,0x30, +0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74, +0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,0x65,0x20, +0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01, +0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC6,0xCC,0xE5,0x73,0xE6, +0xFB,0xD4,0xBB,0xE5,0x2D,0x2D,0x32,0xA6,0xDF,0xE5,0x81,0x3F,0xC9,0xCD,0x25,0x49, +0xB6,0x71,0x2A,0xC3,0xD5,0x94,0x34,0x67,0xA2,0x0A,0x1C,0xB0,0x5F,0x69,0xA6,0x40, +0xB1,0xC4,0xB7,0xB2,0x8F,0xD0,0x98,0xA4,0xA9,0x41,0x59,0x3A,0xD3,0xDC,0x94,0xD6, +0x3C,0xDB,0x74,0x38,0xA4,0x4A,0xCC,0x4D,0x25,0x82,0xF7,0x4A,0xA5,0x53,0x12,0x38, +0xEE,0xF3,0x49,0x6D,0x71,0x91,0x7E,0x63,0xB6,0xAB,0xA6,0x5F,0xC3,0xA4,0x84,0xF8, +0x4F,0x62,0x51,0xBE,0xF8,0xC5,0xEC,0xDB,0x38,0x92,0xE3,0x06,0xE5,0x08,0x91,0x0C, +0xC4,0x28,0x41,0x55,0xFB,0xCB,0x5A,0x89,0x15,0x7E,0x71,0xE8,0x35,0xBF,0x4D,0x72, +0x09,0x3D,0xBE,0x3A,0x38,0x50,0x5B,0x77,0x31,0x1B,0x8D,0xB3,0xC7,0x24,0x45,0x9A, +0xA7,0xAC,0x6D,0x00,0x14,0x5A,0x04,0xB7,0xBA,0x13,0xEB,0x51,0x0A,0x98,0x41,0x41, +0x22,0x4E,0x65,0x61,0x87,0x81,0x41,0x50,0xA6,0x79,0x5C,0x89,0xDE,0x19,0x4A,0x57, +0xD5,0x2E,0xE6,0x5D,0x1C,0x53,0x2C,0x7E,0x98,0xCD,0x1A,0x06,0x16,0xA4,0x68,0x73, +0xD0,0x34,0x04,0x13,0x5C,0xA1,0x71,0xD3,0x5A,0x7C,0x55,0xDB,0x5E,0x64,0xE1,0x37, +0x87,0x30,0x56,0x04,0xE5,0x11,0xB4,0x29,0x80,0x12,0xF1,0x79,0x39,0x88,0xA2,0x02, +0x11,0x7C,0x27,0x66,0xB7,0x88,0xB7,0x78,0xF2,0xCA,0x0A,0xA8,0x38,0xAB,0x0A,0x64, +0xC2,0xBF,0x66,0x5D,0x95,0x84,0xC1,0xA1,0x25,0x1E,0x87,0x5D,0x1A,0x50,0x0B,0x20, +0x12,0xCC,0x41,0xBB,0x6E,0x0B,0x51,0x38,0xB8,0x4B,0xCB,0x02,0x03,0x01,0x00,0x01, +0xA3,0x63,0x30,0x61,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14, +0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,0x02,0xEF, +0x63,0x64,0x2B,0xC3,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80, +0x14,0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,0x02, +0xEF,0x63,0x64,0x2B,0xC3,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x1C,0x1A,0x06,0x97,0xDC,0xD7,0x9C, +0x9F,0x3C,0x88,0x66,0x06,0x08,0x57,0x21,0xDB,0x21,0x47,0xF8,0x2A,0x67,0xAA,0xBF, +0x18,0x32,0x76,0x40,0x10,0x57,0xC1,0x8A,0xF3,0x7A,0xD9,0x11,0x65,0x8E,0x35,0xFA, +0x9E,0xFC,0x45,0xB5,0x9E,0xD9,0x4C,0x31,0x4B,0xB8,0x91,0xE8,0x43,0x2C,0x8E,0xB3, +0x78,0xCE,0xDB,0xE3,0x53,0x79,0x71,0xD6,0xE5,0x21,0x94,0x01,0xDA,0x55,0x87,0x9A, +0x24,0x64,0xF6,0x8A,0x66,0xCC,0xDE,0x9C,0x37,0xCD,0xA8,0x34,0xB1,0x69,0x9B,0x23, +0xC8,0x9E,0x78,0x22,0x2B,0x70,0x43,0xE3,0x55,0x47,0x31,0x61,0x19,0xEF,0x58,0xC5, +0x85,0x2F,0x4E,0x30,0xF6,0xA0,0x31,0x16,0x23,0xC8,0xE7,0xE2,0x65,0x16,0x33,0xCB, +0xBF,0x1A,0x1B,0xA0,0x3D,0xF8,0xCA,0x5E,0x8B,0x31,0x8B,0x60,0x08,0x89,0x2D,0x0C, +0x06,0x5C,0x52,0xB7,0xC4,0xF9,0x0A,0x98,0xD1,0x15,0x5F,0x9F,0x12,0xBE,0x7C,0x36, +0x63,0x38,0xBD,0x44,0xA4,0x7F,0xE4,0x26,0x2B,0x0A,0xC4,0x97,0x69,0x0D,0xE9,0x8C, +0xE2,0xC0,0x10,0x57,0xB8,0xC8,0x76,0x12,0x91,0x55,0xF2,0x48,0x69,0xD8,0xBC,0x2A, +0x02,0x5B,0x0F,0x44,0xD4,0x20,0x31,0xDB,0xF4,0xBA,0x70,0x26,0x5D,0x90,0x60,0x9E, +0xBC,0x4B,0x17,0x09,0x2F,0xB4,0xCB,0x1E,0x43,0x68,0xC9,0x07,0x27,0xC1,0xD2,0x5C, +0xF7,0xEA,0x21,0xB9,0x68,0x12,0x9C,0x3C,0x9C,0xBF,0x9E,0xFC,0x80,0x5C,0x9B,0x63, +0xCD,0xEC,0x47,0xAA,0x25,0x27,0x67,0xA0,0x37,0xF3,0x00,0x82,0x7D,0x54,0xD7,0xA9, +0xF8,0xE9,0x2E,0x13,0xA3,0x77,0xE8,0x1F,0x4A, +}; + + +/* subject:/O=Entrust.net/OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Certification Authority (2048) */ +/* issuer :/O=Entrust.net/OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Certification Authority (2048) */ + + +const unsigned char Entrust_net_Premium_2048_Secure_Server_CA_certificate[1120]={ +0x30,0x82,0x04,0x5C,0x30,0x82,0x03,0x44,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x38, +0x63,0xB9,0x66,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x81,0xB4,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B, +0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x40,0x30,0x3E,0x06, +0x03,0x55,0x04,0x0B,0x14,0x37,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73, +0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x5F,0x32,0x30,0x34,0x38,0x20,0x69, +0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28, +0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30, +0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39, +0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D, +0x69,0x74,0x65,0x64,0x31,0x33,0x30,0x31,0x06,0x03,0x55,0x04,0x03,0x13,0x2A,0x45, +0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x28,0x32,0x30,0x34,0x38,0x29,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31, +0x32,0x32,0x34,0x31,0x37,0x35,0x30,0x35,0x31,0x5A,0x17,0x0D,0x31,0x39,0x31,0x32, +0x32,0x34,0x31,0x38,0x32,0x30,0x35,0x31,0x5A,0x30,0x81,0xB4,0x31,0x14,0x30,0x12, +0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E, +0x65,0x74,0x31,0x40,0x30,0x3E,0x06,0x03,0x55,0x04,0x0B,0x14,0x37,0x77,0x77,0x77, +0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53, +0x5F,0x32,0x30,0x34,0x38,0x20,0x69,0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62,0x79, +0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C,0x69, +0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E, +0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x33,0x30,0x31,0x06, +0x03,0x55,0x04,0x03,0x13,0x2A,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65, +0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x28,0x32,0x30,0x34,0x38,0x29, +0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01, +0x00,0xAD,0x4D,0x4B,0xA9,0x12,0x86,0xB2,0xEA,0xA3,0x20,0x07,0x15,0x16,0x64,0x2A, +0x2B,0x4B,0xD1,0xBF,0x0B,0x4A,0x4D,0x8E,0xED,0x80,0x76,0xA5,0x67,0xB7,0x78,0x40, +0xC0,0x73,0x42,0xC8,0x68,0xC0,0xDB,0x53,0x2B,0xDD,0x5E,0xB8,0x76,0x98,0x35,0x93, +0x8B,0x1A,0x9D,0x7C,0x13,0x3A,0x0E,0x1F,0x5B,0xB7,0x1E,0xCF,0xE5,0x24,0x14,0x1E, +0xB1,0x81,0xA9,0x8D,0x7D,0xB8,0xCC,0x6B,0x4B,0x03,0xF1,0x02,0x0C,0xDC,0xAB,0xA5, +0x40,0x24,0x00,0x7F,0x74,0x94,0xA1,0x9D,0x08,0x29,0xB3,0x88,0x0B,0xF5,0x87,0x77, +0x9D,0x55,0xCD,0xE4,0xC3,0x7E,0xD7,0x6A,0x64,0xAB,0x85,0x14,0x86,0x95,0x5B,0x97, +0x32,0x50,0x6F,0x3D,0xC8,0xBA,0x66,0x0C,0xE3,0xFC,0xBD,0xB8,0x49,0xC1,0x76,0x89, +0x49,0x19,0xFD,0xC0,0xA8,0xBD,0x89,0xA3,0x67,0x2F,0xC6,0x9F,0xBC,0x71,0x19,0x60, +0xB8,0x2D,0xE9,0x2C,0xC9,0x90,0x76,0x66,0x7B,0x94,0xE2,0xAF,0x78,0xD6,0x65,0x53, +0x5D,0x3C,0xD6,0x9C,0xB2,0xCF,0x29,0x03,0xF9,0x2F,0xA4,0x50,0xB2,0xD4,0x48,0xCE, +0x05,0x32,0x55,0x8A,0xFD,0xB2,0x64,0x4C,0x0E,0xE4,0x98,0x07,0x75,0xDB,0x7F,0xDF, +0xB9,0x08,0x55,0x60,0x85,0x30,0x29,0xF9,0x7B,0x48,0xA4,0x69,0x86,0xE3,0x35,0x3F, +0x1E,0x86,0x5D,0x7A,0x7A,0x15,0xBD,0xEF,0x00,0x8E,0x15,0x22,0x54,0x17,0x00,0x90, +0x26,0x93,0xBC,0x0E,0x49,0x68,0x91,0xBF,0xF8,0x47,0xD3,0x9D,0x95,0x42,0xC1,0x0E, +0x4D,0xDF,0x6F,0x26,0xCF,0xC3,0x18,0x21,0x62,0x66,0x43,0x70,0xD6,0xD5,0xC0,0x07, +0xE1,0x02,0x03,0x01,0x00,0x01,0xA3,0x74,0x30,0x72,0x30,0x11,0x06,0x09,0x60,0x86, +0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x1F,0x06, +0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x55,0xE4,0x81,0xD1,0x11,0x80, +0xBE,0xD8,0x89,0xB9,0x08,0xA3,0x31,0xF9,0xA1,0x24,0x09,0x16,0xB9,0x70,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x55,0xE4,0x81,0xD1,0x11,0x80,0xBE, +0xD8,0x89,0xB9,0x08,0xA3,0x31,0xF9,0xA1,0x24,0x09,0x16,0xB9,0x70,0x30,0x1D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04,0x10,0x30,0x0E,0x1B,0x08, +0x56,0x35,0x2E,0x30,0x3A,0x34,0x2E,0x30,0x03,0x02,0x04,0x90,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x59,0x47,0xAC,0x21,0x84,0x8A,0x17,0xC9,0x9C,0x89,0x53,0x1E,0xBA,0x80,0x85,0x1A, +0xC6,0x3C,0x4E,0x3E,0xB1,0x9C,0xB6,0x7C,0xC6,0x92,0x5D,0x18,0x64,0x02,0xE3,0xD3, +0x06,0x08,0x11,0x61,0x7C,0x63,0xE3,0x2B,0x9D,0x31,0x03,0x70,0x76,0xD2,0xA3,0x28, +0xA0,0xF4,0xBB,0x9A,0x63,0x73,0xED,0x6D,0xE5,0x2A,0xDB,0xED,0x14,0xA9,0x2B,0xC6, +0x36,0x11,0xD0,0x2B,0xEB,0x07,0x8B,0xA5,0xDA,0x9E,0x5C,0x19,0x9D,0x56,0x12,0xF5, +0x54,0x29,0xC8,0x05,0xED,0xB2,0x12,0x2A,0x8D,0xF4,0x03,0x1B,0xFF,0xE7,0x92,0x10, +0x87,0xB0,0x3A,0xB5,0xC3,0x9D,0x05,0x37,0x12,0xA3,0xC7,0xF4,0x15,0xB9,0xD5,0xA4, +0x39,0x16,0x9B,0x53,0x3A,0x23,0x91,0xF1,0xA8,0x82,0xA2,0x6A,0x88,0x68,0xC1,0x79, +0x02,0x22,0xBC,0xAA,0xA6,0xD6,0xAE,0xDF,0xB0,0x14,0x5F,0xB8,0x87,0xD0,0xDD,0x7C, +0x7F,0x7B,0xFF,0xAF,0x1C,0xCF,0xE6,0xDB,0x07,0xAD,0x5E,0xDB,0x85,0x9D,0xD0,0x2B, +0x0D,0x33,0xDB,0x04,0xD1,0xE6,0x49,0x40,0x13,0x2B,0x76,0xFB,0x3E,0xE9,0x9C,0x89, +0x0F,0x15,0xCE,0x18,0xB0,0x85,0x78,0x21,0x4F,0x6B,0x4F,0x0E,0xFA,0x36,0x67,0xCD, +0x07,0xF2,0xFF,0x08,0xD0,0xE2,0xDE,0xD9,0xBF,0x2A,0xAF,0xB8,0x87,0x86,0x21,0x3C, +0x04,0xCA,0xB7,0x94,0x68,0x7F,0xCF,0x3C,0xE9,0x98,0xD7,0x38,0xFF,0xEC,0xC0,0xD9, +0x50,0xF0,0x2E,0x4B,0x58,0xAE,0x46,0x6F,0xD0,0x2E,0xC3,0x60,0xDA,0x72,0x55,0x72, +0xBD,0x4C,0x45,0x9E,0x61,0xBA,0xBF,0x84,0x81,0x92,0x03,0xD1,0xD2,0x69,0x7C,0xC5, +}; + + +/* subject:/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority */ +/* issuer :/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority */ + + +const unsigned char Entrust_net_Secure_Server_CA_certificate[1244]={ +0x30,0x82,0x04,0xD8,0x30,0x82,0x04,0x41,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x37, +0x4A,0xD2,0x43,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x81,0xC3,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B,0x30,0x39,0x06,0x03,0x55,0x04, +0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E, +0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62, +0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C, +0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C, +0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74, +0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x3A,0x30,0x38, +0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E, +0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x53,0x65,0x72,0x76,0x65,0x72, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x35, +0x32,0x35,0x31,0x36,0x30,0x39,0x34,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x35,0x32, +0x35,0x31,0x36,0x33,0x39,0x34,0x30,0x5A,0x30,0x81,0xC3,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B, +0x30,0x39,0x06,0x03,0x55,0x04,0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63, +0x6F,0x72,0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69, +0x6D,0x69,0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06, +0x03,0x55,0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45, +0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74, +0x65,0x64,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20, +0x53,0x65,0x72,0x76,0x65,0x72,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x81, +0x9D,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x81,0x8B,0x00,0x30,0x81,0x87,0x02,0x81,0x81,0x00,0xCD,0x28,0x83,0x34,0x54, +0x1B,0x89,0xF3,0x0F,0xAF,0x37,0x91,0x31,0xFF,0xAF,0x31,0x60,0xC9,0xA8,0xE8,0xB2, +0x10,0x68,0xED,0x9F,0xE7,0x93,0x36,0xF1,0x0A,0x64,0xBB,0x47,0xF5,0x04,0x17,0x3F, +0x23,0x47,0x4D,0xC5,0x27,0x19,0x81,0x26,0x0C,0x54,0x72,0x0D,0x88,0x2D,0xD9,0x1F, +0x9A,0x12,0x9F,0xBC,0xB3,0x71,0xD3,0x80,0x19,0x3F,0x47,0x66,0x7B,0x8C,0x35,0x28, +0xD2,0xB9,0x0A,0xDF,0x24,0xDA,0x9C,0xD6,0x50,0x79,0x81,0x7A,0x5A,0xD3,0x37,0xF7, +0xC2,0x4A,0xD8,0x29,0x92,0x26,0x64,0xD1,0xE4,0x98,0x6C,0x3A,0x00,0x8A,0xF5,0x34, +0x9B,0x65,0xF8,0xED,0xE3,0x10,0xFF,0xFD,0xB8,0x49,0x58,0xDC,0xA0,0xDE,0x82,0x39, +0x6B,0x81,0xB1,0x16,0x19,0x61,0xB9,0x54,0xB6,0xE6,0x43,0x02,0x01,0x03,0xA3,0x82, +0x01,0xD7,0x30,0x82,0x01,0xD3,0x30,0x11,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8, +0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x82,0x01,0x19,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x82,0x01,0x10,0x30,0x82,0x01,0x0C,0x30,0x81,0xDE,0xA0,0x81,0xDB, +0xA0,0x81,0xD8,0xA4,0x81,0xD5,0x30,0x81,0xD2,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13, +0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B,0x30,0x39, +0x06,0x03,0x55,0x04,0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75, +0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63,0x6F,0x72, +0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69, +0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64, +0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74,0x72,0x75, +0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x53,0x65, +0x72,0x76,0x65,0x72,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69, +0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x31,0x0D,0x30,0x0B, +0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x29,0xA0,0x27,0xA0, +0x25,0x86,0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x65,0x6E, +0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x52,0x4C,0x2F,0x6E,0x65, +0x74,0x31,0x2E,0x63,0x72,0x6C,0x30,0x2B,0x06,0x03,0x55,0x1D,0x10,0x04,0x24,0x30, +0x22,0x80,0x0F,0x31,0x39,0x39,0x39,0x30,0x35,0x32,0x35,0x31,0x36,0x30,0x39,0x34, +0x30,0x5A,0x81,0x0F,0x32,0x30,0x31,0x39,0x30,0x35,0x32,0x35,0x31,0x36,0x30,0x39, +0x34,0x30,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06, +0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xF0,0x17,0x62, +0x13,0x55,0x3D,0xB3,0xFF,0x0A,0x00,0x6B,0xFB,0x50,0x84,0x97,0xF3,0xED,0x62,0xD0, +0x1A,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xF0,0x17,0x62,0x13, +0x55,0x3D,0xB3,0xFF,0x0A,0x00,0x6B,0xFB,0x50,0x84,0x97,0xF3,0xED,0x62,0xD0,0x1A, +0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x19, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04,0x0C,0x30,0x0A,0x1B, +0x04,0x56,0x34,0x2E,0x30,0x03,0x02,0x04,0x90,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x90,0xDC,0x30,0x02, +0xFA,0x64,0x74,0xC2,0xA7,0x0A,0xA5,0x7C,0x21,0x8D,0x34,0x17,0xA8,0xFB,0x47,0x0E, +0xFF,0x25,0x7C,0x8D,0x13,0x0A,0xFB,0xE4,0x98,0xB5,0xEF,0x8C,0xF8,0xC5,0x10,0x0D, +0xF7,0x92,0xBE,0xF1,0xC3,0xD5,0xD5,0x95,0x6A,0x04,0xBB,0x2C,0xCE,0x26,0x36,0x65, +0xC8,0x31,0xC6,0xE7,0xEE,0x3F,0xE3,0x57,0x75,0x84,0x7A,0x11,0xEF,0x46,0x4F,0x18, +0xF4,0xD3,0x98,0xBB,0xA8,0x87,0x32,0xBA,0x72,0xF6,0x3C,0xE2,0x3D,0x9F,0xD7,0x1D, +0xD9,0xC3,0x60,0x43,0x8C,0x58,0x0E,0x22,0x96,0x2F,0x62,0xA3,0x2C,0x1F,0xBA,0xAD, +0x05,0xEF,0xAB,0x32,0x78,0x87,0xA0,0x54,0x73,0x19,0xB5,0x5C,0x05,0xF9,0x52,0x3E, +0x6D,0x2D,0x45,0x0B,0xF7,0x0A,0x93,0xEA,0xED,0x06,0xF9,0xB2, +}; + + +/* subject:/C=US/O=Entrust, Inc./OU=www.entrust.net/CPS is incorporated by reference/OU=(c) 2006 Entrust, Inc./CN=Entrust Root Certification Authority */ +/* issuer :/C=US/O=Entrust, Inc./OU=www.entrust.net/CPS is incorporated by reference/OU=(c) 2006 Entrust, Inc./CN=Entrust Root Certification Authority */ + + +const unsigned char Entrust_Root_Certification_Authority_certificate[1173]={ +0x30,0x82,0x04,0x91,0x30,0x82,0x03,0x79,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x45, +0x6B,0x50,0x54,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x81,0xB0,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x45,0x6E,0x74, +0x72,0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03, +0x55,0x04,0x0B,0x13,0x30,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74, +0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x73,0x20,0x69,0x6E,0x63,0x6F, +0x72,0x70,0x6F,0x72,0x61,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x65, +0x72,0x65,0x6E,0x63,0x65,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16, +0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x03,0x13, +0x24,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65, +0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68, +0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x32,0x37,0x32, +0x30,0x32,0x33,0x34,0x32,0x5A,0x17,0x0D,0x32,0x36,0x31,0x31,0x32,0x37,0x32,0x30, +0x35,0x33,0x34,0x32,0x5A,0x30,0x81,0xB0,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D, +0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30, +0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72, +0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x73,0x20,0x69, +0x6E,0x63,0x6F,0x72,0x70,0x6F,0x72,0x61,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x72, +0x65,0x66,0x65,0x72,0x65,0x6E,0x63,0x65,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04, +0x0B,0x13,0x16,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x45,0x6E,0x74,0x72, +0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55, +0x04,0x03,0x13,0x24,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00, +0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xB6,0x95,0xB6,0x43,0x42,0xFA,0xC6, +0x6D,0x2A,0x6F,0x48,0xDF,0x94,0x4C,0x39,0x57,0x05,0xEE,0xC3,0x79,0x11,0x41,0x68, +0x36,0xED,0xEC,0xFE,0x9A,0x01,0x8F,0xA1,0x38,0x28,0xFC,0xF7,0x10,0x46,0x66,0x2E, +0x4D,0x1E,0x1A,0xB1,0x1A,0x4E,0xC6,0xD1,0xC0,0x95,0x88,0xB0,0xC9,0xFF,0x31,0x8B, +0x33,0x03,0xDB,0xB7,0x83,0x7B,0x3E,0x20,0x84,0x5E,0xED,0xB2,0x56,0x28,0xA7,0xF8, +0xE0,0xB9,0x40,0x71,0x37,0xC5,0xCB,0x47,0x0E,0x97,0x2A,0x68,0xC0,0x22,0x95,0x62, +0x15,0xDB,0x47,0xD9,0xF5,0xD0,0x2B,0xFF,0x82,0x4B,0xC9,0xAD,0x3E,0xDE,0x4C,0xDB, +0x90,0x80,0x50,0x3F,0x09,0x8A,0x84,0x00,0xEC,0x30,0x0A,0x3D,0x18,0xCD,0xFB,0xFD, +0x2A,0x59,0x9A,0x23,0x95,0x17,0x2C,0x45,0x9E,0x1F,0x6E,0x43,0x79,0x6D,0x0C,0x5C, +0x98,0xFE,0x48,0xA7,0xC5,0x23,0x47,0x5C,0x5E,0xFD,0x6E,0xE7,0x1E,0xB4,0xF6,0x68, +0x45,0xD1,0x86,0x83,0x5B,0xA2,0x8A,0x8D,0xB1,0xE3,0x29,0x80,0xFE,0x25,0x71,0x88, +0xAD,0xBE,0xBC,0x8F,0xAC,0x52,0x96,0x4B,0xAA,0x51,0x8D,0xE4,0x13,0x31,0x19,0xE8, +0x4E,0x4D,0x9F,0xDB,0xAC,0xB3,0x6A,0xD5,0xBC,0x39,0x54,0x71,0xCA,0x7A,0x7A,0x7F, +0x90,0xDD,0x7D,0x1D,0x80,0xD9,0x81,0xBB,0x59,0x26,0xC2,0x11,0xFE,0xE6,0x93,0xE2, +0xF7,0x80,0xE4,0x65,0xFB,0x34,0x37,0x0E,0x29,0x80,0x70,0x4D,0xAF,0x38,0x86,0x2E, +0x9E,0x7F,0x57,0xAF,0x9E,0x17,0xAE,0xEB,0x1C,0xCB,0x28,0x21,0x5F,0xB6,0x1C,0xD8, +0xE7,0xA2,0x04,0x22,0xF9,0xD3,0xDA,0xD8,0xCB,0x02,0x03,0x01,0x00,0x01,0xA3,0x81, +0xB0,0x30,0x81,0xAD,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x2B,0x06,0x03,0x55,0x1D,0x10,0x04,0x24,0x30,0x22, +0x80,0x0F,0x32,0x30,0x30,0x36,0x31,0x31,0x32,0x37,0x32,0x30,0x32,0x33,0x34,0x32, +0x5A,0x81,0x0F,0x32,0x30,0x32,0x36,0x31,0x31,0x32,0x37,0x32,0x30,0x35,0x33,0x34, +0x32,0x5A,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x68, +0x90,0xE4,0x67,0xA4,0xA6,0x53,0x80,0xC7,0x86,0x66,0xA4,0xF1,0xF7,0x4B,0x43,0xFB, +0x84,0xBD,0x6D,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x68,0x90, +0xE4,0x67,0xA4,0xA6,0x53,0x80,0xC7,0x86,0x66,0xA4,0xF1,0xF7,0x4B,0x43,0xFB,0x84, +0xBD,0x6D,0x30,0x1D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04, +0x10,0x30,0x0E,0x1B,0x08,0x56,0x37,0x2E,0x31,0x3A,0x34,0x2E,0x30,0x03,0x02,0x04, +0x90,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x82,0x01,0x01,0x00,0x93,0xD4,0x30,0xB0,0xD7,0x03,0x20,0x2A,0xD0,0xF9,0x63, +0xE8,0x91,0x0C,0x05,0x20,0xA9,0x5F,0x19,0xCA,0x7B,0x72,0x4E,0xD4,0xB1,0xDB,0xD0, +0x96,0xFB,0x54,0x5A,0x19,0x2C,0x0C,0x08,0xF7,0xB2,0xBC,0x85,0xA8,0x9D,0x7F,0x6D, +0x3B,0x52,0xB3,0x2A,0xDB,0xE7,0xD4,0x84,0x8C,0x63,0xF6,0x0F,0xCB,0x26,0x01,0x91, +0x50,0x6C,0xF4,0x5F,0x14,0xE2,0x93,0x74,0xC0,0x13,0x9E,0x30,0x3A,0x50,0xE3,0xB4, +0x60,0xC5,0x1C,0xF0,0x22,0x44,0x8D,0x71,0x47,0xAC,0xC8,0x1A,0xC9,0xE9,0x9B,0x9A, +0x00,0x60,0x13,0xFF,0x70,0x7E,0x5F,0x11,0x4D,0x49,0x1B,0xB3,0x15,0x52,0x7B,0xC9, +0x54,0xDA,0xBF,0x9D,0x95,0xAF,0x6B,0x9A,0xD8,0x9E,0xE9,0xF1,0xE4,0x43,0x8D,0xE2, +0x11,0x44,0x3A,0xBF,0xAF,0xBD,0x83,0x42,0x73,0x52,0x8B,0xAA,0xBB,0xA7,0x29,0xCF, +0xF5,0x64,0x1C,0x0A,0x4D,0xD1,0xBC,0xAA,0xAC,0x9F,0x2A,0xD0,0xFF,0x7F,0x7F,0xDA, +0x7D,0xEA,0xB1,0xED,0x30,0x25,0xC1,0x84,0xDA,0x34,0xD2,0x5B,0x78,0x83,0x56,0xEC, +0x9C,0x36,0xC3,0x26,0xE2,0x11,0xF6,0x67,0x49,0x1D,0x92,0xAB,0x8C,0xFB,0xEB,0xFF, +0x7A,0xEE,0x85,0x4A,0xA7,0x50,0x80,0xF0,0xA7,0x5C,0x4A,0x94,0x2E,0x5F,0x05,0x99, +0x3C,0x52,0x41,0xE0,0xCD,0xB4,0x63,0xCF,0x01,0x43,0xBA,0x9C,0x83,0xDC,0x8F,0x60, +0x3B,0xF3,0x5A,0xB4,0xB4,0x7B,0xAE,0xDA,0x0B,0x90,0x38,0x75,0xEF,0x81,0x1D,0x66, +0xD2,0xF7,0x57,0x70,0x36,0xB3,0xBF,0xFC,0x28,0xAF,0x71,0x25,0x85,0x5B,0x13,0xFE, +0x1E,0x7F,0x5A,0xB4,0x3C, +}; + + +/* subject:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority */ +/* issuer :/C=US/O=Equifax/OU=Equifax Secure Certificate Authority */ + + +const unsigned char Equifax_Secure_CA_certificate[804]={ +0x30,0x82,0x03,0x20,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x35, +0xDE,0xF4,0xCF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71, +0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x38,0x32,0x32,0x31,0x36,0x34,0x31, +0x35,0x31,0x5A,0x17,0x0D,0x31,0x38,0x30,0x38,0x32,0x32,0x31,0x36,0x34,0x31,0x35, +0x31,0x5A,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71, +0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xC1, +0x5D,0xB1,0x58,0x67,0x08,0x62,0xEE,0xA0,0x9A,0x2D,0x1F,0x08,0x6D,0x91,0x14,0x68, +0x98,0x0A,0x1E,0xFE,0xDA,0x04,0x6F,0x13,0x84,0x62,0x21,0xC3,0xD1,0x7C,0xCE,0x9F, +0x05,0xE0,0xB8,0x01,0xF0,0x4E,0x34,0xEC,0xE2,0x8A,0x95,0x04,0x64,0xAC,0xF1,0x6B, +0x53,0x5F,0x05,0xB3,0xCB,0x67,0x80,0xBF,0x42,0x02,0x8E,0xFE,0xDD,0x01,0x09,0xEC, +0xE1,0x00,0x14,0x4F,0xFC,0xFB,0xF0,0x0C,0xDD,0x43,0xBA,0x5B,0x2B,0xE1,0x1F,0x80, +0x70,0x99,0x15,0x57,0x93,0x16,0xF1,0x0F,0x97,0x6A,0xB7,0xC2,0x68,0x23,0x1C,0xCC, +0x4D,0x59,0x30,0xAC,0x51,0x1E,0x3B,0xAF,0x2B,0xD6,0xEE,0x63,0x45,0x7B,0xC5,0xD9, +0x5F,0x50,0xD2,0xE3,0x50,0x0F,0x3A,0x88,0xE7,0xBF,0x14,0xFD,0xE0,0xC7,0xB9,0x02, +0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x09,0x30,0x82,0x01,0x05,0x30,0x70,0x06,0x03, +0x55,0x1D,0x1F,0x04,0x69,0x30,0x67,0x30,0x65,0xA0,0x63,0xA0,0x61,0xA4,0x5F,0x30, +0x5D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x10, +0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71,0x75,0x69,0x66, +0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69,0x66, +0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x31, +0x0D,0x30,0x0B,0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x1A, +0x06,0x03,0x55,0x1D,0x10,0x04,0x13,0x30,0x11,0x81,0x0F,0x32,0x30,0x31,0x38,0x30, +0x38,0x32,0x32,0x31,0x36,0x34,0x31,0x35,0x31,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D, +0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18, +0x30,0x16,0x80,0x14,0x48,0xE6,0x68,0xF9,0x2B,0xD2,0xB2,0x95,0xD7,0x47,0xD8,0x23, +0x20,0x10,0x4F,0x33,0x98,0x90,0x9F,0xD4,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04, +0x16,0x04,0x14,0x48,0xE6,0x68,0xF9,0x2B,0xD2,0xB2,0x95,0xD7,0x47,0xD8,0x23,0x20, +0x10,0x4F,0x33,0x98,0x90,0x9F,0xD4,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x1A,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07, +0x41,0x00,0x04,0x0D,0x30,0x0B,0x1B,0x05,0x56,0x33,0x2E,0x30,0x63,0x03,0x02,0x06, +0xC0,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x81,0x81,0x00,0x58,0xCE,0x29,0xEA,0xFC,0xF7,0xDE,0xB5,0xCE,0x02,0xB9,0x17, +0xB5,0x85,0xD1,0xB9,0xE3,0xE0,0x95,0xCC,0x25,0x31,0x0D,0x00,0xA6,0x92,0x6E,0x7F, +0xB6,0x92,0x63,0x9E,0x50,0x95,0xD1,0x9A,0x6F,0xE4,0x11,0xDE,0x63,0x85,0x6E,0x98, +0xEE,0xA8,0xFF,0x5A,0xC8,0xD3,0x55,0xB2,0x66,0x71,0x57,0xDE,0xC0,0x21,0xEB,0x3D, +0x2A,0xA7,0x23,0x49,0x01,0x04,0x86,0x42,0x7B,0xFC,0xEE,0x7F,0xA2,0x16,0x52,0xB5, +0x67,0x67,0xD3,0x40,0xDB,0x3B,0x26,0x58,0xB2,0x28,0x77,0x3D,0xAE,0x14,0x77,0x61, +0xD6,0xFA,0x2A,0x66,0x27,0xA0,0x0D,0xFA,0xA7,0x73,0x5C,0xEA,0x70,0xF1,0x94,0x21, +0x65,0x44,0x5F,0xFA,0xFC,0xEF,0x29,0x68,0xA9,0xA2,0x87,0x79,0xEF,0x79,0xEF,0x4F, +0xAC,0x07,0x77,0x38, +}; + + +/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure eBusiness CA-1 */ +/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure eBusiness CA-1 */ + + +const unsigned char Equifax_Secure_eBusiness_CA_1_certificate[646]={ +0x30,0x82,0x02,0x82,0x30,0x82,0x01,0xEB,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x04, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x26,0x30,0x24, +0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53, +0x65,0x63,0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20, +0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x31,0x30,0x34, +0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x36,0x32,0x31,0x30,0x34,0x30, +0x30,0x30,0x30,0x5A,0x30,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71, +0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69, +0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30, +0x81,0x89,0x02,0x81,0x81,0x00,0xCE,0x2F,0x19,0xBC,0x17,0xB7,0x77,0xDE,0x93,0xA9, +0x5F,0x5A,0x0D,0x17,0x4F,0x34,0x1A,0x0C,0x98,0xF4,0x22,0xD9,0x59,0xD4,0xC4,0x68, +0x46,0xF0,0xB4,0x35,0xC5,0x85,0x03,0x20,0xC6,0xAF,0x45,0xA5,0x21,0x51,0x45,0x41, +0xEB,0x16,0x58,0x36,0x32,0x6F,0xE2,0x50,0x62,0x64,0xF9,0xFD,0x51,0x9C,0xAA,0x24, +0xD9,0xF4,0x9D,0x83,0x2A,0x87,0x0A,0x21,0xD3,0x12,0x38,0x34,0x6C,0x8D,0x00,0x6E, +0x5A,0xA0,0xD9,0x42,0xEE,0x1A,0x21,0x95,0xF9,0x52,0x4C,0x55,0x5A,0xC5,0x0F,0x38, +0x4F,0x46,0xFA,0x6D,0xF8,0x2E,0x35,0xD6,0x1D,0x7C,0xEB,0xE2,0xF0,0xB0,0x75,0x80, +0xC8,0xA9,0x13,0xAC,0xBE,0x88,0xEF,0x3A,0x6E,0xAB,0x5F,0x2A,0x38,0x62,0x02,0xB0, +0x12,0x7B,0xFE,0x8F,0xA6,0x03,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30, +0x11,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02, +0x00,0x07,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03, +0x01,0x01,0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0x4A,0x78,0x32,0x52,0x11,0xDB,0x59,0x16,0x36,0x5E,0xDF,0xC1,0x14,0x36,0x40,0x6A, +0x47,0x7C,0x4C,0xA1,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4A, +0x78,0x32,0x52,0x11,0xDB,0x59,0x16,0x36,0x5E,0xDF,0xC1,0x14,0x36,0x40,0x6A,0x47, +0x7C,0x4C,0xA1,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04, +0x05,0x00,0x03,0x81,0x81,0x00,0x75,0x5B,0xA8,0x9B,0x03,0x11,0xE6,0xE9,0x56,0x4C, +0xCD,0xF9,0xA9,0x4C,0xC0,0x0D,0x9A,0xF3,0xCC,0x65,0x69,0xE6,0x25,0x76,0xCC,0x59, +0xB7,0xD6,0x54,0xC3,0x1D,0xCD,0x99,0xAC,0x19,0xDD,0xB4,0x85,0xD5,0xE0,0x3D,0xFC, +0x62,0x20,0xA7,0x84,0x4B,0x58,0x65,0xF1,0xE2,0xF9,0x95,0x21,0x3F,0xF5,0xD4,0x7E, +0x58,0x1E,0x47,0x87,0x54,0x3E,0x58,0xA1,0xB5,0xB5,0xF8,0x2A,0xEF,0x71,0xE7,0xBC, +0xC3,0xF6,0xB1,0x49,0x46,0xE2,0xD7,0xA0,0x6B,0xE5,0x56,0x7A,0x9A,0x27,0x98,0x7C, +0x46,0x62,0x14,0xE7,0xC9,0xFC,0x6E,0x03,0x12,0x79,0x80,0x38,0x1D,0x48,0x82,0x8D, +0xFC,0x17,0xFE,0x2A,0x96,0x2B,0xB5,0x62,0xA6,0xA6,0x3D,0xBD,0x7F,0x92,0x59,0xCD, +0x5A,0x2A,0x82,0xB2,0x37,0x79, +}; + + +/* subject:/C=US/O=Equifax Secure/OU=Equifax Secure eBusiness CA-2 */ +/* issuer :/C=US/O=Equifax Secure/OU=Equifax Secure eBusiness CA-2 */ + + +const unsigned char Equifax_Secure_eBusiness_CA_2_certificate[804]={ +0x30,0x82,0x03,0x20,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x37, +0x70,0xCF,0xB5,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03, +0x55,0x04,0x0B,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63, +0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41, +0x2D,0x32,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x33,0x31,0x32,0x31,0x34, +0x34,0x35,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,0x33,0x31,0x32,0x31,0x34,0x34, +0x35,0x5A,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69, +0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03, +0x55,0x04,0x0B,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63, +0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41, +0x2D,0x32,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xE4, +0x39,0x39,0x93,0x1E,0x52,0x06,0x1B,0x28,0x36,0xF8,0xB2,0xA3,0x29,0xC5,0xED,0x8E, +0xB2,0x11,0xBD,0xFE,0xEB,0xE7,0xB4,0x74,0xC2,0x8F,0xFF,0x05,0xE7,0xD9,0x9D,0x06, +0xBF,0x12,0xC8,0x3F,0x0E,0xF2,0xD6,0xD1,0x24,0xB2,0x11,0xDE,0xD1,0x73,0x09,0x8A, +0xD4,0xB1,0x2C,0x98,0x09,0x0D,0x1E,0x50,0x46,0xB2,0x83,0xA6,0x45,0x8D,0x62,0x68, +0xBB,0x85,0x1B,0x20,0x70,0x32,0xAA,0x40,0xCD,0xA6,0x96,0x5F,0xC4,0x71,0x37,0x3F, +0x04,0xF3,0xB7,0x41,0x24,0x39,0x07,0x1A,0x1E,0x2E,0x61,0x58,0xA0,0x12,0x0B,0xE5, +0xA5,0xDF,0xC5,0xAB,0xEA,0x37,0x71,0xCC,0x1C,0xC8,0x37,0x3A,0xB9,0x97,0x52,0xA7, +0xAC,0xC5,0x6A,0x24,0x94,0x4E,0x9C,0x7B,0xCF,0xC0,0x6A,0xD6,0xDF,0x21,0xBD,0x02, +0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x09,0x30,0x82,0x01,0x05,0x30,0x70,0x06,0x03, +0x55,0x1D,0x1F,0x04,0x69,0x30,0x67,0x30,0x65,0xA0,0x63,0xA0,0x61,0xA4,0x5F,0x30, +0x5D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B, +0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65, +0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x32,0x31, +0x0D,0x30,0x0B,0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x1A, +0x06,0x03,0x55,0x1D,0x10,0x04,0x13,0x30,0x11,0x81,0x0F,0x32,0x30,0x31,0x39,0x30, +0x36,0x32,0x33,0x31,0x32,0x31,0x34,0x34,0x35,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D, +0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18, +0x30,0x16,0x80,0x14,0x50,0x9E,0x0B,0xEA,0xAF,0x5E,0xB9,0x20,0x48,0xA6,0x50,0x6A, +0xCB,0xFD,0xD8,0x20,0x7A,0xA7,0x82,0x76,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04, +0x16,0x04,0x14,0x50,0x9E,0x0B,0xEA,0xAF,0x5E,0xB9,0x20,0x48,0xA6,0x50,0x6A,0xCB, +0xFD,0xD8,0x20,0x7A,0xA7,0x82,0x76,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x1A,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07, +0x41,0x00,0x04,0x0D,0x30,0x0B,0x1B,0x05,0x56,0x33,0x2E,0x30,0x63,0x03,0x02,0x06, +0xC0,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x81,0x81,0x00,0x0C,0x86,0x82,0xAD,0xE8,0x4E,0x1A,0xF5,0x8E,0x89,0x27,0xE2, +0x35,0x58,0x3D,0x29,0xB4,0x07,0x8F,0x36,0x50,0x95,0xBF,0x6E,0xC1,0x9E,0xEB,0xC4, +0x90,0xB2,0x85,0xA8,0xBB,0xB7,0x42,0xE0,0x0F,0x07,0x39,0xDF,0xFB,0x9E,0x90,0xB2, +0xD1,0xC1,0x3E,0x53,0x9F,0x03,0x44,0xB0,0x7E,0x4B,0xF4,0x6F,0xE4,0x7C,0x1F,0xE7, +0xE2,0xB1,0xE4,0xB8,0x9A,0xEF,0xC3,0xBD,0xCE,0xDE,0x0B,0x32,0x34,0xD9,0xDE,0x28, +0xED,0x33,0x6B,0xC4,0xD4,0xD7,0x3D,0x12,0x58,0xAB,0x7D,0x09,0x2D,0xCB,0x70,0xF5, +0x13,0x8A,0x94,0xA1,0x27,0xA4,0xD6,0x70,0xC5,0x6D,0x94,0xB5,0xC9,0x7D,0x9D,0xA0, +0xD2,0xC6,0x08,0x49,0xD9,0x66,0x9B,0xA6,0xD3,0xF4,0x0B,0xDC,0xC5,0x26,0x57,0xE1, +0x91,0x30,0xEA,0xCD, +}; + + +/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */ +/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */ + + +const unsigned char Equifax_Secure_Global_eBusiness_CA_certificate[660]={ +0x30,0x82,0x02,0x90,0x30,0x82,0x01,0xF9,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C, +0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78, +0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B, +0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53, +0x65,0x63,0x75,0x72,0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75, +0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39, +0x39,0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30, +0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x30,0x5A,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03, +0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63, +0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04, +0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72, +0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65, +0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89, +0x02,0x81,0x81,0x00,0xBA,0xE7,0x17,0x90,0x02,0x65,0xB1,0x34,0x55,0x3C,0x49,0xC2, +0x51,0xD5,0xDF,0xA7,0xD1,0x37,0x8F,0xD1,0xE7,0x81,0x73,0x41,0x52,0x60,0x9B,0x9D, +0xA1,0x17,0x26,0x78,0xAD,0xC7,0xB1,0xE8,0x26,0x94,0x32,0xB5,0xDE,0x33,0x8D,0x3A, +0x2F,0xDB,0xF2,0x9A,0x7A,0x5A,0x73,0x98,0xA3,0x5C,0xE9,0xFB,0x8A,0x73,0x1B,0x5C, +0xE7,0xC3,0xBF,0x80,0x6C,0xCD,0xA9,0xF4,0xD6,0x2B,0xC0,0xF7,0xF9,0x99,0xAA,0x63, +0xA2,0xB1,0x47,0x02,0x0F,0xD4,0xE4,0x51,0x3A,0x12,0x3C,0x6C,0x8A,0x5A,0x54,0x84, +0x70,0xDB,0xC1,0xC5,0x90,0xCF,0x72,0x45,0xCB,0xA8,0x59,0xC0,0xCD,0x33,0x9D,0x3F, +0xA3,0x96,0xEB,0x85,0x33,0x21,0x1C,0x3E,0x1E,0x3E,0x60,0x6E,0x76,0x9C,0x67,0x85, +0xC5,0xC8,0xC3,0x61,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30,0x11,0x06, +0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07, +0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01, +0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xBE,0xA8, +0xA0,0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B, +0x68,0x6C,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBE,0xA8,0xA0, +0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,0x68, +0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00, +0x03,0x81,0x81,0x00,0x30,0xE2,0x01,0x51,0xAA,0xC7,0xEA,0x5F,0xDA,0xB9,0xD0,0x65, +0x0F,0x30,0xD6,0x3E,0xDA,0x0D,0x14,0x49,0x6E,0x91,0x93,0x27,0x14,0x31,0xEF,0xC4, +0xF7,0x2D,0x45,0xF8,0xEC,0xC7,0xBF,0xA2,0x41,0x0D,0x23,0xB4,0x92,0xF9,0x19,0x00, +0x67,0xBD,0x01,0xAF,0xCD,0xE0,0x71,0xFC,0x5A,0xCF,0x64,0xC4,0xE0,0x96,0x98,0xD0, +0xA3,0x40,0xE2,0x01,0x8A,0xEF,0x27,0x07,0xF1,0x65,0x01,0x8A,0x44,0x2D,0x06,0x65, +0x75,0x52,0xC0,0x86,0x10,0x20,0x21,0x5F,0x6C,0x6B,0x0F,0x6C,0xAE,0x09,0x1C,0xAF, +0xF2,0xA2,0x18,0x34,0xC4,0x75,0xA4,0x73,0x1C,0xF1,0x8D,0xDC,0xEF,0xAD,0xF9,0xB3, +0x76,0xB4,0x92,0xBF,0xDC,0x95,0x10,0x1E,0xBE,0xCB,0xC8,0x3B,0x5A,0x84,0x60,0x19, +0x56,0x94,0xA9,0x55, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA */ + + +const unsigned char GeoTrust_Global_CA_certificate[856]={ +0x30,0x82,0x03,0x54,0x30,0x82,0x02,0x3C,0xA0,0x03,0x02,0x01,0x02,0x02,0x03,0x02, +0x34,0x56,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05, +0x00,0x30,0x42,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53, +0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72, +0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04, +0x03,0x13,0x12,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62, +0x61,0x6C,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x31,0x30, +0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x32,0x30,0x35,0x32,0x31,0x30,0x34, +0x30,0x30,0x30,0x30,0x5A,0x30,0x42,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47, +0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1B,0x30,0x19, +0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F, +0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDA,0xCC,0x18,0x63,0x30,0xFD, +0xF4,0x17,0x23,0x1A,0x56,0x7E,0x5B,0xDF,0x3C,0x6C,0x38,0xE4,0x71,0xB7,0x78,0x91, +0xD4,0xBC,0xA1,0xD8,0x4C,0xF8,0xA8,0x43,0xB6,0x03,0xE9,0x4D,0x21,0x07,0x08,0x88, +0xDA,0x58,0x2F,0x66,0x39,0x29,0xBD,0x05,0x78,0x8B,0x9D,0x38,0xE8,0x05,0xB7,0x6A, +0x7E,0x71,0xA4,0xE6,0xC4,0x60,0xA6,0xB0,0xEF,0x80,0xE4,0x89,0x28,0x0F,0x9E,0x25, +0xD6,0xED,0x83,0xF3,0xAD,0xA6,0x91,0xC7,0x98,0xC9,0x42,0x18,0x35,0x14,0x9D,0xAD, +0x98,0x46,0x92,0x2E,0x4F,0xCA,0xF1,0x87,0x43,0xC1,0x16,0x95,0x57,0x2D,0x50,0xEF, +0x89,0x2D,0x80,0x7A,0x57,0xAD,0xF2,0xEE,0x5F,0x6B,0xD2,0x00,0x8D,0xB9,0x14,0xF8, +0x14,0x15,0x35,0xD9,0xC0,0x46,0xA3,0x7B,0x72,0xC8,0x91,0xBF,0xC9,0x55,0x2B,0xCD, +0xD0,0x97,0x3E,0x9C,0x26,0x64,0xCC,0xDF,0xCE,0x83,0x19,0x71,0xCA,0x4E,0xE6,0xD4, +0xD5,0x7B,0xA9,0x19,0xCD,0x55,0xDE,0xC8,0xEC,0xD2,0x5E,0x38,0x53,0xE5,0x5C,0x4F, +0x8C,0x2D,0xFE,0x50,0x23,0x36,0xFC,0x66,0xE6,0xCB,0x8E,0xA4,0x39,0x19,0x00,0xB7, +0x95,0x02,0x39,0x91,0x0B,0x0E,0xFE,0x38,0x2E,0xD1,0x1D,0x05,0x9A,0xF6,0x4D,0x3E, +0x6F,0x0F,0x07,0x1D,0xAF,0x2C,0x1E,0x8F,0x60,0x39,0xE2,0xFA,0x36,0x53,0x13,0x39, +0xD4,0x5E,0x26,0x2B,0xDB,0x3D,0xA8,0x14,0xBD,0x32,0xEB,0x18,0x03,0x28,0x52,0x04, +0x71,0xE5,0xAB,0x33,0x3D,0xE1,0x38,0xBB,0x07,0x36,0x84,0x62,0x9C,0x79,0xEA,0x16, +0x30,0xF4,0x5F,0xC0,0x2B,0xE8,0x71,0x6B,0xE4,0xF9,0x02,0x03,0x01,0x00,0x01,0xA3, +0x53,0x30,0x51,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC0, +0x7A,0x98,0x68,0x8D,0x89,0xFB,0xAB,0x05,0x64,0x0C,0x11,0x7D,0xAA,0x7D,0x65,0xB8, +0xCA,0xCC,0x4E,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14, +0xC0,0x7A,0x98,0x68,0x8D,0x89,0xFB,0xAB,0x05,0x64,0x0C,0x11,0x7D,0xAA,0x7D,0x65, +0xB8,0xCA,0xCC,0x4E,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x35,0xE3,0x29,0x6A,0xE5,0x2F,0x5D,0x54, +0x8E,0x29,0x50,0x94,0x9F,0x99,0x1A,0x14,0xE4,0x8F,0x78,0x2A,0x62,0x94,0xA2,0x27, +0x67,0x9E,0xD0,0xCF,0x1A,0x5E,0x47,0xE9,0xC1,0xB2,0xA4,0xCF,0xDD,0x41,0x1A,0x05, +0x4E,0x9B,0x4B,0xEE,0x4A,0x6F,0x55,0x52,0xB3,0x24,0xA1,0x37,0x0A,0xEB,0x64,0x76, +0x2A,0x2E,0x2C,0xF3,0xFD,0x3B,0x75,0x90,0xBF,0xFA,0x71,0xD8,0xC7,0x3D,0x37,0xD2, +0xB5,0x05,0x95,0x62,0xB9,0xA6,0xDE,0x89,0x3D,0x36,0x7B,0x38,0x77,0x48,0x97,0xAC, +0xA6,0x20,0x8F,0x2E,0xA6,0xC9,0x0C,0xC2,0xB2,0x99,0x45,0x00,0xC7,0xCE,0x11,0x51, +0x22,0x22,0xE0,0xA5,0xEA,0xB6,0x15,0x48,0x09,0x64,0xEA,0x5E,0x4F,0x74,0xF7,0x05, +0x3E,0xC7,0x8A,0x52,0x0C,0xDB,0x15,0xB4,0xBD,0x6D,0x9B,0xE5,0xC6,0xB1,0x54,0x68, +0xA9,0xE3,0x69,0x90,0xB6,0x9A,0xA5,0x0F,0xB8,0xB9,0x3F,0x20,0x7D,0xAE,0x4A,0xB5, +0xB8,0x9C,0xE4,0x1D,0xB6,0xAB,0xE6,0x94,0xA5,0xC1,0xC7,0x83,0xAD,0xDB,0xF5,0x27, +0x87,0x0E,0x04,0x6C,0xD5,0xFF,0xDD,0xA0,0x5D,0xED,0x87,0x52,0xB7,0x2B,0x15,0x02, +0xAE,0x39,0xA6,0x6A,0x74,0xE9,0xDA,0xC4,0xE7,0xBC,0x4D,0x34,0x1E,0xA9,0x5C,0x4D, +0x33,0x5F,0x92,0x09,0x2F,0x88,0x66,0x5D,0x77,0x97,0xC7,0x1D,0x76,0x13,0xA9,0xD5, +0xE5,0xF1,0x16,0x09,0x11,0x35,0xD5,0xAC,0xDB,0x24,0x71,0x70,0x2C,0x98,0x56,0x0B, +0xD9,0x17,0xB4,0xD1,0xE3,0x51,0x2B,0x5E,0x75,0xE8,0xD5,0xD0,0xDC,0x4F,0x34,0xED, +0xC2,0x05,0x66,0x80,0xA1,0xCB,0xE6,0x33, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA 2 */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA 2 */ + + +const unsigned char GeoTrust_Global_CA_2_certificate[874]={ +0x30,0x82,0x03,0x66,0x30,0x82,0x02,0x4E,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x03,0x13, +0x14,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C, +0x20,0x43,0x41,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33,0x30,0x34,0x30, +0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x33,0x30,0x34,0x30,0x35, +0x30,0x30,0x30,0x30,0x5A,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47, +0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1D,0x30,0x1B, +0x06,0x03,0x55,0x04,0x03,0x13,0x14,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x41,0x20,0x32,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xEF,0x3C,0x4D,0x40, +0x3D,0x10,0xDF,0x3B,0x53,0x00,0xE1,0x67,0xFE,0x94,0x60,0x15,0x3E,0x85,0x88,0xF1, +0x89,0x0D,0x90,0xC8,0x28,0x23,0x99,0x05,0xE8,0x2B,0x20,0x9D,0xC6,0xF3,0x60,0x46, +0xD8,0xC1,0xB2,0xD5,0x8C,0x31,0xD9,0xDC,0x20,0x79,0x24,0x81,0xBF,0x35,0x32,0xFC, +0x63,0x69,0xDB,0xB1,0x2A,0x6B,0xEE,0x21,0x58,0xF2,0x08,0xE9,0x78,0xCB,0x6F,0xCB, +0xFC,0x16,0x52,0xC8,0x91,0xC4,0xFF,0x3D,0x73,0xDE,0xB1,0x3E,0xA7,0xC2,0x7D,0x66, +0xC1,0xF5,0x7E,0x52,0x24,0x1A,0xE2,0xD5,0x67,0x91,0xD0,0x82,0x10,0xD7,0x78,0x4B, +0x4F,0x2B,0x42,0x39,0xBD,0x64,0x2D,0x40,0xA0,0xB0,0x10,0xD3,0x38,0x48,0x46,0x88, +0xA1,0x0C,0xBB,0x3A,0x33,0x2A,0x62,0x98,0xFB,0x00,0x9D,0x13,0x59,0x7F,0x6F,0x3B, +0x72,0xAA,0xEE,0xA6,0x0F,0x86,0xF9,0x05,0x61,0xEA,0x67,0x7F,0x0C,0x37,0x96,0x8B, +0xE6,0x69,0x16,0x47,0x11,0xC2,0x27,0x59,0x03,0xB3,0xA6,0x60,0xC2,0x21,0x40,0x56, +0xFA,0xA0,0xC7,0x7D,0x3A,0x13,0xE3,0xEC,0x57,0xC7,0xB3,0xD6,0xAE,0x9D,0x89,0x80, +0xF7,0x01,0xE7,0x2C,0xF6,0x96,0x2B,0x13,0x0D,0x79,0x2C,0xD9,0xC0,0xE4,0x86,0x7B, +0x4B,0x8C,0x0C,0x72,0x82,0x8A,0xFB,0x17,0xCD,0x00,0x6C,0x3A,0x13,0x3C,0xB0,0x84, +0x87,0x4B,0x16,0x7A,0x29,0xB2,0x4F,0xDB,0x1D,0xD4,0x0B,0xF3,0x66,0x37,0xBD,0xD8, +0xF6,0x57,0xBB,0x5E,0x24,0x7A,0xB8,0x3C,0x8B,0xB9,0xFA,0x92,0x1A,0x1A,0x84,0x9E, +0xD8,0x74,0x8F,0xAA,0x1B,0x7F,0x5E,0xF4,0xFE,0x45,0x22,0x21,0x02,0x03,0x01,0x00, +0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x71,0x38,0x36,0xF2,0x02,0x31,0x53,0x47,0x2B,0x6E,0xBA,0x65,0x46,0xA9,0x10, +0x15,0x58,0x20,0x05,0x09,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16, +0x80,0x14,0x71,0x38,0x36,0xF2,0x02,0x31,0x53,0x47,0x2B,0x6E,0xBA,0x65,0x46,0xA9, +0x10,0x15,0x58,0x20,0x05,0x09,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x03,0xF7,0xB5,0x2B,0xAB,0x5D, +0x10,0xFC,0x7B,0xB2,0xB2,0x5E,0xAC,0x9B,0x0E,0x7E,0x53,0x78,0x59,0x3E,0x42,0x04, +0xFE,0x75,0xA3,0xAD,0xAC,0x81,0x4E,0xD7,0x02,0x8B,0x5E,0xC4,0x2D,0xC8,0x52,0x76, +0xC7,0x2C,0x1F,0xFC,0x81,0x32,0x98,0xD1,0x4B,0xC6,0x92,0x93,0x33,0x35,0x31,0x2F, +0xFC,0xD8,0x1D,0x44,0xDD,0xE0,0x81,0x7F,0x9D,0xE9,0x8B,0xE1,0x64,0x91,0x62,0x0B, +0x39,0x08,0x8C,0xAC,0x74,0x9D,0x59,0xD9,0x7A,0x59,0x52,0x97,0x11,0xB9,0x16,0x7B, +0x6F,0x45,0xD3,0x96,0xD9,0x31,0x7D,0x02,0x36,0x0F,0x9C,0x3B,0x6E,0xCF,0x2C,0x0D, +0x03,0x46,0x45,0xEB,0xA0,0xF4,0x7F,0x48,0x44,0xC6,0x08,0x40,0xCC,0xDE,0x1B,0x70, +0xB5,0x29,0xAD,0xBA,0x8B,0x3B,0x34,0x65,0x75,0x1B,0x71,0x21,0x1D,0x2C,0x14,0x0A, +0xB0,0x96,0x95,0xB8,0xD6,0xEA,0xF2,0x65,0xFB,0x29,0xBA,0x4F,0xEA,0x91,0x93,0x74, +0x69,0xB6,0xF2,0xFF,0xE1,0x1A,0xD0,0x0C,0xD1,0x76,0x85,0xCB,0x8A,0x25,0xBD,0x97, +0x5E,0x2C,0x6F,0x15,0x99,0x26,0xE7,0xB6,0x29,0xFF,0x22,0xEC,0xC9,0x02,0xC7,0x56, +0x00,0xCD,0x49,0xB9,0xB3,0x6C,0x7B,0x53,0x04,0x1A,0xE2,0xA8,0xC9,0xAA,0x12,0x05, +0x23,0xC2,0xCE,0xE7,0xBB,0x04,0x02,0xCC,0xC0,0x47,0xA2,0xE4,0xC4,0x29,0x2F,0x5B, +0x45,0x57,0x89,0x51,0xEE,0x3C,0xEB,0x52,0x08,0xFF,0x07,0x35,0x1E,0x9F,0x35,0x6A, +0x47,0x4A,0x56,0x98,0xD1,0x5A,0x85,0x1F,0x8C,0xF5,0x22,0xBF,0xAB,0xCE,0x83,0xF3, +0xE2,0x22,0x29,0xAE,0x7D,0x83,0x40,0xA8,0xBA,0x6C, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Primary Certification Authority */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Primary Certification Authority */ + + +const unsigned char GeoTrust_Primary_Certification_Authority_certificate[896]={ +0x30,0x82,0x03,0x7C,0x30,0x82,0x02,0x64,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x18, +0xAC,0xB5,0x6A,0xFD,0x69,0xB6,0x15,0x3A,0x63,0x6C,0xAF,0xDA,0xFA,0xC4,0xA1,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x58, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30, +0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28, +0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31, +0x32,0x37,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31, +0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x58,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28,0x47,0x65,0x6F,0x54,0x72,0x75, +0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xBE,0xB8,0x15,0x7B,0xFF,0xD4,0x7C,0x7D,0x67,0xAD,0x83,0x64,0x7B, +0xC8,0x42,0x53,0x2D,0xDF,0xF6,0x84,0x08,0x20,0x61,0xD6,0x01,0x59,0x6A,0x9C,0x44, +0x11,0xAF,0xEF,0x76,0xFD,0x95,0x7E,0xCE,0x61,0x30,0xBB,0x7A,0x83,0x5F,0x02,0xBD, +0x01,0x66,0xCA,0xEE,0x15,0x8D,0x6F,0xA1,0x30,0x9C,0xBD,0xA1,0x85,0x9E,0x94,0x3A, +0xF3,0x56,0x88,0x00,0x31,0xCF,0xD8,0xEE,0x6A,0x96,0x02,0xD9,0xED,0x03,0x8C,0xFB, +0x75,0x6D,0xE7,0xEA,0xB8,0x55,0x16,0x05,0x16,0x9A,0xF4,0xE0,0x5E,0xB1,0x88,0xC0, +0x64,0x85,0x5C,0x15,0x4D,0x88,0xC7,0xB7,0xBA,0xE0,0x75,0xE9,0xAD,0x05,0x3D,0x9D, +0xC7,0x89,0x48,0xE0,0xBB,0x28,0xC8,0x03,0xE1,0x30,0x93,0x64,0x5E,0x52,0xC0,0x59, +0x70,0x22,0x35,0x57,0x88,0x8A,0xF1,0x95,0x0A,0x83,0xD7,0xBC,0x31,0x73,0x01,0x34, +0xED,0xEF,0x46,0x71,0xE0,0x6B,0x02,0xA8,0x35,0x72,0x6B,0x97,0x9B,0x66,0xE0,0xCB, +0x1C,0x79,0x5F,0xD8,0x1A,0x04,0x68,0x1E,0x47,0x02,0xE6,0x9D,0x60,0xE2,0x36,0x97, +0x01,0xDF,0xCE,0x35,0x92,0xDF,0xBE,0x67,0xC7,0x6D,0x77,0x59,0x3B,0x8F,0x9D,0xD6, +0x90,0x15,0x94,0xBC,0x42,0x34,0x10,0xC1,0x39,0xF9,0xB1,0x27,0x3E,0x7E,0xD6,0x8A, +0x75,0xC5,0xB2,0xAF,0x96,0xD3,0xA2,0xDE,0x9B,0xE4,0x98,0xBE,0x7D,0xE1,0xE9,0x81, +0xAD,0xB6,0x6F,0xFC,0xD7,0x0E,0xDA,0xE0,0x34,0xB0,0x0D,0x1A,0x77,0xE7,0xE3,0x08, +0x98,0xEF,0x58,0xFA,0x9C,0x84,0xB7,0x36,0xAF,0xC2,0xDF,0xAC,0xD2,0xF4,0x10,0x06, +0x70,0x71,0x35,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03, +0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06, +0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x2C,0xD5,0x50,0x41,0x97,0x15,0x8B,0xF0, +0x8F,0x36,0x61,0x5B,0x4A,0xFB,0x6B,0xD9,0x99,0xC9,0x33,0x92,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00, +0x5A,0x70,0x7F,0x2C,0xDD,0xB7,0x34,0x4F,0xF5,0x86,0x51,0xA9,0x26,0xBE,0x4B,0xB8, +0xAA,0xF1,0x71,0x0D,0xDC,0x61,0xC7,0xA0,0xEA,0x34,0x1E,0x7A,0x77,0x0F,0x04,0x35, +0xE8,0x27,0x8F,0x6C,0x90,0xBF,0x91,0x16,0x24,0x46,0x3E,0x4A,0x4E,0xCE,0x2B,0x16, +0xD5,0x0B,0x52,0x1D,0xFC,0x1F,0x67,0xA2,0x02,0x45,0x31,0x4F,0xCE,0xF3,0xFA,0x03, +0xA7,0x79,0x9D,0x53,0x6A,0xD9,0xDA,0x63,0x3A,0xF8,0x80,0xD7,0xD3,0x99,0xE1,0xA5, +0xE1,0xBE,0xD4,0x55,0x71,0x98,0x35,0x3A,0xBE,0x93,0xEA,0xAE,0xAD,0x42,0xB2,0x90, +0x6F,0xE0,0xFC,0x21,0x4D,0x35,0x63,0x33,0x89,0x49,0xD6,0x9B,0x4E,0xCA,0xC7,0xE7, +0x4E,0x09,0x00,0xF7,0xDA,0xC7,0xEF,0x99,0x62,0x99,0x77,0xB6,0x95,0x22,0x5E,0x8A, +0xA0,0xAB,0xF4,0xB8,0x78,0x98,0xCA,0x38,0x19,0x99,0xC9,0x72,0x9E,0x78,0xCD,0x4B, +0xAC,0xAF,0x19,0xA0,0x73,0x12,0x2D,0xFC,0xC2,0x41,0xBA,0x81,0x91,0xDA,0x16,0x5A, +0x31,0xB7,0xF9,0xB4,0x71,0x80,0x12,0x48,0x99,0x72,0x73,0x5A,0x59,0x53,0xC1,0x63, +0x52,0x33,0xED,0xA7,0xC9,0xD2,0x39,0x02,0x70,0xFA,0xE0,0xB1,0x42,0x66,0x29,0xAA, +0x9B,0x51,0xED,0x30,0x54,0x22,0x14,0x5F,0xD9,0xAB,0x1D,0xC1,0xE4,0x94,0xF0,0xF8, +0xF5,0x2B,0xF7,0xEA,0xCA,0x78,0x46,0xD6,0xB8,0x91,0xFD,0xA6,0x0D,0x2B,0x1A,0x14, +0x01,0x3E,0x80,0xF0,0x42,0xA0,0x95,0x07,0x5E,0x6D,0xCD,0xCC,0x4B,0xA4,0x45,0x8D, +0xAB,0x12,0xE8,0xB3,0xDE,0x5A,0xE5,0xA0,0x7C,0xE8,0x0F,0x22,0x1D,0x5A,0xE9,0x59, +}; + + +/* subject:/C=US/O=GeoTrust Inc./OU=(c) 2007 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G2 */ +/* issuer :/C=US/O=GeoTrust Inc./OU=(c) 2007 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G2 */ + + +const unsigned char GeoTrust_Primary_Certification_Authority___G2_certificate[690]={ +0x30,0x82,0x02,0xAE,0x30,0x82,0x02,0x35,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x3C, +0xB2,0xF4,0x48,0x0A,0x00,0xE2,0xFE,0xEB,0x24,0x3B,0x5E,0x60,0x3E,0xC3,0x6B,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x98,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49, +0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63, +0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F, +0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36, +0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66, +0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31,0x30,0x35, +0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32, +0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13, +0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39, +0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63,0x29,0x20,0x32,0x30,0x30, +0x37,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x20, +0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64, +0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36,0x30,0x34,0x06,0x03,0x55, +0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69, +0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69, +0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47, +0x32,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05, +0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0x15,0xB1,0xE8,0xFD,0x03,0x15,0x43, +0xE5,0xAC,0xEB,0x87,0x37,0x11,0x62,0xEF,0xD2,0x83,0x36,0x52,0x7D,0x45,0x57,0x0B, +0x4A,0x8D,0x7B,0x54,0x3B,0x3A,0x6E,0x5F,0x15,0x02,0xC0,0x50,0xA6,0xCF,0x25,0x2F, +0x7D,0xCA,0x48,0xB8,0xC7,0x50,0x63,0x1C,0x2A,0x21,0x08,0x7C,0x9A,0x36,0xD8,0x0B, +0xFE,0xD1,0x26,0xC5,0x58,0x31,0x30,0x28,0x25,0xF3,0x5D,0x5D,0xA3,0xB8,0xB6,0xA5, +0xB4,0x92,0xED,0x6C,0x2C,0x9F,0xEB,0xDD,0x43,0x89,0xA2,0x3C,0x4B,0x48,0x91,0x1D, +0x50,0xEC,0x26,0xDF,0xD6,0x60,0x2E,0xBD,0x21,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06, +0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x15,0x5F,0x35,0x57,0x51,0x55,0xFB, +0x25,0xB2,0xAD,0x03,0x69,0xFC,0x01,0xA3,0xFA,0xBE,0x11,0x55,0xD5,0x30,0x0A,0x06, +0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x67,0x00,0x30,0x64,0x02,0x30, +0x64,0x96,0x59,0xA6,0xE8,0x09,0xDE,0x8B,0xBA,0xFA,0x5A,0x88,0x88,0xF0,0x1F,0x91, +0xD3,0x46,0xA8,0xF2,0x4A,0x4C,0x02,0x63,0xFB,0x6C,0x5F,0x38,0xDB,0x2E,0x41,0x93, +0xA9,0x0E,0xE6,0x9D,0xDC,0x31,0x1C,0xB2,0xA0,0xA7,0x18,0x1C,0x79,0xE1,0xC7,0x36, +0x02,0x30,0x3A,0x56,0xAF,0x9A,0x74,0x6C,0xF6,0xFB,0x83,0xE0,0x33,0xD3,0x08,0x5F, +0xA1,0x9C,0xC2,0x5B,0x9F,0x46,0xD6,0xB6,0xCB,0x91,0x06,0x63,0xA2,0x06,0xE7,0x33, +0xAC,0x3E,0xA8,0x81,0x12,0xD0,0xCB,0xBA,0xD0,0x92,0x0B,0xB6,0x9E,0x96,0xAA,0x04, +0x0F,0x8A, +}; + + +/* subject:/C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3 */ +/* issuer :/C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3 */ + + +const unsigned char GeoTrust_Primary_Certification_Authority___G3_certificate[1026]={ +0x30,0x82,0x03,0xFE,0x30,0x82,0x02,0xE6,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x15, +0xAC,0x6E,0x94,0x19,0xB2,0x79,0x4B,0x41,0xF6,0x27,0xA9,0xC3,0x18,0x0F,0x1F,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81, +0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13, +0x30,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x47,0x65,0x6F,0x54,0x72,0x75, +0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75, +0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C, +0x79,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54, +0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F, +0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x30,0x38,0x30, +0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32, +0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55, +0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63,0x29,0x20, +0x32,0x30,0x30,0x38,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E, +0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69, +0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36,0x30,0x34, +0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xDC,0xE2,0x5E,0x62,0x58,0x1D,0x33,0x57,0x39,0x32,0x33, +0xFA,0xEB,0xCB,0x87,0x8C,0xA7,0xD4,0x4A,0xDD,0x06,0x88,0xEA,0x64,0x8E,0x31,0x98, +0xA5,0x38,0x90,0x1E,0x98,0xCF,0x2E,0x63,0x2B,0xF0,0x46,0xBC,0x44,0xB2,0x89,0xA1, +0xC0,0x28,0x0C,0x49,0x70,0x21,0x95,0x9F,0x64,0xC0,0xA6,0x93,0x12,0x02,0x65,0x26, +0x86,0xC6,0xA5,0x89,0xF0,0xFA,0xD7,0x84,0xA0,0x70,0xAF,0x4F,0x1A,0x97,0x3F,0x06, +0x44,0xD5,0xC9,0xEB,0x72,0x10,0x7D,0xE4,0x31,0x28,0xFB,0x1C,0x61,0xE6,0x28,0x07, +0x44,0x73,0x92,0x22,0x69,0xA7,0x03,0x88,0x6C,0x9D,0x63,0xC8,0x52,0xDA,0x98,0x27, +0xE7,0x08,0x4C,0x70,0x3E,0xB4,0xC9,0x12,0xC1,0xC5,0x67,0x83,0x5D,0x33,0xF3,0x03, +0x11,0xEC,0x6A,0xD0,0x53,0xE2,0xD1,0xBA,0x36,0x60,0x94,0x80,0xBB,0x61,0x63,0x6C, +0x5B,0x17,0x7E,0xDF,0x40,0x94,0x1E,0xAB,0x0D,0xC2,0x21,0x28,0x70,0x88,0xFF,0xD6, +0x26,0x6C,0x6C,0x60,0x04,0x25,0x4E,0x55,0x7E,0x7D,0xEF,0xBF,0x94,0x48,0xDE,0xB7, +0x1D,0xDD,0x70,0x8D,0x05,0x5F,0x88,0xA5,0x9B,0xF2,0xC2,0xEE,0xEA,0xD1,0x40,0x41, +0x6D,0x62,0x38,0x1D,0x56,0x06,0xC5,0x03,0x47,0x51,0x20,0x19,0xFC,0x7B,0x10,0x0B, +0x0E,0x62,0xAE,0x76,0x55,0xBF,0x5F,0x77,0xBE,0x3E,0x49,0x01,0x53,0x3D,0x98,0x25, +0x03,0x76,0x24,0x5A,0x1D,0xB4,0xDB,0x89,0xEA,0x79,0xE5,0xB6,0xB3,0x3B,0x3F,0xBA, +0x4C,0x28,0x41,0x7F,0x06,0xAC,0x6A,0x8E,0xC1,0xD0,0xF6,0x05,0x1D,0x7D,0xE6,0x42, +0x86,0xE3,0xA5,0xD5,0x47,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F, +0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30, +0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30, +0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC4,0x79,0xCA,0x8E,0xA1,0x4E, +0x03,0x1D,0x1C,0xDC,0x6B,0xDB,0x31,0x5B,0x94,0x3E,0x3F,0x30,0x7F,0x2D,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01, +0x01,0x00,0x2D,0xC5,0x13,0xCF,0x56,0x80,0x7B,0x7A,0x78,0xBD,0x9F,0xAE,0x2C,0x99, +0xE7,0xEF,0xDA,0xDF,0x94,0x5E,0x09,0x69,0xA7,0xE7,0x6E,0x68,0x8C,0xBD,0x72,0xBE, +0x47,0xA9,0x0E,0x97,0x12,0xB8,0x4A,0xF1,0x64,0xD3,0x39,0xDF,0x25,0x34,0xD4,0xC1, +0xCD,0x4E,0x81,0xF0,0x0F,0x04,0xC4,0x24,0xB3,0x34,0x96,0xC6,0xA6,0xAA,0x30,0xDF, +0x68,0x61,0x73,0xD7,0xF9,0x8E,0x85,0x89,0xEF,0x0E,0x5E,0x95,0x28,0x4A,0x2A,0x27, +0x8F,0x10,0x8E,0x2E,0x7C,0x86,0xC4,0x02,0x9E,0xDA,0x0C,0x77,0x65,0x0E,0x44,0x0D, +0x92,0xFD,0xFD,0xB3,0x16,0x36,0xFA,0x11,0x0D,0x1D,0x8C,0x0E,0x07,0x89,0x6A,0x29, +0x56,0xF7,0x72,0xF4,0xDD,0x15,0x9C,0x77,0x35,0x66,0x57,0xAB,0x13,0x53,0xD8,0x8E, +0xC1,0x40,0xC5,0xD7,0x13,0x16,0x5A,0x72,0xC7,0xB7,0x69,0x01,0xC4,0x7A,0xB1,0x83, +0x01,0x68,0x7D,0x8D,0x41,0xA1,0x94,0x18,0xC1,0x25,0x5C,0xFC,0xF0,0xFE,0x83,0x02, +0x87,0x7C,0x0D,0x0D,0xCF,0x2E,0x08,0x5C,0x4A,0x40,0x0D,0x3E,0xEC,0x81,0x61,0xE6, +0x24,0xDB,0xCA,0xE0,0x0E,0x2D,0x07,0xB2,0x3E,0x56,0xDC,0x8D,0xF5,0x41,0x85,0x07, +0x48,0x9B,0x0C,0x0B,0xCB,0x49,0x3F,0x7D,0xEC,0xB7,0xFD,0xCB,0x8D,0x67,0x89,0x1A, +0xAB,0xED,0xBB,0x1E,0xA3,0x00,0x08,0x08,0x17,0x2A,0x82,0x5C,0x31,0x5D,0x46,0x8A, +0x2D,0x0F,0x86,0x9B,0x74,0xD9,0x45,0xFB,0xD4,0x40,0xB1,0x7A,0xAA,0x68,0x2D,0x86, +0xB2,0x99,0x22,0xE1,0xC1,0x2B,0xC7,0x9C,0xF8,0xF3,0x5F,0xA8,0x82,0x12,0xEB,0x19, +0x11,0x2D, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA */ + + +const unsigned char GeoTrust_Universal_CA_certificate[1388]={ +0x30,0x82,0x05,0x68,0x30,0x82,0x03,0x50,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x03,0x13, +0x15,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72, +0x73,0x61,0x6C,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33,0x30,0x34, +0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x30,0x34,0x30, +0x35,0x30,0x30,0x30,0x30,0x5A,0x30,0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D, +0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1E,0x30, +0x1C,0x06,0x03,0x55,0x04,0x03,0x13,0x15,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74, +0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x30,0x82,0x02, +0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xA6,0x15, +0x55,0xA0,0xA3,0xC6,0xE0,0x1F,0x8C,0x9D,0x21,0x50,0xD7,0xC1,0xBE,0x2B,0x5B,0xB5, +0xA4,0x9E,0xA1,0xD9,0x72,0x58,0xBD,0x00,0x1B,0x4C,0xBF,0x61,0xC9,0x14,0x1D,0x45, +0x82,0xAB,0xC6,0x1D,0x80,0xD6,0x3D,0xEB,0x10,0x9C,0x3A,0xAF,0x6D,0x24,0xF8,0xBC, +0x71,0x01,0x9E,0x06,0xF5,0x7C,0x5F,0x1E,0xC1,0x0E,0x55,0xCA,0x83,0x9A,0x59,0x30, +0xAE,0x19,0xCB,0x30,0x48,0x95,0xED,0x22,0x37,0x8D,0xF4,0x4A,0x9A,0x72,0x66,0x3E, +0xAD,0x95,0xC0,0xE0,0x16,0x00,0xE0,0x10,0x1F,0x2B,0x31,0x0E,0xD7,0x94,0x54,0xD3, +0x42,0x33,0xA0,0x34,0x1D,0x1E,0x45,0x76,0xDD,0x4F,0xCA,0x18,0x37,0xEC,0x85,0x15, +0x7A,0x19,0x08,0xFC,0xD5,0xC7,0x9C,0xF0,0xF2,0xA9,0x2E,0x10,0xA9,0x92,0xE6,0x3D, +0x58,0x3D,0xA9,0x16,0x68,0x3C,0x2F,0x75,0x21,0x18,0x7F,0x28,0x77,0xA5,0xE1,0x61, +0x17,0xB7,0xA6,0xE9,0xF8,0x1E,0x99,0xDB,0x73,0x6E,0xF4,0x0A,0xA2,0x21,0x6C,0xEE, +0xDA,0xAA,0x85,0x92,0x66,0xAF,0xF6,0x7A,0x6B,0x82,0xDA,0xBA,0x22,0x08,0x35,0x0F, +0xCF,0x42,0xF1,0x35,0xFA,0x6A,0xEE,0x7E,0x2B,0x25,0xCC,0x3A,0x11,0xE4,0x6D,0xAF, +0x73,0xB2,0x76,0x1D,0xAD,0xD0,0xB2,0x78,0x67,0x1A,0xA4,0x39,0x1C,0x51,0x0B,0x67, +0x56,0x83,0xFD,0x38,0x5D,0x0D,0xCE,0xDD,0xF0,0xBB,0x2B,0x96,0x1F,0xDE,0x7B,0x32, +0x52,0xFD,0x1D,0xBB,0xB5,0x06,0xA1,0xB2,0x21,0x5E,0xA5,0xD6,0x95,0x68,0x7F,0xF0, +0x99,0x9E,0xDC,0x45,0x08,0x3E,0xE7,0xD2,0x09,0x0D,0x35,0x94,0xDD,0x80,0x4E,0x53, +0x97,0xD7,0xB5,0x09,0x44,0x20,0x64,0x16,0x17,0x03,0x02,0x4C,0x53,0x0D,0x68,0xDE, +0xD5,0xAA,0x72,0x4D,0x93,0x6D,0x82,0x0E,0xDB,0x9C,0xBD,0xCF,0xB4,0xF3,0x5C,0x5D, +0x54,0x7A,0x69,0x09,0x96,0xD6,0xDB,0x11,0xC1,0x8D,0x75,0xA8,0xB4,0xCF,0x39,0xC8, +0xCE,0x3C,0xBC,0x24,0x7C,0xE6,0x62,0xCA,0xE1,0xBD,0x7D,0xA7,0xBD,0x57,0x65,0x0B, +0xE4,0xFE,0x25,0xED,0xB6,0x69,0x10,0xDC,0x28,0x1A,0x46,0xBD,0x01,0x1D,0xD0,0x97, +0xB5,0xE1,0x98,0x3B,0xC0,0x37,0x64,0xD6,0x3D,0x94,0xEE,0x0B,0xE1,0xF5,0x28,0xAE, +0x0B,0x56,0xBF,0x71,0x8B,0x23,0x29,0x41,0x8E,0x86,0xC5,0x4B,0x52,0x7B,0xD8,0x71, +0xAB,0x1F,0x8A,0x15,0xA6,0x3B,0x83,0x5A,0xD7,0x58,0x01,0x51,0xC6,0x4C,0x41,0xD9, +0x7F,0xD8,0x41,0x67,0x72,0xA2,0x28,0xDF,0x60,0x83,0xA9,0x9E,0xC8,0x7B,0xFC,0x53, +0x73,0x72,0x59,0xF5,0x93,0x7A,0x17,0x76,0x0E,0xCE,0xF7,0xE5,0x5C,0xD9,0x0B,0x55, +0x34,0xA2,0xAA,0x5B,0xB5,0x6A,0x54,0xE7,0x13,0xCA,0x57,0xEC,0x97,0x6D,0xF4,0x5E, +0x06,0x2F,0x45,0x8B,0x58,0xD4,0x23,0x16,0x92,0xE4,0x16,0x6E,0x28,0x63,0x59,0x30, +0xDF,0x50,0x01,0x9C,0x63,0x89,0x1A,0x9F,0xDB,0x17,0x94,0x82,0x70,0x37,0xC3,0x24, +0x9E,0x9A,0x47,0xD6,0x5A,0xCA,0x4E,0xA8,0x69,0x89,0x72,0x1F,0x91,0x6C,0xDB,0x7E, +0x9E,0x1B,0xAD,0xC7,0x1F,0x73,0xDD,0x2C,0x4F,0x19,0x65,0xFD,0x7F,0x93,0x40,0x10, +0x2E,0xD2,0xF0,0xED,0x3C,0x9E,0x2E,0x28,0x3E,0x69,0x26,0x33,0xC5,0x7B,0x02,0x03, +0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01, +0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04, +0x16,0x04,0x14,0xDA,0xBB,0x2E,0xAA,0xB0,0x0C,0xB8,0x88,0x26,0x51,0x74,0x5C,0x6D, +0x03,0xD3,0xC0,0xD8,0x8F,0x7A,0xD6,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18, +0x30,0x16,0x80,0x14,0xDA,0xBB,0x2E,0xAA,0xB0,0x0C,0xB8,0x88,0x26,0x51,0x74,0x5C, +0x6D,0x03,0xD3,0xC0,0xD8,0x8F,0x7A,0xD6,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01, +0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x31,0x78,0xE6,0xC7, +0xB5,0xDF,0xB8,0x94,0x40,0xC9,0x71,0xC4,0xA8,0x35,0xEC,0x46,0x1D,0xC2,0x85,0xF3, +0x28,0x58,0x86,0xB0,0x0B,0xFC,0x8E,0xB2,0x39,0x8F,0x44,0x55,0xAB,0x64,0x84,0x5C, +0x69,0xA9,0xD0,0x9A,0x38,0x3C,0xFA,0xE5,0x1F,0x35,0xE5,0x44,0xE3,0x80,0x79,0x94, +0x68,0xA4,0xBB,0xC4,0x9F,0x3D,0xE1,0x34,0xCD,0x30,0x46,0x8B,0x54,0x2B,0x95,0xA5, +0xEF,0xF7,0x3F,0x99,0x84,0xFD,0x35,0xE6,0xCF,0x31,0xC6,0xDC,0x6A,0xBF,0xA7,0xD7, +0x23,0x08,0xE1,0x98,0x5E,0xC3,0x5A,0x08,0x76,0xA9,0xA6,0xAF,0x77,0x2F,0xB7,0x60, +0xBD,0x44,0x46,0x6A,0xEF,0x97,0xFF,0x73,0x95,0xC1,0x8E,0xE8,0x93,0xFB,0xFD,0x31, +0xB7,0xEC,0x57,0x11,0x11,0x45,0x9B,0x30,0xF1,0x1A,0x88,0x39,0xC1,0x4F,0x3C,0xA7, +0x00,0xD5,0xC7,0xFC,0xAB,0x6D,0x80,0x22,0x70,0xA5,0x0C,0xE0,0x5D,0x04,0x29,0x02, +0xFB,0xCB,0xA0,0x91,0xD1,0x7C,0xD6,0xC3,0x7E,0x50,0xD5,0x9D,0x58,0xBE,0x41,0x38, +0xEB,0xB9,0x75,0x3C,0x15,0xD9,0x9B,0xC9,0x4A,0x83,0x59,0xC0,0xDA,0x53,0xFD,0x33, +0xBB,0x36,0x18,0x9B,0x85,0x0F,0x15,0xDD,0xEE,0x2D,0xAC,0x76,0x93,0xB9,0xD9,0x01, +0x8D,0x48,0x10,0xA8,0xFB,0xF5,0x38,0x86,0xF1,0xDB,0x0A,0xC6,0xBD,0x84,0xA3,0x23, +0x41,0xDE,0xD6,0x77,0x6F,0x85,0xD4,0x85,0x1C,0x50,0xE0,0xAE,0x51,0x8A,0xBA,0x8D, +0x3E,0x76,0xE2,0xB9,0xCA,0x27,0xF2,0x5F,0x9F,0xEF,0x6E,0x59,0x0D,0x06,0xD8,0x2B, +0x17,0xA4,0xD2,0x7C,0x6B,0xBB,0x5F,0x14,0x1A,0x48,0x8F,0x1A,0x4C,0xE7,0xB3,0x47, +0x1C,0x8E,0x4C,0x45,0x2B,0x20,0xEE,0x48,0xDF,0xE7,0xDD,0x09,0x8E,0x18,0xA8,0xDA, +0x40,0x8D,0x92,0x26,0x11,0x53,0x61,0x73,0x5D,0xEB,0xBD,0xE7,0xC4,0x4D,0x29,0x37, +0x61,0xEB,0xAC,0x39,0x2D,0x67,0x2E,0x16,0xD6,0xF5,0x00,0x83,0x85,0xA1,0xCC,0x7F, +0x76,0xC4,0x7D,0xE4,0xB7,0x4B,0x66,0xEF,0x03,0x45,0x60,0x69,0xB6,0x0C,0x52,0x96, +0x92,0x84,0x5E,0xA6,0xA3,0xB5,0xA4,0x3E,0x2B,0xD9,0xCC,0xD8,0x1B,0x47,0xAA,0xF2, +0x44,0xDA,0x4F,0xF9,0x03,0xE8,0xF0,0x14,0xCB,0x3F,0xF3,0x83,0xDE,0xD0,0xC1,0x54, +0xE3,0xB7,0xE8,0x0A,0x37,0x4D,0x8B,0x20,0x59,0x03,0x30,0x19,0xA1,0x2C,0xC8,0xBD, +0x11,0x1F,0xDF,0xAE,0xC9,0x4A,0xC5,0xF3,0x27,0x66,0x66,0x86,0xAC,0x68,0x91,0xFF, +0xD9,0xE6,0x53,0x1C,0x0F,0x8B,0x5C,0x69,0x65,0x0A,0x26,0xC8,0x1E,0x34,0xC3,0x5D, +0x51,0x7B,0xD7,0xA9,0x9C,0x06,0xA1,0x36,0xDD,0xD5,0x89,0x94,0xBC,0xD9,0xE4,0x2D, +0x0C,0x5E,0x09,0x6C,0x08,0x97,0x7C,0xA3,0x3D,0x7C,0x93,0xFF,0x3F,0xA1,0x14,0xA7, +0xCF,0xB5,0x5D,0xEB,0xDB,0xDB,0x1C,0xC4,0x76,0xDF,0x88,0xB9,0xBD,0x45,0x05,0x95, +0x1B,0xAE,0xFC,0x46,0x6A,0x4C,0xAF,0x48,0xE3,0xCE,0xAE,0x0F,0xD2,0x7E,0xEB,0xE6, +0x6C,0x9C,0x4F,0x81,0x6A,0x7A,0x64,0xAC,0xBB,0x3E,0xD5,0xE7,0xCB,0x76,0x2E,0xC5, +0xA7,0x48,0xC1,0x5C,0x90,0x0F,0xCB,0xC8,0x3F,0xFA,0xE6,0x32,0xE1,0x8D,0x1B,0x6F, +0xA4,0xE6,0x8E,0xD8,0xF9,0x29,0x48,0x8A,0xCE,0x73,0xFE,0x2C, +}; + + +/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA 2 */ +/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA 2 */ + + +const unsigned char GeoTrust_Universal_CA_2_certificate[1392]={ +0x30,0x82,0x05,0x6C,0x30,0x82,0x03,0x54,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x47,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73, +0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13, +0x17,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72, +0x73,0x61,0x6C,0x20,0x43,0x41,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33, +0x30,0x34,0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x30, +0x34,0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x30,0x47,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x47,0x65,0x6F,0x54,0x72,0x75, +0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x20, +0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02, +0x01,0x00,0xB3,0x54,0x52,0xC1,0xC9,0x3E,0xF2,0xD9,0xDC,0xB1,0x53,0x1A,0x59,0x29, +0xE7,0xB1,0xC3,0x45,0x28,0xE5,0xD7,0xD1,0xED,0xC5,0xC5,0x4B,0xA1,0xAA,0x74,0x7B, +0x57,0xAF,0x4A,0x26,0xFC,0xD8,0xF5,0x5E,0xA7,0x6E,0x19,0xDB,0x74,0x0C,0x4F,0x35, +0x5B,0x32,0x0B,0x01,0xE3,0xDB,0xEB,0x7A,0x77,0x35,0xEA,0xAA,0x5A,0xE0,0xD6,0xE8, +0xA1,0x57,0x94,0xF0,0x90,0xA3,0x74,0x56,0x94,0x44,0x30,0x03,0x1E,0x5C,0x4E,0x2B, +0x85,0x26,0x74,0x82,0x7A,0x0C,0x76,0xA0,0x6F,0x4D,0xCE,0x41,0x2D,0xA0,0x15,0x06, +0x14,0x5F,0xB7,0x42,0xCD,0x7B,0x8F,0x58,0x61,0x34,0xDC,0x2A,0x08,0xF9,0x2E,0xC3, +0x01,0xA6,0x22,0x44,0x1C,0x4C,0x07,0x82,0xE6,0x5B,0xCE,0xD0,0x4A,0x7C,0x04,0xD3, +0x19,0x73,0x27,0xF0,0xAA,0x98,0x7F,0x2E,0xAF,0x4E,0xEB,0x87,0x1E,0x24,0x77,0x6A, +0x5D,0xB6,0xE8,0x5B,0x45,0xBA,0xDC,0xC3,0xA1,0x05,0x6F,0x56,0x8E,0x8F,0x10,0x26, +0xA5,0x49,0xC3,0x2E,0xD7,0x41,0x87,0x22,0xE0,0x4F,0x86,0xCA,0x60,0xB5,0xEA,0xA1, +0x63,0xC0,0x01,0x97,0x10,0x79,0xBD,0x00,0x3C,0x12,0x6D,0x2B,0x15,0xB1,0xAC,0x4B, +0xB1,0xEE,0x18,0xB9,0x4E,0x96,0xDC,0xDC,0x76,0xFF,0x3B,0xBE,0xCF,0x5F,0x03,0xC0, +0xFC,0x3B,0xE8,0xBE,0x46,0x1B,0xFF,0xDA,0x40,0xC2,0x52,0xF7,0xFE,0xE3,0x3A,0xF7, +0x6A,0x77,0x35,0xD0,0xDA,0x8D,0xEB,0x5E,0x18,0x6A,0x31,0xC7,0x1E,0xBA,0x3C,0x1B, +0x28,0xD6,0x6B,0x54,0xC6,0xAA,0x5B,0xD7,0xA2,0x2C,0x1B,0x19,0xCC,0xA2,0x02,0xF6, +0x9B,0x59,0xBD,0x37,0x6B,0x86,0xB5,0x6D,0x82,0xBA,0xD8,0xEA,0xC9,0x56,0xBC,0xA9, +0x36,0x58,0xFD,0x3E,0x19,0xF3,0xED,0x0C,0x26,0xA9,0x93,0x38,0xF8,0x4F,0xC1,0x5D, +0x22,0x06,0xD0,0x97,0xEA,0xE1,0xAD,0xC6,0x55,0xE0,0x81,0x2B,0x28,0x83,0x3A,0xFA, +0xF4,0x7B,0x21,0x51,0x00,0xBE,0x52,0x38,0xCE,0xCD,0x66,0x79,0xA8,0xF4,0x81,0x56, +0xE2,0xD0,0x83,0x09,0x47,0x51,0x5B,0x50,0x6A,0xCF,0xDB,0x48,0x1A,0x5D,0x3E,0xF7, +0xCB,0xF6,0x65,0xF7,0x6C,0xF1,0x95,0xF8,0x02,0x3B,0x32,0x56,0x82,0x39,0x7A,0x5B, +0xBD,0x2F,0x89,0x1B,0xBF,0xA1,0xB4,0xE8,0xFF,0x7F,0x8D,0x8C,0xDF,0x03,0xF1,0x60, +0x4E,0x58,0x11,0x4C,0xEB,0xA3,0x3F,0x10,0x2B,0x83,0x9A,0x01,0x73,0xD9,0x94,0x6D, +0x84,0x00,0x27,0x66,0xAC,0xF0,0x70,0x40,0x09,0x42,0x92,0xAD,0x4F,0x93,0x0D,0x61, +0x09,0x51,0x24,0xD8,0x92,0xD5,0x0B,0x94,0x61,0xB2,0x87,0xB2,0xED,0xFF,0x9A,0x35, +0xFF,0x85,0x54,0xCA,0xED,0x44,0x43,0xAC,0x1B,0x3C,0x16,0x6B,0x48,0x4A,0x0A,0x1C, +0x40,0x88,0x1F,0x92,0xC2,0x0B,0x00,0x05,0xFF,0xF2,0xC8,0x02,0x4A,0xA4,0xAA,0xA9, +0xCC,0x99,0x96,0x9C,0x2F,0x58,0xE0,0x7D,0xE1,0xBE,0xBB,0x07,0xDC,0x5F,0x04,0x72, +0x5C,0x31,0x34,0xC3,0xEC,0x5F,0x2D,0xE0,0x3D,0x64,0x90,0x22,0xE6,0xD1,0xEC,0xB8, +0x2E,0xDD,0x59,0xAE,0xD9,0xA1,0x37,0xBF,0x54,0x35,0xDC,0x73,0x32,0x4F,0x8C,0x04, +0x1E,0x33,0xB2,0xC9,0x46,0xF1,0xD8,0x5C,0xC8,0x55,0x50,0xC9,0x68,0xBD,0xA8,0xBA, +0x36,0x09,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x76,0xF3,0x55,0xE1,0xFA,0xA4,0x36,0xFB,0xF0, +0x9F,0x5C,0x62,0x71,0xED,0x3C,0xF4,0x47,0x38,0x10,0x2B,0x30,0x1F,0x06,0x03,0x55, +0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x76,0xF3,0x55,0xE1,0xFA,0xA4,0x36,0xFB, +0xF0,0x9F,0x5C,0x62,0x71,0xED,0x3C,0xF4,0x47,0x38,0x10,0x2B,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00, +0x66,0xC1,0xC6,0x23,0xF3,0xD9,0xE0,0x2E,0x6E,0x5F,0xE8,0xCF,0xAE,0xB0,0xB0,0x25, +0x4D,0x2B,0xF8,0x3B,0x58,0x9B,0x40,0x24,0x37,0x5A,0xCB,0xAB,0x16,0x49,0xFF,0xB3, +0x75,0x79,0x33,0xA1,0x2F,0x6D,0x70,0x17,0x34,0x91,0xFE,0x67,0x7E,0x8F,0xEC,0x9B, +0xE5,0x5E,0x82,0xA9,0x55,0x1F,0x2F,0xDC,0xD4,0x51,0x07,0x12,0xFE,0xAC,0x16,0x3E, +0x2C,0x35,0xC6,0x63,0xFC,0xDC,0x10,0xEB,0x0D,0xA3,0xAA,0xD0,0x7C,0xCC,0xD1,0xD0, +0x2F,0x51,0x2E,0xC4,0x14,0x5A,0xDE,0xE8,0x19,0xE1,0x3E,0xC6,0xCC,0xA4,0x29,0xE7, +0x2E,0x84,0xAA,0x06,0x30,0x78,0x76,0x54,0x73,0x28,0x98,0x59,0x38,0xE0,0x00,0x0D, +0x62,0xD3,0x42,0x7D,0x21,0x9F,0xAE,0x3D,0x3A,0x8C,0xD5,0xFA,0x77,0x0D,0x18,0x2B, +0x16,0x0E,0x5F,0x36,0xE1,0xFC,0x2A,0xB5,0x30,0x24,0xCF,0xE0,0x63,0x0C,0x7B,0x58, +0x1A,0xFE,0x99,0xBA,0x42,0x12,0xB1,0x91,0xF4,0x7C,0x68,0xE2,0xC8,0xE8,0xAF,0x2C, +0xEA,0xC9,0x7E,0xAE,0xBB,0x2A,0x3D,0x0D,0x15,0xDC,0x34,0x95,0xB6,0x18,0x74,0xA8, +0x6A,0x0F,0xC7,0xB4,0xF4,0x13,0xC4,0xE4,0x5B,0xED,0x0A,0xD2,0xA4,0x97,0x4C,0x2A, +0xED,0x2F,0x6C,0x12,0x89,0x3D,0xF1,0x27,0x70,0xAA,0x6A,0x03,0x52,0x21,0x9F,0x40, +0xA8,0x67,0x50,0xF2,0xF3,0x5A,0x1F,0xDF,0xDF,0x23,0xF6,0xDC,0x78,0x4E,0xE6,0x98, +0x4F,0x55,0x3A,0x53,0xE3,0xEF,0xF2,0xF4,0x9F,0xC7,0x7C,0xD8,0x58,0xAF,0x29,0x22, +0x97,0xB8,0xE0,0xBD,0x91,0x2E,0xB0,0x76,0xEC,0x57,0x11,0xCF,0xEF,0x29,0x44,0xF3, +0xE9,0x85,0x7A,0x60,0x63,0xE4,0x5D,0x33,0x89,0x17,0xD9,0x31,0xAA,0xDA,0xD6,0xF3, +0x18,0x35,0x72,0xCF,0x87,0x2B,0x2F,0x63,0x23,0x84,0x5D,0x84,0x8C,0x3F,0x57,0xA0, +0x88,0xFC,0x99,0x91,0x28,0x26,0x69,0x99,0xD4,0x8F,0x97,0x44,0xBE,0x8E,0xD5,0x48, +0xB1,0xA4,0x28,0x29,0xF1,0x15,0xB4,0xE1,0xE5,0x9E,0xDD,0xF8,0x8F,0xA6,0x6F,0x26, +0xD7,0x09,0x3C,0x3A,0x1C,0x11,0x0E,0xA6,0x6C,0x37,0xF7,0xAD,0x44,0x87,0x2C,0x28, +0xC7,0xD8,0x74,0x82,0xB3,0xD0,0x6F,0x4A,0x57,0xBB,0x35,0x29,0x27,0xA0,0x8B,0xE8, +0x21,0xA7,0x87,0x64,0x36,0x5D,0xCC,0xD8,0x16,0xAC,0xC7,0xB2,0x27,0x40,0x92,0x55, +0x38,0x28,0x8D,0x51,0x6E,0xDD,0x14,0x67,0x53,0x6C,0x71,0x5C,0x26,0x84,0x4D,0x75, +0x5A,0xB6,0x7E,0x60,0x56,0xA9,0x4D,0xAD,0xFB,0x9B,0x1E,0x97,0xF3,0x0D,0xD9,0xD2, +0x97,0x54,0x77,0xDA,0x3D,0x12,0xB7,0xE0,0x1E,0xEF,0x08,0x06,0xAC,0xF9,0x85,0x87, +0xE9,0xA2,0xDC,0xAF,0x7E,0x18,0x12,0x83,0xFD,0x56,0x17,0x41,0x2E,0xD5,0x29,0x82, +0x7D,0x99,0xF4,0x31,0xF6,0x71,0xA9,0xCF,0x2C,0x01,0x27,0xA5,0x05,0xB9,0xAA,0xB2, +0x48,0x4E,0x2A,0xEF,0x9F,0x93,0x52,0x51,0x95,0x3C,0x52,0x73,0x8E,0x56,0x4C,0x17, +0x40,0xC0,0x09,0x28,0xE4,0x8B,0x6A,0x48,0x53,0xDB,0xEC,0xCD,0x55,0x55,0xF1,0xC6, +0xF8,0xE9,0xA2,0x2C,0x4C,0xA6,0xD1,0x26,0x5F,0x7E,0xAF,0x5A,0x4C,0xDA,0x1F,0xA6, +0xF2,0x1C,0x2C,0x7E,0xAE,0x02,0x16,0xD2,0x56,0xD0,0x2F,0x57,0x53,0x47,0xE8,0x92, +}; + + +/* subject:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA */ +/* issuer :/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA */ + + +const unsigned char GlobalSign_Root_CA_certificate[889]={ +0x30,0x82,0x03,0x75,0x30,0x82,0x02,0x5D,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x15,0x4B,0x5A,0xC3,0x94,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x57,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x42,0x45,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04, +0x0A,0x13,0x10,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x6E,0x76, +0x2D,0x73,0x61,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0B,0x13,0x07,0x52,0x6F, +0x6F,0x74,0x20,0x43,0x41,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12, +0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x41,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x39,0x30,0x31,0x31,0x32,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x32,0x38,0x30,0x31,0x32,0x38,0x31,0x32,0x30,0x30,0x30, +0x30,0x5A,0x30,0x57,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x42, +0x45,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0A,0x13,0x10,0x47,0x6C,0x6F,0x62, +0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x6E,0x76,0x2D,0x73,0x61,0x31,0x10,0x30,0x0E, +0x06,0x03,0x55,0x04,0x0B,0x13,0x07,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x31,0x1B, +0x30,0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53, +0x69,0x67,0x6E,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDA,0x0E,0xE6,0x99, +0x8D,0xCE,0xA3,0xE3,0x4F,0x8A,0x7E,0xFB,0xF1,0x8B,0x83,0x25,0x6B,0xEA,0x48,0x1F, +0xF1,0x2A,0xB0,0xB9,0x95,0x11,0x04,0xBD,0xF0,0x63,0xD1,0xE2,0x67,0x66,0xCF,0x1C, +0xDD,0xCF,0x1B,0x48,0x2B,0xEE,0x8D,0x89,0x8E,0x9A,0xAF,0x29,0x80,0x65,0xAB,0xE9, +0xC7,0x2D,0x12,0xCB,0xAB,0x1C,0x4C,0x70,0x07,0xA1,0x3D,0x0A,0x30,0xCD,0x15,0x8D, +0x4F,0xF8,0xDD,0xD4,0x8C,0x50,0x15,0x1C,0xEF,0x50,0xEE,0xC4,0x2E,0xF7,0xFC,0xE9, +0x52,0xF2,0x91,0x7D,0xE0,0x6D,0xD5,0x35,0x30,0x8E,0x5E,0x43,0x73,0xF2,0x41,0xE9, +0xD5,0x6A,0xE3,0xB2,0x89,0x3A,0x56,0x39,0x38,0x6F,0x06,0x3C,0x88,0x69,0x5B,0x2A, +0x4D,0xC5,0xA7,0x54,0xB8,0x6C,0x89,0xCC,0x9B,0xF9,0x3C,0xCA,0xE5,0xFD,0x89,0xF5, +0x12,0x3C,0x92,0x78,0x96,0xD6,0xDC,0x74,0x6E,0x93,0x44,0x61,0xD1,0x8D,0xC7,0x46, +0xB2,0x75,0x0E,0x86,0xE8,0x19,0x8A,0xD5,0x6D,0x6C,0xD5,0x78,0x16,0x95,0xA2,0xE9, +0xC8,0x0A,0x38,0xEB,0xF2,0x24,0x13,0x4F,0x73,0x54,0x93,0x13,0x85,0x3A,0x1B,0xBC, +0x1E,0x34,0xB5,0x8B,0x05,0x8C,0xB9,0x77,0x8B,0xB1,0xDB,0x1F,0x20,0x91,0xAB,0x09, +0x53,0x6E,0x90,0xCE,0x7B,0x37,0x74,0xB9,0x70,0x47,0x91,0x22,0x51,0x63,0x16,0x79, +0xAE,0xB1,0xAE,0x41,0x26,0x08,0xC8,0x19,0x2B,0xD1,0x46,0xAA,0x48,0xD6,0x64,0x2A, +0xD7,0x83,0x34,0xFF,0x2C,0x2A,0xC1,0x6C,0x19,0x43,0x4A,0x07,0x85,0xE7,0xD3,0x7C, +0xF6,0x21,0x68,0xEF,0xEA,0xF2,0x52,0x9F,0x7F,0x93,0x90,0xCF,0x02,0x03,0x01,0x00, +0x01,0xA3,0x42,0x30,0x40,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04, +0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x60,0x7B,0x66,0x1A,0x45,0x0D,0x97,0xCA,0x89,0x50,0x2F,0x7D,0x04,0xCD,0x34, +0xA8,0xFF,0xFC,0xFD,0x4B,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xD6,0x73,0xE7,0x7C,0x4F,0x76,0xD0, +0x8D,0xBF,0xEC,0xBA,0xA2,0xBE,0x34,0xC5,0x28,0x32,0xB5,0x7C,0xFC,0x6C,0x9C,0x2C, +0x2B,0xBD,0x09,0x9E,0x53,0xBF,0x6B,0x5E,0xAA,0x11,0x48,0xB6,0xE5,0x08,0xA3,0xB3, +0xCA,0x3D,0x61,0x4D,0xD3,0x46,0x09,0xB3,0x3E,0xC3,0xA0,0xE3,0x63,0x55,0x1B,0xF2, +0xBA,0xEF,0xAD,0x39,0xE1,0x43,0xB9,0x38,0xA3,0xE6,0x2F,0x8A,0x26,0x3B,0xEF,0xA0, +0x50,0x56,0xF9,0xC6,0x0A,0xFD,0x38,0xCD,0xC4,0x0B,0x70,0x51,0x94,0x97,0x98,0x04, +0xDF,0xC3,0x5F,0x94,0xD5,0x15,0xC9,0x14,0x41,0x9C,0xC4,0x5D,0x75,0x64,0x15,0x0D, +0xFF,0x55,0x30,0xEC,0x86,0x8F,0xFF,0x0D,0xEF,0x2C,0xB9,0x63,0x46,0xF6,0xAA,0xFC, +0xDF,0xBC,0x69,0xFD,0x2E,0x12,0x48,0x64,0x9A,0xE0,0x95,0xF0,0xA6,0xEF,0x29,0x8F, +0x01,0xB1,0x15,0xB5,0x0C,0x1D,0xA5,0xFE,0x69,0x2C,0x69,0x24,0x78,0x1E,0xB3,0xA7, +0x1C,0x71,0x62,0xEE,0xCA,0xC8,0x97,0xAC,0x17,0x5D,0x8A,0xC2,0xF8,0x47,0x86,0x6E, +0x2A,0xC4,0x56,0x31,0x95,0xD0,0x67,0x89,0x85,0x2B,0xF9,0x6C,0xA6,0x5D,0x46,0x9D, +0x0C,0xAA,0x82,0xE4,0x99,0x51,0xDD,0x70,0xB7,0xDB,0x56,0x3D,0x61,0xE4,0x6A,0xE1, +0x5C,0xD6,0xF6,0xFE,0x3D,0xDE,0x41,0xCC,0x07,0xAE,0x63,0x52,0xBF,0x53,0x53,0xF4, +0x2B,0xE9,0xC7,0xFD,0xB6,0xF7,0x82,0x5F,0x85,0xD2,0x41,0x18,0xDB,0x81,0xB3,0x04, +0x1C,0xC5,0x1F,0xA4,0x80,0x6F,0x15,0x20,0xC9,0xDE,0x0C,0x88,0x0A,0x1D,0xD6,0x66, +0x55,0xE2,0xFC,0x48,0xC9,0x29,0x26,0x69,0xE0, +}; + + +/* subject:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign */ +/* issuer :/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign */ + + +const unsigned char GlobalSign_Root_CA___R2_certificate[958]={ +0x30,0x82,0x03,0xBA,0x30,0x82,0x02,0xA2,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x0F,0x86,0x26,0xE6,0x0D,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06, +0x03,0x55,0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x32,0x31,0x13,0x30, +0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69, +0x67,0x6E,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F, +0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x31, +0x35,0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x31,0x31,0x32,0x31,0x35, +0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,0x03,0x55, +0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52, +0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x32,0x31,0x13,0x30,0x11,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61, +0x6C,0x53,0x69,0x67,0x6E,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01, +0x0A,0x02,0x82,0x01,0x01,0x00,0xA6,0xCF,0x24,0x0E,0xBE,0x2E,0x6F,0x28,0x99,0x45, +0x42,0xC4,0xAB,0x3E,0x21,0x54,0x9B,0x0B,0xD3,0x7F,0x84,0x70,0xFA,0x12,0xB3,0xCB, +0xBF,0x87,0x5F,0xC6,0x7F,0x86,0xD3,0xB2,0x30,0x5C,0xD6,0xFD,0xAD,0xF1,0x7B,0xDC, +0xE5,0xF8,0x60,0x96,0x09,0x92,0x10,0xF5,0xD0,0x53,0xDE,0xFB,0x7B,0x7E,0x73,0x88, +0xAC,0x52,0x88,0x7B,0x4A,0xA6,0xCA,0x49,0xA6,0x5E,0xA8,0xA7,0x8C,0x5A,0x11,0xBC, +0x7A,0x82,0xEB,0xBE,0x8C,0xE9,0xB3,0xAC,0x96,0x25,0x07,0x97,0x4A,0x99,0x2A,0x07, +0x2F,0xB4,0x1E,0x77,0xBF,0x8A,0x0F,0xB5,0x02,0x7C,0x1B,0x96,0xB8,0xC5,0xB9,0x3A, +0x2C,0xBC,0xD6,0x12,0xB9,0xEB,0x59,0x7D,0xE2,0xD0,0x06,0x86,0x5F,0x5E,0x49,0x6A, +0xB5,0x39,0x5E,0x88,0x34,0xEC,0xBC,0x78,0x0C,0x08,0x98,0x84,0x6C,0xA8,0xCD,0x4B, +0xB4,0xA0,0x7D,0x0C,0x79,0x4D,0xF0,0xB8,0x2D,0xCB,0x21,0xCA,0xD5,0x6C,0x5B,0x7D, +0xE1,0xA0,0x29,0x84,0xA1,0xF9,0xD3,0x94,0x49,0xCB,0x24,0x62,0x91,0x20,0xBC,0xDD, +0x0B,0xD5,0xD9,0xCC,0xF9,0xEA,0x27,0x0A,0x2B,0x73,0x91,0xC6,0x9D,0x1B,0xAC,0xC8, +0xCB,0xE8,0xE0,0xA0,0xF4,0x2F,0x90,0x8B,0x4D,0xFB,0xB0,0x36,0x1B,0xF6,0x19,0x7A, +0x85,0xE0,0x6D,0xF2,0x61,0x13,0x88,0x5C,0x9F,0xE0,0x93,0x0A,0x51,0x97,0x8A,0x5A, +0xCE,0xAF,0xAB,0xD5,0xF7,0xAA,0x09,0xAA,0x60,0xBD,0xDC,0xD9,0x5F,0xDF,0x72,0xA9, +0x60,0x13,0x5E,0x00,0x01,0xC9,0x4A,0xFA,0x3F,0xA4,0xEA,0x07,0x03,0x21,0x02,0x8E, +0x82,0xCA,0x03,0xC2,0x9B,0x8F,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x9C,0x30,0x81, +0x99,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9B,0xE2,0x07, +0x57,0x67,0x1C,0x1E,0xC0,0x6A,0x06,0xDE,0x59,0xB4,0x9A,0x2D,0xDF,0xDC,0x19,0x86, +0x2E,0x30,0x36,0x06,0x03,0x55,0x1D,0x1F,0x04,0x2F,0x30,0x2D,0x30,0x2B,0xA0,0x29, +0xA0,0x27,0x86,0x25,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x67, +0x6C,0x6F,0x62,0x61,0x6C,0x73,0x69,0x67,0x6E,0x2E,0x6E,0x65,0x74,0x2F,0x72,0x6F, +0x6F,0x74,0x2D,0x72,0x32,0x2E,0x63,0x72,0x6C,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23, +0x04,0x18,0x30,0x16,0x80,0x14,0x9B,0xE2,0x07,0x57,0x67,0x1C,0x1E,0xC0,0x6A,0x06, +0xDE,0x59,0xB4,0x9A,0x2D,0xDF,0xDC,0x19,0x86,0x2E,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x99,0x81, +0x53,0x87,0x1C,0x68,0x97,0x86,0x91,0xEC,0xE0,0x4A,0xB8,0x44,0x0B,0xAB,0x81,0xAC, +0x27,0x4F,0xD6,0xC1,0xB8,0x1C,0x43,0x78,0xB3,0x0C,0x9A,0xFC,0xEA,0x2C,0x3C,0x6E, +0x61,0x1B,0x4D,0x4B,0x29,0xF5,0x9F,0x05,0x1D,0x26,0xC1,0xB8,0xE9,0x83,0x00,0x62, +0x45,0xB6,0xA9,0x08,0x93,0xB9,0xA9,0x33,0x4B,0x18,0x9A,0xC2,0xF8,0x87,0x88,0x4E, +0xDB,0xDD,0x71,0x34,0x1A,0xC1,0x54,0xDA,0x46,0x3F,0xE0,0xD3,0x2A,0xAB,0x6D,0x54, +0x22,0xF5,0x3A,0x62,0xCD,0x20,0x6F,0xBA,0x29,0x89,0xD7,0xDD,0x91,0xEE,0xD3,0x5C, +0xA2,0x3E,0xA1,0x5B,0x41,0xF5,0xDF,0xE5,0x64,0x43,0x2D,0xE9,0xD5,0x39,0xAB,0xD2, +0xA2,0xDF,0xB7,0x8B,0xD0,0xC0,0x80,0x19,0x1C,0x45,0xC0,0x2D,0x8C,0xE8,0xF8,0x2D, +0xA4,0x74,0x56,0x49,0xC5,0x05,0xB5,0x4F,0x15,0xDE,0x6E,0x44,0x78,0x39,0x87,0xA8, +0x7E,0xBB,0xF3,0x79,0x18,0x91,0xBB,0xF4,0x6F,0x9D,0xC1,0xF0,0x8C,0x35,0x8C,0x5D, +0x01,0xFB,0xC3,0x6D,0xB9,0xEF,0x44,0x6D,0x79,0x46,0x31,0x7E,0x0A,0xFE,0xA9,0x82, +0xC1,0xFF,0xEF,0xAB,0x6E,0x20,0xC4,0x50,0xC9,0x5F,0x9D,0x4D,0x9B,0x17,0x8C,0x0C, +0xE5,0x01,0xC9,0xA0,0x41,0x6A,0x73,0x53,0xFA,0xA5,0x50,0xB4,0x6E,0x25,0x0F,0xFB, +0x4C,0x18,0xF4,0xFD,0x52,0xD9,0x8E,0x69,0xB1,0xE8,0x11,0x0F,0xDE,0x88,0xD8,0xFB, +0x1D,0x49,0xF7,0xAA,0xDE,0x95,0xCF,0x20,0x78,0xC2,0x60,0x12,0xDB,0x25,0x40,0x8C, +0x6A,0xFC,0x7E,0x42,0x38,0x40,0x64,0x12,0xF7,0x9E,0x81,0xE1,0x93,0x2E, +}; + + +/* subject:/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign */ +/* issuer :/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign */ + + +const unsigned char GlobalSign_Root_CA___R3_certificate[867]={ +0x30,0x82,0x03,0x5F,0x30,0x82,0x02,0x47,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04, +0x00,0x00,0x00,0x00,0x01,0x21,0x58,0x53,0x08,0xA2,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06, +0x03,0x55,0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x33,0x31,0x13,0x30, +0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69, +0x67,0x6E,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F, +0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x33,0x31, +0x38,0x31,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x31,0x38, +0x31,0x30,0x30,0x30,0x30,0x30,0x5A,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,0x03,0x55, +0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52, +0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x33,0x31,0x13,0x30,0x11,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E, +0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61, +0x6C,0x53,0x69,0x67,0x6E,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01, +0x0A,0x02,0x82,0x01,0x01,0x00,0xCC,0x25,0x76,0x90,0x79,0x06,0x78,0x22,0x16,0xF5, +0xC0,0x83,0xB6,0x84,0xCA,0x28,0x9E,0xFD,0x05,0x76,0x11,0xC5,0xAD,0x88,0x72,0xFC, +0x46,0x02,0x43,0xC7,0xB2,0x8A,0x9D,0x04,0x5F,0x24,0xCB,0x2E,0x4B,0xE1,0x60,0x82, +0x46,0xE1,0x52,0xAB,0x0C,0x81,0x47,0x70,0x6C,0xDD,0x64,0xD1,0xEB,0xF5,0x2C,0xA3, +0x0F,0x82,0x3D,0x0C,0x2B,0xAE,0x97,0xD7,0xB6,0x14,0x86,0x10,0x79,0xBB,0x3B,0x13, +0x80,0x77,0x8C,0x08,0xE1,0x49,0xD2,0x6A,0x62,0x2F,0x1F,0x5E,0xFA,0x96,0x68,0xDF, +0x89,0x27,0x95,0x38,0x9F,0x06,0xD7,0x3E,0xC9,0xCB,0x26,0x59,0x0D,0x73,0xDE,0xB0, +0xC8,0xE9,0x26,0x0E,0x83,0x15,0xC6,0xEF,0x5B,0x8B,0xD2,0x04,0x60,0xCA,0x49,0xA6, +0x28,0xF6,0x69,0x3B,0xF6,0xCB,0xC8,0x28,0x91,0xE5,0x9D,0x8A,0x61,0x57,0x37,0xAC, +0x74,0x14,0xDC,0x74,0xE0,0x3A,0xEE,0x72,0x2F,0x2E,0x9C,0xFB,0xD0,0xBB,0xBF,0xF5, +0x3D,0x00,0xE1,0x06,0x33,0xE8,0x82,0x2B,0xAE,0x53,0xA6,0x3A,0x16,0x73,0x8C,0xDD, +0x41,0x0E,0x20,0x3A,0xC0,0xB4,0xA7,0xA1,0xE9,0xB2,0x4F,0x90,0x2E,0x32,0x60,0xE9, +0x57,0xCB,0xB9,0x04,0x92,0x68,0x68,0xE5,0x38,0x26,0x60,0x75,0xB2,0x9F,0x77,0xFF, +0x91,0x14,0xEF,0xAE,0x20,0x49,0xFC,0xAD,0x40,0x15,0x48,0xD1,0x02,0x31,0x61,0x19, +0x5E,0xB8,0x97,0xEF,0xAD,0x77,0xB7,0x64,0x9A,0x7A,0xBF,0x5F,0xC1,0x13,0xEF,0x9B, +0x62,0xFB,0x0D,0x6C,0xE0,0x54,0x69,0x16,0xA9,0x03,0xDA,0x6E,0xE9,0x83,0x93,0x71, +0x76,0xC6,0x69,0x85,0x82,0x17,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30, +0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30, +0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x8F,0xF0,0x4B,0x7F,0xA8, +0x2E,0x45,0x24,0xAE,0x4D,0x50,0xFA,0x63,0x9A,0x8B,0xDE,0xE2,0xDD,0x1B,0xBC,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x4B,0x40,0xDB,0xC0,0x50,0xAA,0xFE,0xC8,0x0C,0xEF,0xF7,0x96,0x54, +0x45,0x49,0xBB,0x96,0x00,0x09,0x41,0xAC,0xB3,0x13,0x86,0x86,0x28,0x07,0x33,0xCA, +0x6B,0xE6,0x74,0xB9,0xBA,0x00,0x2D,0xAE,0xA4,0x0A,0xD3,0xF5,0xF1,0xF1,0x0F,0x8A, +0xBF,0x73,0x67,0x4A,0x83,0xC7,0x44,0x7B,0x78,0xE0,0xAF,0x6E,0x6C,0x6F,0x03,0x29, +0x8E,0x33,0x39,0x45,0xC3,0x8E,0xE4,0xB9,0x57,0x6C,0xAA,0xFC,0x12,0x96,0xEC,0x53, +0xC6,0x2D,0xE4,0x24,0x6C,0xB9,0x94,0x63,0xFB,0xDC,0x53,0x68,0x67,0x56,0x3E,0x83, +0xB8,0xCF,0x35,0x21,0xC3,0xC9,0x68,0xFE,0xCE,0xDA,0xC2,0x53,0xAA,0xCC,0x90,0x8A, +0xE9,0xF0,0x5D,0x46,0x8C,0x95,0xDD,0x7A,0x58,0x28,0x1A,0x2F,0x1D,0xDE,0xCD,0x00, +0x37,0x41,0x8F,0xED,0x44,0x6D,0xD7,0x53,0x28,0x97,0x7E,0xF3,0x67,0x04,0x1E,0x15, +0xD7,0x8A,0x96,0xB4,0xD3,0xDE,0x4C,0x27,0xA4,0x4C,0x1B,0x73,0x73,0x76,0xF4,0x17, +0x99,0xC2,0x1F,0x7A,0x0E,0xE3,0x2D,0x08,0xAD,0x0A,0x1C,0x2C,0xFF,0x3C,0xAB,0x55, +0x0E,0x0F,0x91,0x7E,0x36,0xEB,0xC3,0x57,0x49,0xBE,0xE1,0x2E,0x2D,0x7C,0x60,0x8B, +0xC3,0x41,0x51,0x13,0x23,0x9D,0xCE,0xF7,0x32,0x6B,0x94,0x01,0xA8,0x99,0xE7,0x2C, +0x33,0x1F,0x3A,0x3B,0x25,0xD2,0x86,0x40,0xCE,0x3B,0x2C,0x86,0x78,0xC9,0x61,0x2F, +0x14,0xBA,0xEE,0xDB,0x55,0x6F,0xDF,0x84,0xEE,0x05,0x09,0x4D,0xBD,0x28,0xD8,0x72, +0xCE,0xD3,0x62,0x50,0x65,0x1E,0xEB,0x92,0x97,0x83,0x31,0xD9,0xB3,0xB5,0xCA,0x47, +0x58,0x3F,0x5F, +}; + + +/* subject:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority */ +/* issuer :/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority */ + + +const unsigned char Go_Daddy_Class_2_CA_certificate[1028]={ +0x30,0x82,0x04,0x00,0x30,0x82,0x02,0xE8,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21, +0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x54,0x68,0x65,0x20,0x47,0x6F,0x20, +0x44,0x61,0x64,0x64,0x79,0x20,0x47,0x72,0x6F,0x75,0x70,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x0B,0x13,0x28,0x47,0x6F,0x20,0x44, +0x61,0x64,0x64,0x79,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F, +0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x36,0x32,0x39,0x31,0x37, +0x30,0x36,0x32,0x30,0x5A,0x17,0x0D,0x33,0x34,0x30,0x36,0x32,0x39,0x31,0x37,0x30, +0x36,0x32,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x54,0x68, +0x65,0x20,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x47,0x72,0x6F,0x75,0x70, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x0B,0x13, +0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x43,0x6C,0x61,0x73,0x73,0x20, +0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x20,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0D, +0x00,0x30,0x82,0x01,0x08,0x02,0x82,0x01,0x01,0x00,0xDE,0x9D,0xD7,0xEA,0x57,0x18, +0x49,0xA1,0x5B,0xEB,0xD7,0x5F,0x48,0x86,0xEA,0xBE,0xDD,0xFF,0xE4,0xEF,0x67,0x1C, +0xF4,0x65,0x68,0xB3,0x57,0x71,0xA0,0x5E,0x77,0xBB,0xED,0x9B,0x49,0xE9,0x70,0x80, +0x3D,0x56,0x18,0x63,0x08,0x6F,0xDA,0xF2,0xCC,0xD0,0x3F,0x7F,0x02,0x54,0x22,0x54, +0x10,0xD8,0xB2,0x81,0xD4,0xC0,0x75,0x3D,0x4B,0x7F,0xC7,0x77,0xC3,0x3E,0x78,0xAB, +0x1A,0x03,0xB5,0x20,0x6B,0x2F,0x6A,0x2B,0xB1,0xC5,0x88,0x7E,0xC4,0xBB,0x1E,0xB0, +0xC1,0xD8,0x45,0x27,0x6F,0xAA,0x37,0x58,0xF7,0x87,0x26,0xD7,0xD8,0x2D,0xF6,0xA9, +0x17,0xB7,0x1F,0x72,0x36,0x4E,0xA6,0x17,0x3F,0x65,0x98,0x92,0xDB,0x2A,0x6E,0x5D, +0xA2,0xFE,0x88,0xE0,0x0B,0xDE,0x7F,0xE5,0x8D,0x15,0xE1,0xEB,0xCB,0x3A,0xD5,0xE2, +0x12,0xA2,0x13,0x2D,0xD8,0x8E,0xAF,0x5F,0x12,0x3D,0xA0,0x08,0x05,0x08,0xB6,0x5C, +0xA5,0x65,0x38,0x04,0x45,0x99,0x1E,0xA3,0x60,0x60,0x74,0xC5,0x41,0xA5,0x72,0x62, +0x1B,0x62,0xC5,0x1F,0x6F,0x5F,0x1A,0x42,0xBE,0x02,0x51,0x65,0xA8,0xAE,0x23,0x18, +0x6A,0xFC,0x78,0x03,0xA9,0x4D,0x7F,0x80,0xC3,0xFA,0xAB,0x5A,0xFC,0xA1,0x40,0xA4, +0xCA,0x19,0x16,0xFE,0xB2,0xC8,0xEF,0x5E,0x73,0x0D,0xEE,0x77,0xBD,0x9A,0xF6,0x79, +0x98,0xBC,0xB1,0x07,0x67,0xA2,0x15,0x0D,0xDD,0xA0,0x58,0xC6,0x44,0x7B,0x0A,0x3E, +0x62,0x28,0x5F,0xBA,0x41,0x07,0x53,0x58,0xCF,0x11,0x7E,0x38,0x74,0xC5,0xF8,0xFF, +0xB5,0x69,0x90,0x8F,0x84,0x74,0xEA,0x97,0x1B,0xAF,0x02,0x01,0x03,0xA3,0x81,0xC0, +0x30,0x81,0xBD,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xD2,0xC4, +0xB0,0xD2,0x91,0xD4,0x4C,0x11,0x71,0xB3,0x61,0xCB,0x3D,0xA1,0xFE,0xDD,0xA8,0x6A, +0xD4,0xE3,0x30,0x81,0x8D,0x06,0x03,0x55,0x1D,0x23,0x04,0x81,0x85,0x30,0x81,0x82, +0x80,0x14,0xD2,0xC4,0xB0,0xD2,0x91,0xD4,0x4C,0x11,0x71,0xB3,0x61,0xCB,0x3D,0xA1, +0xFE,0xDD,0xA8,0x6A,0xD4,0xE3,0xA1,0x67,0xA4,0x65,0x30,0x63,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55, +0x04,0x0A,0x13,0x18,0x54,0x68,0x65,0x20,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79, +0x20,0x47,0x72,0x6F,0x75,0x70,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F, +0x06,0x03,0x55,0x04,0x0B,0x13,0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x82, +0x01,0x00,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03, +0x82,0x01,0x01,0x00,0x32,0x4B,0xF3,0xB2,0xCA,0x3E,0x91,0xFC,0x12,0xC6,0xA1,0x07, +0x8C,0x8E,0x77,0xA0,0x33,0x06,0x14,0x5C,0x90,0x1E,0x18,0xF7,0x08,0xA6,0x3D,0x0A, +0x19,0xF9,0x87,0x80,0x11,0x6E,0x69,0xE4,0x96,0x17,0x30,0xFF,0x34,0x91,0x63,0x72, +0x38,0xEE,0xCC,0x1C,0x01,0xA3,0x1D,0x94,0x28,0xA4,0x31,0xF6,0x7A,0xC4,0x54,0xD7, +0xF6,0xE5,0x31,0x58,0x03,0xA2,0xCC,0xCE,0x62,0xDB,0x94,0x45,0x73,0xB5,0xBF,0x45, +0xC9,0x24,0xB5,0xD5,0x82,0x02,0xAD,0x23,0x79,0x69,0x8D,0xB8,0xB6,0x4D,0xCE,0xCF, +0x4C,0xCA,0x33,0x23,0xE8,0x1C,0x88,0xAA,0x9D,0x8B,0x41,0x6E,0x16,0xC9,0x20,0xE5, +0x89,0x9E,0xCD,0x3B,0xDA,0x70,0xF7,0x7E,0x99,0x26,0x20,0x14,0x54,0x25,0xAB,0x6E, +0x73,0x85,0xE6,0x9B,0x21,0x9D,0x0A,0x6C,0x82,0x0E,0xA8,0xF8,0xC2,0x0C,0xFA,0x10, +0x1E,0x6C,0x96,0xEF,0x87,0x0D,0xC4,0x0F,0x61,0x8B,0xAD,0xEE,0x83,0x2B,0x95,0xF8, +0x8E,0x92,0x84,0x72,0x39,0xEB,0x20,0xEA,0x83,0xED,0x83,0xCD,0x97,0x6E,0x08,0xBC, +0xEB,0x4E,0x26,0xB6,0x73,0x2B,0xE4,0xD3,0xF6,0x4C,0xFE,0x26,0x71,0xE2,0x61,0x11, +0x74,0x4A,0xFF,0x57,0x1A,0x87,0x0F,0x75,0x48,0x2E,0xCF,0x51,0x69,0x17,0xA0,0x02, +0x12,0x61,0x95,0xD5,0xD1,0x40,0xB2,0x10,0x4C,0xEE,0xC4,0xAC,0x10,0x43,0xA6,0xA5, +0x9E,0x0A,0xD5,0x95,0x62,0x9A,0x0D,0xCF,0x88,0x82,0xC5,0x32,0x0C,0xE4,0x2B,0x9F, +0x45,0xE6,0x0D,0x9F,0x28,0x9C,0xB1,0xB9,0x2A,0x5A,0x57,0xAD,0x37,0x0F,0xAF,0x1D, +0x7F,0xDB,0xBD,0x9F, +}; + + +/* subject:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2 */ +/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2 */ + + +const unsigned char Go_Daddy_Root_Certificate_Authority___G2_certificate[969]={ +0x30,0x82,0x03,0xC5,0x30,0x82,0x02,0xAD,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x81,0x83,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E, +0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74, +0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13, +0x11,0x47,0x6F,0x44,0x61,0x64,0x64,0x79,0x2E,0x63,0x6F,0x6D,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28,0x47,0x6F,0x20, +0x44,0x61,0x64,0x64,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79, +0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x39,0x30,0x31,0x30, +0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32,0x33,0x31,0x32,0x33, +0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x83,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x06,0x13,0x02,0x55,0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07, +0x41,0x72,0x69,0x7A,0x6F,0x6E,0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07, +0x13,0x0A,0x53,0x63,0x6F,0x74,0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x1A,0x30,0x18, +0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x47,0x6F,0x44,0x61,0x64,0x64,0x79,0x2E,0x63, +0x6F,0x6D,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04, +0x03,0x13,0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x52,0x6F,0x6F,0x74, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82, +0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xBF,0x71,0x62,0x08, +0xF1,0xFA,0x59,0x34,0xF7,0x1B,0xC9,0x18,0xA3,0xF7,0x80,0x49,0x58,0xE9,0x22,0x83, +0x13,0xA6,0xC5,0x20,0x43,0x01,0x3B,0x84,0xF1,0xE6,0x85,0x49,0x9F,0x27,0xEA,0xF6, +0x84,0x1B,0x4E,0xA0,0xB4,0xDB,0x70,0x98,0xC7,0x32,0x01,0xB1,0x05,0x3E,0x07,0x4E, +0xEE,0xF4,0xFA,0x4F,0x2F,0x59,0x30,0x22,0xE7,0xAB,0x19,0x56,0x6B,0xE2,0x80,0x07, +0xFC,0xF3,0x16,0x75,0x80,0x39,0x51,0x7B,0xE5,0xF9,0x35,0xB6,0x74,0x4E,0xA9,0x8D, +0x82,0x13,0xE4,0xB6,0x3F,0xA9,0x03,0x83,0xFA,0xA2,0xBE,0x8A,0x15,0x6A,0x7F,0xDE, +0x0B,0xC3,0xB6,0x19,0x14,0x05,0xCA,0xEA,0xC3,0xA8,0x04,0x94,0x3B,0x46,0x7C,0x32, +0x0D,0xF3,0x00,0x66,0x22,0xC8,0x8D,0x69,0x6D,0x36,0x8C,0x11,0x18,0xB7,0xD3,0xB2, +0x1C,0x60,0xB4,0x38,0xFA,0x02,0x8C,0xCE,0xD3,0xDD,0x46,0x07,0xDE,0x0A,0x3E,0xEB, +0x5D,0x7C,0xC8,0x7C,0xFB,0xB0,0x2B,0x53,0xA4,0x92,0x62,0x69,0x51,0x25,0x05,0x61, +0x1A,0x44,0x81,0x8C,0x2C,0xA9,0x43,0x96,0x23,0xDF,0xAC,0x3A,0x81,0x9A,0x0E,0x29, +0xC5,0x1C,0xA9,0xE9,0x5D,0x1E,0xB6,0x9E,0x9E,0x30,0x0A,0x39,0xCE,0xF1,0x88,0x80, +0xFB,0x4B,0x5D,0xCC,0x32,0xEC,0x85,0x62,0x43,0x25,0x34,0x02,0x56,0x27,0x01,0x91, +0xB4,0x3B,0x70,0x2A,0x3F,0x6E,0xB1,0xE8,0x9C,0x88,0x01,0x7D,0x9F,0xD4,0xF9,0xDB, +0x53,0x6D,0x60,0x9D,0xBF,0x2C,0xE7,0x58,0xAB,0xB8,0x5F,0x46,0xFC,0xCE,0xC4,0x1B, +0x03,0x3C,0x09,0xEB,0x49,0x31,0x5C,0x69,0x46,0xB3,0xE0,0x47,0x02,0x03,0x01,0x00, +0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x3A,0x9A,0x85,0x07,0x10,0x67,0x28,0xB6,0xEF,0xF6,0xBD,0x05,0x41,0x6E,0x20, +0xC1,0x94,0xDA,0x0F,0xDE,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, +0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x99,0xDB,0x5D,0x79,0xD5,0xF9,0x97, +0x59,0x67,0x03,0x61,0xF1,0x7E,0x3B,0x06,0x31,0x75,0x2D,0xA1,0x20,0x8E,0x4F,0x65, +0x87,0xB4,0xF7,0xA6,0x9C,0xBC,0xD8,0xE9,0x2F,0xD0,0xDB,0x5A,0xEE,0xCF,0x74,0x8C, +0x73,0xB4,0x38,0x42,0xDA,0x05,0x7B,0xF8,0x02,0x75,0xB8,0xFD,0xA5,0xB1,0xD7,0xAE, +0xF6,0xD7,0xDE,0x13,0xCB,0x53,0x10,0x7E,0x8A,0x46,0xD1,0x97,0xFA,0xB7,0x2E,0x2B, +0x11,0xAB,0x90,0xB0,0x27,0x80,0xF9,0xE8,0x9F,0x5A,0xE9,0x37,0x9F,0xAB,0xE4,0xDF, +0x6C,0xB3,0x85,0x17,0x9D,0x3D,0xD9,0x24,0x4F,0x79,0x91,0x35,0xD6,0x5F,0x04,0xEB, +0x80,0x83,0xAB,0x9A,0x02,0x2D,0xB5,0x10,0xF4,0xD8,0x90,0xC7,0x04,0x73,0x40,0xED, +0x72,0x25,0xA0,0xA9,0x9F,0xEC,0x9E,0xAB,0x68,0x12,0x99,0x57,0xC6,0x8F,0x12,0x3A, +0x09,0xA4,0xBD,0x44,0xFD,0x06,0x15,0x37,0xC1,0x9B,0xE4,0x32,0xA3,0xED,0x38,0xE8, +0xD8,0x64,0xF3,0x2C,0x7E,0x14,0xFC,0x02,0xEA,0x9F,0xCD,0xFF,0x07,0x68,0x17,0xDB, +0x22,0x90,0x38,0x2D,0x7A,0x8D,0xD1,0x54,0xF1,0x69,0xE3,0x5F,0x33,0xCA,0x7A,0x3D, +0x7B,0x0A,0xE3,0xCA,0x7F,0x5F,0x39,0xE5,0xE2,0x75,0xBA,0xC5,0x76,0x18,0x33,0xCE, +0x2C,0xF0,0x2F,0x4C,0xAD,0xF7,0xB1,0xE7,0xCE,0x4F,0xA8,0xC4,0x9B,0x4A,0x54,0x06, +0xC5,0x7F,0x7D,0xD5,0x08,0x0F,0xE2,0x1C,0xFE,0x7E,0x17,0xB8,0xAC,0x5E,0xF6,0xD4, +0x16,0xB2,0x43,0x09,0x0C,0x4D,0xF6,0xA7,0x6B,0xB4,0x99,0x84,0x65,0xCA,0x7A,0x88, +0xE2,0xE2,0x44,0xBE,0x5C,0xF7,0xEA,0x1C,0xF5, +}; + + +/* subject:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root */ +/* issuer :/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root */ + + +const unsigned char GTE_CyberTrust_Global_Root_certificate[606]={ +0x30,0x82,0x02,0x5A,0x30,0x82,0x01,0xC3,0x02,0x02,0x01,0xA5,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,0x75,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x18,0x30,0x16,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0F,0x47,0x54,0x45,0x20,0x43,0x6F,0x72,0x70,0x6F,0x72,0x61, +0x74,0x69,0x6F,0x6E,0x31,0x27,0x30,0x25,0x06,0x03,0x55,0x04,0x0B,0x13,0x1E,0x47, +0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x53,0x6F, +0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x23,0x30, +0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x47,0x54,0x45,0x20,0x43,0x79,0x62,0x65, +0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F, +0x6F,0x74,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x38,0x31,0x33,0x30,0x30,0x32,0x39, +0x30,0x30,0x5A,0x17,0x0D,0x31,0x38,0x30,0x38,0x31,0x33,0x32,0x33,0x35,0x39,0x30, +0x30,0x5A,0x30,0x75,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x18,0x30,0x16,0x06,0x03,0x55,0x04,0x0A,0x13,0x0F,0x47,0x54,0x45,0x20, +0x43,0x6F,0x72,0x70,0x6F,0x72,0x61,0x74,0x69,0x6F,0x6E,0x31,0x27,0x30,0x25,0x06, +0x03,0x55,0x04,0x0B,0x13,0x1E,0x47,0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54, +0x72,0x75,0x73,0x74,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x47, +0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C, +0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F,0x74,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30, +0x81,0x89,0x02,0x81,0x81,0x00,0x95,0x0F,0xA0,0xB6,0xF0,0x50,0x9C,0xE8,0x7A,0xC7, +0x88,0xCD,0xDD,0x17,0x0E,0x2E,0xB0,0x94,0xD0,0x1B,0x3D,0x0E,0xF6,0x94,0xC0,0x8A, +0x94,0xC7,0x06,0xC8,0x90,0x97,0xC8,0xB8,0x64,0x1A,0x7A,0x7E,0x6C,0x3C,0x53,0xE1, +0x37,0x28,0x73,0x60,0x7F,0xB2,0x97,0x53,0x07,0x9F,0x53,0xF9,0x6D,0x58,0x94,0xD2, +0xAF,0x8D,0x6D,0x88,0x67,0x80,0xE6,0xED,0xB2,0x95,0xCF,0x72,0x31,0xCA,0xA5,0x1C, +0x72,0xBA,0x5C,0x02,0xE7,0x64,0x42,0xE7,0xF9,0xA9,0x2C,0xD6,0x3A,0x0D,0xAC,0x8D, +0x42,0xAA,0x24,0x01,0x39,0xE6,0x9C,0x3F,0x01,0x85,0x57,0x0D,0x58,0x87,0x45,0xF8, +0xD3,0x85,0xAA,0x93,0x69,0x26,0x85,0x70,0x48,0x80,0x3F,0x12,0x15,0xC7,0x79,0xB4, +0x1F,0x05,0x2F,0x3B,0x62,0x99,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x6D,0xEB, +0x1B,0x09,0xE9,0x5E,0xD9,0x51,0xDB,0x67,0x22,0x61,0xA4,0x2A,0x3C,0x48,0x77,0xE3, +0xA0,0x7C,0xA6,0xDE,0x73,0xA2,0x14,0x03,0x85,0x3D,0xFB,0xAB,0x0E,0x30,0xC5,0x83, +0x16,0x33,0x81,0x13,0x08,0x9E,0x7B,0x34,0x4E,0xDF,0x40,0xC8,0x74,0xD7,0xB9,0x7D, +0xDC,0xF4,0x76,0x55,0x7D,0x9B,0x63,0x54,0x18,0xE9,0xF0,0xEA,0xF3,0x5C,0xB1,0xD9, +0x8B,0x42,0x1E,0xB9,0xC0,0x95,0x4E,0xBA,0xFA,0xD5,0xE2,0x7C,0xF5,0x68,0x61,0xBF, +0x8E,0xEC,0x05,0x97,0x5F,0x5B,0xB0,0xD7,0xA3,0x85,0x34,0xC4,0x24,0xA7,0x0D,0x0F, +0x95,0x93,0xEF,0xCB,0x94,0xD8,0x9E,0x1F,0x9D,0x5C,0x85,0x6D,0xC7,0xAA,0xAE,0x4F, +0x1F,0x22,0xB5,0xCD,0x95,0xAD,0xBA,0xA7,0xCC,0xF9,0xAB,0x0B,0x7A,0x7F, +}; + + +/* subject:/C=US/O=Network Solutions L.L.C./CN=Network Solutions Certificate Authority */ +/* issuer :/C=US/O=Network Solutions L.L.C./CN=Network Solutions Certificate Authority */ + + +const unsigned char Network_Solutions_Certificate_Authority_certificate[1002]={ +0x30,0x82,0x03,0xE6,0x30,0x82,0x02,0xCE,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x57, +0xCB,0x33,0x6F,0xC2,0x5C,0x16,0xE6,0x47,0x16,0x17,0xE3,0x90,0x31,0x68,0xE0,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x62, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21,0x30, +0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x20, +0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x4C,0x2E,0x4C,0x2E,0x43,0x2E, +0x31,0x30,0x30,0x2E,0x06,0x03,0x55,0x04,0x03,0x13,0x27,0x4E,0x65,0x74,0x77,0x6F, +0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x43,0x65,0x72, +0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x30,0x31,0x30,0x30,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35, +0x39,0x5A,0x30,0x62,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x4C,0x2E, +0x4C,0x2E,0x43,0x2E,0x31,0x30,0x30,0x2E,0x06,0x03,0x55,0x04,0x03,0x13,0x27,0x4E, +0x65,0x74,0x77,0x6F,0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xE4,0xBC,0x7E,0x92,0x30,0x6D,0xC6,0xD8,0x8E, +0x2B,0x0B,0xBC,0x46,0xCE,0xE0,0x27,0x96,0xDE,0xDE,0xF9,0xFA,0x12,0xD3,0x3C,0x33, +0x73,0xB3,0x04,0x2F,0xBC,0x71,0x8C,0xE5,0x9F,0xB6,0x22,0x60,0x3E,0x5F,0x5D,0xCE, +0x09,0xFF,0x82,0x0C,0x1B,0x9A,0x51,0x50,0x1A,0x26,0x89,0xDD,0xD5,0x61,0x5D,0x19, +0xDC,0x12,0x0F,0x2D,0x0A,0xA2,0x43,0x5D,0x17,0xD0,0x34,0x92,0x20,0xEA,0x73,0xCF, +0x38,0x2C,0x06,0x26,0x09,0x7A,0x72,0xF7,0xFA,0x50,0x32,0xF8,0xC2,0x93,0xD3,0x69, +0xA2,0x23,0xCE,0x41,0xB1,0xCC,0xE4,0xD5,0x1F,0x36,0xD1,0x8A,0x3A,0xF8,0x8C,0x63, +0xE2,0x14,0x59,0x69,0xED,0x0D,0xD3,0x7F,0x6B,0xE8,0xB8,0x03,0xE5,0x4F,0x6A,0xE5, +0x98,0x63,0x69,0x48,0x05,0xBE,0x2E,0xFF,0x33,0xB6,0xE9,0x97,0x59,0x69,0xF8,0x67, +0x19,0xAE,0x93,0x61,0x96,0x44,0x15,0xD3,0x72,0xB0,0x3F,0xBC,0x6A,0x7D,0xEC,0x48, +0x7F,0x8D,0xC3,0xAB,0xAA,0x71,0x2B,0x53,0x69,0x41,0x53,0x34,0xB5,0xB0,0xB9,0xC5, +0x06,0x0A,0xC4,0xB0,0x45,0xF5,0x41,0x5D,0x6E,0x89,0x45,0x7B,0x3D,0x3B,0x26,0x8C, +0x74,0xC2,0xE5,0xD2,0xD1,0x7D,0xB2,0x11,0xD4,0xFB,0x58,0x32,0x22,0x9A,0x80,0xC9, +0xDC,0xFD,0x0C,0xE9,0x7F,0x5E,0x03,0x97,0xCE,0x3B,0x00,0x14,0x87,0x27,0x70,0x38, +0xA9,0x8E,0x6E,0xB3,0x27,0x76,0x98,0x51,0xE0,0x05,0xE3,0x21,0xAB,0x1A,0xD5,0x85, +0x22,0x3C,0x29,0xB5,0x9A,0x16,0xC5,0x80,0xA8,0xF4,0xBB,0x6B,0x30,0x8F,0x2F,0x46, +0x02,0xA2,0xB1,0x0C,0x22,0xE0,0xD3,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x97,0x30, +0x81,0x94,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x21,0x30,0xC9, +0xFB,0x00,0xD7,0x4E,0x98,0xDA,0x87,0xAA,0x2A,0xD0,0xA7,0x2E,0xB1,0x40,0x31,0xA7, +0x4C,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x52,0x06,0x03,0x55,0x1D,0x1F,0x04,0x4B,0x30,0x49,0x30,0x47,0xA0, +0x45,0xA0,0x43,0x86,0x41,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E, +0x6E,0x65,0x74,0x73,0x6F,0x6C,0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x4E,0x65, +0x74,0x77,0x6F,0x72,0x6B,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x43,0x65, +0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x2E,0x63,0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xBB,0xAE,0x4B,0xE7,0xB7,0x57, +0xEB,0x7F,0xAA,0x2D,0xB7,0x73,0x47,0x85,0x6A,0xC1,0xE4,0xA5,0x1D,0xE4,0xE7,0x3C, +0xE9,0xF4,0x59,0x65,0x77,0xB5,0x7A,0x5B,0x5A,0x8D,0x25,0x36,0xE0,0x7A,0x97,0x2E, +0x38,0xC0,0x57,0x60,0x83,0x98,0x06,0x83,0x9F,0xB9,0x76,0x7A,0x6E,0x50,0xE0,0xBA, +0x88,0x2C,0xFC,0x45,0xCC,0x18,0xB0,0x99,0x95,0x51,0x0E,0xEC,0x1D,0xB8,0x88,0xFF, +0x87,0x50,0x1C,0x82,0xC2,0xE3,0xE0,0x32,0x80,0xBF,0xA0,0x0B,0x47,0xC8,0xC3,0x31, +0xEF,0x99,0x67,0x32,0x80,0x4F,0x17,0x21,0x79,0x0C,0x69,0x5C,0xDE,0x5E,0x34,0xAE, +0x02,0xB5,0x26,0xEA,0x50,0xDF,0x7F,0x18,0x65,0x2C,0xC9,0xF2,0x63,0xE1,0xA9,0x07, +0xFE,0x7C,0x71,0x1F,0x6B,0x33,0x24,0x6A,0x1E,0x05,0xF7,0x05,0x68,0xC0,0x6A,0x12, +0xCB,0x2E,0x5E,0x61,0xCB,0xAE,0x28,0xD3,0x7E,0xC2,0xB4,0x66,0x91,0x26,0x5F,0x3C, +0x2E,0x24,0x5F,0xCB,0x58,0x0F,0xEB,0x28,0xEC,0xAF,0x11,0x96,0xF3,0xDC,0x7B,0x6F, +0xC0,0xA7,0x88,0xF2,0x53,0x77,0xB3,0x60,0x5E,0xAE,0xAE,0x28,0xDA,0x35,0x2C,0x6F, +0x34,0x45,0xD3,0x26,0xE1,0xDE,0xEC,0x5B,0x4F,0x27,0x6B,0x16,0x7C,0xBD,0x44,0x04, +0x18,0x82,0xB3,0x89,0x79,0x17,0x10,0x71,0x3D,0x7A,0xA2,0x16,0x4E,0xF5,0x01,0xCD, +0xA4,0x6C,0x65,0x68,0xA1,0x49,0x76,0x5C,0x43,0xC9,0xD8,0xBC,0x36,0x67,0x6C,0xA5, +0x94,0xB5,0xD4,0xCC,0xB9,0xBD,0x6A,0x35,0x56,0x21,0xDE,0xD8,0xC3,0xEB,0xFB,0xCB, +0xA4,0x60,0x4C,0xB0,0x55,0xA0,0xA0,0x7B,0x57,0xB2, +}; + + +/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 3 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ +/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 3 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ + + +const unsigned char RSA_Root_Certificate_1_certificate[747]={ +0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30, +0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74, +0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61, +0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33, +0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72, +0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69, +0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36, +0x32,0x36,0x30,0x30,0x32,0x32,0x33,0x33,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32, +0x36,0x30,0x30,0x32,0x32,0x33,0x33,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06, +0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69, +0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03, +0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C, +0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E, +0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02, +0x81,0x81,0x00,0xE3,0x98,0x51,0x96,0x1C,0xE8,0xD5,0xB1,0x06,0x81,0x6A,0x57,0xC3, +0x72,0x75,0x93,0xAB,0xCF,0x9E,0xA6,0xFC,0xF3,0x16,0x52,0xD6,0x2D,0x4D,0x9F,0x35, +0x44,0xA8,0x2E,0x04,0x4D,0x07,0x49,0x8A,0x38,0x29,0xF5,0x77,0x37,0xE7,0xB7,0xAB, +0x5D,0xDF,0x36,0x71,0x14,0x99,0x8F,0xDC,0xC2,0x92,0xF1,0xE7,0x60,0x92,0x97,0xEC, +0xD8,0x48,0xDC,0xBF,0xC1,0x02,0x20,0xC6,0x24,0xA4,0x28,0x4C,0x30,0x5A,0x76,0x6D, +0xB1,0x5C,0xF3,0xDD,0xDE,0x9E,0x10,0x71,0xA1,0x88,0xC7,0x5B,0x9B,0x41,0x6D,0xCA, +0xB0,0xB8,0x8E,0x15,0xEE,0xAD,0x33,0x2B,0xCF,0x47,0x04,0x5C,0x75,0x71,0x0A,0x98, +0x24,0x98,0x29,0xA7,0x49,0x59,0xA5,0xDD,0xF8,0xB7,0x43,0x62,0x61,0xF3,0xD3,0xE2, +0xD0,0x55,0x3F,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x56,0xBB,0x02,0x58,0x84, +0x67,0x08,0x2C,0xDF,0x1F,0xDB,0x7B,0x49,0x33,0xF5,0xD3,0x67,0x9D,0xF4,0xB4,0x0A, +0x10,0xB3,0xC9,0xC5,0x2C,0xE2,0x92,0x6A,0x71,0x78,0x27,0xF2,0x70,0x83,0x42,0xD3, +0x3E,0xCF,0xA9,0x54,0xF4,0xF1,0xD8,0x92,0x16,0x8C,0xD1,0x04,0xCB,0x4B,0xAB,0xC9, +0x9F,0x45,0xAE,0x3C,0x8A,0xA9,0xB0,0x71,0x33,0x5D,0xC8,0xC5,0x57,0xDF,0xAF,0xA8, +0x35,0xB3,0x7F,0x89,0x87,0xE9,0xE8,0x25,0x92,0xB8,0x7F,0x85,0x7A,0xAE,0xD6,0xBC, +0x1E,0x37,0x58,0x2A,0x67,0xC9,0x91,0xCF,0x2A,0x81,0x3E,0xED,0xC6,0x39,0xDF,0xC0, +0x3E,0x19,0x9C,0x19,0xCC,0x13,0x4D,0x82,0x41,0xB5,0x8C,0xDE,0xE0,0x3D,0x60,0x08, +0x20,0x0F,0x45,0x7E,0x6B,0xA2,0x7F,0xA3,0x8C,0x15,0xEE, +}; + + +/* subject:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority */ +/* issuer :/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority */ + + +const unsigned char Starfield_Class_2_CA_certificate[1043]={ +0x30,0x82,0x04,0x0F,0x30,0x82,0x02,0xF7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30, +0x68,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x25, +0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65, +0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x0B,0x13,0x29, +0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x43,0x6C,0x61,0x73,0x73,0x20, +0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20, +0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30, +0x36,0x32,0x39,0x31,0x37,0x33,0x39,0x31,0x36,0x5A,0x17,0x0D,0x33,0x34,0x30,0x36, +0x32,0x39,0x31,0x37,0x33,0x39,0x31,0x36,0x5A,0x30,0x68,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04, +0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63, +0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x0B,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69, +0x65,0x6C,0x64,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x30,0x82,0x01,0x20,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0D,0x00,0x30,0x82,0x01,0x08,0x02, +0x82,0x01,0x01,0x00,0xB7,0x32,0xC8,0xFE,0xE9,0x71,0xA6,0x04,0x85,0xAD,0x0C,0x11, +0x64,0xDF,0xCE,0x4D,0xEF,0xC8,0x03,0x18,0x87,0x3F,0xA1,0xAB,0xFB,0x3C,0xA6,0x9F, +0xF0,0xC3,0xA1,0xDA,0xD4,0xD8,0x6E,0x2B,0x53,0x90,0xFB,0x24,0xA4,0x3E,0x84,0xF0, +0x9E,0xE8,0x5F,0xEC,0xE5,0x27,0x44,0xF5,0x28,0xA6,0x3F,0x7B,0xDE,0xE0,0x2A,0xF0, +0xC8,0xAF,0x53,0x2F,0x9E,0xCA,0x05,0x01,0x93,0x1E,0x8F,0x66,0x1C,0x39,0xA7,0x4D, +0xFA,0x5A,0xB6,0x73,0x04,0x25,0x66,0xEB,0x77,0x7F,0xE7,0x59,0xC6,0x4A,0x99,0x25, +0x14,0x54,0xEB,0x26,0xC7,0xF3,0x7F,0x19,0xD5,0x30,0x70,0x8F,0xAF,0xB0,0x46,0x2A, +0xFF,0xAD,0xEB,0x29,0xED,0xD7,0x9F,0xAA,0x04,0x87,0xA3,0xD4,0xF9,0x89,0xA5,0x34, +0x5F,0xDB,0x43,0x91,0x82,0x36,0xD9,0x66,0x3C,0xB1,0xB8,0xB9,0x82,0xFD,0x9C,0x3A, +0x3E,0x10,0xC8,0x3B,0xEF,0x06,0x65,0x66,0x7A,0x9B,0x19,0x18,0x3D,0xFF,0x71,0x51, +0x3C,0x30,0x2E,0x5F,0xBE,0x3D,0x77,0x73,0xB2,0x5D,0x06,0x6C,0xC3,0x23,0x56,0x9A, +0x2B,0x85,0x26,0x92,0x1C,0xA7,0x02,0xB3,0xE4,0x3F,0x0D,0xAF,0x08,0x79,0x82,0xB8, +0x36,0x3D,0xEA,0x9C,0xD3,0x35,0xB3,0xBC,0x69,0xCA,0xF5,0xCC,0x9D,0xE8,0xFD,0x64, +0x8D,0x17,0x80,0x33,0x6E,0x5E,0x4A,0x5D,0x99,0xC9,0x1E,0x87,0xB4,0x9D,0x1A,0xC0, +0xD5,0x6E,0x13,0x35,0x23,0x5E,0xDF,0x9B,0x5F,0x3D,0xEF,0xD6,0xF7,0x76,0xC2,0xEA, +0x3E,0xBB,0x78,0x0D,0x1C,0x42,0x67,0x6B,0x04,0xD8,0xF8,0xD6,0xDA,0x6F,0x8B,0xF2, +0x44,0xA0,0x01,0xAB,0x02,0x01,0x03,0xA3,0x81,0xC5,0x30,0x81,0xC2,0x30,0x1D,0x06, +0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBF,0x5F,0xB7,0xD1,0xCE,0xDD,0x1F,0x86, +0xF4,0x5B,0x55,0xAC,0xDC,0xD7,0x10,0xC2,0x0E,0xA9,0x88,0xE7,0x30,0x81,0x92,0x06, +0x03,0x55,0x1D,0x23,0x04,0x81,0x8A,0x30,0x81,0x87,0x80,0x14,0xBF,0x5F,0xB7,0xD1, +0xCE,0xDD,0x1F,0x86,0xF4,0x5B,0x55,0xAC,0xDC,0xD7,0x10,0xC2,0x0E,0xA9,0x88,0xE7, +0xA1,0x6C,0xA4,0x6A,0x30,0x68,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74, +0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F, +0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30,0x30,0x06,0x03, +0x55,0x04,0x0B,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x43, +0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x82,0x01, +0x00,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x05,0x9D,0x3F,0x88,0x9D,0xD1,0xC9,0x1A,0x55,0xA1,0xAC,0x69,0xF3, +0xF3,0x59,0xDA,0x9B,0x01,0x87,0x1A,0x4F,0x57,0xA9,0xA1,0x79,0x09,0x2A,0xDB,0xF7, +0x2F,0xB2,0x1E,0xCC,0xC7,0x5E,0x6A,0xD8,0x83,0x87,0xA1,0x97,0xEF,0x49,0x35,0x3E, +0x77,0x06,0x41,0x58,0x62,0xBF,0x8E,0x58,0xB8,0x0A,0x67,0x3F,0xEC,0xB3,0xDD,0x21, +0x66,0x1F,0xC9,0x54,0xFA,0x72,0xCC,0x3D,0x4C,0x40,0xD8,0x81,0xAF,0x77,0x9E,0x83, +0x7A,0xBB,0xA2,0xC7,0xF5,0x34,0x17,0x8E,0xD9,0x11,0x40,0xF4,0xFC,0x2C,0x2A,0x4D, +0x15,0x7F,0xA7,0x62,0x5D,0x2E,0x25,0xD3,0x00,0x0B,0x20,0x1A,0x1D,0x68,0xF9,0x17, +0xB8,0xF4,0xBD,0x8B,0xED,0x28,0x59,0xDD,0x4D,0x16,0x8B,0x17,0x83,0xC8,0xB2,0x65, +0xC7,0x2D,0x7A,0xA5,0xAA,0xBC,0x53,0x86,0x6D,0xDD,0x57,0xA4,0xCA,0xF8,0x20,0x41, +0x0B,0x68,0xF0,0xF4,0xFB,0x74,0xBE,0x56,0x5D,0x7A,0x79,0xF5,0xF9,0x1D,0x85,0xE3, +0x2D,0x95,0xBE,0xF5,0x71,0x90,0x43,0xCC,0x8D,0x1F,0x9A,0x00,0x0A,0x87,0x29,0xE9, +0x55,0x22,0x58,0x00,0x23,0xEA,0xE3,0x12,0x43,0x29,0x5B,0x47,0x08,0xDD,0x8C,0x41, +0x6A,0x65,0x06,0xA8,0xE5,0x21,0xAA,0x41,0xB4,0x95,0x21,0x95,0xB9,0x7D,0xD1,0x34, +0xAB,0x13,0xD6,0xAD,0xBC,0xDC,0xE2,0x3D,0x39,0xCD,0xBD,0x3E,0x75,0x70,0xA1,0x18, +0x59,0x03,0xC9,0x22,0xB4,0x8F,0x9C,0xD5,0x5E,0x2A,0xD7,0xA5,0xB6,0xD4,0x0A,0x6D, +0xF8,0xB7,0x40,0x11,0x46,0x9A,0x1F,0x79,0x0E,0x62,0xBF,0x0F,0x97,0xEC,0xE0,0x2F, +0x1F,0x17,0x94, +}; + + +/* subject:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 */ +/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 */ + + +const unsigned char Starfield_Root_Certificate_Authority___G2_certificate[993]={ +0x30,0x82,0x03,0xDD,0x30,0x82,0x02,0xC5,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x81,0x8F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E, +0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74, +0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13, +0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E, +0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30, +0x30,0x06,0x03,0x55,0x04,0x03,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C, +0x64,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47, +0x32,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x39,0x30,0x31,0x30,0x30,0x30,0x30,0x30, +0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39, +0x5A,0x30,0x81,0x8F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55, +0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A, +0x6F,0x6E,0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63, +0x6F,0x74,0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04, +0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63, +0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31, +0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x03,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69, +0x65,0x6C,0x64,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69, +0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D, +0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02, +0x82,0x01,0x01,0x00,0xBD,0xED,0xC1,0x03,0xFC,0xF6,0x8F,0xFC,0x02,0xB1,0x6F,0x5B, +0x9F,0x48,0xD9,0x9D,0x79,0xE2,0xA2,0xB7,0x03,0x61,0x56,0x18,0xC3,0x47,0xB6,0xD7, +0xCA,0x3D,0x35,0x2E,0x89,0x43,0xF7,0xA1,0x69,0x9B,0xDE,0x8A,0x1A,0xFD,0x13,0x20, +0x9C,0xB4,0x49,0x77,0x32,0x29,0x56,0xFD,0xB9,0xEC,0x8C,0xDD,0x22,0xFA,0x72,0xDC, +0x27,0x61,0x97,0xEE,0xF6,0x5A,0x84,0xEC,0x6E,0x19,0xB9,0x89,0x2C,0xDC,0x84,0x5B, +0xD5,0x74,0xFB,0x6B,0x5F,0xC5,0x89,0xA5,0x10,0x52,0x89,0x46,0x55,0xF4,0xB8,0x75, +0x1C,0xE6,0x7F,0xE4,0x54,0xAE,0x4B,0xF8,0x55,0x72,0x57,0x02,0x19,0xF8,0x17,0x71, +0x59,0xEB,0x1E,0x28,0x07,0x74,0xC5,0x9D,0x48,0xBE,0x6C,0xB4,0xF4,0xA4,0xB0,0xF3, +0x64,0x37,0x79,0x92,0xC0,0xEC,0x46,0x5E,0x7F,0xE1,0x6D,0x53,0x4C,0x62,0xAF,0xCD, +0x1F,0x0B,0x63,0xBB,0x3A,0x9D,0xFB,0xFC,0x79,0x00,0x98,0x61,0x74,0xCF,0x26,0x82, +0x40,0x63,0xF3,0xB2,0x72,0x6A,0x19,0x0D,0x99,0xCA,0xD4,0x0E,0x75,0xCC,0x37,0xFB, +0x8B,0x89,0xC1,0x59,0xF1,0x62,0x7F,0x5F,0xB3,0x5F,0x65,0x30,0xF8,0xA7,0xB7,0x4D, +0x76,0x5A,0x1E,0x76,0x5E,0x34,0xC0,0xE8,0x96,0x56,0x99,0x8A,0xB3,0xF0,0x7F,0xA4, +0xCD,0xBD,0xDC,0x32,0x31,0x7C,0x91,0xCF,0xE0,0x5F,0x11,0xF8,0x6B,0xAA,0x49,0x5C, +0xD1,0x99,0x94,0xD1,0xA2,0xE3,0x63,0x5B,0x09,0x76,0xB5,0x56,0x62,0xE1,0x4B,0x74, +0x1D,0x96,0xD4,0x26,0xD4,0x08,0x04,0x59,0xD0,0x98,0x0E,0x0E,0xE6,0xDE,0xFC,0xC3, +0xEC,0x1F,0x90,0xF1,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06, +0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7C,0x0C,0x32,0x1F,0xA7,0xD9,0x30, +0x7F,0xC4,0x7D,0x68,0xA3,0x62,0xA8,0xA1,0xCE,0xAB,0x07,0x5B,0x27,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01, +0x00,0x11,0x59,0xFA,0x25,0x4F,0x03,0x6F,0x94,0x99,0x3B,0x9A,0x1F,0x82,0x85,0x39, +0xD4,0x76,0x05,0x94,0x5E,0xE1,0x28,0x93,0x6D,0x62,0x5D,0x09,0xC2,0xA0,0xA8,0xD4, +0xB0,0x75,0x38,0xF1,0x34,0x6A,0x9D,0xE4,0x9F,0x8A,0x86,0x26,0x51,0xE6,0x2C,0xD1, +0xC6,0x2D,0x6E,0x95,0x20,0x4A,0x92,0x01,0xEC,0xB8,0x8A,0x67,0x7B,0x31,0xE2,0x67, +0x2E,0x8C,0x95,0x03,0x26,0x2E,0x43,0x9D,0x4A,0x31,0xF6,0x0E,0xB5,0x0C,0xBB,0xB7, +0xE2,0x37,0x7F,0x22,0xBA,0x00,0xA3,0x0E,0x7B,0x52,0xFB,0x6B,0xBB,0x3B,0xC4,0xD3, +0x79,0x51,0x4E,0xCD,0x90,0xF4,0x67,0x07,0x19,0xC8,0x3C,0x46,0x7A,0x0D,0x01,0x7D, +0xC5,0x58,0xE7,0x6D,0xE6,0x85,0x30,0x17,0x9A,0x24,0xC4,0x10,0xE0,0x04,0xF7,0xE0, +0xF2,0x7F,0xD4,0xAA,0x0A,0xFF,0x42,0x1D,0x37,0xED,0x94,0xE5,0x64,0x59,0x12,0x20, +0x77,0x38,0xD3,0x32,0x3E,0x38,0x81,0x75,0x96,0x73,0xFA,0x68,0x8F,0xB1,0xCB,0xCE, +0x1F,0xC5,0xEC,0xFA,0x9C,0x7E,0xCF,0x7E,0xB1,0xF1,0x07,0x2D,0xB6,0xFC,0xBF,0xCA, +0xA4,0xBF,0xD0,0x97,0x05,0x4A,0xBC,0xEA,0x18,0x28,0x02,0x90,0xBD,0x54,0x78,0x09, +0x21,0x71,0xD3,0xD1,0x7D,0x1D,0xD9,0x16,0xB0,0xA9,0x61,0x3D,0xD0,0x0A,0x00,0x22, +0xFC,0xC7,0x7B,0xCB,0x09,0x64,0x45,0x0B,0x3B,0x40,0x81,0xF7,0x7D,0x7C,0x32,0xF5, +0x98,0xCA,0x58,0x8E,0x7D,0x2A,0xEE,0x90,0x59,0x73,0x64,0xF9,0x36,0x74,0x5E,0x25, +0xA1,0xF5,0x66,0x05,0x2E,0x7F,0x39,0x15,0xA9,0x2A,0xFB,0x50,0x8B,0x8E,0x85,0x69, +0xF4, +}; + + +/* subject:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 */ +/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 */ + + +const unsigned char Starfield_Services_Root_Certificate_Authority___G2_certificate[1011]={ +0x30,0x82,0x03,0xEF,0x30,0x82,0x02,0xD7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x81,0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E, +0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74, +0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13, +0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E, +0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3B,0x30, +0x39,0x06,0x03,0x55,0x04,0x03,0x13,0x32,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C, +0x64,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x52,0x6F,0x6F,0x74,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68, +0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x39, +0x30,0x39,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31, +0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x10,0x30,0x0E,0x06,0x03, +0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E,0x61,0x31,0x13,0x30,0x11, +0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74,0x74,0x73,0x64,0x61,0x6C, +0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74,0x61,0x72, +0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69, +0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3B,0x30,0x39,0x06,0x03,0x55,0x04, +0x03,0x13,0x32,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x53,0x65,0x72, +0x76,0x69,0x63,0x65,0x73,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79, +0x20,0x2D,0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01, +0x0A,0x02,0x82,0x01,0x01,0x00,0xD5,0x0C,0x3A,0xC4,0x2A,0xF9,0x4E,0xE2,0xF5,0xBE, +0x19,0x97,0x5F,0x8E,0x88,0x53,0xB1,0x1F,0x3F,0xCB,0xCF,0x9F,0x20,0x13,0x6D,0x29, +0x3A,0xC8,0x0F,0x7D,0x3C,0xF7,0x6B,0x76,0x38,0x63,0xD9,0x36,0x60,0xA8,0x9B,0x5E, +0x5C,0x00,0x80,0xB2,0x2F,0x59,0x7F,0xF6,0x87,0xF9,0x25,0x43,0x86,0xE7,0x69,0x1B, +0x52,0x9A,0x90,0xE1,0x71,0xE3,0xD8,0x2D,0x0D,0x4E,0x6F,0xF6,0xC8,0x49,0xD9,0xB6, +0xF3,0x1A,0x56,0xAE,0x2B,0xB6,0x74,0x14,0xEB,0xCF,0xFB,0x26,0xE3,0x1A,0xBA,0x1D, +0x96,0x2E,0x6A,0x3B,0x58,0x94,0x89,0x47,0x56,0xFF,0x25,0xA0,0x93,0x70,0x53,0x83, +0xDA,0x84,0x74,0x14,0xC3,0x67,0x9E,0x04,0x68,0x3A,0xDF,0x8E,0x40,0x5A,0x1D,0x4A, +0x4E,0xCF,0x43,0x91,0x3B,0xE7,0x56,0xD6,0x00,0x70,0xCB,0x52,0xEE,0x7B,0x7D,0xAE, +0x3A,0xE7,0xBC,0x31,0xF9,0x45,0xF6,0xC2,0x60,0xCF,0x13,0x59,0x02,0x2B,0x80,0xCC, +0x34,0x47,0xDF,0xB9,0xDE,0x90,0x65,0x6D,0x02,0xCF,0x2C,0x91,0xA6,0xA6,0xE7,0xDE, +0x85,0x18,0x49,0x7C,0x66,0x4E,0xA3,0x3A,0x6D,0xA9,0xB5,0xEE,0x34,0x2E,0xBA,0x0D, +0x03,0xB8,0x33,0xDF,0x47,0xEB,0xB1,0x6B,0x8D,0x25,0xD9,0x9B,0xCE,0x81,0xD1,0x45, +0x46,0x32,0x96,0x70,0x87,0xDE,0x02,0x0E,0x49,0x43,0x85,0xB6,0x6C,0x73,0xBB,0x64, +0xEA,0x61,0x41,0xAC,0xC9,0xD4,0x54,0xDF,0x87,0x2F,0xC7,0x22,0xB2,0x26,0xCC,0x9F, +0x59,0x54,0x68,0x9F,0xFC,0xBE,0x2A,0x2F,0xC4,0x55,0x1C,0x75,0x40,0x60,0x17,0x85, +0x02,0x55,0x39,0x8B,0x7F,0x05,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30, +0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF, +0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06, +0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9C,0x5F,0x00,0xDF,0xAA, +0x01,0xD7,0x30,0x2B,0x38,0x88,0xA2,0xB8,0x6D,0x4A,0x9C,0xF2,0x11,0x91,0x83,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82, +0x01,0x01,0x00,0x4B,0x36,0xA6,0x84,0x77,0x69,0xDD,0x3B,0x19,0x9F,0x67,0x23,0x08, +0x6F,0x0E,0x61,0xC9,0xFD,0x84,0xDC,0x5F,0xD8,0x36,0x81,0xCD,0xD8,0x1B,0x41,0x2D, +0x9F,0x60,0xDD,0xC7,0x1A,0x68,0xD9,0xD1,0x6E,0x86,0xE1,0x88,0x23,0xCF,0x13,0xDE, +0x43,0xCF,0xE2,0x34,0xB3,0x04,0x9D,0x1F,0x29,0xD5,0xBF,0xF8,0x5E,0xC8,0xD5,0xC1, +0xBD,0xEE,0x92,0x6F,0x32,0x74,0xF2,0x91,0x82,0x2F,0xBD,0x82,0x42,0x7A,0xAD,0x2A, +0xB7,0x20,0x7D,0x4D,0xBC,0x7A,0x55,0x12,0xC2,0x15,0xEA,0xBD,0xF7,0x6A,0x95,0x2E, +0x6C,0x74,0x9F,0xCF,0x1C,0xB4,0xF2,0xC5,0x01,0xA3,0x85,0xD0,0x72,0x3E,0xAD,0x73, +0xAB,0x0B,0x9B,0x75,0x0C,0x6D,0x45,0xB7,0x8E,0x94,0xAC,0x96,0x37,0xB5,0xA0,0xD0, +0x8F,0x15,0x47,0x0E,0xE3,0xE8,0x83,0xDD,0x8F,0xFD,0xEF,0x41,0x01,0x77,0xCC,0x27, +0xA9,0x62,0x85,0x33,0xF2,0x37,0x08,0xEF,0x71,0xCF,0x77,0x06,0xDE,0xC8,0x19,0x1D, +0x88,0x40,0xCF,0x7D,0x46,0x1D,0xFF,0x1E,0xC7,0xE1,0xCE,0xFF,0x23,0xDB,0xC6,0xFA, +0x8D,0x55,0x4E,0xA9,0x02,0xE7,0x47,0x11,0x46,0x3E,0xF4,0xFD,0xBD,0x7B,0x29,0x26, +0xBB,0xA9,0x61,0x62,0x37,0x28,0xB6,0x2D,0x2A,0xF6,0x10,0x86,0x64,0xC9,0x70,0xA7, +0xD2,0xAD,0xB7,0x29,0x70,0x79,0xEA,0x3C,0xDA,0x63,0x25,0x9F,0xFD,0x68,0xB7,0x30, +0xEC,0x70,0xFB,0x75,0x8A,0xB7,0x6D,0x60,0x67,0xB2,0x1E,0xC8,0xB9,0xE9,0xD8,0xA8, +0x6F,0x02,0x8B,0x67,0x0D,0x4D,0x26,0x57,0x71,0xDA,0x20,0xFC,0xC1,0x4A,0x50,0x8D, +0xB1,0x28,0xBA, +}; + + +/* subject:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */ +/* issuer :/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */ + + +const unsigned char StartCom_Certification_Authority_certificate[1931]={ +0x30,0x82,0x07,0x87,0x30,0x82,0x05,0x6F,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x2D, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x7D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F, +0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x0B,0x13, +0x22,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x44,0x69,0x67,0x69,0x74,0x61,0x6C,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x69,0x67,0x6E, +0x69,0x6E,0x67,0x31,0x29,0x30,0x27,0x06,0x03,0x55,0x04,0x03,0x13,0x20,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E, +0x17,0x0D,0x30,0x36,0x30,0x39,0x31,0x37,0x31,0x39,0x34,0x36,0x33,0x37,0x5A,0x17, +0x0D,0x33,0x36,0x30,0x39,0x31,0x37,0x31,0x39,0x34,0x36,0x33,0x36,0x5A,0x30,0x7D, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16,0x30, +0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D, +0x20,0x4C,0x74,0x64,0x2E,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x0B,0x13,0x22, +0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x44,0x69,0x67,0x69,0x74,0x61,0x6C,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x69,0x67,0x6E,0x69, +0x6E,0x67,0x31,0x29,0x30,0x27,0x06,0x03,0x55,0x04,0x03,0x13,0x20,0x53,0x74,0x61, +0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74, +0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x02, +0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00, +0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xC1,0x88, +0xDB,0x09,0xBC,0x6C,0x46,0x7C,0x78,0x9F,0x95,0x7B,0xB5,0x33,0x90,0xF2,0x72,0x62, +0xD6,0xC1,0x36,0x20,0x22,0x24,0x5E,0xCE,0xE9,0x77,0xF2,0x43,0x0A,0xA2,0x06,0x64, +0xA4,0xCC,0x8E,0x36,0xF8,0x38,0xE6,0x23,0xF0,0x6E,0x6D,0xB1,0x3C,0xDD,0x72,0xA3, +0x85,0x1C,0xA1,0xD3,0x3D,0xB4,0x33,0x2B,0xD3,0x2F,0xAF,0xFE,0xEA,0xB0,0x41,0x59, +0x67,0xB6,0xC4,0x06,0x7D,0x0A,0x9E,0x74,0x85,0xD6,0x79,0x4C,0x80,0x37,0x7A,0xDF, +0x39,0x05,0x52,0x59,0xF7,0xF4,0x1B,0x46,0x43,0xA4,0xD2,0x85,0x85,0xD2,0xC3,0x71, +0xF3,0x75,0x62,0x34,0xBA,0x2C,0x8A,0x7F,0x1E,0x8F,0xEE,0xED,0x34,0xD0,0x11,0xC7, +0x96,0xCD,0x52,0x3D,0xBA,0x33,0xD6,0xDD,0x4D,0xDE,0x0B,0x3B,0x4A,0x4B,0x9F,0xC2, +0x26,0x2F,0xFA,0xB5,0x16,0x1C,0x72,0x35,0x77,0xCA,0x3C,0x5D,0xE6,0xCA,0xE1,0x26, +0x8B,0x1A,0x36,0x76,0x5C,0x01,0xDB,0x74,0x14,0x25,0xFE,0xED,0xB5,0xA0,0x88,0x0F, +0xDD,0x78,0xCA,0x2D,0x1F,0x07,0x97,0x30,0x01,0x2D,0x72,0x79,0xFA,0x46,0xD6,0x13, +0x2A,0xA8,0xB9,0xA6,0xAB,0x83,0x49,0x1D,0xE5,0xF2,0xEF,0xDD,0xE4,0x01,0x8E,0x18, +0x0A,0x8F,0x63,0x53,0x16,0x85,0x62,0xA9,0x0E,0x19,0x3A,0xCC,0xB5,0x66,0xA6,0xC2, +0x6B,0x74,0x07,0xE4,0x2B,0xE1,0x76,0x3E,0xB4,0x6D,0xD8,0xF6,0x44,0xE1,0x73,0x62, +0x1F,0x3B,0xC4,0xBE,0xA0,0x53,0x56,0x25,0x6C,0x51,0x09,0xF7,0xAA,0xAB,0xCA,0xBF, +0x76,0xFD,0x6D,0x9B,0xF3,0x9D,0xDB,0xBF,0x3D,0x66,0xBC,0x0C,0x56,0xAA,0xAF,0x98, +0x48,0x95,0x3A,0x4B,0xDF,0xA7,0x58,0x50,0xD9,0x38,0x75,0xA9,0x5B,0xEA,0x43,0x0C, +0x02,0xFF,0x99,0xEB,0xE8,0x6C,0x4D,0x70,0x5B,0x29,0x65,0x9C,0xDD,0xAA,0x5D,0xCC, +0xAF,0x01,0x31,0xEC,0x0C,0xEB,0xD2,0x8D,0xE8,0xEA,0x9C,0x7B,0xE6,0x6E,0xF7,0x27, +0x66,0x0C,0x1A,0x48,0xD7,0x6E,0x42,0xE3,0x3F,0xDE,0x21,0x3E,0x7B,0xE1,0x0D,0x70, +0xFB,0x63,0xAA,0xA8,0x6C,0x1A,0x54,0xB4,0x5C,0x25,0x7A,0xC9,0xA2,0xC9,0x8B,0x16, +0xA6,0xBB,0x2C,0x7E,0x17,0x5E,0x05,0x4D,0x58,0x6E,0x12,0x1D,0x01,0xEE,0x12,0x10, +0x0D,0xC6,0x32,0x7F,0x18,0xFF,0xFC,0xF4,0xFA,0xCD,0x6E,0x91,0xE8,0x36,0x49,0xBE, +0x1A,0x48,0x69,0x8B,0xC2,0x96,0x4D,0x1A,0x12,0xB2,0x69,0x17,0xC1,0x0A,0x90,0xD6, +0xFA,0x79,0x22,0x48,0xBF,0xBA,0x7B,0x69,0xF8,0x70,0xC7,0xFA,0x7A,0x37,0xD8,0xD8, +0x0D,0xD2,0x76,0x4F,0x57,0xFF,0x90,0xB7,0xE3,0x91,0xD2,0xDD,0xEF,0xC2,0x60,0xB7, +0x67,0x3A,0xDD,0xFE,0xAA,0x9C,0xF0,0xD4,0x8B,0x7F,0x72,0x22,0xCE,0xC6,0x9F,0x97, +0xB6,0xF8,0xAF,0x8A,0xA0,0x10,0xA8,0xD9,0xFB,0x18,0xC6,0xB6,0xB5,0x5C,0x52,0x3C, +0x89,0xB6,0x19,0x2A,0x73,0x01,0x0A,0x0F,0x03,0xB3,0x12,0x60,0xF2,0x7A,0x2F,0x81, +0xDB,0xA3,0x6E,0xFF,0x26,0x30,0x97,0xF5,0x8B,0xDD,0x89,0x57,0xB6,0xAD,0x3D,0xB3, +0xAF,0x2B,0xC5,0xB7,0x76,0x02,0xF0,0xA5,0xD6,0x2B,0x9A,0x86,0x14,0x2A,0x72,0xF6, +0xE3,0x33,0x8C,0x5D,0x09,0x4B,0x13,0xDF,0xBB,0x8C,0x74,0x13,0x52,0x4B,0x02,0x03, +0x01,0x00,0x01,0xA3,0x82,0x02,0x10,0x30,0x82,0x02,0x0C,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03, +0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4E,0x0B,0xEF,0x1A,0xA4,0x40,0x5B,0xA5,0x17, +0x69,0x87,0x30,0xCA,0x34,0x68,0x43,0xD0,0x41,0xAE,0xF2,0x30,0x1F,0x06,0x03,0x55, +0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x4E,0x0B,0xEF,0x1A,0xA4,0x40,0x5B,0xA5, +0x17,0x69,0x87,0x30,0xCA,0x34,0x68,0x43,0xD0,0x41,0xAE,0xF2,0x30,0x82,0x01,0x5A, +0x06,0x03,0x55,0x1D,0x20,0x04,0x82,0x01,0x51,0x30,0x82,0x01,0x4D,0x30,0x82,0x01, +0x49,0x06,0x0B,0x2B,0x06,0x01,0x04,0x01,0x81,0xB5,0x37,0x01,0x01,0x01,0x30,0x82, +0x01,0x38,0x30,0x2E,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x22, +0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74, +0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x70,0x6F,0x6C,0x69,0x63,0x79,0x2E,0x70, +0x64,0x66,0x30,0x34,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x28, +0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74, +0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x69,0x6E,0x74,0x65,0x72,0x6D,0x65,0x64, +0x69,0x61,0x74,0x65,0x2E,0x70,0x64,0x66,0x30,0x81,0xCF,0x06,0x08,0x2B,0x06,0x01, +0x05,0x05,0x07,0x02,0x02,0x30,0x81,0xC2,0x30,0x27,0x16,0x20,0x53,0x74,0x61,0x72, +0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69,0x61,0x6C,0x20,0x28,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x29,0x20,0x4C,0x74,0x64,0x2E,0x30,0x03,0x02,0x01, +0x01,0x1A,0x81,0x96,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x20,0x4C,0x69,0x61,0x62, +0x69,0x6C,0x69,0x74,0x79,0x2C,0x20,0x72,0x65,0x61,0x64,0x20,0x74,0x68,0x65,0x20, +0x73,0x65,0x63,0x74,0x69,0x6F,0x6E,0x20,0x2A,0x4C,0x65,0x67,0x61,0x6C,0x20,0x4C, +0x69,0x6D,0x69,0x74,0x61,0x74,0x69,0x6F,0x6E,0x73,0x2A,0x20,0x6F,0x66,0x20,0x74, +0x68,0x65,0x20,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x61,0x76,0x61,0x69,0x6C, +0x61,0x62,0x6C,0x65,0x20,0x61,0x74,0x20,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77, +0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74,0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F, +0x70,0x6F,0x6C,0x69,0x63,0x79,0x2E,0x70,0x64,0x66,0x30,0x11,0x06,0x09,0x60,0x86, +0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x38,0x06, +0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x0D,0x04,0x2B,0x16,0x29,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x46,0x72,0x65,0x65,0x20,0x53,0x53,0x4C,0x20, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75, +0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x8E,0x8F,0xE7,0xDC,0x94, +0x79,0x7C,0xF1,0x85,0x7F,0x9F,0x49,0x6F,0x6B,0xCA,0x5D,0xFB,0x8C,0xFE,0x04,0xC5, +0xC1,0x62,0xD1,0x7D,0x42,0x8A,0xBC,0x53,0xB7,0x94,0x03,0x66,0x30,0x3F,0xB1,0xE7, +0x0A,0xA7,0x50,0x20,0x55,0x25,0x7F,0x76,0x7A,0x14,0x0D,0xEB,0x04,0x0E,0x40,0xE6, +0x3E,0xD8,0x88,0xAB,0x07,0x27,0x83,0xA9,0x75,0xA6,0x37,0x73,0xC7,0xFD,0x4B,0xD2, +0x4D,0xAD,0x17,0x40,0xC8,0x46,0xBE,0x3B,0x7F,0x51,0xFC,0xC3,0xB6,0x05,0x31,0xDC, +0xCD,0x85,0x22,0x4E,0x71,0xB7,0xF2,0x71,0x5E,0xB0,0x1A,0xC6,0xBA,0x93,0x8B,0x78, +0x92,0x4A,0x85,0xF8,0x78,0x0F,0x83,0xFE,0x2F,0xAD,0x2C,0xF7,0xE4,0xA4,0xBB,0x2D, +0xD0,0xE7,0x0D,0x3A,0xB8,0x3E,0xCE,0xF6,0x78,0xF6,0xAE,0x47,0x24,0xCA,0xA3,0x35, +0x36,0xCE,0xC7,0xC6,0x87,0x98,0xDA,0xEC,0xFB,0xE9,0xB2,0xCE,0x27,0x9B,0x88,0xC3, +0x04,0xA1,0xF6,0x0B,0x59,0x68,0xAF,0xC9,0xDB,0x10,0x0F,0x4D,0xF6,0x64,0x63,0x5C, +0xA5,0x12,0x6F,0x92,0xB2,0x93,0x94,0xC7,0x88,0x17,0x0E,0x93,0xB6,0x7E,0x62,0x8B, +0x90,0x7F,0xAB,0x4E,0x9F,0xFC,0xE3,0x75,0x14,0x4F,0x2A,0x32,0xDF,0x5B,0x0D,0xE0, +0xF5,0x7B,0x93,0x0D,0xAB,0xA1,0xCF,0x87,0xE1,0xA5,0x04,0x45,0xE8,0x3C,0x12,0xA5, +0x09,0xC5,0xB0,0xD1,0xB7,0x53,0xF3,0x60,0x14,0xBA,0x85,0x69,0x6A,0x21,0x7C,0x1F, +0x75,0x61,0x17,0x20,0x17,0x7B,0x6C,0x3B,0x41,0x29,0x5C,0xE1,0xAC,0x5A,0xD1,0xCD, +0x8C,0x9B,0xEB,0x60,0x1D,0x19,0xEC,0xF7,0xE5,0xB0,0xDA,0xF9,0x79,0x18,0xA5,0x45, +0x3F,0x49,0x43,0x57,0xD2,0xDD,0x24,0xD5,0x2C,0xA3,0xFD,0x91,0x8D,0x27,0xB5,0xE5, +0xEB,0x14,0x06,0x9A,0x4C,0x7B,0x21,0xBB,0x3A,0xAD,0x30,0x06,0x18,0xC0,0xD8,0xC1, +0x6B,0x2C,0x7F,0x59,0x5C,0x5D,0x91,0xB1,0x70,0x22,0x57,0xEB,0x8A,0x6B,0x48,0x4A, +0xD5,0x0F,0x29,0xEC,0xC6,0x40,0xC0,0x2F,0x88,0x4C,0x68,0x01,0x17,0x77,0xF4,0x24, +0x19,0x4F,0xBD,0xFA,0xE1,0xB2,0x20,0x21,0x4B,0xDD,0x1A,0xD8,0x29,0x7D,0xAA,0xB8, +0xDE,0x54,0xEC,0x21,0x55,0x80,0x6C,0x1E,0xF5,0x30,0xC8,0xA3,0x10,0xE5,0xB2,0xE6, +0x2A,0x14,0x31,0xC3,0x85,0x2D,0x8C,0x98,0xB1,0x86,0x5A,0x4F,0x89,0x59,0x2D,0xB9, +0xC7,0xF7,0x1C,0xC8,0x8A,0x7F,0xC0,0x9D,0x05,0x4A,0xE6,0x42,0x4F,0x62,0xA3,0x6D, +0x29,0xA4,0x1F,0x85,0xAB,0xDB,0xE5,0x81,0xC8,0xAD,0x2A,0x3D,0x4C,0x5D,0x5B,0x84, +0x26,0x71,0xC4,0x85,0x5E,0x71,0x24,0xCA,0xA5,0x1B,0x6C,0xD8,0x61,0xD3,0x1A,0xE0, +0x54,0xDB,0xCE,0xBA,0xA9,0x32,0xB5,0x22,0xF6,0x73,0x41,0x09,0x5D,0xB8,0x17,0x5D, +0x0E,0x0F,0x99,0x90,0xD6,0x47,0xDA,0x6F,0x0A,0x3A,0x62,0x28,0x14,0x67,0x82,0xD9, +0xF1,0xD0,0x80,0x59,0x9B,0xCB,0x31,0xD8,0x9B,0x0F,0x8C,0x77,0x4E,0xB5,0x68,0x8A, +0xF2,0x6C,0xF6,0x24,0x0E,0x2D,0x6C,0x70,0xC5,0x73,0xD1,0xDE,0x14,0xD0,0x71,0x8F, +0xB6,0xD3,0x7B,0x02,0xF6,0xE3,0xB8,0xD4,0x09,0x6E,0x6B,0x9E,0x75,0x84,0x39,0xE6, +0x7F,0x25,0xA5,0xF2,0x48,0x00,0xC0,0xA4,0x01,0xDA,0x3F, +}; + + +/* subject:/C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */ +/* issuer :/C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */ + + +const unsigned char StartCom_Certification_Authority_G2_certificate[1383]={ +0x30,0x82,0x05,0x63,0x30,0x82,0x03,0x4B,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x3B, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30, +0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16, +0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F, +0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2C,0x30,0x2A,0x06,0x03,0x55,0x04,0x03,0x13, +0x23,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66, +0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x31,0x30,0x30,0x31,0x30,0x31,0x30,0x31, +0x30,0x30,0x30,0x31,0x5A,0x17,0x0D,0x33,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35, +0x39,0x30,0x31,0x5A,0x30,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x49,0x4C,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74, +0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2C,0x30,0x2A,0x06, +0x03,0x55,0x04,0x03,0x13,0x23,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x47,0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F, +0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xB6,0x89,0x36,0x5B,0x07,0xB7, +0x20,0x36,0xBD,0x82,0xBB,0xE1,0x16,0x20,0x03,0x95,0x7A,0xAF,0x0E,0xA3,0x55,0xC9, +0x25,0x99,0x4A,0xC5,0xD0,0x56,0x41,0x87,0x90,0x4D,0x21,0x60,0xA4,0x14,0x87,0x3B, +0xCD,0xFD,0xB2,0x3E,0xB4,0x67,0x03,0x6A,0xED,0xE1,0x0F,0x4B,0xC0,0x91,0x85,0x70, +0x45,0xE0,0x42,0x9E,0xDE,0x29,0x23,0xD4,0x01,0x0D,0xA0,0x10,0x79,0xB8,0xDB,0x03, +0xBD,0xF3,0xA9,0x2F,0xD1,0xC6,0xE0,0x0F,0xCB,0x9E,0x8A,0x14,0x0A,0xB8,0xBD,0xF6, +0x56,0x62,0xF1,0xC5,0x72,0xB6,0x32,0x25,0xD9,0xB2,0xF3,0xBD,0x65,0xC5,0x0D,0x2C, +0x6E,0xD5,0x92,0x6F,0x18,0x8B,0x00,0x41,0x14,0x82,0x6F,0x40,0x20,0x26,0x7A,0x28, +0x0F,0xF5,0x1E,0x7F,0x27,0xF7,0x94,0xB1,0x37,0x3D,0xB7,0xC7,0x91,0xF7,0xE2,0x01, +0xEC,0xFD,0x94,0x89,0xE1,0xCC,0x6E,0xD3,0x36,0xD6,0x0A,0x19,0x79,0xAE,0xD7,0x34, +0x82,0x65,0xFF,0x7C,0x42,0xBB,0xB6,0xDD,0x0B,0xA6,0x34,0xAF,0x4B,0x60,0xFE,0x7F, +0x43,0x49,0x06,0x8B,0x8C,0x43,0xB8,0x56,0xF2,0xD9,0x7F,0x21,0x43,0x17,0xEA,0xA7, +0x48,0x95,0x01,0x75,0x75,0xEA,0x2B,0xA5,0x43,0x95,0xEA,0x15,0x84,0x9D,0x08,0x8D, +0x26,0x6E,0x55,0x9B,0xAB,0xDC,0xD2,0x39,0xD2,0x31,0x1D,0x60,0xE2,0xAC,0xCC,0x56, +0x45,0x24,0xF5,0x1C,0x54,0xAB,0xEE,0x86,0xDD,0x96,0x32,0x85,0xF8,0x4C,0x4F,0xE8, +0x95,0x76,0xB6,0x05,0xDD,0x36,0x23,0x67,0xBC,0xFF,0x15,0xE2,0xCA,0x3B,0xE6,0xA6, +0xEC,0x3B,0xEC,0x26,0x11,0x34,0x48,0x8D,0xF6,0x80,0x2B,0x1A,0x23,0x02,0xEB,0x8A, +0x1C,0x3A,0x76,0x2A,0x7B,0x56,0x16,0x1C,0x72,0x2A,0xB3,0xAA,0xE3,0x60,0xA5,0x00, +0x9F,0x04,0x9B,0xE2,0x6F,0x1E,0x14,0x58,0x5B,0xA5,0x6C,0x8B,0x58,0x3C,0xC3,0xBA, +0x4E,0x3A,0x5C,0xF7,0xE1,0x96,0x2B,0x3E,0xEF,0x07,0xBC,0xA4,0xE5,0x5D,0xCC,0x4D, +0x9F,0x0D,0xE1,0xDC,0xAA,0xBB,0xE1,0x6E,0x1A,0xEC,0x8F,0xE1,0xB6,0x4C,0x4D,0x79, +0x72,0x5D,0x17,0x35,0x0B,0x1D,0xD7,0xC1,0x47,0xDA,0x96,0x24,0xE0,0xD0,0x72,0xA8, +0x5A,0x5F,0x66,0x2D,0x10,0xDC,0x2F,0x2A,0x13,0xAE,0x26,0xFE,0x0A,0x1C,0x19,0xCC, +0xD0,0x3E,0x0B,0x9C,0xC8,0x09,0x2E,0xF9,0x5B,0x96,0x7A,0x47,0x9C,0xE9,0x7A,0xF3, +0x05,0x50,0x74,0x95,0x73,0x9E,0x30,0x09,0xF3,0x97,0x82,0x5E,0xE6,0x8F,0x39,0x08, +0x1E,0x59,0xE5,0x35,0x14,0x42,0x13,0xFF,0x00,0x9C,0xF7,0xBE,0xAA,0x50,0xCF,0xE2, +0x51,0x48,0xD7,0xB8,0x6F,0xAF,0xF8,0x4E,0x7E,0x33,0x98,0x92,0x14,0x62,0x3A,0x75, +0x63,0xCF,0x7B,0xFA,0xDE,0x82,0x3B,0xA9,0xBB,0x39,0xE2,0xC4,0xBD,0x2C,0x00,0x0E, +0xC8,0x17,0xAC,0x13,0xEF,0x4D,0x25,0x8E,0xD8,0xB3,0x90,0x2F,0xA9,0xDA,0x29,0x7D, +0x1D,0xAF,0x74,0x3A,0xB2,0x27,0xC0,0xC1,0x1E,0x3E,0x75,0xA3,0x16,0xA9,0xAF,0x7A, +0x22,0x5D,0x9F,0x13,0x1A,0xCF,0xA7,0xA0,0xEB,0xE3,0x86,0x0A,0xD3,0xFD,0xE6,0x96, +0x95,0xD7,0x23,0xC8,0x37,0xDD,0xC4,0x7C,0xAA,0x36,0xAC,0x98,0x1A,0x12,0xB1,0xE0, +0x4E,0xE8,0xB1,0x3B,0xF5,0xD6,0x6F,0xF1,0x30,0xD7,0x02,0x03,0x01,0x00,0x01,0xA3, +0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4B, +0xC5,0xB4,0x40,0x6B,0xAD,0x1C,0xB3,0xA5,0x1C,0x65,0x6E,0x46,0x36,0x89,0x87,0x05, +0x0C,0x0E,0xB6,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B, +0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x73,0x57,0x3F,0x2C,0xD5,0x95,0x32,0x7E,0x37, +0xDB,0x96,0x92,0xEB,0x19,0x5E,0x7E,0x53,0xE7,0x41,0xEC,0x11,0xB6,0x47,0xEF,0xB5, +0xDE,0xED,0x74,0x5C,0xC5,0xF1,0x8E,0x49,0xE0,0xFC,0x6E,0x99,0x13,0xCD,0x9F,0x8A, +0xDA,0xCD,0x3A,0x0A,0xD8,0x3A,0x5A,0x09,0x3F,0x5F,0x34,0xD0,0x2F,0x03,0xD2,0x66, +0x1D,0x1A,0xBD,0x9C,0x90,0x37,0xC8,0x0C,0x8E,0x07,0x5A,0x94,0x45,0x46,0x2A,0xE6, +0xBE,0x7A,0xDA,0xA1,0xA9,0xA4,0x69,0x12,0x92,0xB0,0x7D,0x36,0xD4,0x44,0x87,0xD7, +0x51,0xF1,0x29,0x63,0xD6,0x75,0xCD,0x16,0xE4,0x27,0x89,0x1D,0xF8,0xC2,0x32,0x48, +0xFD,0xDB,0x99,0xD0,0x8F,0x5F,0x54,0x74,0xCC,0xAC,0x67,0x34,0x11,0x62,0xD9,0x0C, +0x0A,0x37,0x87,0xD1,0xA3,0x17,0x48,0x8E,0xD2,0x17,0x1D,0xF6,0xD7,0xFD,0xDB,0x65, +0xEB,0xFD,0xA8,0xD4,0xF5,0xD6,0x4F,0xA4,0x5B,0x75,0xE8,0xC5,0xD2,0x60,0xB2,0xDB, +0x09,0x7E,0x25,0x8B,0x7B,0xBA,0x52,0x92,0x9E,0x3E,0xE8,0xC5,0x77,0xA1,0x3C,0xE0, +0x4A,0x73,0x6B,0x61,0xCF,0x86,0xDC,0x43,0xFF,0xFF,0x21,0xFE,0x23,0x5D,0x24,0x4A, +0xF5,0xD3,0x6D,0x0F,0x62,0x04,0x05,0x57,0x82,0xDA,0x6E,0xA4,0x33,0x25,0x79,0x4B, +0x2E,0x54,0x19,0x8B,0xCC,0x2C,0x3D,0x30,0xE9,0xD1,0x06,0xFF,0xE8,0x32,0x46,0xBE, +0xB5,0x33,0x76,0x77,0xA8,0x01,0x5D,0x96,0xC1,0xC1,0xD5,0xBE,0xAE,0x25,0xC0,0xC9, +0x1E,0x0A,0x09,0x20,0x88,0xA1,0x0E,0xC9,0xF3,0x6F,0x4D,0x82,0x54,0x00,0x20,0xA7, +0xD2,0x8F,0xE4,0x39,0x54,0x17,0x2E,0x8D,0x1E,0xB8,0x1B,0xBB,0x1B,0xBD,0x9A,0x4E, +0x3B,0x10,0x34,0xDC,0x9C,0x88,0x53,0xEF,0xA2,0x31,0x5B,0x58,0x4F,0x91,0x62,0xC8, +0xC2,0x9A,0x9A,0xCD,0x15,0x5D,0x38,0xA9,0xD6,0xBE,0xF8,0x13,0xB5,0x9F,0x12,0x69, +0xF2,0x50,0x62,0xAC,0xFB,0x17,0x37,0xF4,0xEE,0xB8,0x75,0x67,0x60,0x10,0xFB,0x83, +0x50,0xF9,0x44,0xB5,0x75,0x9C,0x40,0x17,0xB2,0xFE,0xFD,0x79,0x5D,0x6E,0x58,0x58, +0x5F,0x30,0xFC,0x00,0xAE,0xAF,0x33,0xC1,0x0E,0x4E,0x6C,0xBA,0xA7,0xA6,0xA1,0x7F, +0x32,0xDB,0x38,0xE0,0xB1,0x72,0x17,0x0A,0x2B,0x91,0xEC,0x6A,0x63,0x26,0xED,0x89, +0xD4,0x78,0xCC,0x74,0x1E,0x05,0xF8,0x6B,0xFE,0x8C,0x6A,0x76,0x39,0x29,0xAE,0x65, +0x23,0x12,0x95,0x08,0x22,0x1C,0x97,0xCE,0x5B,0x06,0xEE,0x0C,0xE2,0xBB,0xBC,0x1F, +0x44,0x93,0xF6,0xD8,0x38,0x45,0x05,0x21,0xED,0xE4,0xAD,0xAB,0x12,0xB6,0x03,0xA4, +0x42,0x2E,0x2D,0xC4,0x09,0x3A,0x03,0x67,0x69,0x84,0x9A,0xE1,0x59,0x90,0x8A,0x28, +0x85,0xD5,0x5D,0x74,0xB1,0xD1,0x0E,0x20,0x58,0x9B,0x13,0xA5,0xB0,0x63,0xA6,0xED, +0x7B,0x47,0xFD,0x45,0x55,0x30,0xA4,0xEE,0x9A,0xD4,0xE6,0xE2,0x87,0xEF,0x98,0xC9, +0x32,0x82,0x11,0x29,0x22,0xBC,0x00,0x0A,0x31,0x5E,0x2D,0x0F,0xC0,0x8E,0xE9,0x6B, +0xB2,0x8F,0x2E,0x06,0xD8,0xD1,0x91,0xC7,0xC6,0x12,0xF4,0x4C,0xFD,0x30,0x17,0xC3, +0xC1,0xDA,0x38,0x5B,0xE3,0xA9,0xEA,0xE6,0xA1,0xBA,0x79,0xEF,0x73,0xD8,0xB6,0x53, +0x57,0x2D,0xF6,0xD0,0xE1,0xD7,0x48, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 2 CA/CN=TC TrustCenter Class 2 CA II */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 2 CA/CN=TC TrustCenter Class 2 CA II */ + + +const unsigned char TC_TrustCenter_Class_2_CA_II_certificate[1198]={ +0x30,0x82,0x04,0xAA,0x30,0x82,0x03,0x92,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x2E, +0x6A,0x00,0x01,0x00,0x02,0x1F,0xD7,0x52,0x21,0x2C,0x11,0x5C,0x3B,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x76,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55, +0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x41,0x31,0x25,0x30, +0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74, +0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43, +0x41,0x20,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x31,0x31,0x32,0x31,0x34, +0x33,0x38,0x34,0x33,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31,0x32,0x32,0x35, +0x39,0x35,0x39,0x5A,0x30,0x76,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43, +0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62, +0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54, +0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73, +0x20,0x32,0x20,0x43,0x41,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C, +0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43, +0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x41,0x20,0x49,0x49,0x30,0x82,0x01,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAB,0x80,0x87, +0x9B,0x8E,0xF0,0xC3,0x7C,0x87,0xD7,0xE8,0x24,0x82,0x11,0xB3,0x3C,0xDD,0x43,0x62, +0xEE,0xF8,0xC3,0x45,0xDA,0xE8,0xE1,0xA0,0x5F,0xD1,0x2A,0xB2,0xEA,0x93,0x68,0xDF, +0xB4,0xC8,0xD6,0x43,0xE9,0xC4,0x75,0x59,0x7F,0xFC,0xE1,0x1D,0xF8,0x31,0x70,0x23, +0x1B,0x88,0x9E,0x27,0xB9,0x7B,0xFD,0x3A,0xD2,0xC9,0xA9,0xE9,0x14,0x2F,0x90,0xBE, +0x03,0x52,0xC1,0x49,0xCD,0xF6,0xFD,0xE4,0x08,0x66,0x0B,0x57,0x8A,0xA2,0x42,0xA0, +0xB8,0xD5,0x7F,0x69,0x5C,0x90,0x32,0xB2,0x97,0x0D,0xCA,0x4A,0xDC,0x46,0x3E,0x02, +0x55,0x89,0x53,0xE3,0x1A,0x5A,0xCB,0x36,0xC6,0x07,0x56,0xF7,0x8C,0xCF,0x11,0xF4, +0x4C,0xBB,0x30,0x70,0x04,0x95,0xA5,0xF6,0x39,0x8C,0xFD,0x73,0x81,0x08,0x7D,0x89, +0x5E,0x32,0x1E,0x22,0xA9,0x22,0x45,0x4B,0xB0,0x66,0x2E,0x30,0xCC,0x9F,0x65,0xFD, +0xFC,0xCB,0x81,0xA9,0xF1,0xE0,0x3B,0xAF,0xA3,0x86,0xD1,0x89,0xEA,0xC4,0x45,0x79, +0x50,0x5D,0xAE,0xE9,0x21,0x74,0x92,0x4D,0x8B,0x59,0x82,0x8F,0x94,0xE3,0xE9,0x4A, +0xF1,0xE7,0x49,0xB0,0x14,0xE3,0xF5,0x62,0xCB,0xD5,0x72,0xBD,0x1F,0xB9,0xD2,0x9F, +0xA0,0xCD,0xA8,0xFA,0x01,0xC8,0xD9,0x0D,0xDF,0xDA,0xFC,0x47,0x9D,0xB3,0xC8,0x54, +0xDF,0x49,0x4A,0xF1,0x21,0xA9,0xFE,0x18,0x4E,0xEE,0x48,0xD4,0x19,0xBB,0xEF,0x7D, +0xE4,0xE2,0x9D,0xCB,0x5B,0xB6,0x6E,0xFF,0xE3,0xCD,0x5A,0xE7,0x74,0x82,0x05,0xBA, +0x80,0x25,0x38,0xCB,0xE4,0x69,0x9E,0xAF,0x41,0xAA,0x1A,0x84,0xF5,0x02,0x03,0x01, +0x00,0x01,0xA3,0x82,0x01,0x34,0x30,0x82,0x01,0x30,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0xE3,0xAB,0x54,0x4C,0x80,0xA1,0xDB,0x56,0x43,0xB7, +0x91,0x4A,0xCB,0xF3,0x82,0x7A,0x13,0x5C,0x08,0xAB,0x30,0x81,0xED,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x81,0xE5,0x30,0x81,0xE2,0x30,0x81,0xDF,0xA0,0x81,0xDC,0xA0,0x81, +0xD9,0x86,0x35,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72, +0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,0x72,0x2E,0x64,0x65,0x2F,0x63,0x72,0x6C, +0x2F,0x76,0x32,0x2F,0x74,0x63,0x5F,0x63,0x6C,0x61,0x73,0x73,0x5F,0x32,0x5F,0x63, +0x61,0x5F,0x49,0x49,0x2E,0x63,0x72,0x6C,0x86,0x81,0x9F,0x6C,0x64,0x61,0x70,0x3A, +0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65, +0x72,0x2E,0x64,0x65,0x2F,0x43,0x4E,0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x25,0x32,0x30,0x43,0x6C,0x61,0x73,0x73, +0x25,0x32,0x30,0x32,0x25,0x32,0x30,0x43,0x41,0x25,0x32,0x30,0x49,0x49,0x2C,0x4F, +0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65, +0x72,0x25,0x32,0x30,0x47,0x6D,0x62,0x48,0x2C,0x4F,0x55,0x3D,0x72,0x6F,0x6F,0x74, +0x63,0x65,0x72,0x74,0x73,0x2C,0x44,0x43,0x3D,0x74,0x72,0x75,0x73,0x74,0x63,0x65, +0x6E,0x74,0x65,0x72,0x2C,0x44,0x43,0x3D,0x64,0x65,0x3F,0x63,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x52,0x65,0x76,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x4C,0x69,0x73,0x74,0x3F,0x62,0x61,0x73,0x65,0x3F,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x8C,0xD7, +0xDF,0x7E,0xEE,0x1B,0x80,0x10,0xB3,0x83,0xF5,0xDB,0x11,0xEA,0x6B,0x4B,0xA8,0x92, +0x18,0xD9,0xF7,0x07,0x39,0xF5,0x2C,0xBE,0x06,0x75,0x7A,0x68,0x53,0x15,0x1C,0xEA, +0x4A,0xED,0x5E,0xFC,0x23,0xB2,0x13,0xA0,0xD3,0x09,0xFF,0xF6,0xF6,0x2E,0x6B,0x41, +0x71,0x79,0xCD,0xE2,0x6D,0xFD,0xAE,0x59,0x6B,0x85,0x1D,0xB8,0x4E,0x22,0x9A,0xED, +0x66,0x39,0x6E,0x4B,0x94,0xE6,0x55,0xFC,0x0B,0x1B,0x8B,0x77,0xC1,0x53,0x13,0x66, +0x89,0xD9,0x28,0xD6,0x8B,0xF3,0x45,0x4A,0x63,0xB7,0xFD,0x7B,0x0B,0x61,0x5D,0xB8, +0x6D,0xBE,0xC3,0xDC,0x5B,0x79,0xD2,0xED,0x86,0xE5,0xA2,0x4D,0xBE,0x5E,0x74,0x7C, +0x6A,0xED,0x16,0x38,0x1F,0x7F,0x58,0x81,0x5A,0x1A,0xEB,0x32,0x88,0x2D,0xB2,0xF3, +0x39,0x77,0x80,0xAF,0x5E,0xB6,0x61,0x75,0x29,0xDB,0x23,0x4D,0x88,0xCA,0x50,0x28, +0xCB,0x85,0xD2,0xD3,0x10,0xA2,0x59,0x6E,0xD3,0x93,0x54,0x00,0x7A,0xA2,0x46,0x95, +0x86,0x05,0x9C,0xA9,0x19,0x98,0xE5,0x31,0x72,0x0C,0x00,0xE2,0x67,0xD9,0x40,0xE0, +0x24,0x33,0x7B,0x6F,0x2C,0xB9,0x5C,0xAB,0x65,0x9D,0x2C,0xAC,0x76,0xEA,0x35,0x99, +0xF5,0x97,0xB9,0x0F,0x24,0xEC,0xC7,0x76,0x21,0x28,0x65,0xAE,0x57,0xE8,0x07,0x88, +0x75,0x4A,0x56,0xA0,0xD2,0x05,0x3A,0xA4,0xE6,0x8D,0x92,0x88,0x2C,0xF3,0xF2,0xE1, +0xC1,0xC6,0x61,0xDB,0x41,0xC5,0xC7,0x9B,0xF7,0x0E,0x1A,0x51,0x45,0xC2,0x61,0x6B, +0xDC,0x64,0x27,0x17,0x8C,0x5A,0xB7,0xDA,0x74,0x28,0xCD,0x97,0xE4,0xBD, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 3 CA/CN=TC TrustCenter Class 3 CA II */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 3 CA/CN=TC TrustCenter Class 3 CA II */ + + +const unsigned char TC_TrustCenter_Class_3_CA_II_certificate[1198]={ +0x30,0x82,0x04,0xAA,0x30,0x82,0x03,0x92,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x4A, +0x47,0x00,0x01,0x00,0x02,0xE5,0xA0,0x5D,0xD6,0x3F,0x00,0x51,0xBF,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x76,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55, +0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43,0x41,0x31,0x25,0x30, +0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74, +0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43, +0x41,0x20,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x31,0x31,0x32,0x31,0x34, +0x34,0x31,0x35,0x37,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31,0x32,0x32,0x35, +0x39,0x35,0x39,0x5A,0x30,0x76,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43, +0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62, +0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54, +0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73, +0x20,0x33,0x20,0x43,0x41,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C, +0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43, +0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43,0x41,0x20,0x49,0x49,0x30,0x82,0x01,0x22, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xB4,0xE0,0xBB, +0x51,0xBB,0x39,0x5C,0x8B,0x04,0xC5,0x4C,0x79,0x1C,0x23,0x86,0x31,0x10,0x63,0x43, +0x55,0x27,0x3F,0xC6,0x45,0xC7,0xA4,0x3D,0xEC,0x09,0x0D,0x1A,0x1E,0x20,0xC2,0x56, +0x1E,0xDE,0x1B,0x37,0x07,0x30,0x22,0x2F,0x6F,0xF1,0x06,0xF1,0xAB,0xAD,0xD6,0xC8, +0xAB,0x61,0xA3,0x2F,0x43,0xC4,0xB0,0xB2,0x2D,0xFC,0xC3,0x96,0x69,0x7B,0x7E,0x8A, +0xE4,0xCC,0xC0,0x39,0x12,0x90,0x42,0x60,0xC9,0xCC,0x35,0x68,0xEE,0xDA,0x5F,0x90, +0x56,0x5F,0xCD,0x1C,0x4D,0x5B,0x58,0x49,0xEB,0x0E,0x01,0x4F,0x64,0xFA,0x2C,0x3C, +0x89,0x58,0xD8,0x2F,0x2E,0xE2,0xB0,0x68,0xE9,0x22,0x3B,0x75,0x89,0xD6,0x44,0x1A, +0x65,0xF2,0x1B,0x97,0x26,0x1D,0x28,0x6D,0xAC,0xE8,0xBD,0x59,0x1D,0x2B,0x24,0xF6, +0xD6,0x84,0x03,0x66,0x88,0x24,0x00,0x78,0x60,0xF1,0xF8,0xAB,0xFE,0x02,0xB2,0x6B, +0xFB,0x22,0xFB,0x35,0xE6,0x16,0xD1,0xAD,0xF6,0x2E,0x12,0xE4,0xFA,0x35,0x6A,0xE5, +0x19,0xB9,0x5D,0xDB,0x3B,0x1E,0x1A,0xFB,0xD3,0xFF,0x15,0x14,0x08,0xD8,0x09,0x6A, +0xBA,0x45,0x9D,0x14,0x79,0x60,0x7D,0xAF,0x40,0x8A,0x07,0x73,0xB3,0x93,0x96,0xD3, +0x74,0x34,0x8D,0x3A,0x37,0x29,0xDE,0x5C,0xEC,0xF5,0xEE,0x2E,0x31,0xC2,0x20,0xDC, +0xBE,0xF1,0x4F,0x7F,0x23,0x52,0xD9,0x5B,0xE2,0x64,0xD9,0x9C,0xAA,0x07,0x08,0xB5, +0x45,0xBD,0xD1,0xD0,0x31,0xC1,0xAB,0x54,0x9F,0xA9,0xD2,0xC3,0x62,0x60,0x03,0xF1, +0xBB,0x39,0x4A,0x92,0x4A,0x3D,0x0A,0xB9,0x9D,0xC5,0xA0,0xFE,0x37,0x02,0x03,0x01, +0x00,0x01,0xA3,0x82,0x01,0x34,0x30,0x82,0x01,0x30,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0xD4,0xA2,0xFC,0x9F,0xB3,0xC3,0xD8,0x03,0xD3,0x57, +0x5C,0x07,0xA4,0xD0,0x24,0xA7,0xC0,0xF2,0x00,0xD4,0x30,0x81,0xED,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x81,0xE5,0x30,0x81,0xE2,0x30,0x81,0xDF,0xA0,0x81,0xDC,0xA0,0x81, +0xD9,0x86,0x35,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72, +0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,0x72,0x2E,0x64,0x65,0x2F,0x63,0x72,0x6C, +0x2F,0x76,0x32,0x2F,0x74,0x63,0x5F,0x63,0x6C,0x61,0x73,0x73,0x5F,0x33,0x5F,0x63, +0x61,0x5F,0x49,0x49,0x2E,0x63,0x72,0x6C,0x86,0x81,0x9F,0x6C,0x64,0x61,0x70,0x3A, +0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65, +0x72,0x2E,0x64,0x65,0x2F,0x43,0x4E,0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x25,0x32,0x30,0x43,0x6C,0x61,0x73,0x73, +0x25,0x32,0x30,0x33,0x25,0x32,0x30,0x43,0x41,0x25,0x32,0x30,0x49,0x49,0x2C,0x4F, +0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65, +0x72,0x25,0x32,0x30,0x47,0x6D,0x62,0x48,0x2C,0x4F,0x55,0x3D,0x72,0x6F,0x6F,0x74, +0x63,0x65,0x72,0x74,0x73,0x2C,0x44,0x43,0x3D,0x74,0x72,0x75,0x73,0x74,0x63,0x65, +0x6E,0x74,0x65,0x72,0x2C,0x44,0x43,0x3D,0x64,0x65,0x3F,0x63,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x65,0x52,0x65,0x76,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x4C,0x69,0x73,0x74,0x3F,0x62,0x61,0x73,0x65,0x3F,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x36,0x60, +0xE4,0x70,0xF7,0x06,0x20,0x43,0xD9,0x23,0x1A,0x42,0xF2,0xF8,0xA3,0xB2,0xB9,0x4D, +0x8A,0xB4,0xF3,0xC2,0x9A,0x55,0x31,0x7C,0xC4,0x3B,0x67,0x9A,0xB4,0xDF,0x4D,0x0E, +0x8A,0x93,0x4A,0x17,0x8B,0x1B,0x8D,0xCA,0x89,0xE1,0xCF,0x3A,0x1E,0xAC,0x1D,0xF1, +0x9C,0x32,0xB4,0x8E,0x59,0x76,0xA2,0x41,0x85,0x25,0x37,0xA0,0x13,0xD0,0xF5,0x7C, +0x4E,0xD5,0xEA,0x96,0xE2,0x6E,0x72,0xC1,0xBB,0x2A,0xFE,0x6C,0x6E,0xF8,0x91,0x98, +0x46,0xFC,0xC9,0x1B,0x57,0x5B,0xEA,0xC8,0x1A,0x3B,0x3F,0xB0,0x51,0x98,0x3C,0x07, +0xDA,0x2C,0x59,0x01,0xDA,0x8B,0x44,0xE8,0xE1,0x74,0xFD,0xA7,0x68,0xDD,0x54,0xBA, +0x83,0x46,0xEC,0xC8,0x46,0xB5,0xF8,0xAF,0x97,0xC0,0x3B,0x09,0x1C,0x8F,0xCE,0x72, +0x96,0x3D,0x33,0x56,0x70,0xBC,0x96,0xCB,0xD8,0xD5,0x7D,0x20,0x9A,0x83,0x9F,0x1A, +0xDC,0x39,0xF1,0xC5,0x72,0xA3,0x11,0x03,0xFD,0x3B,0x42,0x52,0x29,0xDB,0xE8,0x01, +0xF7,0x9B,0x5E,0x8C,0xD6,0x8D,0x86,0x4E,0x19,0xFA,0xBC,0x1C,0xBE,0xC5,0x21,0xA5, +0x87,0x9E,0x78,0x2E,0x36,0xDB,0x09,0x71,0xA3,0x72,0x34,0xF8,0x6C,0xE3,0x06,0x09, +0xF2,0x5E,0x56,0xA5,0xD3,0xDD,0x98,0xFA,0xD4,0xE6,0x06,0xF4,0xF0,0xB6,0x20,0x63, +0x4B,0xEA,0x29,0xBD,0xAA,0x82,0x66,0x1E,0xFB,0x81,0xAA,0xA7,0x37,0xAD,0x13,0x18, +0xE6,0x92,0xC3,0x81,0xC1,0x33,0xBB,0x88,0x1E,0xA1,0xE7,0xE2,0xB4,0xBD,0x31,0x6C, +0x0E,0x51,0x3D,0x6F,0xFB,0x96,0x56,0x80,0xE2,0x36,0x17,0xD1,0xDC,0xE4, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA I */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA I */ + + +const unsigned char TC_TrustCenter_Universal_CA_I_certificate[993]={ +0x30,0x82,0x03,0xDD,0x30,0x82,0x02,0xC5,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x1D, +0xA2,0x00,0x01,0x00,0x02,0xEC,0xB7,0x60,0x80,0x78,0x8D,0xB6,0x06,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x79,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31, +0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x54,0x43,0x20,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73, +0x61,0x6C,0x20,0x43,0x41,0x20,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x33,0x32, +0x32,0x31,0x35,0x35,0x34,0x32,0x38,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31, +0x32,0x32,0x35,0x39,0x35,0x39,0x5A,0x30,0x79,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13, +0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20, +0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0B,0x13,0x1B,0x54, +0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E, +0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,0x26,0x30,0x24,0x06,0x03, +0x55,0x04,0x03,0x13,0x1D,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E, +0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41, +0x20,0x49,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xA4,0x77,0x23,0x96,0x44,0xAF,0x90,0xF4,0x31,0xA7,0x10,0xF4,0x26, +0x87,0x9C,0xF3,0x38,0xD9,0x0F,0x5E,0xDE,0xCF,0x41,0xE8,0x31,0xAD,0xC6,0x74,0x91, +0x24,0x96,0x78,0x1E,0x09,0xA0,0x9B,0x9A,0x95,0x4A,0x4A,0xF5,0x62,0x7C,0x02,0xA8, +0xCA,0xAC,0xFB,0x5A,0x04,0x76,0x39,0xDE,0x5F,0xF1,0xF9,0xB3,0xBF,0xF3,0x03,0x58, +0x55,0xD2,0xAA,0xB7,0xE3,0x04,0x22,0xD1,0xF8,0x94,0xDA,0x22,0x08,0x00,0x8D,0xD3, +0x7C,0x26,0x5D,0xCC,0x77,0x79,0xE7,0x2C,0x78,0x39,0xA8,0x26,0x73,0x0E,0xA2,0x5D, +0x25,0x69,0x85,0x4F,0x55,0x0E,0x9A,0xEF,0xC6,0xB9,0x44,0xE1,0x57,0x3D,0xDF,0x1F, +0x54,0x22,0xE5,0x6F,0x65,0xAA,0x33,0x84,0x3A,0xF3,0xCE,0x7A,0xBE,0x55,0x97,0xAE, +0x8D,0x12,0x0F,0x14,0x33,0xE2,0x50,0x70,0xC3,0x49,0x87,0x13,0xBC,0x51,0xDE,0xD7, +0x98,0x12,0x5A,0xEF,0x3A,0x83,0x33,0x92,0x06,0x75,0x8B,0x92,0x7C,0x12,0x68,0x7B, +0x70,0x6A,0x0F,0xB5,0x9B,0xB6,0x77,0x5B,0x48,0x59,0x9D,0xE4,0xEF,0x5A,0xAD,0xF3, +0xC1,0x9E,0xD4,0xD7,0x45,0x4E,0xCA,0x56,0x34,0x21,0xBC,0x3E,0x17,0x5B,0x6F,0x77, +0x0C,0x48,0x01,0x43,0x29,0xB0,0xDD,0x3F,0x96,0x6E,0xE6,0x95,0xAA,0x0C,0xC0,0x20, +0xB6,0xFD,0x3E,0x36,0x27,0x9C,0xE3,0x5C,0xCF,0x4E,0x81,0xDC,0x19,0xBB,0x91,0x90, +0x7D,0xEC,0xE6,0x97,0x04,0x1E,0x93,0xCC,0x22,0x49,0xD7,0x97,0x86,0xB6,0x13,0x0A, +0x3C,0x43,0x23,0x77,0x7E,0xF0,0xDC,0xE6,0xCD,0x24,0x1F,0x3B,0x83,0x9B,0x34,0x3A, +0x83,0x34,0xE3,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x1F,0x06,0x03, +0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x92,0xA4,0x75,0x2C,0xA4,0x9E,0xBE, +0x81,0x44,0xEB,0x79,0xFC,0x8A,0xC5,0x95,0xA5,0xEB,0x10,0x75,0x73,0x30,0x0F,0x06, +0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E, +0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x1D, +0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x92,0xA4,0x75,0x2C,0xA4,0x9E,0xBE, +0x81,0x44,0xEB,0x79,0xFC,0x8A,0xC5,0x95,0xA5,0xEB,0x10,0x75,0x73,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01, +0x00,0x28,0xD2,0xE0,0x86,0xD5,0xE6,0xF8,0x7B,0xF0,0x97,0xDC,0x22,0x6B,0x3B,0x95, +0x14,0x56,0x0F,0x11,0x30,0xA5,0x9A,0x4F,0x3A,0xB0,0x3A,0xE0,0x06,0xCB,0x65,0xF5, +0xED,0xC6,0x97,0x27,0xFE,0x25,0xF2,0x57,0xE6,0x5E,0x95,0x8C,0x3E,0x64,0x60,0x15, +0x5A,0x7F,0x2F,0x0D,0x01,0xC5,0xB1,0x60,0xFD,0x45,0x35,0xCF,0xF0,0xB2,0xBF,0x06, +0xD9,0xEF,0x5A,0xBE,0xB3,0x62,0x21,0xB4,0xD7,0xAB,0x35,0x7C,0x53,0x3E,0xA6,0x27, +0xF1,0xA1,0x2D,0xDA,0x1A,0x23,0x9D,0xCC,0xDD,0xEC,0x3C,0x2D,0x9E,0x27,0x34,0x5D, +0x0F,0xC2,0x36,0x79,0xBC,0xC9,0x4A,0x62,0x2D,0xED,0x6B,0xD9,0x7D,0x41,0x43,0x7C, +0xB6,0xAA,0xCA,0xED,0x61,0xB1,0x37,0x82,0x15,0x09,0x1A,0x8A,0x16,0x30,0xD8,0xEC, +0xC9,0xD6,0x47,0x72,0x78,0x4B,0x10,0x46,0x14,0x8E,0x5F,0x0E,0xAF,0xEC,0xC7,0x2F, +0xAB,0x10,0xD7,0xB6,0xF1,0x6E,0xEC,0x86,0xB2,0xC2,0xE8,0x0D,0x92,0x73,0xDC,0xA2, +0xF4,0x0F,0x3A,0xBF,0x61,0x23,0x10,0x89,0x9C,0x48,0x40,0x6E,0x70,0x00,0xB3,0xD3, +0xBA,0x37,0x44,0x58,0x11,0x7A,0x02,0x6A,0x88,0xF0,0x37,0x34,0xF0,0x19,0xE9,0xAC, +0xD4,0x65,0x73,0xF6,0x69,0x8C,0x64,0x94,0x3A,0x79,0x85,0x29,0xB0,0x16,0x2B,0x0C, +0x82,0x3F,0x06,0x9C,0xC7,0xFD,0x10,0x2B,0x9E,0x0F,0x2C,0xB6,0x9E,0xE3,0x15,0xBF, +0xD9,0x36,0x1C,0xBA,0x25,0x1A,0x52,0x3D,0x1A,0xEC,0x22,0x0C,0x1C,0xE0,0xA4,0xA2, +0x3D,0xF0,0xE8,0x39,0xCF,0x81,0xC0,0x7B,0xED,0x5D,0x1F,0x6F,0xC5,0xD0,0x0B,0xD7, +0x98, +}; + + +/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA III */ +/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA III */ + + +const unsigned char TC_TrustCenter_Universal_CA_III_certificate[997]={ +0x30,0x82,0x03,0xE1,0x30,0x82,0x02,0xC9,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x63, +0x25,0x00,0x01,0x00,0x02,0x14,0x8D,0x33,0x15,0x02,0xE4,0x6C,0xF4,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x7B,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06, +0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65, +0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74, +0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31, +0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x03,0x13,0x1F,0x54,0x43,0x20,0x54,0x72,0x75, +0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73, +0x61,0x6C,0x20,0x43,0x41,0x20,0x49,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30, +0x39,0x30,0x39,0x30,0x38,0x31,0x35,0x32,0x37,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32, +0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x7B,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04, +0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65, +0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0B,0x13, +0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20, +0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,0x28,0x30,0x26, +0x06,0x03,0x55,0x04,0x03,0x13,0x1F,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43, +0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20, +0x43,0x41,0x20,0x49,0x49,0x49,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC2,0xDA,0x9C,0x62,0xB0,0xB9,0x71,0x12,0xB0, +0x0B,0xC8,0x1A,0x57,0xB2,0xAE,0x83,0x14,0x99,0xB3,0x34,0x4B,0x9B,0x90,0xA2,0xC5, +0xE7,0xE7,0x2F,0x02,0xA0,0x4D,0x2D,0xA4,0xFA,0x85,0xDA,0x9B,0x25,0x85,0x2D,0x40, +0x28,0x20,0x6D,0xEA,0xE0,0xBD,0xB1,0x48,0x83,0x22,0x29,0x44,0x9F,0x4E,0x83,0xEE, +0x35,0x51,0x13,0x73,0x74,0xD5,0xBC,0xF2,0x30,0x66,0x94,0x53,0xC0,0x40,0x36,0x2F, +0x0C,0x84,0x65,0xCE,0x0F,0x6E,0xC2,0x58,0x93,0xE8,0x2C,0x0B,0x3A,0xE9,0xC1,0x8E, +0xFB,0xF2,0x6B,0xCA,0x3C,0xE2,0x9C,0x4E,0x8E,0xE4,0xF9,0x7D,0xD3,0x27,0x9F,0x1B, +0xD5,0x67,0x78,0x87,0x2D,0x7F,0x0B,0x47,0xB3,0xC7,0xE8,0xC9,0x48,0x7C,0xAF,0x2F, +0xCC,0x0A,0xD9,0x41,0xEF,0x9F,0xFE,0x9A,0xE1,0xB2,0xAE,0xF9,0x53,0xB5,0xE5,0xE9, +0x46,0x9F,0x60,0xE3,0xDF,0x8D,0xD3,0x7F,0xFB,0x96,0x7E,0xB3,0xB5,0x72,0xF8,0x4B, +0xAD,0x08,0x79,0xCD,0x69,0x89,0x40,0x27,0xF5,0x2A,0xC1,0xAD,0x43,0xEC,0xA4,0x53, +0xC8,0x61,0xB6,0xF7,0xD2,0x79,0x2A,0x67,0x18,0x76,0x48,0x6D,0x5B,0x25,0x01,0xD1, +0x26,0xC5,0xB7,0x57,0x69,0x23,0x15,0x5B,0x61,0x8A,0xAD,0xF0,0x1B,0x2D,0xD9,0xAF, +0x5C,0xF1,0x26,0x90,0x69,0xA9,0xD5,0x0C,0x40,0xF5,0x33,0x80,0x43,0x8F,0x9C,0xA3, +0x76,0x2A,0x45,0xB4,0xAF,0xBF,0x7F,0x3E,0x87,0x3F,0x76,0xC5,0xCD,0x2A,0xDE,0x20, +0xC5,0x16,0x58,0xCB,0xF9,0x1B,0xF5,0x0F,0xCB,0x0D,0x11,0x52,0x64,0xB8,0xD2,0x76, +0x62,0x77,0x83,0xF1,0x58,0x9F,0xFF,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61, +0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x56,0xE7,0xE1, +0x5B,0x25,0x43,0x80,0xE0,0xF6,0x8C,0xE1,0x71,0xBC,0x8E,0xE5,0x80,0x2F,0xC4,0x48, +0xE2,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01, +0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02, +0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x56,0xE7,0xE1, +0x5B,0x25,0x43,0x80,0xE0,0xF6,0x8C,0xE1,0x71,0xBC,0x8E,0xE5,0x80,0x2F,0xC4,0x48, +0xE2,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00, +0x03,0x82,0x01,0x01,0x00,0x83,0xC7,0xAF,0xEA,0x7F,0x4D,0x0A,0x3C,0x39,0xB1,0x68, +0xBE,0x7B,0x6D,0x89,0x2E,0xE9,0xB3,0x09,0xE7,0x18,0x57,0x8D,0x85,0x9A,0x17,0xF3, +0x76,0x42,0x50,0x13,0x0F,0xC7,0x90,0x6F,0x33,0xAD,0xC5,0x49,0x60,0x2B,0x6C,0x49, +0x58,0x19,0xD4,0xE2,0xBE,0xB7,0xBF,0xAB,0x49,0xBC,0x94,0xC8,0xAB,0xBE,0x28,0x6C, +0x16,0x68,0xE0,0xC8,0x97,0x46,0x20,0xA0,0x68,0x67,0x60,0x88,0x39,0x20,0x51,0xD8, +0x68,0x01,0x11,0xCE,0xA7,0xF6,0x11,0x07,0xF6,0xEC,0xEC,0xAC,0x1A,0x1F,0xB2,0x66, +0x6E,0x56,0x67,0x60,0x7A,0x74,0x5E,0xC0,0x6D,0x97,0x36,0xAE,0xB5,0x0D,0x5D,0x66, +0x73,0xC0,0x25,0x32,0x45,0xD8,0x4A,0x06,0x07,0x8F,0xC4,0xB7,0x07,0xB1,0x4D,0x06, +0x0D,0xE1,0xA5,0xEB,0xF4,0x75,0xCA,0xBA,0x9C,0xD0,0xBD,0xB3,0xD3,0x32,0x24,0x4C, +0xEE,0x7E,0xE2,0x76,0x04,0x4B,0x49,0x53,0xD8,0xF2,0xE9,0x54,0x33,0xFC,0xE5,0x71, +0x1F,0x3D,0x14,0x5C,0x96,0x4B,0xF1,0x3A,0xF2,0x00,0xBB,0x6C,0xB4,0xFA,0x96,0x55, +0x08,0x88,0x09,0xC1,0xCC,0x91,0x19,0x29,0xB0,0x20,0x2D,0xFF,0xCB,0x38,0xA4,0x40, +0xE1,0x17,0xBE,0x79,0x61,0x80,0xFF,0x07,0x03,0x86,0x4C,0x4E,0x7B,0x06,0x9F,0x11, +0x86,0x8D,0x89,0xEE,0x27,0xC4,0xDB,0xE2,0xBC,0x19,0x8E,0x0B,0xC3,0xC3,0x13,0xC7, +0x2D,0x03,0x63,0x3B,0xD3,0xE8,0xE4,0xA2,0x2A,0xC2,0x82,0x08,0x94,0x16,0x54,0xF0, +0xEF,0x1F,0x27,0x90,0x25,0xB8,0x0D,0x0E,0x28,0x1B,0x47,0x77,0x47,0xBD,0x1C,0xA8, +0x25,0xF1,0x94,0xB4,0x66, +}; + + +/* subject:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com */ +/* issuer :/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com */ + + +const unsigned char Thawte_Premium_Server_CA_certificate[811]={ +0x30,0x82,0x03,0x27,0x30,0x82,0x02,0x90,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x81,0xCE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41,0x31, +0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65,0x72, +0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13, +0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03, +0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E,0x73, +0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73, +0x69,0x6F,0x6E,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x54,0x68, +0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x53,0x65,0x72, +0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x28,0x30,0x26,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x19,0x70,0x72,0x65,0x6D,0x69,0x75,0x6D,0x2D,0x73, +0x65,0x72,0x76,0x65,0x72,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F,0x6D, +0x30,0x1E,0x17,0x0D,0x39,0x36,0x30,0x38,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30, +0x5A,0x17,0x0D,0x32,0x30,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A, +0x30,0x81,0xCE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41, +0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65, +0x72,0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07, +0x13,0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06, +0x03,0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E, +0x73,0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03, +0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69, +0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69, +0x73,0x69,0x6F,0x6E,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x54, +0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x53,0x65, +0x72,0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x28,0x30,0x26,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x09,0x01,0x16,0x19,0x70,0x72,0x65,0x6D,0x69,0x75,0x6D,0x2D, +0x73,0x65,0x72,0x76,0x65,0x72,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F, +0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xD2,0x36, +0x36,0x6A,0x8B,0xD7,0xC2,0x5B,0x9E,0xDA,0x81,0x41,0x62,0x8F,0x38,0xEE,0x49,0x04, +0x55,0xD6,0xD0,0xEF,0x1C,0x1B,0x95,0x16,0x47,0xEF,0x18,0x48,0x35,0x3A,0x52,0xF4, +0x2B,0x6A,0x06,0x8F,0x3B,0x2F,0xEA,0x56,0xE3,0xAF,0x86,0x8D,0x9E,0x17,0xF7,0x9E, +0xB4,0x65,0x75,0x02,0x4D,0xEF,0xCB,0x09,0xA2,0x21,0x51,0xD8,0x9B,0xD0,0x67,0xD0, +0xBA,0x0D,0x92,0x06,0x14,0x73,0xD4,0x93,0xCB,0x97,0x2A,0x00,0x9C,0x5C,0x4E,0x0C, +0xBC,0xFA,0x15,0x52,0xFC,0xF2,0x44,0x6E,0xDA,0x11,0x4A,0x6E,0x08,0x9F,0x2F,0x2D, +0xE3,0xF9,0xAA,0x3A,0x86,0x73,0xB6,0x46,0x53,0x58,0xC8,0x89,0x05,0xBD,0x83,0x11, +0xB8,0x73,0x3F,0xAA,0x07,0x8D,0xF4,0x42,0x4D,0xE7,0x40,0x9D,0x1C,0x37,0x02,0x03, +0x01,0x00,0x01,0xA3,0x13,0x30,0x11,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01, +0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x26,0x48,0x2C,0x16,0xC2, +0x58,0xFA,0xE8,0x16,0x74,0x0C,0xAA,0xAA,0x5F,0x54,0x3F,0xF2,0xD7,0xC9,0x78,0x60, +0x5E,0x5E,0x6E,0x37,0x63,0x22,0x77,0x36,0x7E,0xB2,0x17,0xC4,0x34,0xB9,0xF5,0x08, +0x85,0xFC,0xC9,0x01,0x38,0xFF,0x4D,0xBE,0xF2,0x16,0x42,0x43,0xE7,0xBB,0x5A,0x46, +0xFB,0xC1,0xC6,0x11,0x1F,0xF1,0x4A,0xB0,0x28,0x46,0xC9,0xC3,0xC4,0x42,0x7D,0xBC, +0xFA,0xAB,0x59,0x6E,0xD5,0xB7,0x51,0x88,0x11,0xE3,0xA4,0x85,0x19,0x6B,0x82,0x4C, +0xA4,0x0C,0x12,0xAD,0xE9,0xA4,0xAE,0x3F,0xF1,0xC3,0x49,0x65,0x9A,0x8C,0xC5,0xC8, +0x3E,0x25,0xB7,0x94,0x99,0xBB,0x92,0x32,0x71,0x07,0xF0,0x86,0x5E,0xED,0x50,0x27, +0xA6,0x0D,0xA6,0x23,0xF9,0xBB,0xCB,0xA6,0x07,0x14,0x42, +}; + + +/* subject:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA */ +/* issuer :/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA */ + + +const unsigned char thawte_Primary_Root_CA_certificate[1060]={ +0x30,0x82,0x04,0x20,0x30,0x82,0x03,0x08,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x34, +0x4E,0xD5,0x57,0x20,0xD5,0xED,0xEC,0x49,0xF4,0x2F,0xCE,0x37,0xDB,0x2B,0x6D,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0xA9,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15, +0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65, +0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31, +0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30, +0x30,0x36,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20, +0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64, +0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55, +0x04,0x03,0x13,0x16,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61, +0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x36, +0x31,0x31,0x31,0x37,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30, +0x37,0x31,0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xA9,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63, +0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,0x38,0x30,0x36,0x06, +0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x74, +0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F, +0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65, +0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16, +0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x52, +0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82, +0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAC,0xA0,0xF0,0xFB,0x80,0x59,0xD4,0x9C,0xC7, +0xA4,0xCF,0x9D,0xA1,0x59,0x73,0x09,0x10,0x45,0x0C,0x0D,0x2C,0x6E,0x68,0xF1,0x6C, +0x5B,0x48,0x68,0x49,0x59,0x37,0xFC,0x0B,0x33,0x19,0xC2,0x77,0x7F,0xCC,0x10,0x2D, +0x95,0x34,0x1C,0xE6,0xEB,0x4D,0x09,0xA7,0x1C,0xD2,0xB8,0xC9,0x97,0x36,0x02,0xB7, +0x89,0xD4,0x24,0x5F,0x06,0xC0,0xCC,0x44,0x94,0x94,0x8D,0x02,0x62,0x6F,0xEB,0x5A, +0xDD,0x11,0x8D,0x28,0x9A,0x5C,0x84,0x90,0x10,0x7A,0x0D,0xBD,0x74,0x66,0x2F,0x6A, +0x38,0xA0,0xE2,0xD5,0x54,0x44,0xEB,0x1D,0x07,0x9F,0x07,0xBA,0x6F,0xEE,0xE9,0xFD, +0x4E,0x0B,0x29,0xF5,0x3E,0x84,0xA0,0x01,0xF1,0x9C,0xAB,0xF8,0x1C,0x7E,0x89,0xA4, +0xE8,0xA1,0xD8,0x71,0x65,0x0D,0xA3,0x51,0x7B,0xEE,0xBC,0xD2,0x22,0x60,0x0D,0xB9, +0x5B,0x9D,0xDF,0xBA,0xFC,0x51,0x5B,0x0B,0xAF,0x98,0xB2,0xE9,0x2E,0xE9,0x04,0xE8, +0x62,0x87,0xDE,0x2B,0xC8,0xD7,0x4E,0xC1,0x4C,0x64,0x1E,0xDD,0xCF,0x87,0x58,0xBA, +0x4A,0x4F,0xCA,0x68,0x07,0x1D,0x1C,0x9D,0x4A,0xC6,0xD5,0x2F,0x91,0xCC,0x7C,0x71, +0x72,0x1C,0xC5,0xC0,0x67,0xEB,0x32,0xFD,0xC9,0x92,0x5C,0x94,0xDA,0x85,0xC0,0x9B, +0xBF,0x53,0x7D,0x2B,0x09,0xF4,0x8C,0x9D,0x91,0x1F,0x97,0x6A,0x52,0xCB,0xDE,0x09, +0x36,0xA4,0x77,0xD8,0x7B,0x87,0x50,0x44,0xD5,0x3E,0x6E,0x29,0x69,0xFB,0x39,0x49, +0x26,0x1E,0x09,0xA5,0x80,0x7B,0x40,0x2D,0xEB,0xE8,0x27,0x85,0xC9,0xFE,0x61,0xFD, +0x7E,0xE6,0x7C,0x97,0x1D,0xD5,0x9D,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40, +0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01, +0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01, +0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7B,0x5B,0x45,0xCF, +0xAF,0xCE,0xCB,0x7A,0xFD,0x31,0x92,0x1A,0x6A,0xB6,0xF3,0x46,0xEB,0x57,0x48,0x50, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03, +0x82,0x01,0x01,0x00,0x79,0x11,0xC0,0x4B,0xB3,0x91,0xB6,0xFC,0xF0,0xE9,0x67,0xD4, +0x0D,0x6E,0x45,0xBE,0x55,0xE8,0x93,0xD2,0xCE,0x03,0x3F,0xED,0xDA,0x25,0xB0,0x1D, +0x57,0xCB,0x1E,0x3A,0x76,0xA0,0x4C,0xEC,0x50,0x76,0xE8,0x64,0x72,0x0C,0xA4,0xA9, +0xF1,0xB8,0x8B,0xD6,0xD6,0x87,0x84,0xBB,0x32,0xE5,0x41,0x11,0xC0,0x77,0xD9,0xB3, +0x60,0x9D,0xEB,0x1B,0xD5,0xD1,0x6E,0x44,0x44,0xA9,0xA6,0x01,0xEC,0x55,0x62,0x1D, +0x77,0xB8,0x5C,0x8E,0x48,0x49,0x7C,0x9C,0x3B,0x57,0x11,0xAC,0xAD,0x73,0x37,0x8E, +0x2F,0x78,0x5C,0x90,0x68,0x47,0xD9,0x60,0x60,0xE6,0xFC,0x07,0x3D,0x22,0x20,0x17, +0xC4,0xF7,0x16,0xE9,0xC4,0xD8,0x72,0xF9,0xC8,0x73,0x7C,0xDF,0x16,0x2F,0x15,0xA9, +0x3E,0xFD,0x6A,0x27,0xB6,0xA1,0xEB,0x5A,0xBA,0x98,0x1F,0xD5,0xE3,0x4D,0x64,0x0A, +0x9D,0x13,0xC8,0x61,0xBA,0xF5,0x39,0x1C,0x87,0xBA,0xB8,0xBD,0x7B,0x22,0x7F,0xF6, +0xFE,0xAC,0x40,0x79,0xE5,0xAC,0x10,0x6F,0x3D,0x8F,0x1B,0x79,0x76,0x8B,0xC4,0x37, +0xB3,0x21,0x18,0x84,0xE5,0x36,0x00,0xEB,0x63,0x20,0x99,0xB9,0xE9,0xFE,0x33,0x04, +0xBB,0x41,0xC8,0xC1,0x02,0xF9,0x44,0x63,0x20,0x9E,0x81,0xCE,0x42,0xD3,0xD6,0x3F, +0x2C,0x76,0xD3,0x63,0x9C,0x59,0xDD,0x8F,0xA6,0xE1,0x0E,0xA0,0x2E,0x41,0xF7,0x2E, +0x95,0x47,0xCF,0xBC,0xFD,0x33,0xF3,0xF6,0x0B,0x61,0x7E,0x7E,0x91,0x2B,0x81,0x47, +0xC2,0x27,0x30,0xEE,0xA7,0x10,0x5D,0x37,0x8F,0x5C,0x39,0x2B,0xE4,0x04,0xF0,0x7B, +0x8D,0x56,0x8C,0x68, +}; + + +/* subject:/C=US/O=thawte, Inc./OU=(c) 2007 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G2 */ +/* issuer :/C=US/O=thawte, Inc./OU=(c) 2007 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G2 */ + + +const unsigned char thawte_Primary_Root_CA___G2_certificate[652]={ +0x30,0x82,0x02,0x88,0x30,0x82,0x02,0x0D,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x35, +0xFC,0x26,0x5C,0xD9,0x84,0x4F,0xC9,0x3D,0x26,0x3D,0x57,0x9B,0xAE,0xD7,0x56,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x84,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29, +0x20,0x32,0x30,0x30,0x37,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69, +0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22, +0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72, +0x69,0x6D,0x61,0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20, +0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31,0x30,0x35,0x30,0x30,0x30,0x30, +0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32,0x33,0x35,0x39,0x35, +0x39,0x5A,0x30,0x81,0x84,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, +0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61, +0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x38,0x30,0x36,0x06,0x03,0x55, +0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x74,0x68,0x61, +0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20, +0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F, +0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x74,0x68, +0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x52,0x6F,0x6F, +0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x32,0x30,0x76,0x30,0x10,0x06,0x07,0x2A, +0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00, +0x04,0xA2,0xD5,0x9C,0x82,0x7B,0x95,0x9D,0xF1,0x52,0x78,0x87,0xFE,0x8A,0x16,0xBF, +0x05,0xE6,0xDF,0xA3,0x02,0x4F,0x0D,0x07,0xC6,0x00,0x51,0xBA,0x0C,0x02,0x52,0x2D, +0x22,0xA4,0x42,0x39,0xC4,0xFE,0x8F,0xEA,0xC9,0xC1,0xBE,0xD4,0x4D,0xFF,0x9F,0x7A, +0x9E,0xE2,0xB1,0x7C,0x9A,0xAD,0xA7,0x86,0x09,0x73,0x87,0xD1,0xE7,0x9A,0xE3,0x7A, +0xA5,0xAA,0x6E,0xFB,0xBA,0xB3,0x70,0xC0,0x67,0x88,0xA2,0x35,0xD4,0xA3,0x9A,0xB1, +0xFD,0xAD,0xC2,0xEF,0x31,0xFA,0xA8,0xB9,0xF3,0xFB,0x08,0xC6,0x91,0xD1,0xFB,0x29, +0x95,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04, +0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF, +0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04, +0x14,0x9A,0xD8,0x00,0x30,0x00,0xE7,0x6B,0x7F,0x85,0x18,0xEE,0x8B,0xB6,0xCE,0x8A, +0x0C,0xF8,0x11,0xE1,0xBB,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03, +0x03,0x03,0x69,0x00,0x30,0x66,0x02,0x31,0x00,0xDD,0xF8,0xE0,0x57,0x47,0x5B,0xA7, +0xE6,0x0A,0xC3,0xBD,0xF5,0x80,0x8A,0x97,0x35,0x0D,0x1B,0x89,0x3C,0x54,0x86,0x77, +0x28,0xCA,0xA1,0xF4,0x79,0xDE,0xB5,0xE6,0x38,0xB0,0xF0,0x65,0x70,0x8C,0x7F,0x02, +0x54,0xC2,0xBF,0xFF,0xD8,0xA1,0x3E,0xD9,0xCF,0x02,0x31,0x00,0xC4,0x8D,0x94,0xFC, +0xDC,0x53,0xD2,0xDC,0x9D,0x78,0x16,0x1F,0x15,0x33,0x23,0x53,0x52,0xE3,0x5A,0x31, +0x5D,0x9D,0xCA,0xAE,0xBD,0x13,0x29,0x44,0x0D,0x27,0x5B,0xA8,0xE7,0x68,0x9C,0x12, +0xF7,0x58,0x3F,0x2E,0x72,0x02,0x57,0xA3,0x8F,0xA1,0x14,0x2E, +}; + + +/* subject:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2008 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G3 */ +/* issuer :/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2008 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G3 */ + + +const unsigned char thawte_Primary_Root_CA___G3_certificate[1070]={ +0x30,0x82,0x04,0x2A,0x30,0x82,0x03,0x12,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x60, +0x01,0x97,0xB7,0x46,0xA7,0xEA,0xB4,0xB4,0x9A,0xD6,0x4B,0x2F,0xF7,0x90,0xFB,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81, +0xAE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15, +0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C, +0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F, +0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65, +0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31, +0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30, +0x30,0x38,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20, +0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64, +0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03,0x55, +0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61, +0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x33,0x30, +0x1E,0x17,0x0D,0x30,0x38,0x30,0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A, +0x17,0x0D,0x33,0x37,0x31,0x32,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30, +0x81,0xAE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31, +0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13, +0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53, +0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E, +0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32, +0x30,0x30,0x38,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E, +0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65, +0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03, +0x55,0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D, +0x61,0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x33, +0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01, +0x00,0xB2,0xBF,0x27,0x2C,0xFB,0xDB,0xD8,0x5B,0xDD,0x78,0x7B,0x1B,0x9E,0x77,0x66, +0x81,0xCB,0x3E,0xBC,0x7C,0xAE,0xF3,0xA6,0x27,0x9A,0x34,0xA3,0x68,0x31,0x71,0x38, +0x33,0x62,0xE4,0xF3,0x71,0x66,0x79,0xB1,0xA9,0x65,0xA3,0xA5,0x8B,0xD5,0x8F,0x60, +0x2D,0x3F,0x42,0xCC,0xAA,0x6B,0x32,0xC0,0x23,0xCB,0x2C,0x41,0xDD,0xE4,0xDF,0xFC, +0x61,0x9C,0xE2,0x73,0xB2,0x22,0x95,0x11,0x43,0x18,0x5F,0xC4,0xB6,0x1F,0x57,0x6C, +0x0A,0x05,0x58,0x22,0xC8,0x36,0x4C,0x3A,0x7C,0xA5,0xD1,0xCF,0x86,0xAF,0x88,0xA7, +0x44,0x02,0x13,0x74,0x71,0x73,0x0A,0x42,0x59,0x02,0xF8,0x1B,0x14,0x6B,0x42,0xDF, +0x6F,0x5F,0xBA,0x6B,0x82,0xA2,0x9D,0x5B,0xE7,0x4A,0xBD,0x1E,0x01,0x72,0xDB,0x4B, +0x74,0xE8,0x3B,0x7F,0x7F,0x7D,0x1F,0x04,0xB4,0x26,0x9B,0xE0,0xB4,0x5A,0xAC,0x47, +0x3D,0x55,0xB8,0xD7,0xB0,0x26,0x52,0x28,0x01,0x31,0x40,0x66,0xD8,0xD9,0x24,0xBD, +0xF6,0x2A,0xD8,0xEC,0x21,0x49,0x5C,0x9B,0xF6,0x7A,0xE9,0x7F,0x55,0x35,0x7E,0x96, +0x6B,0x8D,0x93,0x93,0x27,0xCB,0x92,0xBB,0xEA,0xAC,0x40,0xC0,0x9F,0xC2,0xF8,0x80, +0xCF,0x5D,0xF4,0x5A,0xDC,0xCE,0x74,0x86,0xA6,0x3E,0x6C,0x0B,0x53,0xCA,0xBD,0x92, +0xCE,0x19,0x06,0x72,0xE6,0x0C,0x5C,0x38,0x69,0xC7,0x04,0xD6,0xBC,0x6C,0xCE,0x5B, +0xF6,0xF7,0x68,0x9C,0xDC,0x25,0x15,0x48,0x88,0xA1,0xE9,0xA9,0xF8,0x98,0x9C,0xE0, +0xF3,0xD5,0x31,0x28,0x61,0x11,0x6C,0x67,0x96,0x8D,0x39,0x99,0xCB,0xC2,0x45,0x24, +0x39,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55, +0x1D,0x0E,0x04,0x16,0x04,0x14,0xAD,0x6C,0xAA,0x94,0x60,0x9C,0xED,0xE4,0xFF,0xFA, +0x3E,0x0A,0x74,0x2B,0x63,0x03,0xF7,0xB6,0x59,0xBF,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x1A,0x40, +0xD8,0x95,0x65,0xAC,0x09,0x92,0x89,0xC6,0x39,0xF4,0x10,0xE5,0xA9,0x0E,0x66,0x53, +0x5D,0x78,0xDE,0xFA,0x24,0x91,0xBB,0xE7,0x44,0x51,0xDF,0xC6,0x16,0x34,0x0A,0xEF, +0x6A,0x44,0x51,0xEA,0x2B,0x07,0x8A,0x03,0x7A,0xC3,0xEB,0x3F,0x0A,0x2C,0x52,0x16, +0xA0,0x2B,0x43,0xB9,0x25,0x90,0x3F,0x70,0xA9,0x33,0x25,0x6D,0x45,0x1A,0x28,0x3B, +0x27,0xCF,0xAA,0xC3,0x29,0x42,0x1B,0xDF,0x3B,0x4C,0xC0,0x33,0x34,0x5B,0x41,0x88, +0xBF,0x6B,0x2B,0x65,0xAF,0x28,0xEF,0xB2,0xF5,0xC3,0xAA,0x66,0xCE,0x7B,0x56,0xEE, +0xB7,0xC8,0xCB,0x67,0xC1,0xC9,0x9C,0x1A,0x18,0xB8,0xC4,0xC3,0x49,0x03,0xF1,0x60, +0x0E,0x50,0xCD,0x46,0xC5,0xF3,0x77,0x79,0xF7,0xB6,0x15,0xE0,0x38,0xDB,0xC7,0x2F, +0x28,0xA0,0x0C,0x3F,0x77,0x26,0x74,0xD9,0x25,0x12,0xDA,0x31,0xDA,0x1A,0x1E,0xDC, +0x29,0x41,0x91,0x22,0x3C,0x69,0xA7,0xBB,0x02,0xF2,0xB6,0x5C,0x27,0x03,0x89,0xF4, +0x06,0xEA,0x9B,0xE4,0x72,0x82,0xE3,0xA1,0x09,0xC1,0xE9,0x00,0x19,0xD3,0x3E,0xD4, +0x70,0x6B,0xBA,0x71,0xA6,0xAA,0x58,0xAE,0xF4,0xBB,0xE9,0x6C,0xB6,0xEF,0x87,0xCC, +0x9B,0xBB,0xFF,0x39,0xE6,0x56,0x61,0xD3,0x0A,0xA7,0xC4,0x5C,0x4C,0x60,0x7B,0x05, +0x77,0x26,0x7A,0xBF,0xD8,0x07,0x52,0x2C,0x62,0xF7,0x70,0x63,0xD9,0x39,0xBC,0x6F, +0x1C,0xC2,0x79,0xDC,0x76,0x29,0xAF,0xCE,0xC5,0x2C,0x64,0x04,0x5E,0x88,0x36,0x6E, +0x31,0xD4,0x40,0x1A,0x62,0x34,0x36,0x3F,0x35,0x01,0xAE,0xAC,0x63,0xA0, +}; + + +/* subject:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Server CA/emailAddress=server-certs@thawte.com */ +/* issuer :/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Server CA/emailAddress=server-certs@thawte.com */ + + +const unsigned char Thawte_Server_CA_certificate[791]={ +0x30,0x82,0x03,0x13,0x30,0x82,0x02,0x7C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30, +0x81,0xC4,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41,0x31, +0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65,0x72, +0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13, +0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03, +0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E,0x73, +0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03,0x55, +0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73, +0x69,0x6F,0x6E,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x03,0x13,0x10,0x54,0x68, +0x61,0x77,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x26, +0x30,0x24,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x09,0x01,0x16,0x17,0x73, +0x65,0x72,0x76,0x65,0x72,0x2D,0x63,0x65,0x72,0x74,0x73,0x40,0x74,0x68,0x61,0x77, +0x74,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x36,0x30,0x38,0x30,0x31, +0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,0x31,0x32,0x33,0x31,0x32, +0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xC4,0x31,0x0B,0x30,0x09,0x06,0x03,0x55, +0x04,0x06,0x13,0x02,0x5A,0x41,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13, +0x0C,0x57,0x65,0x73,0x74,0x65,0x72,0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30, +0x10,0x06,0x03,0x55,0x04,0x07,0x13,0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77, +0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77, +0x74,0x65,0x20,0x43,0x6F,0x6E,0x73,0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63, +0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65, +0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,0x19,0x30,0x17,0x06,0x03, +0x55,0x04,0x03,0x13,0x10,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x53,0x65,0x72,0x76, +0x65,0x72,0x20,0x43,0x41,0x31,0x26,0x30,0x24,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7, +0x0D,0x01,0x09,0x01,0x16,0x17,0x73,0x65,0x72,0x76,0x65,0x72,0x2D,0x63,0x65,0x72, +0x74,0x73,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03, +0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xD3,0xA4,0x50,0x6E,0xC8,0xFF, +0x56,0x6B,0xE6,0xCF,0x5D,0xB6,0xEA,0x0C,0x68,0x75,0x47,0xA2,0xAA,0xC2,0xDA,0x84, +0x25,0xFC,0xA8,0xF4,0x47,0x51,0xDA,0x85,0xB5,0x20,0x74,0x94,0x86,0x1E,0x0F,0x75, +0xC9,0xE9,0x08,0x61,0xF5,0x06,0x6D,0x30,0x6E,0x15,0x19,0x02,0xE9,0x52,0xC0,0x62, +0xDB,0x4D,0x99,0x9E,0xE2,0x6A,0x0C,0x44,0x38,0xCD,0xFE,0xBE,0xE3,0x64,0x09,0x70, +0xC5,0xFE,0xB1,0x6B,0x29,0xB6,0x2F,0x49,0xC8,0x3B,0xD4,0x27,0x04,0x25,0x10,0x97, +0x2F,0xE7,0x90,0x6D,0xC0,0x28,0x42,0x99,0xD7,0x4C,0x43,0xDE,0xC3,0xF5,0x21,0x6D, +0x54,0x9F,0x5D,0xC3,0x58,0xE1,0xC0,0xE4,0xD9,0x5B,0xB0,0xB8,0xDC,0xB4,0x7B,0xDF, +0x36,0x3A,0xC2,0xB5,0x66,0x22,0x12,0xD6,0x87,0x0D,0x02,0x03,0x01,0x00,0x01,0xA3, +0x13,0x30,0x11,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x07,0xFA,0x4C,0x69,0x5C,0xFB,0x95,0xCC,0x46, +0xEE,0x85,0x83,0x4D,0x21,0x30,0x8E,0xCA,0xD9,0xA8,0x6F,0x49,0x1A,0xE6,0xDA,0x51, +0xE3,0x60,0x70,0x6C,0x84,0x61,0x11,0xA1,0x1A,0xC8,0x48,0x3E,0x59,0x43,0x7D,0x4F, +0x95,0x3D,0xA1,0x8B,0xB7,0x0B,0x62,0x98,0x7A,0x75,0x8A,0xDD,0x88,0x4E,0x4E,0x9E, +0x40,0xDB,0xA8,0xCC,0x32,0x74,0xB9,0x6F,0x0D,0xC6,0xE3,0xB3,0x44,0x0B,0xD9,0x8A, +0x6F,0x9A,0x29,0x9B,0x99,0x18,0x28,0x3B,0xD1,0xE3,0x40,0x28,0x9A,0x5A,0x3C,0xD5, +0xB5,0xE7,0x20,0x1B,0x8B,0xCA,0xA4,0xAB,0x8D,0xE9,0x51,0xD9,0xE2,0x4C,0x2C,0x59, +0xA9,0xDA,0xB9,0xB2,0x75,0x1B,0xF6,0x42,0xF2,0xEF,0xC7,0xF2,0x18,0xF9,0x89,0xBC, +0xA3,0xFF,0x8A,0x23,0x2E,0x70,0x47, +}; + + +/* subject:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC */ +/* issuer :/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC */ + + +const unsigned char UTN_DATACorp_SGC_Root_CA_certificate[1122]={ +0x30,0x82,0x04,0x5E,0x30,0x82,0x03,0x46,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x44, +0xBE,0x0C,0x8B,0x50,0x00,0x21,0xB4,0x11,0xD3,0x2A,0x68,0x06,0xA9,0xAD,0x69,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x93,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20, +0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54, +0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72, +0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x03, +0x13,0x12,0x55,0x54,0x4E,0x20,0x2D,0x20,0x44,0x41,0x54,0x41,0x43,0x6F,0x72,0x70, +0x20,0x53,0x47,0x43,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x34,0x31,0x38, +0x35,0x37,0x32,0x31,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,0x34,0x31,0x39,0x30, +0x36,0x33,0x30,0x5A,0x30,0x81,0x93,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06, +0x13,0x02,0x55,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55, +0x54,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74, +0x20,0x4C,0x61,0x6B,0x65,0x20,0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03, +0x55,0x04,0x0A,0x13,0x15,0x54,0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55, +0x53,0x54,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03, +0x55,0x04,0x0B,0x13,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E, +0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1B,0x30, +0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x55,0x54,0x4E,0x20,0x2D,0x20,0x44,0x41, +0x54,0x41,0x43,0x6F,0x72,0x70,0x20,0x53,0x47,0x43,0x30,0x82,0x01,0x22,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01, +0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDF,0xEE,0x58,0x10,0xA2, +0x2B,0x6E,0x55,0xC4,0x8E,0xBF,0x2E,0x46,0x09,0xE7,0xE0,0x08,0x0F,0x2E,0x2B,0x7A, +0x13,0x94,0x1B,0xBD,0xF6,0xB6,0x80,0x8E,0x65,0x05,0x93,0x00,0x1E,0xBC,0xAF,0xE2, +0x0F,0x8E,0x19,0x0D,0x12,0x47,0xEC,0xAC,0xAD,0xA3,0xFA,0x2E,0x70,0xF8,0xDE,0x6E, +0xFB,0x56,0x42,0x15,0x9E,0x2E,0x5C,0xEF,0x23,0xDE,0x21,0xB9,0x05,0x76,0x27,0x19, +0x0F,0x4F,0xD6,0xC3,0x9C,0xB4,0xBE,0x94,0x19,0x63,0xF2,0xA6,0x11,0x0A,0xEB,0x53, +0x48,0x9C,0xBE,0xF2,0x29,0x3B,0x16,0xE8,0x1A,0xA0,0x4C,0xA6,0xC9,0xF4,0x18,0x59, +0x68,0xC0,0x70,0xF2,0x53,0x00,0xC0,0x5E,0x50,0x82,0xA5,0x56,0x6F,0x36,0xF9,0x4A, +0xE0,0x44,0x86,0xA0,0x4D,0x4E,0xD6,0x47,0x6E,0x49,0x4A,0xCB,0x67,0xD7,0xA6,0xC4, +0x05,0xB9,0x8E,0x1E,0xF4,0xFC,0xFF,0xCD,0xE7,0x36,0xE0,0x9C,0x05,0x6C,0xB2,0x33, +0x22,0x15,0xD0,0xB4,0xE0,0xCC,0x17,0xC0,0xB2,0xC0,0xF4,0xFE,0x32,0x3F,0x29,0x2A, +0x95,0x7B,0xD8,0xF2,0xA7,0x4E,0x0F,0x54,0x7C,0xA1,0x0D,0x80,0xB3,0x09,0x03,0xC1, +0xFF,0x5C,0xDD,0x5E,0x9A,0x3E,0xBC,0xAE,0xBC,0x47,0x8A,0x6A,0xAE,0x71,0xCA,0x1F, +0xB1,0x2A,0xB8,0x5F,0x42,0x05,0x0B,0xEC,0x46,0x30,0xD1,0x72,0x0B,0xCA,0xE9,0x56, +0x6D,0xF5,0xEF,0xDF,0x78,0xBE,0x61,0xBA,0xB2,0xA5,0xAE,0x04,0x4C,0xBC,0xA8,0xAC, +0x69,0x15,0x97,0xBD,0xEF,0xEB,0xB4,0x8C,0xBF,0x35,0xF8,0xD4,0xC3,0xD1,0x28,0x0E, +0x5C,0x3A,0x9F,0x70,0x18,0x33,0x20,0x77,0xC4,0xA2,0xAF,0x02,0x03,0x01,0x00,0x01, +0xA3,0x81,0xAB,0x30,0x81,0xA8,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03, +0x02,0x01,0xC6,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x53, +0x32,0xD1,0xB3,0xCF,0x7F,0xFA,0xE0,0xF1,0xA0,0x5D,0x85,0x4E,0x92,0xD2,0x9E,0x45, +0x1D,0xB4,0x4F,0x30,0x3D,0x06,0x03,0x55,0x1D,0x1F,0x04,0x36,0x30,0x34,0x30,0x32, +0xA0,0x30,0xA0,0x2E,0x86,0x2C,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C, +0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x55, +0x54,0x4E,0x2D,0x44,0x41,0x54,0x41,0x43,0x6F,0x72,0x70,0x53,0x47,0x43,0x2E,0x63, +0x72,0x6C,0x30,0x2A,0x06,0x03,0x55,0x1D,0x25,0x04,0x23,0x30,0x21,0x06,0x08,0x2B, +0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x0A,0x2B,0x06,0x01,0x04,0x01,0x82,0x37, +0x0A,0x03,0x03,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x04,0x01,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01, +0x01,0x00,0x27,0x35,0x97,0x00,0x8A,0x8B,0x28,0xBD,0xC6,0x33,0x30,0x1E,0x29,0xFC, +0xE2,0xF7,0xD5,0x98,0xD4,0x40,0xBB,0x60,0xCA,0xBF,0xAB,0x17,0x2C,0x09,0x36,0x7F, +0x50,0xFA,0x41,0xDC,0xAE,0x96,0x3A,0x0A,0x23,0x3E,0x89,0x59,0xC9,0xA3,0x07,0xED, +0x1B,0x37,0xAD,0xFC,0x7C,0xBE,0x51,0x49,0x5A,0xDE,0x3A,0x0A,0x54,0x08,0x16,0x45, +0xC2,0x99,0xB1,0x87,0xCD,0x8C,0x68,0xE0,0x69,0x03,0xE9,0xC4,0x4E,0x98,0xB2,0x3B, +0x8C,0x16,0xB3,0x0E,0xA0,0x0C,0x98,0x50,0x9B,0x93,0xA9,0x70,0x09,0xC8,0x2C,0xA3, +0x8F,0xDF,0x02,0xE4,0xE0,0x71,0x3A,0xF1,0xB4,0x23,0x72,0xA0,0xAA,0x01,0xDF,0xDF, +0x98,0x3E,0x14,0x50,0xA0,0x31,0x26,0xBD,0x28,0xE9,0x5A,0x30,0x26,0x75,0xF9,0x7B, +0x60,0x1C,0x8D,0xF3,0xCD,0x50,0x26,0x6D,0x04,0x27,0x9A,0xDF,0xD5,0x0D,0x45,0x47, +0x29,0x6B,0x2C,0xE6,0x76,0xD9,0xA9,0x29,0x7D,0x32,0xDD,0xC9,0x36,0x3C,0xBD,0xAE, +0x35,0xF1,0x11,0x9E,0x1D,0xBB,0x90,0x3F,0x12,0x47,0x4E,0x8E,0xD7,0x7E,0x0F,0x62, +0x73,0x1D,0x52,0x26,0x38,0x1C,0x18,0x49,0xFD,0x30,0x74,0x9A,0xC4,0xE5,0x22,0x2F, +0xD8,0xC0,0x8D,0xED,0x91,0x7A,0x4C,0x00,0x8F,0x72,0x7F,0x5D,0xDA,0xDD,0x1B,0x8B, +0x45,0x6B,0xE7,0xDD,0x69,0x97,0xA8,0xC5,0x56,0x4C,0x0F,0x0C,0xF6,0x9F,0x7A,0x91, +0x37,0xF6,0x97,0x82,0xE0,0xDD,0x71,0x69,0xFF,0x76,0x3F,0x60,0x4D,0x3C,0xCF,0xF7, +0x99,0xF9,0xC6,0x57,0xF4,0xC9,0x55,0x39,0x78,0xBA,0x2C,0x79,0xC9,0xA6,0x88,0x2B, +0xF4,0x08, +}; + + +/* subject:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware */ +/* issuer :/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware */ + + +const unsigned char UTN_USERFirst_Hardware_Root_CA_certificate[1144]={ +0x30,0x82,0x04,0x74,0x30,0x82,0x03,0x5C,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x44, +0xBE,0x0C,0x8B,0x50,0x00,0x24,0xB4,0x11,0xD3,0x36,0x2A,0xFE,0x65,0x0A,0xFD,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x97,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20, +0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54, +0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68, +0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72, +0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03, +0x13,0x16,0x55,0x54,0x4E,0x2D,0x55,0x53,0x45,0x52,0x46,0x69,0x72,0x73,0x74,0x2D, +0x48,0x61,0x72,0x64,0x77,0x61,0x72,0x65,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x37, +0x30,0x39,0x31,0x38,0x31,0x30,0x34,0x32,0x5A,0x17,0x0D,0x31,0x39,0x30,0x37,0x30, +0x39,0x31,0x38,0x31,0x39,0x32,0x32,0x5A,0x30,0x81,0x97,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04, +0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x07,0x13,0x0E, +0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20,0x43,0x69,0x74,0x79,0x31,0x1E, +0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54,0x68,0x65,0x20,0x55,0x53,0x45, +0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21, +0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F, +0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F, +0x6D,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,0x55,0x54,0x4E,0x2D, +0x55,0x53,0x45,0x52,0x46,0x69,0x72,0x73,0x74,0x2D,0x48,0x61,0x72,0x64,0x77,0x61, +0x72,0x65,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82, +0x01,0x01,0x00,0xB1,0xF7,0xC3,0x38,0x3F,0xB4,0xA8,0x7F,0xCF,0x39,0x82,0x51,0x67, +0xD0,0x6D,0x9F,0xD2,0xFF,0x58,0xF3,0xE7,0x9F,0x2B,0xEC,0x0D,0x89,0x54,0x99,0xB9, +0x38,0x99,0x16,0xF7,0xE0,0x21,0x79,0x48,0xC2,0xBB,0x61,0x74,0x12,0x96,0x1D,0x3C, +0x6A,0x72,0xD5,0x3C,0x10,0x67,0x3A,0x39,0xED,0x2B,0x13,0xCD,0x66,0xEB,0x95,0x09, +0x33,0xA4,0x6C,0x97,0xB1,0xE8,0xC6,0xEC,0xC1,0x75,0x79,0x9C,0x46,0x5E,0x8D,0xAB, +0xD0,0x6A,0xFD,0xB9,0x2A,0x55,0x17,0x10,0x54,0xB3,0x19,0xF0,0x9A,0xF6,0xF1,0xB1, +0x5D,0xB6,0xA7,0x6D,0xFB,0xE0,0x71,0x17,0x6B,0xA2,0x88,0xFB,0x00,0xDF,0xFE,0x1A, +0x31,0x77,0x0C,0x9A,0x01,0x7A,0xB1,0x32,0xE3,0x2B,0x01,0x07,0x38,0x6E,0xC3,0xA5, +0x5E,0x23,0xBC,0x45,0x9B,0x7B,0x50,0xC1,0xC9,0x30,0x8F,0xDB,0xE5,0x2B,0x7A,0xD3, +0x5B,0xFB,0x33,0x40,0x1E,0xA0,0xD5,0x98,0x17,0xBC,0x8B,0x87,0xC3,0x89,0xD3,0x5D, +0xA0,0x8E,0xB2,0xAA,0xAA,0xF6,0x8E,0x69,0x88,0x06,0xC5,0xFA,0x89,0x21,0xF3,0x08, +0x9D,0x69,0x2E,0x09,0x33,0x9B,0x29,0x0D,0x46,0x0F,0x8C,0xCC,0x49,0x34,0xB0,0x69, +0x51,0xBD,0xF9,0x06,0xCD,0x68,0xAD,0x66,0x4C,0xBC,0x3E,0xAC,0x61,0xBD,0x0A,0x88, +0x0E,0xC8,0xDF,0x3D,0xEE,0x7C,0x04,0x4C,0x9D,0x0A,0x5E,0x6B,0x91,0xD6,0xEE,0xC7, +0xED,0x28,0x8D,0xAB,0x4D,0x87,0x89,0x73,0xD0,0x6E,0xA4,0xD0,0x1E,0x16,0x8B,0x14, +0xE1,0x76,0x44,0x03,0x7F,0x63,0xAC,0xE4,0xCD,0x49,0x9C,0xC5,0x92,0xF4,0xAB,0x32, +0xA1,0x48,0x5B,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xB9,0x30,0x81,0xB6,0x30,0x0B, +0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0xC6,0x30,0x0F,0x06,0x03,0x55, +0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03, +0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xA1,0x72,0x5F,0x26,0x1B,0x28,0x98,0x43,0x95, +0x5D,0x07,0x37,0xD5,0x85,0x96,0x9D,0x4B,0xD2,0xC3,0x45,0x30,0x44,0x06,0x03,0x55, +0x1D,0x1F,0x04,0x3D,0x30,0x3B,0x30,0x39,0xA0,0x37,0xA0,0x35,0x86,0x33,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75, +0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x55,0x54,0x4E,0x2D,0x55,0x53,0x45,0x52,0x46, +0x69,0x72,0x73,0x74,0x2D,0x48,0x61,0x72,0x64,0x77,0x61,0x72,0x65,0x2E,0x63,0x72, +0x6C,0x30,0x31,0x06,0x03,0x55,0x1D,0x25,0x04,0x2A,0x30,0x28,0x06,0x08,0x2B,0x06, +0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x05, +0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x06,0x06,0x08,0x2B,0x06,0x01,0x05, +0x05,0x07,0x03,0x07,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x47,0x19,0x0F,0xDE,0x74,0xC6,0x99,0x97, +0xAF,0xFC,0xAD,0x28,0x5E,0x75,0x8E,0xEB,0x2D,0x67,0xEE,0x4E,0x7B,0x2B,0xD7,0x0C, +0xFF,0xF6,0xDE,0xCB,0x55,0xA2,0x0A,0xE1,0x4C,0x54,0x65,0x93,0x60,0x6B,0x9F,0x12, +0x9C,0xAD,0x5E,0x83,0x2C,0xEB,0x5A,0xAE,0xC0,0xE4,0x2D,0xF4,0x00,0x63,0x1D,0xB8, +0xC0,0x6C,0xF2,0xCF,0x49,0xBB,0x4D,0x93,0x6F,0x06,0xA6,0x0A,0x22,0xB2,0x49,0x62, +0x08,0x4E,0xFF,0xC8,0xC8,0x14,0xB2,0x88,0x16,0x5D,0xE7,0x01,0xE4,0x12,0x95,0xE5, +0x45,0x34,0xB3,0x8B,0x69,0xBD,0xCF,0xB4,0x85,0x8F,0x75,0x51,0x9E,0x7D,0x3A,0x38, +0x3A,0x14,0x48,0x12,0xC6,0xFB,0xA7,0x3B,0x1A,0x8D,0x0D,0x82,0x40,0x07,0xE8,0x04, +0x08,0x90,0xA1,0x89,0xCB,0x19,0x50,0xDF,0xCA,0x1C,0x01,0xBC,0x1D,0x04,0x19,0x7B, +0x10,0x76,0x97,0x3B,0xEE,0x90,0x90,0xCA,0xC4,0x0E,0x1F,0x16,0x6E,0x75,0xEF,0x33, +0xF8,0xD3,0x6F,0x5B,0x1E,0x96,0xE3,0xE0,0x74,0x77,0x74,0x7B,0x8A,0xA2,0x6E,0x2D, +0xDD,0x76,0xD6,0x39,0x30,0x82,0xF0,0xAB,0x9C,0x52,0xF2,0x2A,0xC7,0xAF,0x49,0x5E, +0x7E,0xC7,0x68,0xE5,0x82,0x81,0xC8,0x6A,0x27,0xF9,0x27,0x88,0x2A,0xD5,0x58,0x50, +0x95,0x1F,0xF0,0x3B,0x1C,0x57,0xBB,0x7D,0x14,0x39,0x62,0x2B,0x9A,0xC9,0x94,0x92, +0x2A,0xA3,0x22,0x0C,0xFF,0x89,0x26,0x7D,0x5F,0x23,0x2B,0x47,0xD7,0x15,0x1D,0xA9, +0x6A,0x9E,0x51,0x0D,0x2A,0x51,0x9E,0x81,0xF9,0xD4,0x3B,0x5E,0x70,0x12,0x7F,0x10, +0x32,0x9C,0x1E,0xBB,0x9D,0xF8,0x66,0xA8, +}; + + +/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 1 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ +/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 1 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ + + +const unsigned char ValiCert_Class_1_VA_certificate[747]={ +0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30, +0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74, +0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61, +0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33, +0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72, +0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69, +0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36, +0x32,0x35,0x32,0x32,0x32,0x33,0x34,0x38,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32, +0x35,0x32,0x32,0x32,0x33,0x34,0x38,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06, +0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69, +0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03, +0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x31,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C, +0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E, +0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02, +0x81,0x81,0x00,0xD8,0x59,0x82,0x7A,0x89,0xB8,0x96,0xBA,0xA6,0x2F,0x68,0x6F,0x58, +0x2E,0xA7,0x54,0x1C,0x06,0x6E,0xF4,0xEA,0x8D,0x48,0xBC,0x31,0x94,0x17,0xF0,0xF3, +0x4E,0xBC,0xB2,0xB8,0x35,0x92,0x76,0xB0,0xD0,0xA5,0xA5,0x01,0xD7,0x00,0x03,0x12, +0x22,0x19,0x08,0xF8,0xFF,0x11,0x23,0x9B,0xCE,0x07,0xF5,0xBF,0x69,0x1A,0x26,0xFE, +0x4E,0xE9,0xD1,0x7F,0x9D,0x2C,0x40,0x1D,0x59,0x68,0x6E,0xA6,0xF8,0x58,0xB0,0x9D, +0x1A,0x8F,0xD3,0x3F,0xF1,0xDC,0x19,0x06,0x81,0xA8,0x0E,0xE0,0x3A,0xDD,0xC8,0x53, +0x45,0x09,0x06,0xE6,0x0F,0x70,0xC3,0xFA,0x40,0xA6,0x0E,0xE2,0x56,0x05,0x0F,0x18, +0x4D,0xFC,0x20,0x82,0xD1,0x73,0x55,0x74,0x8D,0x76,0x72,0xA0,0x1D,0x9D,0x1D,0xC0, +0xDD,0x3F,0x71,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x50,0x68,0x3D,0x49,0xF4, +0x2C,0x1C,0x06,0x94,0xDF,0x95,0x60,0x7F,0x96,0x7B,0x17,0xFE,0x4F,0x71,0xAD,0x64, +0xC8,0xDD,0x77,0xD2,0xEF,0x59,0x55,0xE8,0x3F,0xE8,0x8E,0x05,0x2A,0x21,0xF2,0x07, +0xD2,0xB5,0xA7,0x52,0xFE,0x9C,0xB1,0xB6,0xE2,0x5B,0x77,0x17,0x40,0xEA,0x72,0xD6, +0x23,0xCB,0x28,0x81,0x32,0xC3,0x00,0x79,0x18,0xEC,0x59,0x17,0x89,0xC9,0xC6,0x6A, +0x1E,0x71,0xC9,0xFD,0xB7,0x74,0xA5,0x25,0x45,0x69,0xC5,0x48,0xAB,0x19,0xE1,0x45, +0x8A,0x25,0x6B,0x19,0xEE,0xE5,0xBB,0x12,0xF5,0x7F,0xF7,0xA6,0x8D,0x51,0xC3,0xF0, +0x9D,0x74,0xB7,0xA9,0x3E,0xA0,0xA5,0xFF,0xB6,0x49,0x03,0x13,0xDA,0x22,0xCC,0xED, +0x71,0x82,0x2B,0x99,0xCF,0x3A,0xB7,0xF5,0x2D,0x72,0xC8, +}; + + +/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ +/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */ + + +const unsigned char ValiCert_Class_2_VA_certificate[747]={ +0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30, +0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74, +0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77, +0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61, +0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33, +0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20, +0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74, +0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72, +0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69, +0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36, +0x32,0x36,0x30,0x30,0x31,0x39,0x35,0x34,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32, +0x36,0x30,0x30,0x31,0x39,0x35,0x34,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06, +0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56, +0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69, +0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03, +0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C, +0x61,0x73,0x73,0x20,0x32,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C, +0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74, +0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70, +0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E, +0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D, +0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65, +0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02, +0x81,0x81,0x00,0xCE,0x3A,0x71,0xCA,0xE5,0xAB,0xC8,0x59,0x92,0x55,0xD7,0xAB,0xD8, +0x74,0x0E,0xF9,0xEE,0xD9,0xF6,0x55,0x47,0x59,0x65,0x47,0x0E,0x05,0x55,0xDC,0xEB, +0x98,0x36,0x3C,0x5C,0x53,0x5D,0xD3,0x30,0xCF,0x38,0xEC,0xBD,0x41,0x89,0xED,0x25, +0x42,0x09,0x24,0x6B,0x0A,0x5E,0xB3,0x7C,0xDD,0x52,0x2D,0x4C,0xE6,0xD4,0xD6,0x7D, +0x5A,0x59,0xA9,0x65,0xD4,0x49,0x13,0x2D,0x24,0x4D,0x1C,0x50,0x6F,0xB5,0xC1,0x85, +0x54,0x3B,0xFE,0x71,0xE4,0xD3,0x5C,0x42,0xF9,0x80,0xE0,0x91,0x1A,0x0A,0x5B,0x39, +0x36,0x67,0xF3,0x3F,0x55,0x7C,0x1B,0x3F,0xB4,0x5F,0x64,0x73,0x34,0xE3,0xB4,0x12, +0xBF,0x87,0x64,0xF8,0xDA,0x12,0xFF,0x37,0x27,0xC1,0xB3,0x43,0xBB,0xEF,0x7B,0x6E, +0x2E,0x69,0xF7,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x3B,0x7F,0x50,0x6F,0x6F, +0x50,0x94,0x99,0x49,0x62,0x38,0x38,0x1F,0x4B,0xF8,0xA5,0xC8,0x3E,0xA7,0x82,0x81, +0xF6,0x2B,0xC7,0xE8,0xC5,0xCE,0xE8,0x3A,0x10,0x82,0xCB,0x18,0x00,0x8E,0x4D,0xBD, +0xA8,0x58,0x7F,0xA1,0x79,0x00,0xB5,0xBB,0xE9,0x8D,0xAF,0x41,0xD9,0x0F,0x34,0xEE, +0x21,0x81,0x19,0xA0,0x32,0x49,0x28,0xF4,0xC4,0x8E,0x56,0xD5,0x52,0x33,0xFD,0x50, +0xD5,0x7E,0x99,0x6C,0x03,0xE4,0xC9,0x4C,0xFC,0xCB,0x6C,0xAB,0x66,0xB3,0x4A,0x21, +0x8C,0xE5,0xB5,0x0C,0x32,0x3E,0x10,0xB2,0xCC,0x6C,0xA1,0xDC,0x9A,0x98,0x4C,0x02, +0x5B,0xF3,0xCE,0xB9,0x9E,0xA5,0x72,0x0E,0x4A,0xB7,0x3F,0x3C,0xE6,0x16,0x68,0xF8, +0xBE,0xED,0x74,0x4C,0xBC,0x5B,0xD5,0x62,0x1F,0x43,0xDD, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */ +/* issuer :/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */ + + +const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority_certificate[576]={ +0x30,0x82,0x02,0x3C,0x30,0x82,0x01,0xA5,0x02,0x10,0x3C,0x91,0x31,0xCB,0x1F,0xF6, +0xD0,0x1B,0x0E,0x9A,0xB8,0xD0,0x44,0xBF,0x12,0xBE,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x5F,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x37,0x30,0x35,0x06,0x03,0x55,0x04,0x0B,0x13,0x2E,0x43,0x6C,0x61,0x73, +0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61, +0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E, +0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x36, +0x30,0x31,0x32,0x39,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x38,0x30, +0x38,0x30,0x32,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x5F,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55, +0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x37,0x30,0x35,0x06,0x03,0x55,0x04,0x0B,0x13,0x2E,0x43,0x6C,0x61, +0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D, +0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x81,0x9F,0x30,0x0D, +0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D, +0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xC9,0x5C,0x59,0x9E,0xF2,0x1B,0x8A,0x01, +0x14,0xB4,0x10,0xDF,0x04,0x40,0xDB,0xE3,0x57,0xAF,0x6A,0x45,0x40,0x8F,0x84,0x0C, +0x0B,0xD1,0x33,0xD9,0xD9,0x11,0xCF,0xEE,0x02,0x58,0x1F,0x25,0xF7,0x2A,0xA8,0x44, +0x05,0xAA,0xEC,0x03,0x1F,0x78,0x7F,0x9E,0x93,0xB9,0x9A,0x00,0xAA,0x23,0x7D,0xD6, +0xAC,0x85,0xA2,0x63,0x45,0xC7,0x72,0x27,0xCC,0xF4,0x4C,0xC6,0x75,0x71,0xD2,0x39, +0xEF,0x4F,0x42,0xF0,0x75,0xDF,0x0A,0x90,0xC6,0x8E,0x20,0x6F,0x98,0x0F,0xF8,0xAC, +0x23,0x5F,0x70,0x29,0x36,0xA4,0xC9,0x86,0xE7,0xB1,0x9A,0x20,0xCB,0x53,0xA5,0x85, +0xE7,0x3D,0xBE,0x7D,0x9A,0xFE,0x24,0x45,0x33,0xDC,0x76,0x15,0xED,0x0F,0xA2,0x71, +0x64,0x4C,0x65,0x2E,0x81,0x68,0x45,0xA7,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06, +0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00, +0x10,0x72,0x52,0xA9,0x05,0x14,0x19,0x32,0x08,0x41,0xF0,0xC5,0x6B,0x0A,0xCC,0x7E, +0x0F,0x21,0x19,0xCD,0xE4,0x67,0xDC,0x5F,0xA9,0x1B,0xE6,0xCA,0xE8,0x73,0x9D,0x22, +0xD8,0x98,0x6E,0x73,0x03,0x61,0x91,0xC5,0x7C,0xB0,0x45,0x40,0x6E,0x44,0x9D,0x8D, +0xB0,0xB1,0x96,0x74,0x61,0x2D,0x0D,0xA9,0x45,0xD2,0xA4,0x92,0x2A,0xD6,0x9A,0x75, +0x97,0x6E,0x3F,0x53,0xFD,0x45,0x99,0x60,0x1D,0xA8,0x2B,0x4C,0xF9,0x5E,0xA7,0x09, +0xD8,0x75,0x30,0xD7,0xD2,0x65,0x60,0x3D,0x67,0xD6,0x48,0x55,0x75,0x69,0x3F,0x91, +0xF5,0x48,0x0B,0x47,0x69,0x22,0x69,0x82,0x96,0xBE,0xC9,0xC8,0x38,0x86,0x4A,0x7A, +0x2C,0x73,0x19,0x48,0x69,0x4E,0x6B,0x7C,0x65,0xBF,0x0F,0xFC,0x70,0xCE,0x88,0x90, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network */ +/* issuer :/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network */ + + +const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority___G2_certificate[774]={ +0x30,0x82,0x03,0x02,0x30,0x82,0x02,0x6B,0x02,0x10,0x7D,0xD9,0xFE,0x07,0xCF,0xA8, +0x1E,0xB7,0x10,0x79,0x67,0xFB,0xA7,0x89,0x34,0xC6,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xC1,0x31,0x0B,0x30,0x09, +0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55, +0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x31,0x3C,0x30,0x3A,0x06,0x03,0x55,0x04,0x0B,0x13,0x33,0x43,0x6C,0x61, +0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D, +0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F, +0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32, +0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x31, +0x39,0x39,0x38,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E, +0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69, +0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D, +0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20, +0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x30,0x1E,0x17, +0x0D,0x39,0x38,0x30,0x35,0x31,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D, +0x32,0x38,0x30,0x38,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xC1, +0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30, +0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3C,0x30,0x3A,0x06,0x03,0x55,0x04,0x0B,0x13, +0x33,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x32,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x38,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74, +0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79, +0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01, +0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xCC,0x5E, +0xD1,0x11,0x5D,0x5C,0x69,0xD0,0xAB,0xD3,0xB9,0x6A,0x4C,0x99,0x1F,0x59,0x98,0x30, +0x8E,0x16,0x85,0x20,0x46,0x6D,0x47,0x3F,0xD4,0x85,0x20,0x84,0xE1,0x6D,0xB3,0xF8, +0xA4,0xED,0x0C,0xF1,0x17,0x0F,0x3B,0xF9,0xA7,0xF9,0x25,0xD7,0xC1,0xCF,0x84,0x63, +0xF2,0x7C,0x63,0xCF,0xA2,0x47,0xF2,0xC6,0x5B,0x33,0x8E,0x64,0x40,0x04,0x68,0xC1, +0x80,0xB9,0x64,0x1C,0x45,0x77,0xC7,0xD8,0x6E,0xF5,0x95,0x29,0x3C,0x50,0xE8,0x34, +0xD7,0x78,0x1F,0xA8,0xBA,0x6D,0x43,0x91,0x95,0x8F,0x45,0x57,0x5E,0x7E,0xC5,0xFB, +0xCA,0xA4,0x04,0xEB,0xEA,0x97,0x37,0x54,0x30,0x6F,0xBB,0x01,0x47,0x32,0x33,0xCD, +0xDC,0x57,0x9B,0x64,0x69,0x61,0xF8,0x9B,0x1D,0x1C,0x89,0x4F,0x5C,0x67,0x02,0x03, +0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x03,0x81,0x81,0x00,0x51,0x4D,0xCD,0xBE,0x5C,0xCB,0x98,0x19,0x9C,0x15, +0xB2,0x01,0x39,0x78,0x2E,0x4D,0x0F,0x67,0x70,0x70,0x99,0xC6,0x10,0x5A,0x94,0xA4, +0x53,0x4D,0x54,0x6D,0x2B,0xAF,0x0D,0x5D,0x40,0x8B,0x64,0xD3,0xD7,0xEE,0xDE,0x56, +0x61,0x92,0x5F,0xA6,0xC4,0x1D,0x10,0x61,0x36,0xD3,0x2C,0x27,0x3C,0xE8,0x29,0x09, +0xB9,0x11,0x64,0x74,0xCC,0xB5,0x73,0x9F,0x1C,0x48,0xA9,0xBC,0x61,0x01,0xEE,0xE2, +0x17,0xA6,0x0C,0xE3,0x40,0x08,0x3B,0x0E,0xE7,0xEB,0x44,0x73,0x2A,0x9A,0xF1,0x69, +0x92,0xEF,0x71,0x14,0xC3,0x39,0xAC,0x71,0xA7,0x91,0x09,0x6F,0xE4,0x71,0x06,0xB3, +0xBA,0x59,0x57,0x26,0x79,0x00,0xF6,0xF8,0x0D,0xA2,0x33,0x30,0x28,0xD4,0xAA,0x58, +0xA0,0x9D,0x9D,0x69,0x91,0xFD, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3 */ + + +const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority___G3_certificate[1054]={ +0x30,0x82,0x04,0x1A,0x30,0x82,0x03,0x02,0x02,0x11,0x00,0x9B,0x7E,0x06,0x49,0xA3, +0x3E,0x62,0xB9,0xD5,0xEE,0x90,0x48,0x71,0x29,0xEF,0x57,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xCA,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65, +0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74, +0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79, +0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C, +0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31,0x30,0x30, +0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31,0x36, +0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E, +0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20, +0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72, +0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30, +0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xCB,0xBA,0x9C,0x52,0xFC,0x78,0x1F,0x1A,0x1E,0x6F,0x1B, +0x37,0x73,0xBD,0xF8,0xC9,0x6B,0x94,0x12,0x30,0x4F,0xF0,0x36,0x47,0xF5,0xD0,0x91, +0x0A,0xF5,0x17,0xC8,0xA5,0x61,0xC1,0x16,0x40,0x4D,0xFB,0x8A,0x61,0x90,0xE5,0x76, +0x20,0xC1,0x11,0x06,0x7D,0xAB,0x2C,0x6E,0xA6,0xF5,0x11,0x41,0x8E,0xFA,0x2D,0xAD, +0x2A,0x61,0x59,0xA4,0x67,0x26,0x4C,0xD0,0xE8,0xBC,0x52,0x5B,0x70,0x20,0x04,0x58, +0xD1,0x7A,0xC9,0xA4,0x69,0xBC,0x83,0x17,0x64,0xAD,0x05,0x8B,0xBC,0xD0,0x58,0xCE, +0x8D,0x8C,0xF5,0xEB,0xF0,0x42,0x49,0x0B,0x9D,0x97,0x27,0x67,0x32,0x6E,0xE1,0xAE, +0x93,0x15,0x1C,0x70,0xBC,0x20,0x4D,0x2F,0x18,0xDE,0x92,0x88,0xE8,0x6C,0x85,0x57, +0x11,0x1A,0xE9,0x7E,0xE3,0x26,0x11,0x54,0xA2,0x45,0x96,0x55,0x83,0xCA,0x30,0x89, +0xE8,0xDC,0xD8,0xA3,0xED,0x2A,0x80,0x3F,0x7F,0x79,0x65,0x57,0x3E,0x15,0x20,0x66, +0x08,0x2F,0x95,0x93,0xBF,0xAA,0x47,0x2F,0xA8,0x46,0x97,0xF0,0x12,0xE2,0xFE,0xC2, +0x0A,0x2B,0x51,0xE6,0x76,0xE6,0xB7,0x46,0xB7,0xE2,0x0D,0xA6,0xCC,0xA8,0xC3,0x4C, +0x59,0x55,0x89,0xE6,0xE8,0x53,0x5C,0x1C,0xEA,0x9D,0xF0,0x62,0x16,0x0B,0xA7,0xC9, +0x5F,0x0C,0xF0,0xDE,0xC2,0x76,0xCE,0xAF,0xF7,0x6A,0xF2,0xFA,0x41,0xA6,0xA2,0x33, +0x14,0xC9,0xE5,0x7A,0x63,0xD3,0x9E,0x62,0x37,0xD5,0x85,0x65,0x9E,0x0E,0xE6,0x53, +0x24,0x74,0x1B,0x5E,0x1D,0x12,0x53,0x5B,0xC7,0x2C,0xE7,0x83,0x49,0x3B,0x15,0xAE, +0x8A,0x68,0xB9,0x57,0x97,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x11,0x14, +0x96,0xC1,0xAB,0x92,0x08,0xF7,0x3F,0x2F,0xC9,0xB2,0xFE,0xE4,0x5A,0x9F,0x64,0xDE, +0xDB,0x21,0x4F,0x86,0x99,0x34,0x76,0x36,0x57,0xDD,0xD0,0x15,0x2F,0xC5,0xAD,0x7F, +0x15,0x1F,0x37,0x62,0x73,0x3E,0xD4,0xE7,0x5F,0xCE,0x17,0x03,0xDB,0x35,0xFA,0x2B, +0xDB,0xAE,0x60,0x09,0x5F,0x1E,0x5F,0x8F,0x6E,0xBB,0x0B,0x3D,0xEA,0x5A,0x13,0x1E, +0x0C,0x60,0x6F,0xB5,0xC0,0xB5,0x23,0x22,0x2E,0x07,0x0B,0xCB,0xA9,0x74,0xCB,0x47, +0xBB,0x1D,0xC1,0xD7,0xA5,0x6B,0xCC,0x2F,0xD2,0x42,0xFD,0x49,0xDD,0xA7,0x89,0xCF, +0x53,0xBA,0xDA,0x00,0x5A,0x28,0xBF,0x82,0xDF,0xF8,0xBA,0x13,0x1D,0x50,0x86,0x82, +0xFD,0x8E,0x30,0x8F,0x29,0x46,0xB0,0x1E,0x3D,0x35,0xDA,0x38,0x62,0x16,0x18,0x4A, +0xAD,0xE6,0xB6,0x51,0x6C,0xDE,0xAF,0x62,0xEB,0x01,0xD0,0x1E,0x24,0xFE,0x7A,0x8F, +0x12,0x1A,0x12,0x68,0xB8,0xFB,0x66,0x99,0x14,0x14,0x45,0x5C,0xAE,0xE7,0xAE,0x69, +0x17,0x81,0x2B,0x5A,0x37,0xC9,0x5E,0x2A,0xF4,0xC6,0xE2,0xA1,0x5C,0x54,0x9B,0xA6, +0x54,0x00,0xCF,0xF0,0xF1,0xC1,0xC7,0x98,0x30,0x1A,0x3B,0x36,0x16,0xDB,0xA3,0x6E, +0xEA,0xFD,0xAD,0xB2,0xC2,0xDA,0xEF,0x02,0x47,0x13,0x8A,0xC0,0xF1,0xB3,0x31,0xAD, +0x4F,0x1C,0xE1,0x4F,0x9C,0xAF,0x0F,0x0C,0x9D,0xF7,0x78,0x0D,0xD8,0xF4,0x35,0x56, +0x80,0xDA,0xB7,0x6D,0x17,0x8F,0x9D,0x1E,0x81,0x64,0xE1,0xFE,0xC5,0x45,0xBA,0xAD, +0x6B,0xB9,0x0A,0x7A,0x4E,0x4F,0x4B,0x84,0xEE,0x4B,0xF1,0x7D,0xDD,0x11, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4 */ + + +const unsigned char VeriSign_Class_3_Public_Primary_Certification_Authority___G4_certificate[904]={ +0x30,0x82,0x03,0x84,0x30,0x82,0x03,0x0A,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x2F, +0x80,0xFE,0x23,0x8C,0x0E,0x22,0x0F,0x48,0x67,0x12,0x28,0x91,0x87,0xAC,0xB3,0x30, +0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0xCA,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65, +0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31, +0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75, +0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C, +0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62, +0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x34,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31, +0x30,0x35,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31, +0x38,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06, +0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04, +0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63, +0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F, +0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29, +0x20,0x32,0x30,0x30,0x37,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F, +0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45, +0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63, +0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69, +0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79, +0x20,0x2D,0x20,0x47,0x34,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D, +0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0xA7,0x56,0x7A, +0x7C,0x52,0xDA,0x64,0x9B,0x0E,0x2D,0x5C,0xD8,0x5E,0xAC,0x92,0x3D,0xFE,0x01,0xE6, +0x19,0x4A,0x3D,0x14,0x03,0x4B,0xFA,0x60,0x27,0x20,0xD9,0x83,0x89,0x69,0xFA,0x54, +0xC6,0x9A,0x18,0x5E,0x55,0x2A,0x64,0xDE,0x06,0xF6,0x8D,0x4A,0x3B,0xAD,0x10,0x3C, +0x65,0x3D,0x90,0x88,0x04,0x89,0xE0,0x30,0x61,0xB3,0xAE,0x5D,0x01,0xA7,0x7B,0xDE, +0x7C,0xB2,0xBE,0xCA,0x65,0x61,0x00,0x86,0xAE,0xDA,0x8F,0x7B,0xD0,0x89,0xAD,0x4D, +0x1D,0x59,0x9A,0x41,0xB1,0xBC,0x47,0x80,0xDC,0x9E,0x62,0xC3,0xF9,0xA3,0x81,0xB2, +0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30, +0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04, +0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01,0x0C, +0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30,0x59,0x30,0x57,0x30,0x55,0x16,0x09, +0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66,0x30,0x21,0x30,0x1F,0x30,0x07,0x06, +0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F,0xE5,0xD3,0x1A,0x86,0xAC,0x8D,0x8E, +0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C,0x7B,0x19,0x2E,0x30,0x25,0x16,0x23, +0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F,0x67,0x6F,0x2E,0x76,0x65,0x72,0x69, +0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F,0x76,0x73,0x6C,0x6F,0x67,0x6F,0x2E, +0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xB3,0x16, +0x91,0xFD,0xEE,0xA6,0x6E,0xE4,0xB5,0x2E,0x49,0x8F,0x87,0x78,0x81,0x80,0xEC,0xE5, +0xB1,0xB5,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x68, +0x00,0x30,0x65,0x02,0x30,0x66,0x21,0x0C,0x18,0x26,0x60,0x5A,0x38,0x7B,0x56,0x42, +0xE0,0xA7,0xFC,0x36,0x84,0x51,0x91,0x20,0x2C,0x76,0x4D,0x43,0x3D,0xC4,0x1D,0x84, +0x23,0xD0,0xAC,0xD6,0x7C,0x35,0x06,0xCE,0xCD,0x69,0xBD,0x90,0x0D,0xDB,0x6C,0x48, +0x42,0x1D,0x0E,0xAA,0x42,0x02,0x31,0x00,0x9C,0x3D,0x48,0x39,0x23,0x39,0x58,0x1A, +0x15,0x12,0x59,0x6A,0x9E,0xEF,0xD5,0x59,0xB2,0x1D,0x52,0x2C,0x99,0x71,0xCD,0xC7, +0x29,0xDF,0x1B,0x2A,0x61,0x7B,0x71,0xD1,0xDE,0xF3,0xC0,0xE5,0x0D,0x3A,0x4A,0xAA, +0x2D,0xA7,0xD8,0x86,0x2A,0xDD,0x2E,0x10, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 */ + + +const unsigned char VeriSign_Class_3_Public_Primary_Certification_Authority___G5_certificate[1239]={ +0x30,0x82,0x04,0xD3,0x30,0x82,0x03,0xBB,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x18, +0xDA,0xD1,0x9E,0x26,0x7D,0xE8,0xBB,0x4A,0x21,0x58,0xCD,0xCC,0x6B,0x3B,0x4A,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0xCA,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B, +0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74, +0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04, +0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72, +0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20, +0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20, +0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43, +0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74, +0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x35,0x30,0x1E,0x17,0x0D,0x30, +0x36,0x31,0x31,0x30,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36, +0x30,0x37,0x31,0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B, +0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06, +0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20, +0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65, +0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31, +0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75, +0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C, +0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62, +0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x35,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00, +0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAF,0x24,0x08,0x08,0x29,0x7A,0x35, +0x9E,0x60,0x0C,0xAA,0xE7,0x4B,0x3B,0x4E,0xDC,0x7C,0xBC,0x3C,0x45,0x1C,0xBB,0x2B, +0xE0,0xFE,0x29,0x02,0xF9,0x57,0x08,0xA3,0x64,0x85,0x15,0x27,0xF5,0xF1,0xAD,0xC8, +0x31,0x89,0x5D,0x22,0xE8,0x2A,0xAA,0xA6,0x42,0xB3,0x8F,0xF8,0xB9,0x55,0xB7,0xB1, +0xB7,0x4B,0xB3,0xFE,0x8F,0x7E,0x07,0x57,0xEC,0xEF,0x43,0xDB,0x66,0x62,0x15,0x61, +0xCF,0x60,0x0D,0xA4,0xD8,0xDE,0xF8,0xE0,0xC3,0x62,0x08,0x3D,0x54,0x13,0xEB,0x49, +0xCA,0x59,0x54,0x85,0x26,0xE5,0x2B,0x8F,0x1B,0x9F,0xEB,0xF5,0xA1,0x91,0xC2,0x33, +0x49,0xD8,0x43,0x63,0x6A,0x52,0x4B,0xD2,0x8F,0xE8,0x70,0x51,0x4D,0xD1,0x89,0x69, +0x7B,0xC7,0x70,0xF6,0xB3,0xDC,0x12,0x74,0xDB,0x7B,0x5D,0x4B,0x56,0xD3,0x96,0xBF, +0x15,0x77,0xA1,0xB0,0xF4,0xA2,0x25,0xF2,0xAF,0x1C,0x92,0x67,0x18,0xE5,0xF4,0x06, +0x04,0xEF,0x90,0xB9,0xE4,0x00,0xE4,0xDD,0x3A,0xB5,0x19,0xFF,0x02,0xBA,0xF4,0x3C, +0xEE,0xE0,0x8B,0xEB,0x37,0x8B,0xEC,0xF4,0xD7,0xAC,0xF2,0xF6,0xF0,0x3D,0xAF,0xDD, +0x75,0x91,0x33,0x19,0x1D,0x1C,0x40,0xCB,0x74,0x24,0x19,0x21,0x93,0xD9,0x14,0xFE, +0xAC,0x2A,0x52,0xC7,0x8F,0xD5,0x04,0x49,0xE4,0x8D,0x63,0x47,0x88,0x3C,0x69,0x83, +0xCB,0xFE,0x47,0xBD,0x2B,0x7E,0x4F,0xC5,0x95,0xAE,0x0E,0x9D,0xD4,0xD1,0x43,0xC0, +0x67,0x73,0xE3,0x14,0x08,0x7E,0xE5,0x3F,0x9F,0x73,0xB8,0x33,0x0A,0xCF,0x5D,0x3F, +0x34,0x87,0x96,0x8A,0xEE,0x53,0xE8,0x25,0x15,0x02,0x03,0x01,0x00,0x01,0xA3,0x81, +0xB2,0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05, +0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04, +0x04,0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01, +0x0C,0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30,0x59,0x30,0x57,0x30,0x55,0x16, +0x09,0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66,0x30,0x21,0x30,0x1F,0x30,0x07, +0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F,0xE5,0xD3,0x1A,0x86,0xAC,0x8D, +0x8E,0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C,0x7B,0x19,0x2E,0x30,0x25,0x16, +0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F,0x67,0x6F,0x2E,0x76,0x65,0x72, +0x69,0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F,0x76,0x73,0x6C,0x6F,0x67,0x6F, +0x2E,0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7F, +0xD3,0x65,0xA7,0xC2,0xDD,0xEC,0xBB,0xF0,0x30,0x09,0xF3,0x43,0x39,0xFA,0x02,0xAF, +0x33,0x31,0x33,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05, +0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x93,0x24,0x4A,0x30,0x5F,0x62,0xCF,0xD8,0x1A, +0x98,0x2F,0x3D,0xEA,0xDC,0x99,0x2D,0xBD,0x77,0xF6,0xA5,0x79,0x22,0x38,0xEC,0xC4, +0xA7,0xA0,0x78,0x12,0xAD,0x62,0x0E,0x45,0x70,0x64,0xC5,0xE7,0x97,0x66,0x2D,0x98, +0x09,0x7E,0x5F,0xAF,0xD6,0xCC,0x28,0x65,0xF2,0x01,0xAA,0x08,0x1A,0x47,0xDE,0xF9, +0xF9,0x7C,0x92,0x5A,0x08,0x69,0x20,0x0D,0xD9,0x3E,0x6D,0x6E,0x3C,0x0D,0x6E,0xD8, +0xE6,0x06,0x91,0x40,0x18,0xB9,0xF8,0xC1,0xED,0xDF,0xDB,0x41,0xAA,0xE0,0x96,0x20, +0xC9,0xCD,0x64,0x15,0x38,0x81,0xC9,0x94,0xEE,0xA2,0x84,0x29,0x0B,0x13,0x6F,0x8E, +0xDB,0x0C,0xDD,0x25,0x02,0xDB,0xA4,0x8B,0x19,0x44,0xD2,0x41,0x7A,0x05,0x69,0x4A, +0x58,0x4F,0x60,0xCA,0x7E,0x82,0x6A,0x0B,0x02,0xAA,0x25,0x17,0x39,0xB5,0xDB,0x7F, +0xE7,0x84,0x65,0x2A,0x95,0x8A,0xBD,0x86,0xDE,0x5E,0x81,0x16,0x83,0x2D,0x10,0xCC, +0xDE,0xFD,0xA8,0x82,0x2A,0x6D,0x28,0x1F,0x0D,0x0B,0xC4,0xE5,0xE7,0x1A,0x26,0x19, +0xE1,0xF4,0x11,0x6F,0x10,0xB5,0x95,0xFC,0xE7,0x42,0x05,0x32,0xDB,0xCE,0x9D,0x51, +0x5E,0x28,0xB6,0x9E,0x85,0xD3,0x5B,0xEF,0xA5,0x7D,0x45,0x40,0x72,0x8E,0xB7,0x0E, +0x6B,0x0E,0x06,0xFB,0x33,0x35,0x48,0x71,0xB8,0x9D,0x27,0x8B,0xC4,0x65,0x5F,0x0D, +0x86,0x76,0x9C,0x44,0x7A,0xF6,0x95,0x5C,0xF6,0x5D,0x32,0x08,0x33,0xA4,0x54,0xB6, +0x18,0x3F,0x68,0x5C,0xF2,0x42,0x4A,0x85,0x38,0x54,0x83,0x5F,0xD1,0xE8,0x2C,0xF2, +0xAC,0x11,0xD6,0xA8,0xED,0x63,0x6A, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 4 Public Primary Certification Authority - G3 */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 4 Public Primary Certification Authority - G3 */ + + +const unsigned char Verisign_Class_4_Public_Primary_Certification_Authority___G3_certificate[1054]={ +0x30,0x82,0x04,0x1A,0x30,0x82,0x03,0x02,0x02,0x11,0x00,0xEC,0xA0,0xA7,0x8B,0x6E, +0x75,0x6A,0x01,0xCF,0xC4,0x7C,0xCC,0x2F,0x94,0x5E,0xD7,0x30,0x0D,0x06,0x09,0x2A, +0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xCA,0x31,0x0B,0x30, +0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03, +0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65, +0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74, +0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28, +0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74, +0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79, +0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x34,0x20,0x50,0x75,0x62,0x6C, +0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69, +0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69, +0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31,0x30,0x30, +0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31,0x36, +0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06,0x03, +0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A, +0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E, +0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53, +0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72, +0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20, +0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49, +0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72, +0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30, +0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E, +0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x34,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20, +0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63, +0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20, +0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86, +0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A, +0x02,0x82,0x01,0x01,0x00,0xAD,0xCB,0xA5,0x11,0x69,0xC6,0x59,0xAB,0xF1,0x8F,0xB5, +0x19,0x0F,0x56,0xCE,0xCC,0xB5,0x1F,0x20,0xE4,0x9E,0x26,0x25,0x4B,0xE0,0x73,0x65, +0x89,0x59,0xDE,0xD0,0x83,0xE4,0xF5,0x0F,0xB5,0xBB,0xAD,0xF1,0x7C,0xE8,0x21,0xFC, +0xE4,0xE8,0x0C,0xEE,0x7C,0x45,0x22,0x19,0x76,0x92,0xB4,0x13,0xB7,0x20,0x5B,0x09, +0xFA,0x61,0xAE,0xA8,0xF2,0xA5,0x8D,0x85,0xC2,0x2A,0xD6,0xDE,0x66,0x36,0xD2,0x9B, +0x02,0xF4,0xA8,0x92,0x60,0x7C,0x9C,0x69,0xB4,0x8F,0x24,0x1E,0xD0,0x86,0x52,0xF6, +0x32,0x9C,0x41,0x58,0x1E,0x22,0xBD,0xCD,0x45,0x62,0x95,0x08,0x6E,0xD0,0x66,0xDD, +0x53,0xA2,0xCC,0xF0,0x10,0xDC,0x54,0x73,0x8B,0x04,0xA1,0x46,0x33,0x33,0x5C,0x17, +0x40,0xB9,0x9E,0x4D,0xD3,0xF3,0xBE,0x55,0x83,0xE8,0xB1,0x89,0x8E,0x5A,0x7C,0x9A, +0x96,0x22,0x90,0x3B,0x88,0x25,0xF2,0xD2,0x53,0x88,0x02,0x0C,0x0B,0x78,0xF2,0xE6, +0x37,0x17,0x4B,0x30,0x46,0x07,0xE4,0x80,0x6D,0xA6,0xD8,0x96,0x2E,0xE8,0x2C,0xF8, +0x11,0xB3,0x38,0x0D,0x66,0xA6,0x9B,0xEA,0xC9,0x23,0x5B,0xDB,0x8E,0xE2,0xF3,0x13, +0x8E,0x1A,0x59,0x2D,0xAA,0x02,0xF0,0xEC,0xA4,0x87,0x66,0xDC,0xC1,0x3F,0xF5,0xD8, +0xB9,0xF4,0xEC,0x82,0xC6,0xD2,0x3D,0x95,0x1D,0xE5,0xC0,0x4F,0x84,0xC9,0xD9,0xA3, +0x44,0x28,0x06,0x6A,0xD7,0x45,0xAC,0xF0,0x6B,0x6A,0xEF,0x4E,0x5F,0xF8,0x11,0x82, +0x1E,0x38,0x63,0x34,0x66,0x50,0xD4,0x3E,0x93,0x73,0xFA,0x30,0xC3,0x66,0xAD,0xFF, +0x93,0x2D,0x97,0xEF,0x03,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86, +0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x8F,0xFA, +0x25,0x6B,0x4F,0x5B,0xE4,0xA4,0x4E,0x27,0x55,0xAB,0x22,0x15,0x59,0x3C,0xCA,0xB5, +0x0A,0xD4,0x4A,0xDB,0xAB,0xDD,0xA1,0x5F,0x53,0xC5,0xA0,0x57,0x39,0xC2,0xCE,0x47, +0x2B,0xBE,0x3A,0xC8,0x56,0xBF,0xC2,0xD9,0x27,0x10,0x3A,0xB1,0x05,0x3C,0xC0,0x77, +0x31,0xBB,0x3A,0xD3,0x05,0x7B,0x6D,0x9A,0x1C,0x30,0x8C,0x80,0xCB,0x93,0x93,0x2A, +0x83,0xAB,0x05,0x51,0x82,0x02,0x00,0x11,0x67,0x6B,0xF3,0x88,0x61,0x47,0x5F,0x03, +0x93,0xD5,0x5B,0x0D,0xE0,0xF1,0xD4,0xA1,0x32,0x35,0x85,0xB2,0x3A,0xDB,0xB0,0x82, +0xAB,0xD1,0xCB,0x0A,0xBC,0x4F,0x8C,0x5B,0xC5,0x4B,0x00,0x3B,0x1F,0x2A,0x82,0xA6, +0x7E,0x36,0x85,0xDC,0x7E,0x3C,0x67,0x00,0xB5,0xE4,0x3B,0x52,0xE0,0xA8,0xEB,0x5D, +0x15,0xF9,0xC6,0x6D,0xF0,0xAD,0x1D,0x0E,0x85,0xB7,0xA9,0x9A,0x73,0x14,0x5A,0x5B, +0x8F,0x41,0x28,0xC0,0xD5,0xE8,0x2D,0x4D,0xA4,0x5E,0xCD,0xAA,0xD9,0xED,0xCE,0xDC, +0xD8,0xD5,0x3C,0x42,0x1D,0x17,0xC1,0x12,0x5D,0x45,0x38,0xC3,0x38,0xF3,0xFC,0x85, +0x2E,0x83,0x46,0x48,0xB2,0xD7,0x20,0x5F,0x92,0x36,0x8F,0xE7,0x79,0x0F,0x98,0x5E, +0x99,0xE8,0xF0,0xD0,0xA4,0xBB,0xF5,0x53,0xBD,0x2A,0xCE,0x59,0xB0,0xAF,0x6E,0x7F, +0x6C,0xBB,0xD2,0x1E,0x00,0xB0,0x21,0xED,0xF8,0x41,0x62,0x82,0xB9,0xD8,0xB2,0xC4, +0xBB,0x46,0x50,0xF3,0x31,0xC5,0x8F,0x01,0xA8,0x74,0xEB,0xF5,0x78,0x27,0xDA,0xE7, +0xF7,0x66,0x43,0xF3,0x9E,0x83,0x3E,0x20,0xAA,0xC3,0x35,0x60,0x91,0xCE, +}; + + +/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority */ +/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority */ + + +const unsigned char VeriSign_Universal_Root_Certification_Authority_certificate[1213]={ +0x30,0x82,0x04,0xB9,0x30,0x82,0x03,0xA1,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x40, +0x1A,0xC4,0x64,0x21,0xB3,0x13,0x21,0x03,0x0E,0xBB,0xE4,0x12,0x1A,0xC5,0x1D,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81, +0xBD,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B, +0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74, +0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04, +0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72, +0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20, +0x6F,0x6E,0x6C,0x79,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x03,0x13,0x2F,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61, +0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E, +0x17,0x0D,0x30,0x38,0x30,0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17, +0x0D,0x33,0x37,0x31,0x32,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81, +0xBD,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17, +0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67, +0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B, +0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74, +0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04, +0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x56,0x65,0x72,0x69, +0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72, +0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20, +0x6F,0x6E,0x6C,0x79,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x03,0x13,0x2F,0x56, +0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61, +0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61, +0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82, +0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05, +0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC7, +0x61,0x37,0x5E,0xB1,0x01,0x34,0xDB,0x62,0xD7,0x15,0x9B,0xFF,0x58,0x5A,0x8C,0x23, +0x23,0xD6,0x60,0x8E,0x91,0xD7,0x90,0x98,0x83,0x7A,0xE6,0x58,0x19,0x38,0x8C,0xC5, +0xF6,0xE5,0x64,0x85,0xB4,0xA2,0x71,0xFB,0xED,0xBD,0xB9,0xDA,0xCD,0x4D,0x00,0xB4, +0xC8,0x2D,0x73,0xA5,0xC7,0x69,0x71,0x95,0x1F,0x39,0x3C,0xB2,0x44,0x07,0x9C,0xE8, +0x0E,0xFA,0x4D,0x4A,0xC4,0x21,0xDF,0x29,0x61,0x8F,0x32,0x22,0x61,0x82,0xC5,0x87, +0x1F,0x6E,0x8C,0x7C,0x5F,0x16,0x20,0x51,0x44,0xD1,0x70,0x4F,0x57,0xEA,0xE3,0x1C, +0xE3,0xCC,0x79,0xEE,0x58,0xD8,0x0E,0xC2,0xB3,0x45,0x93,0xC0,0x2C,0xE7,0x9A,0x17, +0x2B,0x7B,0x00,0x37,0x7A,0x41,0x33,0x78,0xE1,0x33,0xE2,0xF3,0x10,0x1A,0x7F,0x87, +0x2C,0xBE,0xF6,0xF5,0xF7,0x42,0xE2,0xE5,0xBF,0x87,0x62,0x89,0x5F,0x00,0x4B,0xDF, +0xC5,0xDD,0xE4,0x75,0x44,0x32,0x41,0x3A,0x1E,0x71,0x6E,0x69,0xCB,0x0B,0x75,0x46, +0x08,0xD1,0xCA,0xD2,0x2B,0x95,0xD0,0xCF,0xFB,0xB9,0x40,0x6B,0x64,0x8C,0x57,0x4D, +0xFC,0x13,0x11,0x79,0x84,0xED,0x5E,0x54,0xF6,0x34,0x9F,0x08,0x01,0xF3,0x10,0x25, +0x06,0x17,0x4A,0xDA,0xF1,0x1D,0x7A,0x66,0x6B,0x98,0x60,0x66,0xA4,0xD9,0xEF,0xD2, +0x2E,0x82,0xF1,0xF0,0xEF,0x09,0xEA,0x44,0xC9,0x15,0x6A,0xE2,0x03,0x6E,0x33,0xD3, +0xAC,0x9F,0x55,0x00,0xC7,0xF6,0x08,0x6A,0x94,0xB9,0x5F,0xDC,0xE0,0x33,0xF1,0x84, +0x60,0xF9,0x5B,0x27,0x11,0xB4,0xFC,0x16,0xF2,0xBB,0x56,0x6A,0x80,0x25,0x8D,0x02, +0x03,0x01,0x00,0x01,0xA3,0x81,0xB2,0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D, +0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55, +0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B, +0x06,0x01,0x05,0x05,0x07,0x01,0x0C,0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30, +0x59,0x30,0x57,0x30,0x55,0x16,0x09,0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66, +0x30,0x21,0x30,0x1F,0x30,0x07,0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F, +0xE5,0xD3,0x1A,0x86,0xAC,0x8D,0x8E,0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C, +0x7B,0x19,0x2E,0x30,0x25,0x16,0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F, +0x67,0x6F,0x2E,0x76,0x65,0x72,0x69,0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F, +0x76,0x73,0x6C,0x6F,0x67,0x6F,0x2E,0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D, +0x0E,0x04,0x16,0x04,0x14,0xB6,0x77,0xFA,0x69,0x48,0x47,0x9F,0x53,0x12,0xD5,0xC2, +0xEA,0x07,0x32,0x76,0x07,0xD1,0x97,0x07,0x19,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48, +0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x4A,0xF8,0xF8, +0xB0,0x03,0xE6,0x2C,0x67,0x7B,0xE4,0x94,0x77,0x63,0xCC,0x6E,0x4C,0xF9,0x7D,0x0E, +0x0D,0xDC,0xC8,0xB9,0x35,0xB9,0x70,0x4F,0x63,0xFA,0x24,0xFA,0x6C,0x83,0x8C,0x47, +0x9D,0x3B,0x63,0xF3,0x9A,0xF9,0x76,0x32,0x95,0x91,0xB1,0x77,0xBC,0xAC,0x9A,0xBE, +0xB1,0xE4,0x31,0x21,0xC6,0x81,0x95,0x56,0x5A,0x0E,0xB1,0xC2,0xD4,0xB1,0xA6,0x59, +0xAC,0xF1,0x63,0xCB,0xB8,0x4C,0x1D,0x59,0x90,0x4A,0xEF,0x90,0x16,0x28,0x1F,0x5A, +0xAE,0x10,0xFB,0x81,0x50,0x38,0x0C,0x6C,0xCC,0xF1,0x3D,0xC3,0xF5,0x63,0xE3,0xB3, +0xE3,0x21,0xC9,0x24,0x39,0xE9,0xFD,0x15,0x66,0x46,0xF4,0x1B,0x11,0xD0,0x4D,0x73, +0xA3,0x7D,0x46,0xF9,0x3D,0xED,0xA8,0x5F,0x62,0xD4,0xF1,0x3F,0xF8,0xE0,0x74,0x57, +0x2B,0x18,0x9D,0x81,0xB4,0xC4,0x28,0xDA,0x94,0x97,0xA5,0x70,0xEB,0xAC,0x1D,0xBE, +0x07,0x11,0xF0,0xD5,0xDB,0xDD,0xE5,0x8C,0xF0,0xD5,0x32,0xB0,0x83,0xE6,0x57,0xE2, +0x8F,0xBF,0xBE,0xA1,0xAA,0xBF,0x3D,0x1D,0xB5,0xD4,0x38,0xEA,0xD7,0xB0,0x5C,0x3A, +0x4F,0x6A,0x3F,0x8F,0xC0,0x66,0x6C,0x63,0xAA,0xE9,0xD9,0xA4,0x16,0xF4,0x81,0xD1, +0x95,0x14,0x0E,0x7D,0xCD,0x95,0x34,0xD9,0xD2,0x8F,0x70,0x73,0x81,0x7B,0x9C,0x7E, +0xBD,0x98,0x61,0xD8,0x45,0x87,0x98,0x90,0xC5,0xEB,0x86,0x30,0xC6,0x35,0xBF,0xF0, +0xFF,0xC3,0x55,0x88,0x83,0x4B,0xEF,0x05,0x92,0x06,0x71,0xF2,0xB8,0x98,0x93,0xB7, +0xEC,0xCD,0x82,0x61,0xF1,0x38,0xE6,0x4F,0x97,0x98,0x2A,0x5A,0x8D, +}; + + +/* subject:/C=US/OU=www.xrampsecurity.com/O=XRamp Security Services Inc/CN=XRamp Global Certification Authority */ +/* issuer :/C=US/OU=www.xrampsecurity.com/O=XRamp Security Services Inc/CN=XRamp Global Certification Authority */ + + +const unsigned char XRamp_Global_CA_Root_certificate[1076]={ +0x30,0x82,0x04,0x30,0x30,0x82,0x03,0x18,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x50, +0x94,0x6C,0xEC,0x18,0xEA,0xD5,0x9C,0x4D,0xD5,0x97,0xEF,0x75,0x8F,0xA0,0xAD,0x30, +0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81, +0x82,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1E, +0x30,0x1C,0x06,0x03,0x55,0x04,0x0B,0x13,0x15,0x77,0x77,0x77,0x2E,0x78,0x72,0x61, +0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x2E,0x63,0x6F,0x6D,0x31,0x24, +0x30,0x22,0x06,0x03,0x55,0x04,0x0A,0x13,0x1B,0x58,0x52,0x61,0x6D,0x70,0x20,0x53, +0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73, +0x20,0x49,0x6E,0x63,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x58, +0x52,0x61,0x6D,0x70,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x65,0x72,0x74, +0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72, +0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x31,0x31,0x30,0x31,0x31,0x37,0x31, +0x34,0x30,0x34,0x5A,0x17,0x0D,0x33,0x35,0x30,0x31,0x30,0x31,0x30,0x35,0x33,0x37, +0x31,0x39,0x5A,0x30,0x81,0x82,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13, +0x02,0x55,0x53,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0B,0x13,0x15,0x77,0x77, +0x77,0x2E,0x78,0x72,0x61,0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x2E, +0x63,0x6F,0x6D,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0A,0x13,0x1B,0x58,0x52, +0x61,0x6D,0x70,0x20,0x53,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x20,0x53,0x65,0x72, +0x76,0x69,0x63,0x65,0x73,0x20,0x49,0x6E,0x63,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55, +0x04,0x03,0x13,0x24,0x58,0x52,0x61,0x6D,0x70,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C, +0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41, +0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09, +0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00, +0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0x98,0x24,0x1E,0xBD,0x15,0xB4,0xBA, +0xDF,0xC7,0x8C,0xA5,0x27,0xB6,0x38,0x0B,0x69,0xF3,0xB6,0x4E,0xA8,0x2C,0x2E,0x21, +0x1D,0x5C,0x44,0xDF,0x21,0x5D,0x7E,0x23,0x74,0xFE,0x5E,0x7E,0xB4,0x4A,0xB7,0xA6, +0xAD,0x1F,0xAE,0xE0,0x06,0x16,0xE2,0x9B,0x5B,0xD9,0x67,0x74,0x6B,0x5D,0x80,0x8F, +0x29,0x9D,0x86,0x1B,0xD9,0x9C,0x0D,0x98,0x6D,0x76,0x10,0x28,0x58,0xE4,0x65,0xB0, +0x7F,0x4A,0x98,0x79,0x9F,0xE0,0xC3,0x31,0x7E,0x80,0x2B,0xB5,0x8C,0xC0,0x40,0x3B, +0x11,0x86,0xD0,0xCB,0xA2,0x86,0x36,0x60,0xA4,0xD5,0x30,0x82,0x6D,0xD9,0x6E,0xD0, +0x0F,0x12,0x04,0x33,0x97,0x5F,0x4F,0x61,0x5A,0xF0,0xE4,0xF9,0x91,0xAB,0xE7,0x1D, +0x3B,0xBC,0xE8,0xCF,0xF4,0x6B,0x2D,0x34,0x7C,0xE2,0x48,0x61,0x1C,0x8E,0xF3,0x61, +0x44,0xCC,0x6F,0xA0,0x4A,0xA9,0x94,0xB0,0x4D,0xDA,0xE7,0xA9,0x34,0x7A,0x72,0x38, +0xA8,0x41,0xCC,0x3C,0x94,0x11,0x7D,0xEB,0xC8,0xA6,0x8C,0xB7,0x86,0xCB,0xCA,0x33, +0x3B,0xD9,0x3D,0x37,0x8B,0xFB,0x7A,0x3E,0x86,0x2C,0xE7,0x73,0xD7,0x0A,0x57,0xAC, +0x64,0x9B,0x19,0xEB,0xF4,0x0F,0x04,0x08,0x8A,0xAC,0x03,0x17,0x19,0x64,0xF4,0x5A, +0x25,0x22,0x8D,0x34,0x2C,0xB2,0xF6,0x68,0x1D,0x12,0x6D,0xD3,0x8A,0x1E,0x14,0xDA, +0xC4,0x8F,0xA6,0xE2,0x23,0x85,0xD5,0x7A,0x0D,0xBD,0x6A,0xE0,0xE9,0xEC,0xEC,0x17, +0xBB,0x42,0x1B,0x67,0xAA,0x25,0xED,0x45,0x83,0x21,0xFC,0xC1,0xC9,0x7C,0xD5,0x62, +0x3E,0xFA,0xF2,0xC5,0x2D,0xD3,0xFD,0xD4,0x65,0x02,0x03,0x01,0x00,0x01,0xA3,0x81, +0x9F,0x30,0x81,0x9C,0x30,0x13,0x06,0x09,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x14, +0x02,0x04,0x06,0x1E,0x04,0x00,0x43,0x00,0x41,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F, +0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF, +0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16, +0x04,0x14,0xC6,0x4F,0xA2,0x3D,0x06,0x63,0x84,0x09,0x9C,0xCE,0x62,0xE4,0x04,0xAC, +0x8D,0x5C,0xB5,0xE9,0xB6,0x1B,0x30,0x36,0x06,0x03,0x55,0x1D,0x1F,0x04,0x2F,0x30, +0x2D,0x30,0x2B,0xA0,0x29,0xA0,0x27,0x86,0x25,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F, +0x63,0x72,0x6C,0x2E,0x78,0x72,0x61,0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74, +0x79,0x2E,0x63,0x6F,0x6D,0x2F,0x58,0x47,0x43,0x41,0x2E,0x63,0x72,0x6C,0x30,0x10, +0x06,0x09,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x15,0x01,0x04,0x03,0x02,0x01,0x01, +0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03, +0x82,0x01,0x01,0x00,0x91,0x15,0x39,0x03,0x01,0x1B,0x67,0xFB,0x4A,0x1C,0xF9,0x0A, +0x60,0x5B,0xA1,0xDA,0x4D,0x97,0x62,0xF9,0x24,0x53,0x27,0xD7,0x82,0x64,0x4E,0x90, +0x2E,0xC3,0x49,0x1B,0x2B,0x9A,0xDC,0xFC,0xA8,0x78,0x67,0x35,0xF1,0x1D,0xF0,0x11, +0xBD,0xB7,0x48,0xE3,0x10,0xF6,0x0D,0xDF,0x3F,0xD2,0xC9,0xB6,0xAA,0x55,0xA4,0x48, +0xBA,0x02,0xDB,0xDE,0x59,0x2E,0x15,0x5B,0x3B,0x9D,0x16,0x7D,0x47,0xD7,0x37,0xEA, +0x5F,0x4D,0x76,0x12,0x36,0xBB,0x1F,0xD7,0xA1,0x81,0x04,0x46,0x20,0xA3,0x2C,0x6D, +0xA9,0x9E,0x01,0x7E,0x3F,0x29,0xCE,0x00,0x93,0xDF,0xFD,0xC9,0x92,0x73,0x89,0x89, +0x64,0x9E,0xE7,0x2B,0xE4,0x1C,0x91,0x2C,0xD2,0xB9,0xCE,0x7D,0xCE,0x6F,0x31,0x99, +0xD3,0xE6,0xBE,0xD2,0x1E,0x90,0xF0,0x09,0x14,0x79,0x5C,0x23,0xAB,0x4D,0xD2,0xDA, +0x21,0x1F,0x4D,0x99,0x79,0x9D,0xE1,0xCF,0x27,0x9F,0x10,0x9B,0x1C,0x88,0x0D,0xB0, +0x8A,0x64,0x41,0x31,0xB8,0x0E,0x6C,0x90,0x24,0xA4,0x9B,0x5C,0x71,0x8F,0xBA,0xBB, +0x7E,0x1C,0x1B,0xDB,0x6A,0x80,0x0F,0x21,0xBC,0xE9,0xDB,0xA6,0xB7,0x40,0xF4,0xB2, +0x8B,0xA9,0xB1,0xE4,0xEF,0x9A,0x1A,0xD0,0x3D,0x69,0x99,0xEE,0xA8,0x28,0xA3,0xE1, +0x3C,0xB3,0xF0,0xB2,0x11,0x9C,0xCF,0x7C,0x40,0xE6,0xDD,0xE7,0x43,0x7D,0xA2,0xD8, +0x3A,0xB5,0xA9,0x8D,0xF2,0x34,0x99,0xC4,0xD4,0x10,0xE1,0x06,0xFD,0x09,0x84,0x10, +0x3B,0xEE,0xC4,0x4C,0xF4,0xEC,0x27,0x7C,0x42,0xC2,0x74,0x7C,0x82,0x8A,0x09,0xC9, +0xB4,0x03,0x25,0xBC, +}; + + +const unsigned char* kSSLCertCertificateList[] = { + AddTrust_External_Root_certificate, + AddTrust_Low_Value_Services_Root_certificate, + AddTrust_Public_Services_Root_certificate, + AddTrust_Qualified_Certificates_Root_certificate, + AffirmTrust_Commercial_certificate, + AffirmTrust_Networking_certificate, + AffirmTrust_Premium_certificate, + AffirmTrust_Premium_ECC_certificate, + America_Online_Root_Certification_Authority_1_certificate, + America_Online_Root_Certification_Authority_2_certificate, + Baltimore_CyberTrust_Root_certificate, + Comodo_AAA_Services_root_certificate, + COMODO_Certification_Authority_certificate, + COMODO_ECC_Certification_Authority_certificate, + Comodo_Secure_Services_root_certificate, + Comodo_Trusted_Services_root_certificate, + Cybertrust_Global_Root_certificate, + DigiCert_Assured_ID_Root_CA_certificate, + DigiCert_Global_Root_CA_certificate, + DigiCert_High_Assurance_EV_Root_CA_certificate, + Entrust_net_Premium_2048_Secure_Server_CA_certificate, + Entrust_net_Secure_Server_CA_certificate, + Entrust_Root_Certification_Authority_certificate, + Equifax_Secure_CA_certificate, + Equifax_Secure_eBusiness_CA_1_certificate, + Equifax_Secure_eBusiness_CA_2_certificate, + Equifax_Secure_Global_eBusiness_CA_certificate, + GeoTrust_Global_CA_certificate, + GeoTrust_Global_CA_2_certificate, + GeoTrust_Primary_Certification_Authority_certificate, + GeoTrust_Primary_Certification_Authority___G2_certificate, + GeoTrust_Primary_Certification_Authority___G3_certificate, + GeoTrust_Universal_CA_certificate, + GeoTrust_Universal_CA_2_certificate, + GlobalSign_Root_CA_certificate, + GlobalSign_Root_CA___R2_certificate, + GlobalSign_Root_CA___R3_certificate, + Go_Daddy_Class_2_CA_certificate, + Go_Daddy_Root_Certificate_Authority___G2_certificate, + GTE_CyberTrust_Global_Root_certificate, + Network_Solutions_Certificate_Authority_certificate, + RSA_Root_Certificate_1_certificate, + Starfield_Class_2_CA_certificate, + Starfield_Root_Certificate_Authority___G2_certificate, + Starfield_Services_Root_Certificate_Authority___G2_certificate, + StartCom_Certification_Authority_certificate, + StartCom_Certification_Authority_G2_certificate, + TC_TrustCenter_Class_2_CA_II_certificate, + TC_TrustCenter_Class_3_CA_II_certificate, + TC_TrustCenter_Universal_CA_I_certificate, + TC_TrustCenter_Universal_CA_III_certificate, + Thawte_Premium_Server_CA_certificate, + thawte_Primary_Root_CA_certificate, + thawte_Primary_Root_CA___G2_certificate, + thawte_Primary_Root_CA___G3_certificate, + Thawte_Server_CA_certificate, + UTN_DATACorp_SGC_Root_CA_certificate, + UTN_USERFirst_Hardware_Root_CA_certificate, + ValiCert_Class_1_VA_certificate, + ValiCert_Class_2_VA_certificate, + Verisign_Class_3_Public_Primary_Certification_Authority_certificate, + Verisign_Class_3_Public_Primary_Certification_Authority___G2_certificate, + Verisign_Class_3_Public_Primary_Certification_Authority___G3_certificate, + VeriSign_Class_3_Public_Primary_Certification_Authority___G4_certificate, + VeriSign_Class_3_Public_Primary_Certification_Authority___G5_certificate, + Verisign_Class_4_Public_Primary_Certification_Authority___G3_certificate, + VeriSign_Universal_Root_Certification_Authority_certificate, + XRamp_Global_CA_Root_certificate, +}; + +const size_t kSSLCertCertificateSizeList[] = { + 1082, + 1052, + 1049, + 1058, + 848, + 848, + 1354, + 514, + 936, + 1448, + 891, + 1078, + 1057, + 653, + 1091, + 1095, + 933, + 955, + 947, + 969, + 1120, + 1244, + 1173, + 804, + 646, + 804, + 660, + 856, + 874, + 896, + 690, + 1026, + 1388, + 1392, + 889, + 958, + 867, + 1028, + 969, + 606, + 1002, + 747, + 1043, + 993, + 1011, + 1931, + 1383, + 1198, + 1198, + 993, + 997, + 811, + 1060, + 652, + 1070, + 791, + 1122, + 1144, + 747, + 747, + 576, + 774, + 1054, + 904, + 1239, + 1054, + 1213, + 1076, +}; + +} // namspace rtc diff --git a/webrtc/base/sslsocketfactory.cc b/webrtc/base/sslsocketfactory.cc new file mode 100644 index 000000000..0e37ab84e --- /dev/null +++ b/webrtc/base/sslsocketfactory.cc @@ -0,0 +1,175 @@ +/* + * Copyright 2007 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 "webrtc/base/autodetectproxy.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslsocketfactory.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// ProxySocketAdapter +// TODO: Consider combining AutoDetectProxy and ProxySocketAdapter. I think +// the socket adapter is the more appropriate idiom for automatic proxy +// detection. We may or may not want to combine proxydetect.* as well. +/////////////////////////////////////////////////////////////////////////////// + +class ProxySocketAdapter : public AsyncSocketAdapter { + public: + ProxySocketAdapter(SslSocketFactory* factory, int family, int type) + : AsyncSocketAdapter(NULL), factory_(factory), family_(family), + type_(type), detect_(NULL) { + } + virtual ~ProxySocketAdapter() { + Close(); + } + + virtual int Connect(const SocketAddress& addr) { + ASSERT(NULL == detect_); + ASSERT(NULL == socket_); + remote_ = addr; + if (remote_.IsAnyIP() && remote_.hostname().empty()) { + LOG_F(LS_ERROR) << "Empty address"; + return SOCKET_ERROR; + } + Url url("/", remote_.HostAsURIString(), remote_.port()); + detect_ = new AutoDetectProxy(factory_->agent_); + detect_->set_server_url(url.url()); + detect_->SignalWorkDone.connect(this, + &ProxySocketAdapter::OnProxyDetectionComplete); + detect_->Start(); + return SOCKET_ERROR; + } + virtual int GetError() const { + if (socket_) { + return socket_->GetError(); + } + return detect_ ? EWOULDBLOCK : EADDRNOTAVAIL; + } + virtual int Close() { + if (socket_) { + return socket_->Close(); + } + if (detect_) { + detect_->Destroy(false); + detect_ = NULL; + } + return 0; + } + virtual ConnState GetState() const { + if (socket_) { + return socket_->GetState(); + } + return detect_ ? CS_CONNECTING : CS_CLOSED; + } + +private: + // AutoDetectProxy Slots + void OnProxyDetectionComplete(SignalThread* thread) { + ASSERT(detect_ == thread); + Attach(factory_->CreateProxySocket(detect_->proxy(), family_, type_)); + detect_->Release(); + detect_ = NULL; + if (0 == AsyncSocketAdapter::Connect(remote_)) { + SignalConnectEvent(this); + } else if (!IsBlockingError(socket_->GetError())) { + SignalCloseEvent(this, socket_->GetError()); + } + } + + SslSocketFactory* factory_; + int family_; + int type_; + SocketAddress remote_; + AutoDetectProxy* detect_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SslSocketFactory +/////////////////////////////////////////////////////////////////////////////// + +Socket* SslSocketFactory::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* SslSocketFactory::CreateSocket(int family, int type) { + return factory_->CreateSocket(family, type); +} + +AsyncSocket* SslSocketFactory::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* SslSocketFactory::CreateAsyncSocket(int family, int type) { + if (autodetect_proxy_) { + return new ProxySocketAdapter(this, family, type); + } else { + return CreateProxySocket(proxy_, family, type); + } +} + + +AsyncSocket* SslSocketFactory::CreateProxySocket(const ProxyInfo& proxy, + int family, + int type) { + AsyncSocket* socket = factory_->CreateAsyncSocket(family, type); + if (!socket) + return NULL; + + // Binary logging happens at the lowest level + if (!logging_label_.empty() && binary_mode_) { + socket = new LoggingSocketAdapter(socket, logging_level_, + logging_label_.c_str(), binary_mode_); + } + + if (proxy.type) { + AsyncSocket* proxy_socket = 0; + if (proxy_.type == PROXY_SOCKS5) { + proxy_socket = new AsyncSocksProxySocket(socket, proxy.address, + proxy.username, proxy.password); + } else { + // Note: we are trying unknown proxies as HTTPS currently + AsyncHttpsProxySocket* http_proxy = + new AsyncHttpsProxySocket(socket, agent_, proxy.address, + proxy.username, proxy.password); + http_proxy->SetForceConnect(force_connect_ || !hostname_.empty()); + proxy_socket = http_proxy; + } + if (!proxy_socket) { + delete socket; + return NULL; + } + socket = proxy_socket; // for our purposes the proxy is now the socket + } + + if (!hostname_.empty()) { + if (SSLAdapter* ssl_adapter = SSLAdapter::Create(socket)) { + ssl_adapter->set_ignore_bad_cert(ignore_bad_cert_); + ssl_adapter->StartSSL(hostname_.c_str(), true); + socket = ssl_adapter; + } else { + LOG_F(LS_ERROR) << "SSL unavailable"; + } + } + + // Regular logging occurs at the highest level + if (!logging_label_.empty() && !binary_mode_) { + socket = new LoggingSocketAdapter(socket, logging_level_, + logging_label_.c_str(), binary_mode_); + } + return socket; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/sslsocketfactory.h b/webrtc/base/sslsocketfactory.h new file mode 100644 index 000000000..edb23dbb8 --- /dev/null +++ b/webrtc/base/sslsocketfactory.h @@ -0,0 +1,81 @@ +/* + * Copyright 2007 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_BASE_SSLSOCKETFACTORY_H__ +#define WEBRTC_BASE_SSLSOCKETFACTORY_H__ + +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/socketserver.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// SslSocketFactory +/////////////////////////////////////////////////////////////////////////////// + +class SslSocketFactory : public SocketFactory { + public: + SslSocketFactory(SocketFactory* factory, const std::string& user_agent) + : factory_(factory), agent_(user_agent), autodetect_proxy_(true), + force_connect_(false), logging_level_(LS_VERBOSE), binary_mode_(false), + ignore_bad_cert_(false) { + } + + void SetAutoDetectProxy() { + autodetect_proxy_ = true; + } + void SetForceConnect(bool force) { + force_connect_ = force; + } + void SetProxy(const ProxyInfo& proxy) { + autodetect_proxy_ = false; + proxy_ = proxy; + } + bool autodetect_proxy() const { return autodetect_proxy_; } + const ProxyInfo& proxy() const { return proxy_; } + + void UseSSL(const char* hostname) { hostname_ = hostname; } + void DisableSSL() { hostname_.clear(); } + void SetIgnoreBadCert(bool ignore) { ignore_bad_cert_ = ignore; } + bool ignore_bad_cert() const { return ignore_bad_cert_; } + + void SetLogging(LoggingSeverity level, const std::string& label, + bool binary_mode = false) { + logging_level_ = level; + logging_label_ = label; + binary_mode_ = binary_mode; + } + + // SocketFactory Interface + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + private: + friend class ProxySocketAdapter; + AsyncSocket* CreateProxySocket(const ProxyInfo& proxy, int family, int type); + + SocketFactory* factory_; + std::string agent_; + bool autodetect_proxy_, force_connect_; + ProxyInfo proxy_; + std::string hostname_, logging_label_; + LoggingSeverity logging_level_; + bool binary_mode_; + bool ignore_bad_cert_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLSOCKETFACTORY_H__ diff --git a/webrtc/base/sslstreamadapter.cc b/webrtc/base/sslstreamadapter.cc new file mode 100644 index 000000000..44df2eedd --- /dev/null +++ b/webrtc/base/sslstreamadapter.cc @@ -0,0 +1,77 @@ +/* + * Copyright 2004 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. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/sslconfig.h" + +#if SSL_USE_SCHANNEL + +// SChannel support for DTLS and peer-to-peer mode are not +// done. +#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL + +#include "webrtc/base/opensslstreamadapter.h" + +#elif SSL_USE_NSS // && !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + +#include "webrtc/base/nssstreamadapter.h" + +#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +SSLStreamAdapter* SSLStreamAdapter::Create(StreamInterface* stream) { +#if SSL_USE_SCHANNEL + return NULL; +#elif SSL_USE_OPENSSL // !SSL_USE_SCHANNEL + return new OpenSSLStreamAdapter(stream); +#elif SSL_USE_NSS // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + return new NSSStreamAdapter(stream); +#else // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS + return NULL; +#endif +} + +// Note: this matches the logic above with SCHANNEL dominating +#if SSL_USE_SCHANNEL +bool SSLStreamAdapter::HaveDtls() { return false; } +bool SSLStreamAdapter::HaveDtlsSrtp() { return false; } +bool SSLStreamAdapter::HaveExporter() { return false; } +#elif SSL_USE_OPENSSL +bool SSLStreamAdapter::HaveDtls() { + return OpenSSLStreamAdapter::HaveDtls(); +} +bool SSLStreamAdapter::HaveDtlsSrtp() { + return OpenSSLStreamAdapter::HaveDtlsSrtp(); +} +bool SSLStreamAdapter::HaveExporter() { + return OpenSSLStreamAdapter::HaveExporter(); +} +#elif SSL_USE_NSS +bool SSLStreamAdapter::HaveDtls() { + return NSSStreamAdapter::HaveDtls(); +} +bool SSLStreamAdapter::HaveDtlsSrtp() { + return NSSStreamAdapter::HaveDtlsSrtp(); +} +bool SSLStreamAdapter::HaveExporter() { + return NSSStreamAdapter::HaveExporter(); +} +#endif // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/sslstreamadapter.h b/webrtc/base/sslstreamadapter.h new file mode 100644 index 000000000..ffe6b2f7b --- /dev/null +++ b/webrtc/base/sslstreamadapter.h @@ -0,0 +1,162 @@ +/* + * Copyright 2004 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_BASE_SSLSTREAMADAPTER_H_ +#define WEBRTC_BASE_SSLSTREAMADAPTER_H_ + +#include +#include + +#include "webrtc/base/stream.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +// SSLStreamAdapter : A StreamInterfaceAdapter that does SSL/TLS. +// After SSL has been started, the stream will only open on successful +// SSL verification of certificates, and the communication is +// encrypted of course. +// +// This class was written with SSLAdapter as a starting point. It +// offers a similar interface, with two differences: there is no +// support for a restartable SSL connection, and this class has a +// peer-to-peer mode. +// +// The SSL library requires initialization and cleanup. Static method +// for doing this are in SSLAdapter. They should possibly be moved out +// to a neutral class. + + +enum SSLRole { SSL_CLIENT, SSL_SERVER }; +enum SSLMode { SSL_MODE_TLS, SSL_MODE_DTLS }; + +// Errors for Read -- in the high range so no conflict with OpenSSL. +enum { SSE_MSG_TRUNC = 0xff0001 }; + +class SSLStreamAdapter : public StreamAdapterInterface { + public: + // Instantiate an SSLStreamAdapter wrapping the given stream, + // (using the selected implementation for the platform). + // Caller is responsible for freeing the returned object. + static SSLStreamAdapter* Create(StreamInterface* stream); + + explicit SSLStreamAdapter(StreamInterface* stream) + : StreamAdapterInterface(stream), ignore_bad_cert_(false) { } + + void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; } + bool ignore_bad_cert() const { return ignore_bad_cert_; } + + // Specify our SSL identity: key and certificate. Mostly this is + // only used in the peer-to-peer mode (unless we actually want to + // provide a client certificate to a server). + // SSLStream takes ownership of the SSLIdentity object and will + // free it when appropriate. Should be called no more than once on a + // given SSLStream instance. + virtual void SetIdentity(SSLIdentity* identity) = 0; + + // Call this to indicate that we are to play the server's role in + // the peer-to-peer mode. + // The default argument is for backward compatibility + // TODO(ekr@rtfm.com): rename this SetRole to reflect its new function + virtual void SetServerRole(SSLRole role = SSL_SERVER) = 0; + + // Do DTLS or TLS + virtual void SetMode(SSLMode mode) = 0; + + // The mode of operation is selected by calling either + // StartSSLWithServer or StartSSLWithPeer. + // Use of the stream prior to calling either of these functions will + // pass data in clear text. + // Calling one of these functions causes SSL negotiation to begin as + // soon as possible: right away if the underlying wrapped stream is + // already opened, or else as soon as it opens. + // + // These functions return a negative error code on failure. + // Returning 0 means success so far, but negotiation is probably not + // complete and will continue asynchronously. In that case, the + // exposed stream will open after successful negotiation and + // verification, or an SE_CLOSE event will be raised if negotiation + // fails. + + // StartSSLWithServer starts SSL negotiation with a server in + // traditional mode. server_name specifies the expected server name + // which the server's certificate needs to specify. + virtual int StartSSLWithServer(const char* server_name) = 0; + + // StartSSLWithPeer starts negotiation in the special peer-to-peer + // mode. + // Generally, SetIdentity() and possibly SetServerRole() should have + // been called before this. + // SetPeerCertificate() or SetPeerCertificateDigest() must also be called. + // It may be called after StartSSLWithPeer() but must be called before the + // underlying stream opens. + virtual int StartSSLWithPeer() = 0; + + // Specify the digest of the certificate that our peer is expected to use in + // peer-to-peer mode. Only this certificate will be accepted during + // SSL verification. The certificate is assumed to have been + // obtained through some other secure channel (such as the XMPP + // channel). Unlike SetPeerCertificate(), this must specify the + // terminal certificate, not just a CA. + // SSLStream makes a copy of the digest value. + virtual bool SetPeerCertificateDigest(const std::string& digest_alg, + const unsigned char* digest_val, + size_t digest_len) = 0; + + // Retrieves the peer's X.509 certificate, if a connection has been + // established. It returns the transmitted over SSL, including the entire + // chain. The returned certificate is owned by the caller. + virtual bool GetPeerCertificate(SSLCertificate** cert) const = 0; + + // Key Exporter interface from RFC 5705 + // Arguments are: + // label -- the exporter label. + // part of the RFC defining each exporter + // usage (IN) + // context/context_len -- a context to bind to for this connection; + // optional, can be NULL, 0 (IN) + // use_context -- whether to use the context value + // (needed to distinguish no context from + // zero-length ones). + // result -- where to put the computed value + // result_len -- the length of the computed value + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + return false; // Default is unsupported + } + + + // DTLS-SRTP interface + virtual bool SetDtlsSrtpCiphers(const std::vector& ciphers) { + return false; + } + + virtual bool GetDtlsSrtpCipher(std::string* cipher) { + return false; + } + + // Capabilities testing + static bool HaveDtls(); + static bool HaveDtlsSrtp(); + static bool HaveExporter(); + + // If true, the server certificate need not match the configured + // server_name, and in fact missing certificate authority and other + // verification errors are ignored. + bool ignore_bad_cert_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLSTREAMADAPTER_H_ diff --git a/webrtc/base/sslstreamadapter_unittest.cc b/webrtc/base/sslstreamadapter_unittest.cc new file mode 100644 index 000000000..af78bfff5 --- /dev/null +++ b/webrtc/base/sslstreamadapter_unittest.cc @@ -0,0 +1,940 @@ +/* + * Copyright 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 "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslconfig.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/stream.h" + +static const int kBlockSize = 4096; +static const char kAES_CM_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80"; +static const char kAES_CM_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32"; +static const char kExporterLabel[] = "label"; +static const unsigned char kExporterContext[] = "context"; +static int kExporterContextLen = sizeof(kExporterContext); + +static const char kRSA_PRIVATE_KEY_PEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAECgYAvgOs4FJcgvp+TuREx7YtiYVsH\n" + "mwQPTum2z/8VzWGwR8BBHBvIpVe1MbD/Y4seyI2aco/7UaisatSgJhsU46/9Y4fq\n" + "2TwXH9QANf4at4d9n/R6rzwpAJOpgwZgKvdQjkfrKTtgLV+/dawvpxUYkRH4JZM1\n" + "CVGukMfKNrSVH4Ap4QJBAOJmGV1ASPnB4r4nc99at7JuIJmd7fmuVUwUgYi4XgaR\n" + "WhScBsgYwZ/JoywdyZJgnbcrTDuVcWG56B3vXbhdpMsCQQDf9zeJrjnPZ3Cqm79y\n" + "kdqANep0uwZciiNiWxsQrCHztywOvbFhdp8iYVFG9EK8DMY41Y5TxUwsHD+67zao\n" + "ZNqJAkEA1suLUP/GvL8IwuRneQd2tWDqqRQ/Td3qq03hP7e77XtF/buya3Ghclo5\n" + "54czUR89QyVfJEC6278nzA7n2h1uVQJAcG6mztNL6ja/dKZjYZye2CY44QjSlLo0\n" + "MTgTSjdfg/28fFn2Jjtqf9Pi/X+50LWI/RcYMC2no606wRk9kyOuIQJBAK6VSAim\n" + "1pOEjsYQn0X5KEIrz1G3bfCbB848Ime3U2/FWlCHMr6ch8kCZ5d1WUeJD3LbwMNG\n" + "UCXiYxSsu20QNVw=\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char kCERT_PEM[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIBmTCCAQKgAwIBAgIEbzBSAjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZX\n" + "ZWJSVEMwHhcNMTQwMTAyMTgyNDQ3WhcNMTQwMjAxMTgyNDQ3WjARMQ8wDQYDVQQD\n" + "EwZXZWJSVEMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMYRkbhmI7kVA/rM\n" + "czsZ+6JDhDvnkF+vn6yCAGuRPV03zuRqZtDy4N4to7PZu9PjqrRl7nDMXrG3YG9y\n" + "rlIAZ72KjcKKFAJxQyAKLCIdawKRyp8RdK3LEySWEZb0AV58IadqPZDTNHHRX8dz\n" + "5aTSMsbbkZ+C/OzTnbiMqLL/vg6jAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAUflI\n" + "VUe5Krqf5RVa5C3u/UTAOAUJBiDS3VANTCLBxjuMsvqOG0WvaYWP3HYPgrz0jXK2\n" + "LJE/mGw3MyFHEqi81jh95J+ypl6xKW6Rm8jKLR87gUvCaVYn/Z4/P3AqcQTB7wOv\n" + "UD0A8qfhfDM+LK6rPAnCsVN0NRDY3jvd6rzix9M=\n" + "-----END CERTIFICATE-----\n"; + +#define MAYBE_SKIP_TEST(feature) \ + if (!(rtc::SSLStreamAdapter::feature())) { \ + LOG(LS_INFO) << "Feature disabled... skipping"; \ + return; \ + } + +class SSLStreamAdapterTestBase; + +class SSLDummyStream : public rtc::StreamInterface, + public sigslot::has_slots<> { + public: + explicit SSLDummyStream(SSLStreamAdapterTestBase *test, + const std::string &side, + rtc::FifoBuffer *in, + rtc::FifoBuffer *out) : + test_(test), + side_(side), + in_(in), + out_(out), + first_packet_(true) { + in_->SignalEvent.connect(this, &SSLDummyStream::OnEventIn); + out_->SignalEvent.connect(this, &SSLDummyStream::OnEventOut); + } + + virtual rtc::StreamState GetState() const { return rtc::SS_OPEN; } + + virtual rtc::StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + rtc::StreamResult r; + + r = in_->Read(buffer, buffer_len, read, error); + if (r == rtc::SR_BLOCK) + return rtc::SR_BLOCK; + if (r == rtc::SR_EOS) + return rtc::SR_EOS; + + if (r != rtc::SR_SUCCESS) { + ADD_FAILURE(); + return rtc::SR_ERROR; + } + + return rtc::SR_SUCCESS; + } + + // Catch readability events on in and pass them up. + virtual void OnEventIn(rtc::StreamInterface *stream, int sig, + int err) { + int mask = (rtc::SE_READ | rtc::SE_CLOSE); + + if (sig & mask) { + LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig=" + << sig << " forwarding upward"; + PostEvent(sig & mask, 0); + } + } + + // Catch writeability events on out and pass them up. + virtual void OnEventOut(rtc::StreamInterface *stream, int sig, + int err) { + if (sig & rtc::SE_WRITE) { + LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig=" + << sig << " forwarding upward"; + + PostEvent(sig & rtc::SE_WRITE, 0); + } + } + + // Write to the outgoing FifoBuffer + rtc::StreamResult WriteData(const void* data, size_t data_len, + size_t* written, int* error) { + return out_->Write(data, data_len, written, error); + } + + // Defined later + virtual rtc::StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + virtual void Close() { + LOG(LS_INFO) << "Closing outbound stream"; + out_->Close(); + } + + private: + SSLStreamAdapterTestBase *test_; + const std::string side_; + rtc::FifoBuffer *in_; + rtc::FifoBuffer *out_; + bool first_packet_; +}; + +static const int kFifoBufferSize = 4096; + +class SSLStreamAdapterTestBase : public testing::Test, + public sigslot::has_slots<> { + public: + SSLStreamAdapterTestBase(const std::string& client_cert_pem, + const std::string& client_private_key_pem, + bool dtls) : + client_buffer_(kFifoBufferSize), server_buffer_(kFifoBufferSize), + client_stream_( + new SSLDummyStream(this, "c2s", &client_buffer_, &server_buffer_)), + server_stream_( + new SSLDummyStream(this, "s2c", &server_buffer_, &client_buffer_)), + client_ssl_(rtc::SSLStreamAdapter::Create(client_stream_)), + server_ssl_(rtc::SSLStreamAdapter::Create(server_stream_)), + client_identity_(NULL), server_identity_(NULL), + delay_(0), mtu_(1460), loss_(0), lose_first_packet_(false), + damage_(false), dtls_(dtls), + handshake_wait_(5000), identities_set_(false) { + // Set use of the test RNG to get predictable loss patterns. + rtc::SetRandomTestMode(true); + + // Set up the slots + client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + + if (!client_cert_pem.empty() && !client_private_key_pem.empty()) { + client_identity_ = rtc::SSLIdentity::FromPEMStrings( + client_private_key_pem, client_cert_pem); + } else { + client_identity_ = rtc::SSLIdentity::Generate("client"); + } + server_identity_ = rtc::SSLIdentity::Generate("server"); + + client_ssl_->SetIdentity(client_identity_); + server_ssl_->SetIdentity(server_identity_); + } + + ~SSLStreamAdapterTestBase() { + // Put it back for the next test. + rtc::SetRandomTestMode(false); + } + + static void SetUpTestCase() { + rtc::InitializeSSL(); + } + + static void TearDownTestCase() { + rtc::CleanupSSL(); + } + + // Recreate the client/server identities with the specified validity period. + // |not_before| and |not_after| are offsets from the current time in number + // of seconds. + void ResetIdentitiesWithValidity(int not_before, int not_after) { + client_stream_ = + new SSLDummyStream(this, "c2s", &client_buffer_, &server_buffer_); + server_stream_ = + new SSLDummyStream(this, "s2c", &server_buffer_, &client_buffer_); + + client_ssl_.reset(rtc::SSLStreamAdapter::Create(client_stream_)); + server_ssl_.reset(rtc::SSLStreamAdapter::Create(server_stream_)); + + client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent); + + rtc::SSLIdentityParams client_params; + client_params.common_name = "client"; + client_params.not_before = not_before; + client_params.not_after = not_after; + client_identity_ = rtc::SSLIdentity::GenerateForTest(client_params); + + rtc::SSLIdentityParams server_params; + server_params.common_name = "server"; + server_params.not_before = not_before; + server_params.not_after = not_after; + server_identity_ = rtc::SSLIdentity::GenerateForTest(server_params); + + client_ssl_->SetIdentity(client_identity_); + server_ssl_->SetIdentity(server_identity_); + } + + virtual void OnEvent(rtc::StreamInterface *stream, int sig, int err) { + LOG(LS_INFO) << "SSLStreamAdapterTestBase::OnEvent sig=" << sig; + + if (sig & rtc::SE_READ) { + ReadData(stream); + } + + if ((stream == client_ssl_.get()) && (sig & rtc::SE_WRITE)) { + WriteData(); + } + } + + void SetPeerIdentitiesByDigest(bool correct) { + unsigned char digest[20]; + size_t digest_len; + bool rv; + + LOG(LS_INFO) << "Setting peer identities by digest"; + + rv = server_identity_->certificate().ComputeDigest(rtc::DIGEST_SHA_1, + digest, 20, + &digest_len); + ASSERT_TRUE(rv); + if (!correct) { + LOG(LS_INFO) << "Setting bogus digest for server cert"; + digest[0]++; + } + rv = client_ssl_->SetPeerCertificateDigest(rtc::DIGEST_SHA_1, digest, + digest_len); + ASSERT_TRUE(rv); + + + rv = client_identity_->certificate().ComputeDigest(rtc::DIGEST_SHA_1, + digest, 20, &digest_len); + ASSERT_TRUE(rv); + if (!correct) { + LOG(LS_INFO) << "Setting bogus digest for client cert"; + digest[0]++; + } + rv = server_ssl_->SetPeerCertificateDigest(rtc::DIGEST_SHA_1, digest, + digest_len); + ASSERT_TRUE(rv); + + identities_set_ = true; + } + + void TestHandshake(bool expect_success = true) { + server_ssl_->SetMode(dtls_ ? rtc::SSL_MODE_DTLS : + rtc::SSL_MODE_TLS); + client_ssl_->SetMode(dtls_ ? rtc::SSL_MODE_DTLS : + rtc::SSL_MODE_TLS); + + if (!dtls_) { + // Make sure we simulate a reliable network for TLS. + // This is just a check to make sure that people don't write wrong + // tests. + ASSERT((mtu_ == 1460) && (loss_ == 0) && (lose_first_packet_ == 0)); + } + + if (!identities_set_) + SetPeerIdentitiesByDigest(true); + + // Start the handshake + int rv; + + server_ssl_->SetServerRole(); + rv = server_ssl_->StartSSLWithPeer(); + ASSERT_EQ(0, rv); + + rv = client_ssl_->StartSSLWithPeer(); + ASSERT_EQ(0, rv); + + // Now run the handshake + if (expect_success) { + EXPECT_TRUE_WAIT((client_ssl_->GetState() == rtc::SS_OPEN) + && (server_ssl_->GetState() == rtc::SS_OPEN), + handshake_wait_); + } else { + EXPECT_TRUE_WAIT(client_ssl_->GetState() == rtc::SS_CLOSED, + handshake_wait_); + } + } + + rtc::StreamResult DataWritten(SSLDummyStream *from, const void *data, + size_t data_len, size_t *written, + int *error) { + // Randomly drop loss_ percent of packets + if (rtc::CreateRandomId() % 100 < static_cast(loss_)) { + LOG(LS_INFO) << "Randomly dropping packet, size=" << data_len; + *written = data_len; + return rtc::SR_SUCCESS; + } + if (dtls_ && (data_len > mtu_)) { + LOG(LS_INFO) << "Dropping packet > mtu, size=" << data_len; + *written = data_len; + return rtc::SR_SUCCESS; + } + + // Optionally damage application data (type 23). Note that we don't damage + // handshake packets and we damage the last byte to keep the header + // intact but break the MAC. + if (damage_ && (*static_cast(data) == 23)) { + std::vector buf(data_len); + + LOG(LS_INFO) << "Damaging packet"; + + memcpy(&buf[0], data, data_len); + buf[data_len - 1]++; + + return from->WriteData(&buf[0], data_len, written, error); + } + + return from->WriteData(data, data_len, written, error); + } + + void SetDelay(int delay) { + delay_ = delay; + } + int GetDelay() { return delay_; } + + void SetLoseFirstPacket(bool lose) { + lose_first_packet_ = lose; + } + bool GetLoseFirstPacket() { return lose_first_packet_; } + + void SetLoss(int percent) { + loss_ = percent; + } + + void SetDamage() { + damage_ = true; + } + + void SetMtu(size_t mtu) { + mtu_ = mtu; + } + + void SetHandshakeWait(int wait) { + handshake_wait_ = wait; + } + + void SetDtlsSrtpCiphers(const std::vector &ciphers, + bool client) { + if (client) + client_ssl_->SetDtlsSrtpCiphers(ciphers); + else + server_ssl_->SetDtlsSrtpCiphers(ciphers); + } + + bool GetDtlsSrtpCipher(bool client, std::string *retval) { + if (client) + return client_ssl_->GetDtlsSrtpCipher(retval); + else + return server_ssl_->GetDtlsSrtpCipher(retval); + } + + bool GetPeerCertificate(bool client, rtc::SSLCertificate** cert) { + if (client) + return client_ssl_->GetPeerCertificate(cert); + else + return server_ssl_->GetPeerCertificate(cert); + } + + bool ExportKeyingMaterial(const char *label, + const unsigned char *context, + size_t context_len, + bool use_context, + bool client, + unsigned char *result, + size_t result_len) { + if (client) + return client_ssl_->ExportKeyingMaterial(label, + context, context_len, + use_context, + result, result_len); + else + return server_ssl_->ExportKeyingMaterial(label, + context, context_len, + use_context, + result, result_len); + } + + // To be implemented by subclasses. + virtual void WriteData() = 0; + virtual void ReadData(rtc::StreamInterface *stream) = 0; + virtual void TestTransfer(int size) = 0; + + protected: + rtc::FifoBuffer client_buffer_; + rtc::FifoBuffer server_buffer_; + SSLDummyStream *client_stream_; // freed by client_ssl_ destructor + SSLDummyStream *server_stream_; // freed by server_ssl_ destructor + rtc::scoped_ptr client_ssl_; + rtc::scoped_ptr server_ssl_; + rtc::SSLIdentity *client_identity_; // freed by client_ssl_ destructor + rtc::SSLIdentity *server_identity_; // freed by server_ssl_ destructor + int delay_; + size_t mtu_; + int loss_; + bool lose_first_packet_; + bool damage_; + bool dtls_; + int handshake_wait_; + bool identities_set_; +}; + +class SSLStreamAdapterTestTLS : public SSLStreamAdapterTestBase { + public: + SSLStreamAdapterTestTLS() : + SSLStreamAdapterTestBase("", "", false) { + }; + + // Test data transfer for TLS + virtual void TestTransfer(int size) { + LOG(LS_INFO) << "Starting transfer test with " << size << " bytes"; + // Create some dummy data to send. + size_t received; + + send_stream_.ReserveSize(size); + for (int i = 0; i < size; ++i) { + char ch = static_cast(i); + send_stream_.Write(&ch, 1, NULL, NULL); + } + send_stream_.Rewind(); + + // Prepare the receive stream. + recv_stream_.ReserveSize(size); + + // Start sending + WriteData(); + + // Wait for the client to close + EXPECT_TRUE_WAIT(server_ssl_->GetState() == rtc::SS_CLOSED, 10000); + + // Now check the data + recv_stream_.GetSize(&received); + + EXPECT_EQ(static_cast(size), received); + EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(), + recv_stream_.GetBuffer(), size)); + } + + void WriteData() { + size_t position, tosend, size; + rtc::StreamResult rv; + size_t sent; + char block[kBlockSize]; + + send_stream_.GetSize(&size); + if (!size) + return; + + for (;;) { + send_stream_.GetPosition(&position); + if (send_stream_.Read(block, sizeof(block), &tosend, NULL) != + rtc::SR_EOS) { + rv = client_ssl_->Write(block, tosend, &sent, 0); + + if (rv == rtc::SR_SUCCESS) { + send_stream_.SetPosition(position + sent); + LOG(LS_VERBOSE) << "Sent: " << position + sent; + } else if (rv == rtc::SR_BLOCK) { + LOG(LS_VERBOSE) << "Blocked..."; + send_stream_.SetPosition(position); + break; + } else { + ADD_FAILURE(); + break; + } + } else { + // Now close + LOG(LS_INFO) << "Wrote " << position << " bytes. Closing"; + client_ssl_->Close(); + break; + } + } + }; + + virtual void ReadData(rtc::StreamInterface *stream) { + char buffer[1600]; + size_t bread; + int err2; + rtc::StreamResult r; + + for (;;) { + r = stream->Read(buffer, sizeof(buffer), &bread, &err2); + + if (r == rtc::SR_ERROR || r == rtc::SR_EOS) { + // Unfortunately, errors are the way that the stream adapter + // signals close in OpenSSL + stream->Close(); + return; + } + + if (r == rtc::SR_BLOCK) + break; + + ASSERT_EQ(rtc::SR_SUCCESS, r); + LOG(LS_INFO) << "Read " << bread; + + recv_stream_.Write(buffer, bread, NULL, NULL); + } + } + + private: + rtc::MemoryStream send_stream_; + rtc::MemoryStream recv_stream_; +}; + +class SSLStreamAdapterTestDTLS : public SSLStreamAdapterTestBase { + public: + SSLStreamAdapterTestDTLS() : + SSLStreamAdapterTestBase("", "", true), + packet_size_(1000), count_(0), sent_(0) { + } + + SSLStreamAdapterTestDTLS(const std::string& cert_pem, + const std::string& private_key_pem) : + SSLStreamAdapterTestBase(cert_pem, private_key_pem, true), + packet_size_(1000), count_(0), sent_(0) { + } + + virtual void WriteData() { + unsigned char *packet = new unsigned char[1600]; + + do { + memset(packet, sent_ & 0xff, packet_size_); + *(reinterpret_cast(packet)) = sent_; + + size_t sent; + int rv = client_ssl_->Write(packet, packet_size_, &sent, 0); + if (rv == rtc::SR_SUCCESS) { + LOG(LS_VERBOSE) << "Sent: " << sent_; + sent_++; + } else if (rv == rtc::SR_BLOCK) { + LOG(LS_VERBOSE) << "Blocked..."; + break; + } else { + ADD_FAILURE(); + break; + } + } while (sent_ < count_); + + delete [] packet; + } + + virtual void ReadData(rtc::StreamInterface *stream) { + unsigned char buffer[2000]; + size_t bread; + int err2; + rtc::StreamResult r; + + for (;;) { + r = stream->Read(buffer, 2000, &bread, &err2); + + if (r == rtc::SR_ERROR) { + // Unfortunately, errors are the way that the stream adapter + // signals close right now + stream->Close(); + return; + } + + if (r == rtc::SR_BLOCK) + break; + + ASSERT_EQ(rtc::SR_SUCCESS, r); + LOG(LS_INFO) << "Read " << bread; + + // Now parse the datagram + ASSERT_EQ(packet_size_, bread); + unsigned char* ptr_to_buffer = buffer; + uint32_t packet_num = *(reinterpret_cast(ptr_to_buffer)); + + for (size_t i = 4; i < packet_size_; i++) { + ASSERT_EQ((packet_num & 0xff), buffer[i]); + } + received_.insert(packet_num); + } + } + + virtual void TestTransfer(int count) { + count_ = count; + + WriteData(); + + EXPECT_TRUE_WAIT(sent_ == count_, 10000); + LOG(LS_INFO) << "sent_ == " << sent_; + + if (damage_) { + WAIT(false, 2000); + EXPECT_EQ(0U, received_.size()); + } else if (loss_ == 0) { + EXPECT_EQ_WAIT(static_cast(sent_), received_.size(), 1000); + } else { + LOG(LS_INFO) << "Sent " << sent_ << " packets; received " << + received_.size(); + } + }; + + private: + size_t packet_size_; + int count_; + int sent_; + std::set received_; +}; + + +rtc::StreamResult SSLDummyStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + *written = data_len; + + LOG(LS_INFO) << "Writing to loopback " << data_len; + + if (first_packet_) { + first_packet_ = false; + if (test_->GetLoseFirstPacket()) { + LOG(LS_INFO) << "Losing initial packet of length " << data_len; + return rtc::SR_SUCCESS; + } + } + + return test_->DataWritten(this, data, data_len, written, error); + + return rtc::SR_SUCCESS; +}; + +class SSLStreamAdapterTestDTLSFromPEMStrings : public SSLStreamAdapterTestDTLS { + public: + SSLStreamAdapterTestDTLSFromPEMStrings() : + SSLStreamAdapterTestDTLS(kCERT_PEM, kRSA_PRIVATE_KEY_PEM) { + } +}; + +// Basic tests: TLS + +// Test that we cannot read/write if we have not yet handshaked. +// This test only applies to NSS because OpenSSL has passthrough +// semantics for I/O before the handshake is started. +#if SSL_USE_NSS +TEST_F(SSLStreamAdapterTestTLS, TestNoReadWriteBeforeConnect) { + rtc::StreamResult rv; + char block[kBlockSize]; + size_t dummy; + + rv = client_ssl_->Write(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_BLOCK, rv); + + rv = client_ssl_->Read(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_BLOCK, rv); +} +#endif + + +// Test that we can make a handshake work +TEST_F(SSLStreamAdapterTestTLS, TestTLSConnect) { + TestHandshake(); +}; + +// Test transfer -- trivial +TEST_F(SSLStreamAdapterTestTLS, TestTLSTransfer) { + TestHandshake(); + TestTransfer(100000); +}; + +// Test read-write after close. +TEST_F(SSLStreamAdapterTestTLS, ReadWriteAfterClose) { + TestHandshake(); + TestTransfer(100000); + client_ssl_->Close(); + + rtc::StreamResult rv; + char block[kBlockSize]; + size_t dummy; + + // It's an error to write after closed. + rv = client_ssl_->Write(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_ERROR, rv); + + // But after closed read gives you EOS. + rv = client_ssl_->Read(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_EOS, rv); +}; + +// Test a handshake with a bogus peer digest +TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusDigest) { + SetPeerIdentitiesByDigest(false); + TestHandshake(false); +}; + +// Test moving a bunch of data + +// Basic tests: DTLS +// Test that we can make a handshake work +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnect) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); +}; + +// Test that we can make a handshake work if the first packet in +// each direction is lost. This gives us predictable loss +// rather than having to tune random +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithLostFirstPacket) { + MAYBE_SKIP_TEST(HaveDtls); + SetLoseFirstPacket(true); + TestHandshake(); +}; + +// Test a handshake with loss and delay +TEST_F(SSLStreamAdapterTestDTLS, + TestDTLSConnectWithLostFirstPacketDelay2s) { + MAYBE_SKIP_TEST(HaveDtls); + SetLoseFirstPacket(true); + SetDelay(2000); + SetHandshakeWait(20000); + TestHandshake(); +}; + +// Test a handshake with small MTU +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithSmallMtu) { + MAYBE_SKIP_TEST(HaveDtls); + SetMtu(700); + SetHandshakeWait(20000); + TestHandshake(); +}; + +// Test transfer -- trivial +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransfer) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); + TestTransfer(100); +}; + +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithLoss) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); + SetLoss(10); + TestTransfer(100); +}; + +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithDamage) { + MAYBE_SKIP_TEST(HaveDtls); + SetDamage(); // Must be called first because first packet + // write happens at end of handshake. + TestHandshake(); + TestTransfer(100); +}; + +// Test DTLS-SRTP with all high ciphers +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHigh) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector high; + high.push_back(kAES_CM_HMAC_SHA1_80); + SetDtlsSrtpCiphers(high, true); + SetDtlsSrtpCiphers(high, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80); +}; + +// Test DTLS-SRTP with all low ciphers +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpLow) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector low; + low.push_back(kAES_CM_HMAC_SHA1_32); + SetDtlsSrtpCiphers(low, true); + SetDtlsSrtpCiphers(low, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_32); +}; + + +// Test DTLS-SRTP with a mismatch -- should not converge +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHighLow) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector high; + high.push_back(kAES_CM_HMAC_SHA1_80); + std::vector low; + low.push_back(kAES_CM_HMAC_SHA1_32); + SetDtlsSrtpCiphers(high, true); + SetDtlsSrtpCiphers(low, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_FALSE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_FALSE(GetDtlsSrtpCipher(false, &server_cipher)); +}; + +// Test DTLS-SRTP with each side being mixed -- should select high +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpMixed) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + std::vector mixed; + mixed.push_back(kAES_CM_HMAC_SHA1_80); + mixed.push_back(kAES_CM_HMAC_SHA1_32); + SetDtlsSrtpCiphers(mixed, true); + SetDtlsSrtpCiphers(mixed, false); + TestHandshake(); + + std::string client_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher)); + std::string server_cipher; + ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher)); + + ASSERT_EQ(client_cipher, server_cipher); + ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80); +}; + +// Test an exporter +TEST_F(SSLStreamAdapterTestDTLS, TestDTLSExporter) { + MAYBE_SKIP_TEST(HaveExporter); + TestHandshake(); + unsigned char client_out[20]; + unsigned char server_out[20]; + + bool result; + result = ExportKeyingMaterial(kExporterLabel, + kExporterContext, kExporterContextLen, + true, true, + client_out, sizeof(client_out)); + ASSERT_TRUE(result); + + result = ExportKeyingMaterial(kExporterLabel, + kExporterContext, kExporterContextLen, + true, false, + server_out, sizeof(server_out)); + ASSERT_TRUE(result); + + ASSERT_TRUE(!memcmp(client_out, server_out, sizeof(client_out))); +} + +// Test not yet valid certificates are not rejected. +TEST_F(SSLStreamAdapterTestDTLS, TestCertNotYetValid) { + MAYBE_SKIP_TEST(HaveDtls); + long one_day = 60 * 60 * 24; + // Make the certificates not valid until one day later. + ResetIdentitiesWithValidity(one_day, one_day); + TestHandshake(); +} + +// Test expired certificates are not rejected. +TEST_F(SSLStreamAdapterTestDTLS, TestCertExpired) { + MAYBE_SKIP_TEST(HaveDtls); + long one_day = 60 * 60 * 24; + // Make the certificates already expired. + ResetIdentitiesWithValidity(-one_day, -one_day); + TestHandshake(); +} + +// Test data transfer using certs created from strings. +TEST_F(SSLStreamAdapterTestDTLSFromPEMStrings, TestTransfer) { + MAYBE_SKIP_TEST(HaveDtls); + TestHandshake(); + TestTransfer(100); +} + +// Test getting the remote certificate. +TEST_F(SSLStreamAdapterTestDTLSFromPEMStrings, TestDTLSGetPeerCertificate) { + MAYBE_SKIP_TEST(HaveDtls); + + // Peer certificates haven't been received yet. + rtc::scoped_ptr client_peer_cert; + ASSERT_FALSE(GetPeerCertificate(true, client_peer_cert.accept())); + ASSERT_FALSE(client_peer_cert != NULL); + + rtc::scoped_ptr server_peer_cert; + ASSERT_FALSE(GetPeerCertificate(false, server_peer_cert.accept())); + ASSERT_FALSE(server_peer_cert != NULL); + + TestHandshake(); + + // The client should have a peer certificate after the handshake. + ASSERT_TRUE(GetPeerCertificate(true, client_peer_cert.accept())); + ASSERT_TRUE(client_peer_cert != NULL); + + // It's not kCERT_PEM. + std::string client_peer_string = client_peer_cert->ToPEMString(); + ASSERT_NE(kCERT_PEM, client_peer_string); + + // It must not have a chain, because the test certs are self-signed. + rtc::SSLCertChain* client_peer_chain; + ASSERT_FALSE(client_peer_cert->GetChain(&client_peer_chain)); + + // The server should have a peer certificate after the handshake. + ASSERT_TRUE(GetPeerCertificate(false, server_peer_cert.accept())); + ASSERT_TRUE(server_peer_cert != NULL); + + // It's kCERT_PEM + ASSERT_EQ(kCERT_PEM, server_peer_cert->ToPEMString()); + + // It must not have a chain, because the test certs are self-signed. + rtc::SSLCertChain* server_peer_chain; + ASSERT_FALSE(server_peer_cert->GetChain(&server_peer_chain)); +} diff --git a/webrtc/base/sslstreamadapterhelper.cc b/webrtc/base/sslstreamadapterhelper.cc new file mode 100644 index 000000000..d9c6afd40 --- /dev/null +++ b/webrtc/base/sslstreamadapterhelper.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2004 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 + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "webrtc/base/sslstreamadapterhelper.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +void SSLStreamAdapterHelper::SetIdentity(SSLIdentity* identity) { + ASSERT(identity_.get() == NULL); + identity_.reset(identity); +} + +void SSLStreamAdapterHelper::SetServerRole(SSLRole role) { + role_ = role; +} + +int SSLStreamAdapterHelper::StartSSLWithServer(const char* server_name) { + ASSERT(server_name != NULL && server_name[0] != '\0'); + ssl_server_name_ = server_name; + return StartSSL(); +} + +int SSLStreamAdapterHelper::StartSSLWithPeer() { + ASSERT(ssl_server_name_.empty()); + // It is permitted to specify peer_certificate_ only later. + return StartSSL(); +} + +void SSLStreamAdapterHelper::SetMode(SSLMode mode) { + ASSERT(state_ == SSL_NONE); + ssl_mode_ = mode; +} + +StreamState SSLStreamAdapterHelper::GetState() const { + switch (state_) { + case SSL_WAIT: + case SSL_CONNECTING: + return SS_OPENING; + case SSL_CONNECTED: + return SS_OPEN; + default: + return SS_CLOSED; + }; + // not reached +} + +bool SSLStreamAdapterHelper::GetPeerCertificate(SSLCertificate** cert) const { + if (!peer_certificate_) + return false; + + *cert = peer_certificate_->GetReference(); + return true; +} + +bool SSLStreamAdapterHelper::SetPeerCertificateDigest( + const std::string &digest_alg, + const unsigned char* digest_val, + size_t digest_len) { + ASSERT(peer_certificate_.get() == NULL); + ASSERT(peer_certificate_digest_algorithm_.empty()); + ASSERT(ssl_server_name_.empty()); + size_t expected_len; + + if (!GetDigestLength(digest_alg, &expected_len)) { + LOG(LS_WARNING) << "Unknown digest algorithm: " << digest_alg; + return false; + } + if (expected_len != digest_len) + return false; + + peer_certificate_digest_value_.SetData(digest_val, digest_len); + peer_certificate_digest_algorithm_ = digest_alg; + + return true; +} + +void SSLStreamAdapterHelper::Error(const char* context, int err, bool signal) { + LOG(LS_WARNING) << "SSLStreamAdapterHelper::Error(" + << context << ", " << err << "," << signal << ")"; + state_ = SSL_ERROR; + ssl_error_code_ = err; + Cleanup(); + if (signal) + StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err); +} + +void SSLStreamAdapterHelper::Close() { + Cleanup(); + ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR); + StreamAdapterInterface::Close(); +} + +int SSLStreamAdapterHelper::StartSSL() { + ASSERT(state_ == SSL_NONE); + + if (StreamAdapterInterface::GetState() != SS_OPEN) { + state_ = SSL_WAIT; + return 0; + } + + state_ = SSL_CONNECTING; + int err = BeginSSL(); + if (err) { + Error("BeginSSL", err, false); + return err; + } + + return 0; +} + +} // namespace rtc + diff --git a/webrtc/base/sslstreamadapterhelper.h b/webrtc/base/sslstreamadapterhelper.h new file mode 100644 index 000000000..ef06597b8 --- /dev/null +++ b/webrtc/base/sslstreamadapterhelper.h @@ -0,0 +1,118 @@ +/* + * Copyright 2004 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_BASE_SSLSTREAMADAPTERHELPER_H_ +#define WEBRTC_BASE_SSLSTREAMADAPTERHELPER_H_ + +#include +#include + +#include "webrtc/base/buffer.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/base/sslstreamadapter.h" + +namespace rtc { + +// SSLStreamAdapterHelper : A stream adapter which implements much +// of the logic that is common between the known implementations +// (NSS and OpenSSL) +class SSLStreamAdapterHelper : public SSLStreamAdapter { + public: + explicit SSLStreamAdapterHelper(StreamInterface* stream) + : SSLStreamAdapter(stream), + state_(SSL_NONE), + role_(SSL_CLIENT), + ssl_error_code_(0), // Not meaningful yet + ssl_mode_(SSL_MODE_TLS) {} + + + // Overrides of SSLStreamAdapter + virtual void SetIdentity(SSLIdentity* identity); + virtual void SetServerRole(SSLRole role = SSL_SERVER); + virtual void SetMode(SSLMode mode); + + virtual int StartSSLWithServer(const char* server_name); + virtual int StartSSLWithPeer(); + + virtual bool SetPeerCertificateDigest(const std::string& digest_alg, + const unsigned char* digest_val, + size_t digest_len); + virtual bool GetPeerCertificate(SSLCertificate** cert) const; + virtual StreamState GetState() const; + virtual void Close(); + + protected: + // Internal helper methods + // The following method returns 0 on success and a negative + // error code on failure. The error code may be either -1 or + // from the impl on some other error cases, so it can't really be + // interpreted unfortunately. + + // Perform SSL negotiation steps. + int ContinueSSL(); + + // Error handler helper. signal is given as true for errors in + // asynchronous contexts (when an error code was not returned + // through some other method), and in that case an SE_CLOSE event is + // raised on the stream with the specified error. + // A 0 error means a graceful close, otherwise there is not really enough + // context to interpret the error code. + virtual void Error(const char* context, int err, bool signal); + + // Must be implemented by descendents + virtual int BeginSSL() = 0; + virtual void Cleanup() = 0; + virtual bool GetDigestLength(const std::string& algorithm, + size_t* length) = 0; + + enum SSLState { + // Before calling one of the StartSSL methods, data flows + // in clear text. + SSL_NONE, + SSL_WAIT, // waiting for the stream to open to start SSL negotiation + SSL_CONNECTING, // SSL negotiation in progress + SSL_CONNECTED, // SSL stream successfully established + SSL_ERROR, // some SSL error occurred, stream is closed + SSL_CLOSED // Clean close + }; + + // MSG_MAX is the maximum generic stream message number. + enum { MSG_DTLS_TIMEOUT = MSG_MAX + 1 }; + + SSLState state_; + SSLRole role_; + int ssl_error_code_; // valid when state_ == SSL_ERROR + + // Our key and certificate, mostly useful in peer-to-peer mode. + scoped_ptr identity_; + // in traditional mode, the server name that the server's certificate + // must specify. Empty in peer-to-peer mode. + std::string ssl_server_name_; + // The peer's certificate. Only used for GetPeerCertificate. + scoped_ptr peer_certificate_; + + // The digest of the certificate that the peer must present. + Buffer peer_certificate_digest_value_; + std::string peer_certificate_digest_algorithm_; + + // Do DTLS or not + SSLMode ssl_mode_; + + private: + // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT, + // depending on whether the underlying stream is already open or + // not. Returns 0 on success and a negative value on error. + int StartSSL(); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SSLSTREAMADAPTERHELPER_H_ diff --git a/webrtc/base/stream.cc b/webrtc/base/stream.cc new file mode 100644 index 000000000..9aa10d773 --- /dev/null +++ b/webrtc/base/stream.cc @@ -0,0 +1,1335 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_POSIX) +#include +#endif // WEBRTC_POSIX +#include +#include +#include +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#define fileno _fileno +#endif + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// StreamInterface +/////////////////////////////////////////////////////////////////////////////// +StreamInterface::~StreamInterface() { +} + +StreamResult StreamInterface::WriteAll(const void* data, size_t data_len, + size_t* written, int* error) { + StreamResult result = SR_SUCCESS; + size_t total_written = 0, current_written; + while (total_written < data_len) { + result = Write(static_cast(data) + total_written, + data_len - total_written, ¤t_written, error); + if (result != SR_SUCCESS) + break; + total_written += current_written; + } + if (written) + *written = total_written; + return result; +} + +StreamResult StreamInterface::ReadAll(void* buffer, size_t buffer_len, + size_t* read, int* error) { + StreamResult result = SR_SUCCESS; + size_t total_read = 0, current_read; + while (total_read < buffer_len) { + result = Read(static_cast(buffer) + total_read, + buffer_len - total_read, ¤t_read, error); + if (result != SR_SUCCESS) + break; + total_read += current_read; + } + if (read) + *read = total_read; + return result; +} + +StreamResult StreamInterface::ReadLine(std::string* line) { + line->clear(); + StreamResult result = SR_SUCCESS; + while (true) { + char ch; + result = Read(&ch, sizeof(ch), NULL, NULL); + if (result != SR_SUCCESS) { + break; + } + if (ch == '\n') { + break; + } + line->push_back(ch); + } + if (!line->empty()) { // give back the line we've collected so far with + result = SR_SUCCESS; // a success code. Otherwise return the last code + } + return result; +} + +void StreamInterface::PostEvent(Thread* t, int events, int err) { + t->Post(this, MSG_POST_EVENT, new StreamEventData(events, err)); +} + +void StreamInterface::PostEvent(int events, int err) { + PostEvent(Thread::Current(), events, err); +} + +StreamInterface::StreamInterface() { +} + +void StreamInterface::OnMessage(Message* msg) { + if (MSG_POST_EVENT == msg->message_id) { + StreamEventData* pe = static_cast(msg->pdata); + SignalEvent(this, pe->events, pe->error); + delete msg->pdata; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamAdapterInterface +/////////////////////////////////////////////////////////////////////////////// + +StreamAdapterInterface::StreamAdapterInterface(StreamInterface* stream, + bool owned) + : stream_(stream), owned_(owned) { + if (NULL != stream_) + stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent); +} + +void StreamAdapterInterface::Attach(StreamInterface* stream, bool owned) { + if (NULL != stream_) + stream_->SignalEvent.disconnect(this); + if (owned_) + delete stream_; + stream_ = stream; + owned_ = owned; + if (NULL != stream_) + stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent); +} + +StreamInterface* StreamAdapterInterface::Detach() { + if (NULL != stream_) + stream_->SignalEvent.disconnect(this); + StreamInterface* stream = stream_; + stream_ = NULL; + return stream; +} + +StreamAdapterInterface::~StreamAdapterInterface() { + if (owned_) + delete stream_; +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamTap +/////////////////////////////////////////////////////////////////////////////// + +StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap) + : StreamAdapterInterface(stream), tap_(), tap_result_(SR_SUCCESS), + tap_error_(0) { + AttachTap(tap); +} + +void StreamTap::AttachTap(StreamInterface* tap) { + tap_.reset(tap); +} + +StreamInterface* StreamTap::DetachTap() { + return tap_.release(); +} + +StreamResult StreamTap::GetTapResult(int* error) { + if (error) { + *error = tap_error_; + } + return tap_result_; +} + +StreamResult StreamTap::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + size_t backup_read; + if (!read) { + read = &backup_read; + } + StreamResult res = StreamAdapterInterface::Read(buffer, buffer_len, + read, error); + if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) { + tap_result_ = tap_->WriteAll(buffer, *read, NULL, &tap_error_); + } + return res; +} + +StreamResult StreamTap::Write(const void* data, size_t data_len, + size_t* written, int* error) { + size_t backup_written; + if (!written) { + written = &backup_written; + } + StreamResult res = StreamAdapterInterface::Write(data, data_len, + written, error); + if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) { + tap_result_ = tap_->WriteAll(data, *written, NULL, &tap_error_); + } + return res; +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamSegment +/////////////////////////////////////////////////////////////////////////////// + +StreamSegment::StreamSegment(StreamInterface* stream) + : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0), + length_(SIZE_UNKNOWN) { + // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN. + stream->GetPosition(&start_); +} + +StreamSegment::StreamSegment(StreamInterface* stream, size_t length) + : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0), + length_(length) { + // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN. + stream->GetPosition(&start_); +} + +StreamResult StreamSegment::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (SIZE_UNKNOWN != length_) { + if (pos_ >= length_) + return SR_EOS; + buffer_len = _min(buffer_len, length_ - pos_); + } + size_t backup_read; + if (!read) { + read = &backup_read; + } + StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, + read, error); + if (SR_SUCCESS == result) { + pos_ += *read; + } + return result; +} + +bool StreamSegment::SetPosition(size_t position) { + if (SIZE_UNKNOWN == start_) + return false; // Not seekable + if ((SIZE_UNKNOWN != length_) && (position > length_)) + return false; // Seek past end of segment + if (!StreamAdapterInterface::SetPosition(start_ + position)) + return false; + pos_ = position; + return true; +} + +bool StreamSegment::GetPosition(size_t* position) const { + if (SIZE_UNKNOWN == start_) + return false; // Not seekable + if (!StreamAdapterInterface::GetPosition(position)) + return false; + if (position) { + ASSERT(*position >= start_); + *position -= start_; + } + return true; +} + +bool StreamSegment::GetSize(size_t* size) const { + if (!StreamAdapterInterface::GetSize(size)) + return false; + if (size) { + if (SIZE_UNKNOWN != start_) { + ASSERT(*size >= start_); + *size -= start_; + } + if (SIZE_UNKNOWN != length_) { + *size = _min(*size, length_); + } + } + return true; +} + +bool StreamSegment::GetAvailable(size_t* size) const { + if (!StreamAdapterInterface::GetAvailable(size)) + return false; + if (size && (SIZE_UNKNOWN != length_)) + *size = _min(*size, length_ - pos_); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// NullStream +/////////////////////////////////////////////////////////////////////////////// + +NullStream::NullStream() { +} + +NullStream::~NullStream() { +} + +StreamState NullStream::GetState() const { + return SS_OPEN; +} + +StreamResult NullStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (error) *error = -1; + return SR_ERROR; +} + +StreamResult NullStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (written) *written = data_len; + return SR_SUCCESS; +} + +void NullStream::Close() { +} + +/////////////////////////////////////////////////////////////////////////////// +// FileStream +/////////////////////////////////////////////////////////////////////////////// + +FileStream::FileStream() : file_(NULL) { +} + +FileStream::~FileStream() { + FileStream::Close(); +} + +bool FileStream::Open(const std::string& filename, const char* mode, + int* error) { + Close(); +#if defined(WEBRTC_WIN) + std::wstring wfilename; + if (Utf8ToWindowsFilename(filename, &wfilename)) { + file_ = _wfopen(wfilename.c_str(), ToUtf16(mode).c_str()); + } else { + if (error) { + *error = -1; + return false; + } + } +#else + file_ = fopen(filename.c_str(), mode); +#endif + if (!file_ && error) { + *error = errno; + } + return (file_ != NULL); +} + +bool FileStream::OpenShare(const std::string& filename, const char* mode, + int shflag, int* error) { + Close(); +#if defined(WEBRTC_WIN) + std::wstring wfilename; + if (Utf8ToWindowsFilename(filename, &wfilename)) { + file_ = _wfsopen(wfilename.c_str(), ToUtf16(mode).c_str(), shflag); + if (!file_ && error) { + *error = errno; + return false; + } + return file_ != NULL; + } else { + if (error) { + *error = -1; + } + return false; + } +#else + return Open(filename, mode, error); +#endif +} + +bool FileStream::DisableBuffering() { + if (!file_) + return false; + return (setvbuf(file_, NULL, _IONBF, 0) == 0); +} + +StreamState FileStream::GetState() const { + return (file_ == NULL) ? SS_CLOSED : SS_OPEN; +} + +StreamResult FileStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (!file_) + return SR_EOS; + size_t result = fread(buffer, 1, buffer_len, file_); + if ((result == 0) && (buffer_len > 0)) { + if (feof(file_)) + return SR_EOS; + if (error) + *error = errno; + return SR_ERROR; + } + if (read) + *read = result; + return SR_SUCCESS; +} + +StreamResult FileStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (!file_) + return SR_EOS; + size_t result = fwrite(data, 1, data_len, file_); + if ((result == 0) && (data_len > 0)) { + if (error) + *error = errno; + return SR_ERROR; + } + if (written) + *written = result; + return SR_SUCCESS; +} + +void FileStream::Close() { + if (file_) { + DoClose(); + file_ = NULL; + } +} + +bool FileStream::SetPosition(size_t position) { + if (!file_) + return false; + return (fseek(file_, static_cast(position), SEEK_SET) == 0); +} + +bool FileStream::GetPosition(size_t* position) const { + ASSERT(NULL != position); + if (!file_) + return false; + long result = ftell(file_); + if (result < 0) + return false; + if (position) + *position = result; + return true; +} + +bool FileStream::GetSize(size_t* size) const { + ASSERT(NULL != size); + if (!file_) + return false; + struct stat file_stats; + if (fstat(fileno(file_), &file_stats) != 0) + return false; + if (size) + *size = file_stats.st_size; + return true; +} + +bool FileStream::GetAvailable(size_t* size) const { + ASSERT(NULL != size); + if (!GetSize(size)) + return false; + long result = ftell(file_); + if (result < 0) + return false; + if (size) + *size -= result; + return true; +} + +bool FileStream::ReserveSize(size_t size) { + // TODO: extend the file to the proper length + return true; +} + +bool FileStream::GetSize(const std::string& filename, size_t* size) { + struct stat file_stats; + if (stat(filename.c_str(), &file_stats) != 0) + return false; + *size = file_stats.st_size; + return true; +} + +bool FileStream::Flush() { + if (file_) { + return (0 == fflush(file_)); + } + // try to flush empty file? + ASSERT(false); + return false; +} + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) + +bool FileStream::TryLock() { + if (file_ == NULL) { + // Stream not open. + ASSERT(false); + return false; + } + + return flock(fileno(file_), LOCK_EX|LOCK_NB) == 0; +} + +bool FileStream::Unlock() { + if (file_ == NULL) { + // Stream not open. + ASSERT(false); + return false; + } + + return flock(fileno(file_), LOCK_UN) == 0; +} + +#endif + +void FileStream::DoClose() { + fclose(file_); +} + +CircularFileStream::CircularFileStream(size_t max_size) + : max_write_size_(max_size), + position_(0), + marked_position_(max_size / 2), + last_write_position_(0), + read_segment_(READ_LATEST), + read_segment_available_(0) { +} + +bool CircularFileStream::Open( + const std::string& filename, const char* mode, int* error) { + if (!FileStream::Open(filename.c_str(), mode, error)) + return false; + + if (strchr(mode, "r") != NULL) { // Opened in read mode. + // Check if the buffer has been overwritten and determine how to read the + // log in time sequence. + size_t file_size; + GetSize(&file_size); + if (file_size == position_) { + // The buffer has not been overwritten yet. Read 0 .. file_size + read_segment_ = READ_LATEST; + read_segment_available_ = file_size; + } else { + // The buffer has been over written. There are three segments: The first + // one is 0 .. marked_position_, which is the marked earliest log. The + // second one is position_ .. file_size, which is the middle log. The + // last one is marked_position_ .. position_, which is the latest log. + read_segment_ = READ_MARKED; + read_segment_available_ = marked_position_; + last_write_position_ = position_; + } + + // Read from the beginning. + position_ = 0; + SetPosition(position_); + } + + return true; +} + +StreamResult CircularFileStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (read_segment_available_ == 0) { + size_t file_size; + switch (read_segment_) { + case READ_MARKED: // Finished READ_MARKED and start READ_MIDDLE. + read_segment_ = READ_MIDDLE; + position_ = last_write_position_; + SetPosition(position_); + GetSize(&file_size); + read_segment_available_ = file_size - position_; + break; + + case READ_MIDDLE: // Finished READ_MIDDLE and start READ_LATEST. + read_segment_ = READ_LATEST; + position_ = marked_position_; + SetPosition(position_); + read_segment_available_ = last_write_position_ - position_; + break; + + default: // Finished READ_LATEST and return EOS. + return rtc::SR_EOS; + } + } + + size_t local_read; + if (!read) read = &local_read; + + size_t to_read = rtc::_min(buffer_len, read_segment_available_); + rtc::StreamResult result + = rtc::FileStream::Read(buffer, to_read, read, error); + if (result == rtc::SR_SUCCESS) { + read_segment_available_ -= *read; + position_ += *read; + } + return result; +} + +StreamResult CircularFileStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (position_ >= max_write_size_) { + ASSERT(position_ == max_write_size_); + position_ = marked_position_; + SetPosition(position_); + } + + size_t local_written; + if (!written) written = &local_written; + + size_t to_eof = max_write_size_ - position_; + size_t to_write = rtc::_min(data_len, to_eof); + rtc::StreamResult result + = rtc::FileStream::Write(data, to_write, written, error); + if (result == rtc::SR_SUCCESS) { + position_ += *written; + } + return result; +} + +AsyncWriteStream::~AsyncWriteStream() { + write_thread_->Clear(this, 0, NULL); + ClearBufferAndWrite(); + + CritScope cs(&crit_stream_); + stream_.reset(); +} + +// This is needed by some stream writers, such as RtpDumpWriter. +bool AsyncWriteStream::GetPosition(size_t* position) const { + CritScope cs(&crit_stream_); + return stream_->GetPosition(position); +} + +// This is needed by some stream writers, such as the plugin log writers. +StreamResult AsyncWriteStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + CritScope cs(&crit_stream_); + return stream_->Read(buffer, buffer_len, read, error); +} + +void AsyncWriteStream::Close() { + if (state_ == SS_CLOSED) { + return; + } + + write_thread_->Clear(this, 0, NULL); + ClearBufferAndWrite(); + + CritScope cs(&crit_stream_); + stream_->Close(); + state_ = SS_CLOSED; +} + +StreamResult AsyncWriteStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (state_ == SS_CLOSED) { + return SR_ERROR; + } + + size_t previous_buffer_length = 0; + { + CritScope cs(&crit_buffer_); + previous_buffer_length = buffer_.length(); + buffer_.AppendData(data, data_len); + } + + if (previous_buffer_length == 0) { + // If there's stuff already in the buffer, then we already called + // Post and the write_thread_ hasn't pulled it out yet, so we + // don't need to re-Post. + write_thread_->Post(this, 0, NULL); + } + // Return immediately, assuming that it works. + if (written) { + *written = data_len; + } + return SR_SUCCESS; +} + +void AsyncWriteStream::OnMessage(rtc::Message* pmsg) { + ClearBufferAndWrite(); +} + +bool AsyncWriteStream::Flush() { + if (state_ == SS_CLOSED) { + return false; + } + + ClearBufferAndWrite(); + + CritScope cs(&crit_stream_); + return stream_->Flush(); +} + +void AsyncWriteStream::ClearBufferAndWrite() { + Buffer to_write; + { + CritScope cs_buffer(&crit_buffer_); + buffer_.TransferTo(&to_write); + } + + if (to_write.length() > 0) { + CritScope cs(&crit_stream_); + stream_->WriteAll(to_write.data(), to_write.length(), NULL, NULL); + } +} + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) + +// Have to identically rewrite the FileStream destructor or else it would call +// the base class's Close() instead of the sub-class's. +POpenStream::~POpenStream() { + POpenStream::Close(); +} + +bool POpenStream::Open(const std::string& subcommand, + const char* mode, + int* error) { + Close(); + file_ = popen(subcommand.c_str(), mode); + if (file_ == NULL) { + if (error) + *error = errno; + return false; + } + return true; +} + +bool POpenStream::OpenShare(const std::string& subcommand, const char* mode, + int shflag, int* error) { + return Open(subcommand, mode, error); +} + +void POpenStream::DoClose() { + wait_status_ = pclose(file_); +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// +// MemoryStream +/////////////////////////////////////////////////////////////////////////////// + +MemoryStreamBase::MemoryStreamBase() + : buffer_(NULL), buffer_length_(0), data_length_(0), + seek_position_(0) { +} + +StreamState MemoryStreamBase::GetState() const { + return SS_OPEN; +} + +StreamResult MemoryStreamBase::Read(void* buffer, size_t bytes, + size_t* bytes_read, int* error) { + if (seek_position_ >= data_length_) { + return SR_EOS; + } + size_t available = data_length_ - seek_position_; + if (bytes > available) { + // Read partial buffer + bytes = available; + } + memcpy(buffer, &buffer_[seek_position_], bytes); + seek_position_ += bytes; + if (bytes_read) { + *bytes_read = bytes; + } + return SR_SUCCESS; +} + +StreamResult MemoryStreamBase::Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error) { + size_t available = buffer_length_ - seek_position_; + if (0 == available) { + // Increase buffer size to the larger of: + // a) new position rounded up to next 256 bytes + // b) double the previous length + size_t new_buffer_length = _max(((seek_position_ + bytes) | 0xFF) + 1, + buffer_length_ * 2); + StreamResult result = DoReserve(new_buffer_length, error); + if (SR_SUCCESS != result) { + return result; + } + ASSERT(buffer_length_ >= new_buffer_length); + available = buffer_length_ - seek_position_; + } + + if (bytes > available) { + bytes = available; + } + memcpy(&buffer_[seek_position_], buffer, bytes); + seek_position_ += bytes; + if (data_length_ < seek_position_) { + data_length_ = seek_position_; + } + if (bytes_written) { + *bytes_written = bytes; + } + return SR_SUCCESS; +} + +void MemoryStreamBase::Close() { + // nothing to do +} + +bool MemoryStreamBase::SetPosition(size_t position) { + if (position > data_length_) + return false; + seek_position_ = position; + return true; +} + +bool MemoryStreamBase::GetPosition(size_t* position) const { + if (position) + *position = seek_position_; + return true; +} + +bool MemoryStreamBase::GetSize(size_t* size) const { + if (size) + *size = data_length_; + return true; +} + +bool MemoryStreamBase::GetAvailable(size_t* size) const { + if (size) + *size = data_length_ - seek_position_; + return true; +} + +bool MemoryStreamBase::ReserveSize(size_t size) { + return (SR_SUCCESS == DoReserve(size, NULL)); +} + +StreamResult MemoryStreamBase::DoReserve(size_t size, int* error) { + return (buffer_length_ >= size) ? SR_SUCCESS : SR_EOS; +} + +/////////////////////////////////////////////////////////////////////////////// + +MemoryStream::MemoryStream() + : buffer_alloc_(NULL) { +} + +MemoryStream::MemoryStream(const char* data) + : buffer_alloc_(NULL) { + SetData(data, strlen(data)); +} + +MemoryStream::MemoryStream(const void* data, size_t length) + : buffer_alloc_(NULL) { + SetData(data, length); +} + +MemoryStream::~MemoryStream() { + delete [] buffer_alloc_; +} + +void MemoryStream::SetData(const void* data, size_t length) { + data_length_ = buffer_length_ = length; + delete [] buffer_alloc_; + buffer_alloc_ = new char[buffer_length_ + kAlignment]; + buffer_ = reinterpret_cast(ALIGNP(buffer_alloc_, kAlignment)); + memcpy(buffer_, data, data_length_); + seek_position_ = 0; +} + +StreamResult MemoryStream::DoReserve(size_t size, int* error) { + if (buffer_length_ >= size) + return SR_SUCCESS; + + if (char* new_buffer_alloc = new char[size + kAlignment]) { + char* new_buffer = reinterpret_cast( + ALIGNP(new_buffer_alloc, kAlignment)); + memcpy(new_buffer, buffer_, data_length_); + delete [] buffer_alloc_; + buffer_alloc_ = new_buffer_alloc; + buffer_ = new_buffer; + buffer_length_ = size; + return SR_SUCCESS; + } + + if (error) { + *error = ENOMEM; + } + return SR_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// + +ExternalMemoryStream::ExternalMemoryStream() { +} + +ExternalMemoryStream::ExternalMemoryStream(void* data, size_t length) { + SetData(data, length); +} + +ExternalMemoryStream::~ExternalMemoryStream() { +} + +void ExternalMemoryStream::SetData(void* data, size_t length) { + data_length_ = buffer_length_ = length; + buffer_ = static_cast(data); + seek_position_ = 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// FifoBuffer +/////////////////////////////////////////////////////////////////////////////// + +FifoBuffer::FifoBuffer(size_t size) + : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size), + data_length_(0), read_position_(0), owner_(Thread::Current()) { + // all events are done on the owner_ thread +} + +FifoBuffer::FifoBuffer(size_t size, Thread* owner) + : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size), + data_length_(0), read_position_(0), owner_(owner) { + // all events are done on the owner_ thread +} + +FifoBuffer::~FifoBuffer() { +} + +bool FifoBuffer::GetBuffered(size_t* size) const { + CritScope cs(&crit_); + *size = data_length_; + return true; +} + +bool FifoBuffer::SetCapacity(size_t size) { + CritScope cs(&crit_); + if (data_length_ > size) { + return false; + } + + if (size != buffer_length_) { + char* buffer = new char[size]; + const size_t copy = data_length_; + const size_t tail_copy = _min(copy, buffer_length_ - read_position_); + memcpy(buffer, &buffer_[read_position_], tail_copy); + memcpy(buffer + tail_copy, &buffer_[0], copy - tail_copy); + buffer_.reset(buffer); + read_position_ = 0; + buffer_length_ = size; + } + return true; +} + +StreamResult FifoBuffer::ReadOffset(void* buffer, size_t bytes, + size_t offset, size_t* bytes_read) { + CritScope cs(&crit_); + return ReadOffsetLocked(buffer, bytes, offset, bytes_read); +} + +StreamResult FifoBuffer::WriteOffset(const void* buffer, size_t bytes, + size_t offset, size_t* bytes_written) { + CritScope cs(&crit_); + return WriteOffsetLocked(buffer, bytes, offset, bytes_written); +} + +StreamState FifoBuffer::GetState() const { + return state_; +} + +StreamResult FifoBuffer::Read(void* buffer, size_t bytes, + size_t* bytes_read, int* error) { + CritScope cs(&crit_); + const bool was_writable = data_length_ < buffer_length_; + size_t copy = 0; + StreamResult result = ReadOffsetLocked(buffer, bytes, 0, ©); + + if (result == SR_SUCCESS) { + // If read was successful then adjust the read position and number of + // bytes buffered. + read_position_ = (read_position_ + copy) % buffer_length_; + data_length_ -= copy; + if (bytes_read) { + *bytes_read = copy; + } + + // if we were full before, and now we're not, post an event + if (!was_writable && copy > 0) { + PostEvent(owner_, SE_WRITE, 0); + } + } + return result; +} + +StreamResult FifoBuffer::Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error) { + CritScope cs(&crit_); + + const bool was_readable = (data_length_ > 0); + size_t copy = 0; + StreamResult result = WriteOffsetLocked(buffer, bytes, 0, ©); + + if (result == SR_SUCCESS) { + // If write was successful then adjust the number of readable bytes. + data_length_ += copy; + if (bytes_written) { + *bytes_written = copy; + } + + // if we didn't have any data to read before, and now we do, post an event + if (!was_readable && copy > 0) { + PostEvent(owner_, SE_READ, 0); + } + } + return result; +} + +void FifoBuffer::Close() { + CritScope cs(&crit_); + state_ = SS_CLOSED; +} + +const void* FifoBuffer::GetReadData(size_t* size) { + CritScope cs(&crit_); + *size = (read_position_ + data_length_ <= buffer_length_) ? + data_length_ : buffer_length_ - read_position_; + return &buffer_[read_position_]; +} + +void FifoBuffer::ConsumeReadData(size_t size) { + CritScope cs(&crit_); + ASSERT(size <= data_length_); + const bool was_writable = data_length_ < buffer_length_; + read_position_ = (read_position_ + size) % buffer_length_; + data_length_ -= size; + if (!was_writable && size > 0) { + PostEvent(owner_, SE_WRITE, 0); + } +} + +void* FifoBuffer::GetWriteBuffer(size_t* size) { + CritScope cs(&crit_); + if (state_ == SS_CLOSED) { + return NULL; + } + + // if empty, reset the write position to the beginning, so we can get + // the biggest possible block + if (data_length_ == 0) { + read_position_ = 0; + } + + const size_t write_position = (read_position_ + data_length_) + % buffer_length_; + *size = (write_position > read_position_ || data_length_ == 0) ? + buffer_length_ - write_position : read_position_ - write_position; + return &buffer_[write_position]; +} + +void FifoBuffer::ConsumeWriteBuffer(size_t size) { + CritScope cs(&crit_); + ASSERT(size <= buffer_length_ - data_length_); + const bool was_readable = (data_length_ > 0); + data_length_ += size; + if (!was_readable && size > 0) { + PostEvent(owner_, SE_READ, 0); + } +} + +bool FifoBuffer::GetWriteRemaining(size_t* size) const { + CritScope cs(&crit_); + *size = buffer_length_ - data_length_; + return true; +} + +StreamResult FifoBuffer::ReadOffsetLocked(void* buffer, + size_t bytes, + size_t offset, + size_t* bytes_read) { + if (offset >= data_length_) { + return (state_ != SS_CLOSED) ? SR_BLOCK : SR_EOS; + } + + const size_t available = data_length_ - offset; + const size_t read_position = (read_position_ + offset) % buffer_length_; + const size_t copy = _min(bytes, available); + const size_t tail_copy = _min(copy, buffer_length_ - read_position); + char* const p = static_cast(buffer); + memcpy(p, &buffer_[read_position], tail_copy); + memcpy(p + tail_copy, &buffer_[0], copy - tail_copy); + + if (bytes_read) { + *bytes_read = copy; + } + return SR_SUCCESS; +} + +StreamResult FifoBuffer::WriteOffsetLocked(const void* buffer, + size_t bytes, + size_t offset, + size_t* bytes_written) { + if (state_ == SS_CLOSED) { + return SR_EOS; + } + + if (data_length_ + offset >= buffer_length_) { + return SR_BLOCK; + } + + const size_t available = buffer_length_ - data_length_ - offset; + const size_t write_position = (read_position_ + data_length_ + offset) + % buffer_length_; + const size_t copy = _min(bytes, available); + const size_t tail_copy = _min(copy, buffer_length_ - write_position); + const char* const p = static_cast(buffer); + memcpy(&buffer_[write_position], p, tail_copy); + memcpy(&buffer_[0], p + tail_copy, copy - tail_copy); + + if (bytes_written) { + *bytes_written = copy; + } + return SR_SUCCESS; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// LoggingAdapter +/////////////////////////////////////////////////////////////////////////////// + +LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level, + const std::string& label, bool hex_mode) + : StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) { + set_label(label); +} + +void LoggingAdapter::set_label(const std::string& label) { + label_.assign("["); + label_.append(label); + label_.append("]"); +} + +StreamResult LoggingAdapter::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + size_t local_read; if (!read) read = &local_read; + StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, read, + error); + if (result == SR_SUCCESS) { + LogMultiline(level_, label_.c_str(), true, buffer, *read, hex_mode_, &lms_); + } + return result; +} + +StreamResult LoggingAdapter::Write(const void* data, size_t data_len, + size_t* written, int* error) { + size_t local_written; + if (!written) written = &local_written; + StreamResult result = StreamAdapterInterface::Write(data, data_len, written, + error); + if (result == SR_SUCCESS) { + LogMultiline(level_, label_.c_str(), false, data, *written, hex_mode_, + &lms_); + } + return result; +} + +void LoggingAdapter::Close() { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed locally"; + StreamAdapterInterface::Close(); +} + +void LoggingAdapter::OnEvent(StreamInterface* stream, int events, int err) { + if (events & SE_OPEN) { + LOG_V(level_) << label_ << " Open"; + } else if (events & SE_CLOSE) { + LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_); + LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_); + LOG_V(level_) << label_ << " Closed with error: " << err; + } + StreamAdapterInterface::OnEvent(stream, events, err); +} + +/////////////////////////////////////////////////////////////////////////////// +// StringStream - Reads/Writes to an external std::string +/////////////////////////////////////////////////////////////////////////////// + +StringStream::StringStream(std::string& str) + : str_(str), read_pos_(0), read_only_(false) { +} + +StringStream::StringStream(const std::string& str) + : str_(const_cast(str)), read_pos_(0), read_only_(true) { +} + +StreamState StringStream::GetState() const { + return SS_OPEN; +} + +StreamResult StringStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + size_t available = _min(buffer_len, str_.size() - read_pos_); + if (!available) + return SR_EOS; + memcpy(buffer, str_.data() + read_pos_, available); + read_pos_ += available; + if (read) + *read = available; + return SR_SUCCESS; +} + +StreamResult StringStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (read_only_) { + if (error) { + *error = -1; + } + return SR_ERROR; + } + str_.append(static_cast(data), + static_cast(data) + data_len); + if (written) + *written = data_len; + return SR_SUCCESS; +} + +void StringStream::Close() { +} + +bool StringStream::SetPosition(size_t position) { + if (position > str_.size()) + return false; + read_pos_ = position; + return true; +} + +bool StringStream::GetPosition(size_t* position) const { + if (position) + *position = read_pos_; + return true; +} + +bool StringStream::GetSize(size_t* size) const { + if (size) + *size = str_.size(); + return true; +} + +bool StringStream::GetAvailable(size_t* size) const { + if (size) + *size = str_.size() - read_pos_; + return true; +} + +bool StringStream::ReserveSize(size_t size) { + if (read_only_) + return false; + str_.reserve(size); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamReference +/////////////////////////////////////////////////////////////////////////////// + +StreamReference::StreamReference(StreamInterface* stream) + : StreamAdapterInterface(stream, false) { + // owner set to false so the destructor does not free the stream. + stream_ref_count_ = new StreamRefCount(stream); +} + +StreamInterface* StreamReference::NewReference() { + stream_ref_count_->AddReference(); + return new StreamReference(stream_ref_count_, stream()); +} + +StreamReference::~StreamReference() { + stream_ref_count_->Release(); +} + +StreamReference::StreamReference(StreamRefCount* stream_ref_count, + StreamInterface* stream) + : StreamAdapterInterface(stream, false), + stream_ref_count_(stream_ref_count) { +} + +/////////////////////////////////////////////////////////////////////////////// + +StreamResult Flow(StreamInterface* source, + char* buffer, size_t buffer_len, + StreamInterface* sink, + size_t* data_len /* = NULL */) { + ASSERT(buffer_len > 0); + + StreamResult result; + size_t count, read_pos, write_pos; + if (data_len) { + read_pos = *data_len; + } else { + read_pos = 0; + } + + bool end_of_stream = false; + do { + // Read until buffer is full, end of stream, or error + while (!end_of_stream && (read_pos < buffer_len)) { + result = source->Read(buffer + read_pos, buffer_len - read_pos, + &count, NULL); + if (result == SR_EOS) { + end_of_stream = true; + } else if (result != SR_SUCCESS) { + if (data_len) { + *data_len = read_pos; + } + return result; + } else { + read_pos += count; + } + } + + // Write until buffer is empty, or error (including end of stream) + write_pos = 0; + while (write_pos < read_pos) { + result = sink->Write(buffer + write_pos, read_pos - write_pos, + &count, NULL); + if (result != SR_SUCCESS) { + if (data_len) { + *data_len = read_pos - write_pos; + if (write_pos > 0) { + memmove(buffer, buffer + write_pos, *data_len); + } + } + return result; + } + write_pos += count; + } + + read_pos = 0; + } while (!end_of_stream); + + if (data_len) { + *data_len = 0; + } + return SR_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/stream.h b/webrtc/base/stream.h new file mode 100644 index 000000000..00ded372c --- /dev/null +++ b/webrtc/base/stream.h @@ -0,0 +1,820 @@ +/* + * Copyright 2004 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_BASE_STREAM_H_ +#define WEBRTC_BASE_STREAM_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// StreamInterface is a generic asynchronous stream interface, supporting read, +// write, and close operations, and asynchronous signalling of state changes. +// The interface is designed with file, memory, and socket implementations in +// mind. Some implementations offer extended operations, such as seeking. +/////////////////////////////////////////////////////////////////////////////// + +// The following enumerations are declared outside of the StreamInterface +// class for brevity in use. + +// The SS_OPENING state indicates that the stream will signal open or closed +// in the future. +enum StreamState { SS_CLOSED, SS_OPENING, SS_OPEN }; + +// Stream read/write methods return this value to indicate various success +// and failure conditions described below. +enum StreamResult { SR_ERROR, SR_SUCCESS, SR_BLOCK, SR_EOS }; + +// StreamEvents are used to asynchronously signal state transitionss. The flags +// may be combined. +// SE_OPEN: The stream has transitioned to the SS_OPEN state +// SE_CLOSE: The stream has transitioned to the SS_CLOSED state +// SE_READ: Data is available, so Read is likely to not return SR_BLOCK +// SE_WRITE: Data can be written, so Write is likely to not return SR_BLOCK +enum StreamEvent { SE_OPEN = 1, SE_READ = 2, SE_WRITE = 4, SE_CLOSE = 8 }; + +class Thread; + +struct StreamEventData : public MessageData { + int events, error; + StreamEventData(int ev, int er) : events(ev), error(er) { } +}; + +class StreamInterface : public MessageHandler { + public: + enum { + MSG_POST_EVENT = 0xF1F1, MSG_MAX = MSG_POST_EVENT + }; + + virtual ~StreamInterface(); + + virtual StreamState GetState() const = 0; + + // Read attempts to fill buffer of size buffer_len. Write attempts to send + // data_len bytes stored in data. The variables read and write are set only + // on SR_SUCCESS (see below). Likewise, error is only set on SR_ERROR. + // Read and Write return a value indicating: + // SR_ERROR: an error occurred, which is returned in a non-null error + // argument. Interpretation of the error requires knowledge of the + // stream's concrete type, which limits its usefulness. + // SR_SUCCESS: some number of bytes were successfully written, which is + // returned in a non-null read/write argument. + // SR_BLOCK: the stream is in non-blocking mode, and the operation would + // block, or the stream is in SS_OPENING state. + // SR_EOS: the end-of-stream has been reached, or the stream is in the + // SS_CLOSED state. + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) = 0; + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) = 0; + // Attempt to transition to the SS_CLOSED state. SE_CLOSE will not be + // signalled as a result of this call. + virtual void Close() = 0; + + // Streams may signal one or more StreamEvents to indicate state changes. + // The first argument identifies the stream on which the state change occured. + // The second argument is a bit-wise combination of StreamEvents. + // If SE_CLOSE is signalled, then the third argument is the associated error + // code. Otherwise, the value is undefined. + // Note: Not all streams will support asynchronous event signalling. However, + // SS_OPENING and SR_BLOCK returned from stream member functions imply that + // certain events will be raised in the future. + sigslot::signal3 SignalEvent; + + // Like calling SignalEvent, but posts a message to the specified thread, + // which will call SignalEvent. This helps unroll the stack and prevent + // re-entrancy. + void PostEvent(Thread* t, int events, int err); + // Like the aforementioned method, but posts to the current thread. + void PostEvent(int events, int err); + + // + // OPTIONAL OPERATIONS + // + // Not all implementations will support the following operations. In general, + // a stream will only support an operation if it reasonably efficient to do + // so. For example, while a socket could buffer incoming data to support + // seeking, it will not do so. Instead, a buffering stream adapter should + // be used. + // + // Even though several of these operations are related, you should + // always use whichever operation is most relevant. For example, you may + // be tempted to use GetSize() and GetPosition() to deduce the result of + // GetAvailable(). However, a stream which is read-once may support the + // latter operation but not the former. + // + + // The following four methods are used to avoid copying data multiple times. + + // GetReadData returns a pointer to a buffer which is owned by the stream. + // The buffer contains data_len bytes. NULL is returned if no data is + // available, or if the method fails. If the caller processes the data, it + // must call ConsumeReadData with the number of processed bytes. GetReadData + // does not require a matching call to ConsumeReadData if the data is not + // processed. Read and ConsumeReadData invalidate the buffer returned by + // GetReadData. + virtual const void* GetReadData(size_t* data_len) { return NULL; } + virtual void ConsumeReadData(size_t used) {} + + // GetWriteBuffer returns a pointer to a buffer which is owned by the stream. + // The buffer has a capacity of buf_len bytes. NULL is returned if there is + // no buffer available, or if the method fails. The call may write data to + // the buffer, and then call ConsumeWriteBuffer with the number of bytes + // written. GetWriteBuffer does not require a matching call to + // ConsumeWriteData if no data is written. Write, ForceWrite, and + // ConsumeWriteData invalidate the buffer returned by GetWriteBuffer. + // TODO: Allow the caller to specify a minimum buffer size. If the specified + // amount of buffer is not yet available, return NULL and Signal SE_WRITE + // when it is available. If the requested amount is too large, return an + // error. + virtual void* GetWriteBuffer(size_t* buf_len) { return NULL; } + virtual void ConsumeWriteBuffer(size_t used) {} + + // Write data_len bytes found in data, circumventing any throttling which + // would could cause SR_BLOCK to be returned. Returns true if all the data + // was written. Otherwise, the method is unsupported, or an unrecoverable + // error occurred, and the error value is set. This method should be used + // sparingly to write critical data which should not be throttled. A stream + // which cannot circumvent its blocking constraints should not implement this + // method. + // NOTE: This interface is being considered experimentally at the moment. It + // would be used by JUDP and BandwidthStream as a way to circumvent certain + // soft limits in writing. + //virtual bool ForceWrite(const void* data, size_t data_len, int* error) { + // if (error) *error = -1; + // return false; + //} + + // Seek to a byte offset from the beginning of the stream. Returns false if + // the stream does not support seeking, or cannot seek to the specified + // position. + virtual bool SetPosition(size_t position) { return false; } + + // Get the byte offset of the current position from the start of the stream. + // Returns false if the position is not known. + virtual bool GetPosition(size_t* position) const { return false; } + + // Get the byte length of the entire stream. Returns false if the length + // is not known. + virtual bool GetSize(size_t* size) const { return false; } + + // Return the number of Read()-able bytes remaining before end-of-stream. + // Returns false if not known. + virtual bool GetAvailable(size_t* size) const { return false; } + + // Return the number of Write()-able bytes remaining before end-of-stream. + // Returns false if not known. + virtual bool GetWriteRemaining(size_t* size) const { return false; } + + // Return true if flush is successful. + virtual bool Flush() { return false; } + + // Communicates the amount of data which will be written to the stream. The + // stream may choose to preallocate memory to accomodate this data. The + // stream may return false to indicate that there is not enough room (ie, + // Write will return SR_EOS/SR_ERROR at some point). Note that calling this + // function should not affect the existing state of data in the stream. + virtual bool ReserveSize(size_t size) { return true; } + + // + // CONVENIENCE METHODS + // + // These methods are implemented in terms of other methods, for convenience. + // + + // Seek to the start of the stream. + inline bool Rewind() { return SetPosition(0); } + + // WriteAll is a helper function which repeatedly calls Write until all the + // data is written, or something other than SR_SUCCESS is returned. Note that + // unlike Write, the argument 'written' is always set, and may be non-zero + // on results other than SR_SUCCESS. The remaining arguments have the + // same semantics as Write. + StreamResult WriteAll(const void* data, size_t data_len, + size_t* written, int* error); + + // Similar to ReadAll. Calls Read until buffer_len bytes have been read, or + // until a non-SR_SUCCESS result is returned. 'read' is always set. + StreamResult ReadAll(void* buffer, size_t buffer_len, + size_t* read, int* error); + + // ReadLine is a helper function which repeatedly calls Read until it hits + // the end-of-line character, or something other than SR_SUCCESS. + // TODO: this is too inefficient to keep here. Break this out into a buffered + // readline object or adapter + StreamResult ReadLine(std::string* line); + + protected: + StreamInterface(); + + // MessageHandler Interface + virtual void OnMessage(Message* msg); + + private: + DISALLOW_EVIL_CONSTRUCTORS(StreamInterface); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamAdapterInterface is a convenient base-class for adapting a stream. +// By default, all operations are pass-through. Override the methods that you +// require adaptation. Streams should really be upgraded to reference-counted. +// In the meantime, use the owned flag to indicate whether the adapter should +// own the adapted stream. +/////////////////////////////////////////////////////////////////////////////// + +class StreamAdapterInterface : public StreamInterface, + public sigslot::has_slots<> { + public: + explicit StreamAdapterInterface(StreamInterface* stream, bool owned = true); + + // Core Stream Interface + virtual StreamState GetState() const { + return stream_->GetState(); + } + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + return stream_->Read(buffer, buffer_len, read, error); + } + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + return stream_->Write(data, data_len, written, error); + } + virtual void Close() { + stream_->Close(); + } + + // Optional Stream Interface + /* Note: Many stream adapters were implemented prior to this Read/Write + interface. Therefore, a simple pass through of data in those cases may + be broken. At a later time, we should do a once-over pass of all + adapters, and make them compliant with these interfaces, after which this + code can be uncommented. + virtual const void* GetReadData(size_t* data_len) { + return stream_->GetReadData(data_len); + } + virtual void ConsumeReadData(size_t used) { + stream_->ConsumeReadData(used); + } + + virtual void* GetWriteBuffer(size_t* buf_len) { + return stream_->GetWriteBuffer(buf_len); + } + virtual void ConsumeWriteBuffer(size_t used) { + stream_->ConsumeWriteBuffer(used); + } + */ + + /* Note: This interface is currently undergoing evaluation. + virtual bool ForceWrite(const void* data, size_t data_len, int* error) { + return stream_->ForceWrite(data, data_len, error); + } + */ + + virtual bool SetPosition(size_t position) { + return stream_->SetPosition(position); + } + virtual bool GetPosition(size_t* position) const { + return stream_->GetPosition(position); + } + virtual bool GetSize(size_t* size) const { + return stream_->GetSize(size); + } + virtual bool GetAvailable(size_t* size) const { + return stream_->GetAvailable(size); + } + virtual bool GetWriteRemaining(size_t* size) const { + return stream_->GetWriteRemaining(size); + } + virtual bool ReserveSize(size_t size) { + return stream_->ReserveSize(size); + } + virtual bool Flush() { + return stream_->Flush(); + } + + void Attach(StreamInterface* stream, bool owned = true); + StreamInterface* Detach(); + + protected: + virtual ~StreamAdapterInterface(); + + // Note that the adapter presents itself as the origin of the stream events, + // since users of the adapter may not recognize the adapted object. + virtual void OnEvent(StreamInterface* stream, int events, int err) { + SignalEvent(this, events, err); + } + StreamInterface* stream() { return stream_; } + + private: + StreamInterface* stream_; + bool owned_; + DISALLOW_EVIL_CONSTRUCTORS(StreamAdapterInterface); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamTap is a non-modifying, pass-through adapter, which copies all data +// in either direction to the tap. Note that errors or blocking on writing to +// the tap will prevent further tap writes from occurring. +/////////////////////////////////////////////////////////////////////////////// + +class StreamTap : public StreamAdapterInterface { + public: + explicit StreamTap(StreamInterface* stream, StreamInterface* tap); + + void AttachTap(StreamInterface* tap); + StreamInterface* DetachTap(); + StreamResult GetTapResult(int* error); + + // StreamAdapterInterface Interface + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + private: + scoped_ptr tap_; + StreamResult tap_result_; + int tap_error_; + DISALLOW_EVIL_CONSTRUCTORS(StreamTap); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamSegment adapts a read stream, to expose a subset of the adapted +// stream's data. This is useful for cases where a stream contains multiple +// documents concatenated together. StreamSegment can expose a subset of +// the data as an independent stream, including support for rewinding and +// seeking. +/////////////////////////////////////////////////////////////////////////////// + +class StreamSegment : public StreamAdapterInterface { + public: + // The current position of the adapted stream becomes the beginning of the + // segment. If a length is specified, it bounds the length of the segment. + explicit StreamSegment(StreamInterface* stream); + explicit StreamSegment(StreamInterface* stream, size_t length); + + // StreamAdapterInterface Interface + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + + private: + size_t start_, pos_, length_; + DISALLOW_EVIL_CONSTRUCTORS(StreamSegment); +}; + +/////////////////////////////////////////////////////////////////////////////// +// NullStream gives errors on read, and silently discards all written data. +/////////////////////////////////////////////////////////////////////////////// + +class NullStream : public StreamInterface { + public: + NullStream(); + virtual ~NullStream(); + + // StreamInterface Interface + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// FileStream is a simple implementation of a StreamInterface, which does not +// support asynchronous notification. +/////////////////////////////////////////////////////////////////////////////// + +class FileStream : public StreamInterface { + public: + FileStream(); + virtual ~FileStream(); + + // The semantics of filename and mode are the same as stdio's fopen + virtual bool Open(const std::string& filename, const char* mode, int* error); + virtual bool OpenShare(const std::string& filename, const char* mode, + int shflag, int* error); + + // By default, reads and writes are buffered for efficiency. Disabling + // buffering causes writes to block until the bytes on disk are updated. + virtual bool DisableBuffering(); + + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + virtual bool ReserveSize(size_t size); + + virtual bool Flush(); + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) + // Tries to aquire an exclusive lock on the file. + // Use OpenShare(...) on win32 to get similar functionality. + bool TryLock(); + bool Unlock(); +#endif + + // Note: Deprecated in favor of Filesystem::GetFileSize(). + static bool GetSize(const std::string& filename, size_t* size); + + protected: + virtual void DoClose(); + + FILE* file_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(FileStream); +}; + +// A stream that caps the output at a certain size, dropping content from the +// middle of the logical stream and maintaining equal parts of the start/end of +// the logical stream. +class CircularFileStream : public FileStream { + public: + explicit CircularFileStream(size_t max_size); + + virtual bool Open(const std::string& filename, const char* mode, int* error); + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + private: + enum ReadSegment { + READ_MARKED, // Read 0 .. marked_position_ + READ_MIDDLE, // Read position_ .. file_size + READ_LATEST, // Read marked_position_ .. position_ if the buffer was + // overwritten or 0 .. position_ otherwise. + }; + + size_t max_write_size_; + size_t position_; + size_t marked_position_; + size_t last_write_position_; + ReadSegment read_segment_; + size_t read_segment_available_; +}; + +// A stream which pushes writes onto a separate thread and +// returns from the write call immediately. +class AsyncWriteStream : public StreamInterface { + public: + // Takes ownership of the stream, but not the thread. + AsyncWriteStream(StreamInterface* stream, rtc::Thread* write_thread) + : stream_(stream), + write_thread_(write_thread), + state_(stream ? stream->GetState() : SS_CLOSED) { + } + + virtual ~AsyncWriteStream(); + + // StreamInterface Interface + virtual StreamState GetState() const { return state_; } + // This is needed by some stream writers, such as RtpDumpWriter. + virtual bool GetPosition(size_t* position) const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool Flush(); + + protected: + // From MessageHandler + virtual void OnMessage(rtc::Message* pmsg); + virtual void ClearBufferAndWrite(); + + private: + rtc::scoped_ptr stream_; + Thread* write_thread_; + StreamState state_; + Buffer buffer_; + mutable CriticalSection crit_stream_; + CriticalSection crit_buffer_; + + DISALLOW_EVIL_CONSTRUCTORS(AsyncWriteStream); +}; + + +#if defined(WEBRTC_POSIX) && !defined(__native_client__) +// A FileStream that is actually not a file, but the output or input of a +// sub-command. See "man 3 popen" for documentation of the underlying OS popen() +// function. +class POpenStream : public FileStream { + public: + POpenStream() : wait_status_(-1) {} + virtual ~POpenStream(); + + virtual bool Open(const std::string& subcommand, const char* mode, + int* error); + // Same as Open(). shflag is ignored. + virtual bool OpenShare(const std::string& subcommand, const char* mode, + int shflag, int* error); + + // Returns the wait status from the last Close() of an Open()'ed stream, or + // -1 if no Open()+Close() has been done on this object. Meaning of the number + // is documented in "man 2 wait". + int GetWaitStatus() const { return wait_status_; } + + protected: + virtual void DoClose(); + + private: + int wait_status_; +}; +#endif // WEBRTC_POSIX + +/////////////////////////////////////////////////////////////////////////////// +// MemoryStream is a simple implementation of a StreamInterface over in-memory +// data. Data is read and written at the current seek position. Reads return +// end-of-stream when they reach the end of data. Writes actually extend the +// end of data mark. +/////////////////////////////////////////////////////////////////////////////// + +class MemoryStreamBase : public StreamInterface { + public: + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t bytes, size_t* bytes_read, + int* error); + virtual StreamResult Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + virtual bool ReserveSize(size_t size); + + char* GetBuffer() { return buffer_; } + const char* GetBuffer() const { return buffer_; } + + protected: + MemoryStreamBase(); + + virtual StreamResult DoReserve(size_t size, int* error); + + // Invariant: 0 <= seek_position <= data_length_ <= buffer_length_ + char* buffer_; + size_t buffer_length_; + size_t data_length_; + size_t seek_position_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(MemoryStreamBase); +}; + +// MemoryStream dynamically resizes to accomodate written data. + +class MemoryStream : public MemoryStreamBase { + public: + MemoryStream(); + explicit MemoryStream(const char* data); // Calls SetData(data, strlen(data)) + MemoryStream(const void* data, size_t length); // Calls SetData(data, length) + virtual ~MemoryStream(); + + void SetData(const void* data, size_t length); + + protected: + virtual StreamResult DoReserve(size_t size, int* error); + // Memory Streams are aligned for efficiency. + static const int kAlignment = 16; + char* buffer_alloc_; +}; + +// ExternalMemoryStream adapts an external memory buffer, so writes which would +// extend past the end of the buffer will return end-of-stream. + +class ExternalMemoryStream : public MemoryStreamBase { + public: + ExternalMemoryStream(); + ExternalMemoryStream(void* data, size_t length); + virtual ~ExternalMemoryStream(); + + void SetData(void* data, size_t length); +}; + +// FifoBuffer allows for efficient, thread-safe buffering of data between +// writer and reader. As the data can wrap around the end of the buffer, +// MemoryStreamBase can't help us here. + +class FifoBuffer : public StreamInterface { + public: + // Creates a FIFO buffer with the specified capacity. + explicit FifoBuffer(size_t length); + // Creates a FIFO buffer with the specified capacity and owner + FifoBuffer(size_t length, Thread* owner); + virtual ~FifoBuffer(); + // Gets the amount of data currently readable from the buffer. + bool GetBuffered(size_t* data_len) const; + // Resizes the buffer to the specified capacity. Fails if data_length_ > size + bool SetCapacity(size_t length); + + // Read into |buffer| with an offset from the current read position, offset + // is specified in number of bytes. + // This method doesn't adjust read position nor the number of available + // bytes, user has to call ConsumeReadData() to do this. + StreamResult ReadOffset(void* buffer, size_t bytes, size_t offset, + size_t* bytes_read); + + // Write |buffer| with an offset from the current write position, offset is + // specified in number of bytes. + // This method doesn't adjust the number of buffered bytes, user has to call + // ConsumeWriteBuffer() to do this. + StreamResult WriteOffset(const void* buffer, size_t bytes, size_t offset, + size_t* bytes_written); + + // StreamInterface methods + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t bytes, + size_t* bytes_read, int* error); + virtual StreamResult Write(const void* buffer, size_t bytes, + size_t* bytes_written, int* error); + virtual void Close(); + virtual const void* GetReadData(size_t* data_len); + virtual void ConsumeReadData(size_t used); + virtual void* GetWriteBuffer(size_t* buf_len); + virtual void ConsumeWriteBuffer(size_t used); + virtual bool GetWriteRemaining(size_t* size) const; + + private: + // Helper method that implements ReadOffset. Caller must acquire a lock + // when calling this method. + StreamResult ReadOffsetLocked(void* buffer, size_t bytes, size_t offset, + size_t* bytes_read); + + // Helper method that implements WriteOffset. Caller must acquire a lock + // when calling this method. + StreamResult WriteOffsetLocked(const void* buffer, size_t bytes, + size_t offset, size_t* bytes_written); + + StreamState state_; // keeps the opened/closed state of the stream + scoped_ptr buffer_; // the allocated buffer + size_t buffer_length_; // size of the allocated buffer + size_t data_length_; // amount of readable data in the buffer + size_t read_position_; // offset to the readable data + Thread* owner_; // stream callbacks are dispatched on this thread + mutable CriticalSection crit_; // object lock + DISALLOW_EVIL_CONSTRUCTORS(FifoBuffer); +}; + +/////////////////////////////////////////////////////////////////////////////// + +class LoggingAdapter : public StreamAdapterInterface { + public: + LoggingAdapter(StreamInterface* stream, LoggingSeverity level, + const std::string& label, bool hex_mode = false); + + void set_label(const std::string& label); + + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + + protected: + virtual void OnEvent(StreamInterface* stream, int events, int err); + + private: + LoggingSeverity level_; + std::string label_; + bool hex_mode_; + LogMultilineState lms_; + + DISALLOW_EVIL_CONSTRUCTORS(LoggingAdapter); +}; + +/////////////////////////////////////////////////////////////////////////////// +// StringStream - Reads/Writes to an external std::string +/////////////////////////////////////////////////////////////////////////////// + +class StringStream : public StreamInterface { + public: + explicit StringStream(std::string& str); + explicit StringStream(const std::string& str); + + virtual StreamState GetState() const; + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + virtual bool SetPosition(size_t position); + virtual bool GetPosition(size_t* position) const; + virtual bool GetSize(size_t* size) const; + virtual bool GetAvailable(size_t* size) const; + virtual bool ReserveSize(size_t size); + + private: + std::string& str_; + size_t read_pos_; + bool read_only_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamReference - A reference counting stream adapter +/////////////////////////////////////////////////////////////////////////////// + +// Keep in mind that the streams and adapters defined in this file are +// not thread-safe, so this has limited uses. + +// A StreamRefCount holds the reference count and a pointer to the +// wrapped stream. It deletes the wrapped stream when there are no +// more references. We can then have multiple StreamReference +// instances pointing to one StreamRefCount, all wrapping the same +// stream. + +class StreamReference : public StreamAdapterInterface { + class StreamRefCount; + public: + // Constructor for the first reference to a stream + // Note: get more references through NewReference(). Use this + // constructor only once on a given stream. + explicit StreamReference(StreamInterface* stream); + StreamInterface* GetStream() { return stream(); } + StreamInterface* NewReference(); + virtual ~StreamReference(); + + private: + class StreamRefCount { + public: + explicit StreamRefCount(StreamInterface* stream) + : stream_(stream), ref_count_(1) { + } + void AddReference() { + CritScope lock(&cs_); + ++ref_count_; + } + void Release() { + int ref_count; + { // Atomic ops would have been a better fit here. + CritScope lock(&cs_); + ref_count = --ref_count_; + } + if (ref_count == 0) { + delete stream_; + delete this; + } + } + private: + StreamInterface* stream_; + int ref_count_; + CriticalSection cs_; + DISALLOW_EVIL_CONSTRUCTORS(StreamRefCount); + }; + + // Constructor for adding references + explicit StreamReference(StreamRefCount* stream_ref_count, + StreamInterface* stream); + + StreamRefCount* stream_ref_count_; + DISALLOW_EVIL_CONSTRUCTORS(StreamReference); +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Flow attempts to move bytes from source to sink via buffer of size +// buffer_len. The function returns SR_SUCCESS when source reaches +// end-of-stream (returns SR_EOS), and all the data has been written successful +// to sink. Alternately, if source returns SR_BLOCK or SR_ERROR, or if sink +// returns SR_BLOCK, SR_ERROR, or SR_EOS, then the function immediately returns +// with the unexpected StreamResult value. +// data_len is the length of the valid data in buffer. in case of error +// this is the data that read from source but can't move to destination. +// as a pass in parameter, it indicates data in buffer that should move to sink +StreamResult Flow(StreamInterface* source, + char* buffer, size_t buffer_len, + StreamInterface* sink, size_t* data_len = NULL); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_STREAM_H_ diff --git a/webrtc/base/stream_unittest.cc b/webrtc/base/stream_unittest.cc new file mode 100644 index 000000000..a6664d716 --- /dev/null +++ b/webrtc/base/stream_unittest.cc @@ -0,0 +1,492 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/stream.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// TestStream +/////////////////////////////////////////////////////////////////////////////// + +class TestStream : public StreamInterface { + public: + TestStream() : pos_(0) { } + + virtual StreamState GetState() const { return SS_OPEN; } + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + unsigned char* uc_buffer = static_cast(buffer); + for (size_t i = 0; i < buffer_len; ++i) { + uc_buffer[i] = static_cast(pos_++); + } + if (read) + *read = buffer_len; + return SR_SUCCESS; + } + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (error) + *error = -1; + return SR_ERROR; + } + virtual void Close() { } + virtual bool SetPosition(size_t position) { + pos_ = position; + return true; + } + virtual bool GetPosition(size_t* position) const { + if (position) *position = pos_; + return true; + } + virtual bool GetSize(size_t* size) const { + return false; + } + virtual bool GetAvailable(size_t* size) const { + return false; + } + + private: + size_t pos_; +}; + +bool VerifyTestBuffer(unsigned char* buffer, size_t len, + unsigned char value) { + bool passed = true; + for (size_t i = 0; i < len; ++i) { + if (buffer[i] != value++) { + passed = false; + break; + } + } + // Ensure that we don't pass again without re-writing + memset(buffer, 0, len); + return passed; +} + +void SeekTest(StreamInterface* stream, const unsigned char value) { + size_t bytes; + unsigned char buffer[13] = { 0 }; + const size_t kBufSize = sizeof(buffer); + + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value)); + EXPECT_TRUE(stream->GetPosition(&bytes)); + EXPECT_EQ(13U, bytes); + + EXPECT_TRUE(stream->SetPosition(7)); + + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value + 7)); + EXPECT_TRUE(stream->GetPosition(&bytes)); + EXPECT_EQ(20U, bytes); +} + +TEST(StreamSegment, TranslatesPosition) { + TestStream* test = new TestStream; + // Verify behavior of original stream + SeekTest(test, 0); + StreamSegment* segment = new StreamSegment(test); + // Verify behavior of adapted stream (all values offset by 20) + SeekTest(segment, 20); + delete segment; +} + +TEST(StreamSegment, SupportsArtificialTermination) { + TestStream* test = new TestStream; + + size_t bytes; + unsigned char buffer[5000] = { 0 }; + const size_t kBufSize = sizeof(buffer); + + { + StreamInterface* stream = test; + + // Read a lot of bytes + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 0)); + + // Test seeking far ahead + EXPECT_TRUE(stream->SetPosition(12345)); + + // Read a bunch more bytes + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(bytes, kBufSize); + EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 12345 % 256)); + } + + // Create a segment of test stream in range [100,600) + EXPECT_TRUE(test->SetPosition(100)); + StreamSegment* segment = new StreamSegment(test, 500); + + { + StreamInterface* stream = segment; + + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(500U, bytes); + EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS); + + // Test seeking past "end" of stream + EXPECT_FALSE(stream->SetPosition(12345)); + EXPECT_FALSE(stream->SetPosition(501)); + + // Test seeking to end (edge case) + EXPECT_TRUE(stream->SetPosition(500)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS); + + // Test seeking to start + EXPECT_TRUE(stream->SetPosition(0)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS); + EXPECT_EQ(500U, bytes); + EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100)); + EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS); + } + + delete segment; +} + +TEST(FifoBufferTest, TestAll) { + const size_t kSize = 16; + const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + char out[kSize * 2]; + void* p; + const void* q; + size_t bytes; + FifoBuffer buf(kSize); + StreamInterface* stream = &buf; + + // Test assumptions about base state + EXPECT_EQ(SS_OPEN, stream->GetState()); + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_TRUE(NULL != stream->GetReadData(&bytes)); + EXPECT_EQ((size_t)0, bytes); + stream->ConsumeReadData(0); + EXPECT_TRUE(NULL != stream->GetWriteBuffer(&bytes)); + EXPECT_EQ(kSize, bytes); + stream->ConsumeWriteBuffer(0); + + // Try a full write + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + + // Try a write that should block + EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL)); + + // Try a full read + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try a read that should block + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try a too-big write + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 2, &bytes, NULL)); + EXPECT_EQ(bytes, kSize); + + // Try a too-big read + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try some small writes and reads + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + + // Try wraparound reads and writes in the following pattern + // WWWWWWWWWWWW.... 0123456789AB.... + // RRRRRRRRXXXX.... ........89AB.... + // WWWW....XXXXWWWW 4567....89AB0123 + // XXXX....RRRRXXXX 4567........0123 + // XXXXWWWWWWWWXXXX 4567012345670123 + // RRRRXXXXXXXXRRRR ....01234567.... + // ....RRRRRRRR.... ................ + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(kSize * 3 / 4, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 4, &bytes, NULL)); + EXPECT_EQ(kSize / 4 , bytes); + EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2 , bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(kSize / 2 , bytes); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + + // Use GetWriteBuffer to reset the read_position for the next tests + stream->GetWriteBuffer(&bytes); + stream->ConsumeWriteBuffer(0); + + // Try using GetReadData to do a full read + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(q, in, kSize)); + stream->ConsumeReadData(kSize); + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try using GetReadData to do some small reads + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(q, in, kSize / 2)); + stream->ConsumeReadData(kSize / 2); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(q, in + kSize / 2, kSize / 2)); + stream->ConsumeReadData(kSize / 2); + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try using GetReadData in a wraparound case + // WWWWWWWWWWWWWWWW 0123456789ABCDEF + // RRRRRRRRRRRRXXXX ............CDEF + // WWWWWWWW....XXXX 01234567....CDEF + // ............RRRR 01234567........ + // RRRRRRRR........ ................ + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize / 4, bytes); + EXPECT_EQ(0, memcmp(q, in + kSize * 3 / 4, kSize / 4)); + stream->ConsumeReadData(kSize / 4); + q = stream->GetReadData(&bytes); + EXPECT_TRUE(NULL != q); + EXPECT_EQ(kSize / 2, bytes); + EXPECT_EQ(0, memcmp(q, in, kSize / 2)); + stream->ConsumeReadData(kSize / 2); + + // Use GetWriteBuffer to reset the read_position for the next tests + stream->GetWriteBuffer(&bytes); + stream->ConsumeWriteBuffer(0); + + // Try using GetWriteBuffer to do a full write + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize, bytes); + memcpy(p, in, kSize); + stream->ConsumeWriteBuffer(kSize); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try using GetWriteBuffer to do some small writes + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize, bytes); + memcpy(p, in, kSize / 2); + stream->ConsumeWriteBuffer(kSize / 2); + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize / 2, bytes); + memcpy(p, in + kSize / 2, kSize / 2); + stream->ConsumeWriteBuffer(kSize / 2); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Try using GetWriteBuffer in a wraparound case + // WWWWWWWWWWWW.... 0123456789AB.... + // RRRRRRRRXXXX.... ........89AB.... + // ........XXXXWWWW ........89AB0123 + // WWWW....XXXXXXXX 4567....89AB0123 + // RRRR....RRRRRRRR ................ + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize / 4, bytes); + memcpy(p, in, kSize / 4); + stream->ConsumeWriteBuffer(kSize / 4); + p = stream->GetWriteBuffer(&bytes); + EXPECT_TRUE(NULL != p); + EXPECT_EQ(kSize / 2, bytes); + memcpy(p, in + kSize / 4, kSize / 4); + stream->ConsumeWriteBuffer(kSize / 4); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL)); + EXPECT_EQ(kSize * 3 / 4, bytes); + EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4)); + EXPECT_EQ(0, memcmp(in, out + kSize / 4, kSize / 4)); + + // Check that the stream is now empty + EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL)); + + // Try growing the buffer + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_TRUE(buf.SetCapacity(kSize * 2)); + EXPECT_EQ(SR_SUCCESS, stream->Write(in + kSize, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL)); + EXPECT_EQ(kSize * 2, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize * 2)); + + // Try shrinking the buffer + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_TRUE(buf.SetCapacity(kSize)); + EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL)); + EXPECT_EQ(kSize, bytes); + EXPECT_EQ(0, memcmp(in, out, kSize)); + + // Write to the stream, close it, read the remaining bytes + EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL)); + stream->Close(); + EXPECT_EQ(SS_CLOSED, stream->GetState()); + EXPECT_EQ(SR_EOS, stream->Write(in, kSize / 2, &bytes, NULL)); + EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL)); + EXPECT_EQ(0, memcmp(in, out, kSize / 2)); + EXPECT_EQ(SR_EOS, stream->Read(out, kSize / 2, &bytes, NULL)); +} + +TEST(FifoBufferTest, FullBufferCheck) { + FifoBuffer buff(10); + buff.ConsumeWriteBuffer(10); + + size_t free; + EXPECT_TRUE(buff.GetWriteBuffer(&free) != NULL); + EXPECT_EQ(0U, free); +} + +TEST(FifoBufferTest, WriteOffsetAndReadOffset) { + const size_t kSize = 16; + const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + char out[kSize * 2]; + FifoBuffer buf(kSize); + + // Write 14 bytes. + EXPECT_EQ(SR_SUCCESS, buf.Write(in, 14, NULL, NULL)); + + // Make sure data is in |buf|. + size_t buffered; + EXPECT_TRUE(buf.GetBuffered(&buffered)); + EXPECT_EQ(14u, buffered); + + // Read 10 bytes. + buf.ConsumeReadData(10); + + // There should be now 12 bytes of available space. + size_t remaining; + EXPECT_TRUE(buf.GetWriteRemaining(&remaining)); + EXPECT_EQ(12u, remaining); + + // Write at offset 12, this should fail. + EXPECT_EQ(SR_BLOCK, buf.WriteOffset(in, 10, 12, NULL)); + + // Write 8 bytes at offset 4, this wraps around the buffer. + EXPECT_EQ(SR_SUCCESS, buf.WriteOffset(in, 8, 4, NULL)); + + // Number of available space remains the same until we call + // ConsumeWriteBuffer(). + EXPECT_TRUE(buf.GetWriteRemaining(&remaining)); + EXPECT_EQ(12u, remaining); + buf.ConsumeWriteBuffer(12); + + // There's 4 bytes bypassed and 4 bytes no read so skip them and verify the + // 8 bytes written. + size_t read; + EXPECT_EQ(SR_SUCCESS, buf.ReadOffset(out, 8, 8, &read)); + EXPECT_EQ(8u, read); + EXPECT_EQ(0, memcmp(out, in, 8)); + + // There should still be 16 bytes available for reading. + EXPECT_TRUE(buf.GetBuffered(&buffered)); + EXPECT_EQ(16u, buffered); + + // Read at offset 16, this should fail since we don't have that much data. + EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL)); +} + +TEST(AsyncWriteTest, TestWrite) { + FifoBuffer* buf = new FifoBuffer(100); + AsyncWriteStream stream(buf, Thread::Current()); + EXPECT_EQ(SS_OPEN, stream.GetState()); + + // Write "abc". Will go to the logging thread, which is the current + // thread. + stream.Write("abc", 3, NULL, NULL); + char bytes[100]; + size_t count; + // Messages on the thread's queue haven't been processed, so "abc" + // hasn't been written yet. + EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 0, &count)); + // Now we process the messages on the thread's queue, so "abc" has + // been written. + EXPECT_TRUE_WAIT(SR_SUCCESS == buf->ReadOffset(&bytes, 3, 0, &count), 10); + EXPECT_EQ(3u, count); + EXPECT_EQ(0, memcmp(bytes, "abc", 3)); + + // Write "def". Will go to the logging thread, which is the current + // thread. + stream.Write("d", 1, &count, NULL); + stream.Write("e", 1, &count, NULL); + stream.Write("f", 1, &count, NULL); + EXPECT_EQ(1u, count); + // Messages on the thread's queue haven't been processed, so "def" + // hasn't been written yet. + EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 3, &count)); + // Flush() causes the message to be processed, so "def" has now been + // written. + stream.Flush(); + EXPECT_EQ(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 3, &count)); + EXPECT_EQ(3u, count); + EXPECT_EQ(0, memcmp(bytes, "def", 3)); + + // Write "xyz". Will go to the logging thread, which is the current + // thread. + stream.Write("xyz", 3, &count, NULL); + EXPECT_EQ(3u, count); + // Messages on the thread's queue haven't been processed, so "xyz" + // hasn't been written yet. + EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 6, &count)); + // Close() causes the message to be processed, so "xyz" has now been + // written. + stream.Close(); + EXPECT_EQ(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 6, &count)); + EXPECT_EQ(3u, count); + EXPECT_EQ(0, memcmp(bytes, "xyz", 3)); + EXPECT_EQ(SS_CLOSED, stream.GetState()); + + // Is't closed, so the writes should fail. + EXPECT_EQ(SR_ERROR, stream.Write("000", 3, NULL, NULL)); + +} + +} // namespace rtc diff --git a/webrtc/base/stringdigest.h b/webrtc/base/stringdigest.h new file mode 100644 index 000000000..7cf6f329a --- /dev/null +++ b/webrtc/base/stringdigest.h @@ -0,0 +1,17 @@ +/* + * Copyright 2004 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_BASE_STRINGDIGEST_H_ +#define WEBRTC_BASE_STRINGDIGEST_H_ + +// TODO: Update remaining callers to use messagedigest.h instead +#include "webrtc/base/messagedigest.h" + +#endif // WEBRTC_BASE_STRINGDIGEST_H_ diff --git a/webrtc/base/stringencode.cc b/webrtc/base/stringencode.cc new file mode 100644 index 000000000..1e0a1493c --- /dev/null +++ b/webrtc/base/stringencode.cc @@ -0,0 +1,657 @@ +/* + * Copyright 2004 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 "webrtc/base/stringencode.h" + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +///////////////////////////////////////////////////////////////////////////// +// String Encoding Utilities +///////////////////////////////////////////////////////////////////////////// + +size_t escape(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch == escape) || ::strchr(illegal, ch)) { + if (bufpos + 2 >= buflen) + break; + buffer[bufpos++] = escape; + } + buffer[bufpos++] = ch; + } + + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t unescape(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch == escape) && (srcpos < srclen)) { + ch = source[srcpos++]; + } + buffer[bufpos++] = ch; + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t encode(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch != escape) && !::strchr(illegal, ch)) { + buffer[bufpos++] = ch; + } else if (bufpos + 3 >= buflen) { + break; + } else { + buffer[bufpos+0] = escape; + buffer[bufpos+1] = hex_encode((static_cast(ch) >> 4) & 0xF); + buffer[bufpos+2] = hex_encode((static_cast(ch) ) & 0xF); + bufpos += 3; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t decode(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape) { + if (buflen <= 0) + return 0; + + unsigned char h1, h2; + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch == escape) + && (srcpos + 1 < srclen) + && hex_decode(source[srcpos], &h1) + && hex_decode(source[srcpos+1], &h2)) { + buffer[bufpos++] = (h1 << 4) | h2; + srcpos += 2; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +const char* unsafe_filename_characters() { + // It might be better to have a single specification which is the union of + // all operating systems, unless one system is overly restrictive. +#if defined(WEBRTC_WIN) + return "\\/:*?\"<>|"; +#else // !WEBRTC_WIN + // TODO + ASSERT(false); + return ""; +#endif // !WEBRTC_WIN +} + +const unsigned char URL_UNSAFE = 0x1; // 0-33 "#$%&+,/:;<=>?@[\]^`{|} 127 +const unsigned char XML_UNSAFE = 0x2; // "&'<> +const unsigned char HTML_UNSAFE = 0x2; // "&'<> + +// ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 6 5 7 8 9 : ; < = > ? +//@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ +//` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ + +const unsigned char ASCII_CLASS[128] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,0,3,1,1,1,3,2,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,3,1,3,1, + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0, + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1, +}; + +size_t url_encode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + if (NULL == buffer) + return srclen * 3 + 1; + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if ((ch < 128) && (ASCII_CLASS[ch] & URL_UNSAFE)) { + if (bufpos + 3 >= buflen) { + break; + } + buffer[bufpos+0] = '%'; + buffer[bufpos+1] = hex_encode((ch >> 4) & 0xF); + buffer[bufpos+2] = hex_encode((ch ) & 0xF); + bufpos += 3; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t url_decode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + if (NULL == buffer) + return srclen + 1; + if (buflen <= 0) + return 0; + + unsigned char h1, h2; + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if (ch == '+') { + buffer[bufpos++] = ' '; + } else if ((ch == '%') + && (srcpos + 1 < srclen) + && hex_decode(source[srcpos], &h1) + && hex_decode(source[srcpos+1], &h2)) + { + buffer[bufpos++] = (h1 << 4) | h2; + srcpos += 2; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t utf8_decode(const char* source, size_t srclen, unsigned long* value) { + const unsigned char* s = reinterpret_cast(source); + if ((s[0] & 0x80) == 0x00) { // Check s[0] == 0xxxxxxx + *value = s[0]; + return 1; + } + if ((srclen < 2) || ((s[1] & 0xC0) != 0x80)) { // Check s[1] != 10xxxxxx + return 0; + } + // Accumulate the trailer byte values in value16, and combine it with the + // relevant bits from s[0], once we've determined the sequence length. + unsigned long value16 = (s[1] & 0x3F); + if ((s[0] & 0xE0) == 0xC0) { // Check s[0] == 110xxxxx + *value = ((s[0] & 0x1F) << 6) | value16; + return 2; + } + if ((srclen < 3) || ((s[2] & 0xC0) != 0x80)) { // Check s[2] != 10xxxxxx + return 0; + } + value16 = (value16 << 6) | (s[2] & 0x3F); + if ((s[0] & 0xF0) == 0xE0) { // Check s[0] == 1110xxxx + *value = ((s[0] & 0x0F) << 12) | value16; + return 3; + } + if ((srclen < 4) || ((s[3] & 0xC0) != 0x80)) { // Check s[3] != 10xxxxxx + return 0; + } + value16 = (value16 << 6) | (s[3] & 0x3F); + if ((s[0] & 0xF8) == 0xF0) { // Check s[0] == 11110xxx + *value = ((s[0] & 0x07) << 18) | value16; + return 4; + } + return 0; +} + +size_t utf8_encode(char* buffer, size_t buflen, unsigned long value) { + if ((value <= 0x7F) && (buflen >= 1)) { + buffer[0] = static_cast(value); + return 1; + } + if ((value <= 0x7FF) && (buflen >= 2)) { + buffer[0] = 0xC0 | static_cast(value >> 6); + buffer[1] = 0x80 | static_cast(value & 0x3F); + return 2; + } + if ((value <= 0xFFFF) && (buflen >= 3)) { + buffer[0] = 0xE0 | static_cast(value >> 12); + buffer[1] = 0x80 | static_cast((value >> 6) & 0x3F); + buffer[2] = 0x80 | static_cast(value & 0x3F); + return 3; + } + if ((value <= 0x1FFFFF) && (buflen >= 4)) { + buffer[0] = 0xF0 | static_cast(value >> 18); + buffer[1] = 0x80 | static_cast((value >> 12) & 0x3F); + buffer[2] = 0x80 | static_cast((value >> 6) & 0x3F); + buffer[3] = 0x80 | static_cast(value & 0x3F); + return 4; + } + return 0; +} + +size_t html_encode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos]; + if (ch < 128) { + srcpos += 1; + if (ASCII_CLASS[ch] & HTML_UNSAFE) { + const char * escseq = 0; + size_t esclen = 0; + switch (ch) { + case '<': escseq = "<"; esclen = 4; break; + case '>': escseq = ">"; esclen = 4; break; + case '\'': escseq = "'"; esclen = 5; break; + case '\"': escseq = """; esclen = 6; break; + case '&': escseq = "&"; esclen = 5; break; + default: ASSERT(false); + } + if (bufpos + esclen >= buflen) { + break; + } + memcpy(buffer + bufpos, escseq, esclen); + bufpos += esclen; + } else { + buffer[bufpos++] = ch; + } + } else { + // Largest value is 0x1FFFFF => � (10 characters) + char escseq[11]; + unsigned long val; + if (size_t vallen = utf8_decode(&source[srcpos], srclen - srcpos, &val)) { + srcpos += vallen; + } else { + // Not a valid utf8 sequence, just use the raw character. + val = static_cast(source[srcpos++]); + } + size_t esclen = sprintfn(escseq, ARRAY_SIZE(escseq), "&#%lu;", val); + if (bufpos + esclen >= buflen) { + break; + } + memcpy(buffer + bufpos, escseq, esclen); + bufpos += esclen; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t html_decode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + return xml_decode(buffer, buflen, source, srclen); +} + +size_t xml_encode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if ((ch < 128) && (ASCII_CLASS[ch] & XML_UNSAFE)) { + const char * escseq = 0; + size_t esclen = 0; + switch (ch) { + case '<': escseq = "<"; esclen = 4; break; + case '>': escseq = ">"; esclen = 4; break; + case '\'': escseq = "'"; esclen = 6; break; + case '\"': escseq = """; esclen = 6; break; + case '&': escseq = "&"; esclen = 5; break; + default: ASSERT(false); + } + if (bufpos + esclen >= buflen) { + break; + } + memcpy(buffer + bufpos, escseq, esclen); + bufpos += esclen; + } else { + buffer[bufpos++] = ch; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +size_t xml_decode(char * buffer, size_t buflen, + const char * source, size_t srclen) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen <= 0) + return 0; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + unsigned char ch = source[srcpos++]; + if (ch != '&') { + buffer[bufpos++] = ch; + } else if ((srcpos + 2 < srclen) + && (memcmp(source + srcpos, "lt;", 3) == 0)) { + buffer[bufpos++] = '<'; + srcpos += 3; + } else if ((srcpos + 2 < srclen) + && (memcmp(source + srcpos, "gt;", 3) == 0)) { + buffer[bufpos++] = '>'; + srcpos += 3; + } else if ((srcpos + 4 < srclen) + && (memcmp(source + srcpos, "apos;", 5) == 0)) { + buffer[bufpos++] = '\''; + srcpos += 5; + } else if ((srcpos + 4 < srclen) + && (memcmp(source + srcpos, "quot;", 5) == 0)) { + buffer[bufpos++] = '\"'; + srcpos += 5; + } else if ((srcpos + 3 < srclen) + && (memcmp(source + srcpos, "amp;", 4) == 0)) { + buffer[bufpos++] = '&'; + srcpos += 4; + } else if ((srcpos < srclen) && (source[srcpos] == '#')) { + int int_base = 10; + if ((srcpos + 1 < srclen) && (source[srcpos+1] == 'x')) { + int_base = 16; + srcpos += 1; + } + char * ptr; + // TODO: Fix hack (ptr may go past end of data) + unsigned long val = strtoul(source + srcpos + 1, &ptr, int_base); + if ((static_cast(ptr - source) < srclen) && (*ptr == ';')) { + srcpos = ptr - source + 1; + } else { + // Not a valid escape sequence. + break; + } + if (size_t esclen = utf8_encode(buffer + bufpos, buflen - bufpos, val)) { + bufpos += esclen; + } else { + // Not enough room to encode the character, or illegal character + break; + } + } else { + // Unrecognized escape sequence. + break; + } + } + buffer[bufpos] = '\0'; + return bufpos; +} + +static const char HEX[] = "0123456789abcdef"; + +char hex_encode(unsigned char val) { + ASSERT(val < 16); + return (val < 16) ? HEX[val] : '!'; +} + +bool hex_decode(char ch, unsigned char* val) { + if ((ch >= '0') && (ch <= '9')) { + *val = ch - '0'; + } else if ((ch >= 'A') && (ch <= 'Z')) { + *val = (ch - 'A') + 10; + } else if ((ch >= 'a') && (ch <= 'z')) { + *val = (ch - 'a') + 10; + } else { + return false; + } + return true; +} + +size_t hex_encode(char* buffer, size_t buflen, + const char* csource, size_t srclen) { + return hex_encode_with_delimiter(buffer, buflen, csource, srclen, 0); +} + +size_t hex_encode_with_delimiter(char* buffer, size_t buflen, + const char* csource, size_t srclen, + char delimiter) { + ASSERT(NULL != buffer); // TODO: estimate output size + if (buflen == 0) + return 0; + + // Init and check bounds. + const unsigned char* bsource = + reinterpret_cast(csource); + size_t srcpos = 0, bufpos = 0; + size_t needed = delimiter ? (srclen * 3) : (srclen * 2 + 1); + if (buflen < needed) + return 0; + + while (srcpos < srclen) { + unsigned char ch = bsource[srcpos++]; + buffer[bufpos ] = hex_encode((ch >> 4) & 0xF); + buffer[bufpos+1] = hex_encode((ch ) & 0xF); + bufpos += 2; + + // Don't write a delimiter after the last byte. + if (delimiter && (srcpos < srclen)) { + buffer[bufpos] = delimiter; + ++bufpos; + } + } + + // Null terminate. + buffer[bufpos] = '\0'; + return bufpos; +} + +std::string hex_encode(const char* source, size_t srclen) { + return hex_encode_with_delimiter(source, srclen, 0); +} + +std::string hex_encode_with_delimiter(const char* source, size_t srclen, + char delimiter) { + const size_t kBufferSize = srclen * 3; + char* buffer = STACK_ARRAY(char, kBufferSize); + size_t length = hex_encode_with_delimiter(buffer, kBufferSize, + source, srclen, delimiter); + ASSERT(srclen == 0 || length > 0); + return std::string(buffer, length); +} + +size_t hex_decode(char * cbuffer, size_t buflen, + const char * source, size_t srclen) { + return hex_decode_with_delimiter(cbuffer, buflen, source, srclen, 0); +} + +size_t hex_decode_with_delimiter(char* cbuffer, size_t buflen, + const char* source, size_t srclen, + char delimiter) { + ASSERT(NULL != cbuffer); // TODO: estimate output size + if (buflen == 0) + return 0; + + // Init and bounds check. + unsigned char* bbuffer = reinterpret_cast(cbuffer); + size_t srcpos = 0, bufpos = 0; + size_t needed = (delimiter) ? (srclen + 1) / 3 : srclen / 2; + if (buflen < needed) + return 0; + + while (srcpos < srclen) { + if ((srclen - srcpos) < 2) { + // This means we have an odd number of bytes. + return 0; + } + + unsigned char h1, h2; + if (!hex_decode(source[srcpos], &h1) || + !hex_decode(source[srcpos + 1], &h2)) + return 0; + + bbuffer[bufpos++] = (h1 << 4) | h2; + srcpos += 2; + + // Remove the delimiter if needed. + if (delimiter && (srclen - srcpos) > 1) { + if (source[srcpos] != delimiter) + return 0; + ++srcpos; + } + } + + return bufpos; +} + +size_t hex_decode(char* buffer, size_t buflen, const std::string& source) { + return hex_decode_with_delimiter(buffer, buflen, source, 0); +} +size_t hex_decode_with_delimiter(char* buffer, size_t buflen, + const std::string& source, char delimiter) { + return hex_decode_with_delimiter(buffer, buflen, + source.c_str(), source.length(), delimiter); +} + +size_t transform(std::string& value, size_t maxlen, const std::string& source, + Transform t) { + char* buffer = STACK_ARRAY(char, maxlen + 1); + size_t length = t(buffer, maxlen + 1, source.data(), source.length()); + value.assign(buffer, length); + return length; +} + +std::string s_transform(const std::string& source, Transform t) { + // Ask transformation function to approximate the destination size (returns upper bound) + size_t maxlen = t(NULL, 0, source.data(), source.length()); + char * buffer = STACK_ARRAY(char, maxlen); + size_t len = t(buffer, maxlen, source.data(), source.length()); + std::string result(buffer, len); + return result; +} + +size_t tokenize(const std::string& source, char delimiter, + std::vector* fields) { + ASSERT(NULL != fields); + fields->clear(); + size_t last = 0; + for (size_t i = 0; i < source.length(); ++i) { + if (source[i] == delimiter) { + if (i != last) { + fields->push_back(source.substr(last, i - last)); + } + last = i + 1; + } + } + if (last != source.length()) { + fields->push_back(source.substr(last, source.length() - last)); + } + return fields->size(); +} + +size_t tokenize_append(const std::string& source, char delimiter, + std::vector* fields) { + if (!fields) return 0; + + std::vector new_fields; + tokenize(source, delimiter, &new_fields); + fields->insert(fields->end(), new_fields.begin(), new_fields.end()); + return fields->size(); +} + +size_t tokenize(const std::string& source, char delimiter, char start_mark, + char end_mark, std::vector* fields) { + if (!fields) return 0; + fields->clear(); + + std::string remain_source = source; + while (!remain_source.empty()) { + size_t start_pos = remain_source.find(start_mark); + if (std::string::npos == start_pos) break; + std::string pre_mark; + if (start_pos > 0) { + pre_mark = remain_source.substr(0, start_pos - 1); + } + + ++start_pos; + size_t end_pos = remain_source.find(end_mark, start_pos); + if (std::string::npos == end_pos) break; + + // We have found the matching marks. First tokenize the pre-mask. Then add + // the marked part as a single field. Finally, loop back for the post-mark. + tokenize_append(pre_mark, delimiter, fields); + fields->push_back(remain_source.substr(start_pos, end_pos - start_pos)); + remain_source = remain_source.substr(end_pos + 1); + } + + return tokenize_append(remain_source, delimiter, fields); +} + +size_t split(const std::string& source, char delimiter, + std::vector* fields) { + ASSERT(NULL != fields); + fields->clear(); + size_t last = 0; + for (size_t i = 0; i < source.length(); ++i) { + if (source[i] == delimiter) { + fields->push_back(source.substr(last, i - last)); + last = i + 1; + } + } + fields->push_back(source.substr(last, source.length() - last)); + return fields->size(); +} + +char make_char_safe_for_filename(char c) { + if (c < 32) + return '_'; + + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '*': + case '?': + return '_'; + + default: + return c; + } +} + +/* +void sprintf(std::string& value, size_t maxlen, const char * format, ...) { + char * buffer = STACK_ARRAY(char, maxlen + 1); + va_list args; + va_start(args, format); + value.assign(buffer, vsprintfn(buffer, maxlen + 1, format, args)); + va_end(args); +} +*/ + +///////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/stringencode.h b/webrtc/base/stringencode.h new file mode 100644 index 000000000..b6c666f98 --- /dev/null +++ b/webrtc/base/stringencode.h @@ -0,0 +1,210 @@ +/* + * Copyright 2004 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_BASE_STRINGENCODE_H_ +#define WEBRTC_BASE_STRINGENCODE_H_ + +#include +#include +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// String Encoding Utilities +////////////////////////////////////////////////////////////////////// + +// Convert an unsigned value to it's utf8 representation. Returns the length +// of the encoded string, or 0 if the encoding is longer than buflen - 1. +size_t utf8_encode(char* buffer, size_t buflen, unsigned long value); +// Decode the utf8 encoded value pointed to by source. Returns the number of +// bytes used by the encoding, or 0 if the encoding is invalid. +size_t utf8_decode(const char* source, size_t srclen, unsigned long* value); + +// Escaping prefixes illegal characters with the escape character. Compact, but +// illegal characters still appear in the string. +size_t escape(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape); +// Note: in-place unescaping (buffer == source) is allowed. +size_t unescape(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape); + +// Encoding replaces illegal characters with the escape character and 2 hex +// chars, so it's a little less compact than escape, but completely removes +// illegal characters. note that hex digits should not be used as illegal +// characters. +size_t encode(char * buffer, size_t buflen, + const char * source, size_t srclen, + const char * illegal, char escape); +// Note: in-place decoding (buffer == source) is allowed. +size_t decode(char * buffer, size_t buflen, + const char * source, size_t srclen, + char escape); + +// Returns a list of characters that may be unsafe for use in the name of a +// file, suitable for passing to the 'illegal' member of escape or encode. +const char* unsafe_filename_characters(); + +// url_encode is an encode operation with a predefined set of illegal characters +// and escape character (for use in URLs, obviously). +size_t url_encode(char * buffer, size_t buflen, + const char * source, size_t srclen); +// Note: in-place decoding (buffer == source) is allowed. +size_t url_decode(char * buffer, size_t buflen, + const char * source, size_t srclen); + +// html_encode prevents data embedded in html from containing markup. +size_t html_encode(char * buffer, size_t buflen, + const char * source, size_t srclen); +// Note: in-place decoding (buffer == source) is allowed. +size_t html_decode(char * buffer, size_t buflen, + const char * source, size_t srclen); + +// xml_encode makes data suitable for inside xml attributes and values. +size_t xml_encode(char * buffer, size_t buflen, + const char * source, size_t srclen); +// Note: in-place decoding (buffer == source) is allowed. +size_t xml_decode(char * buffer, size_t buflen, + const char * source, size_t srclen); + +// Convert an unsigned value from 0 to 15 to the hex character equivalent... +char hex_encode(unsigned char val); +// ...and vice-versa. +bool hex_decode(char ch, unsigned char* val); + +// hex_encode shows the hex representation of binary data in ascii. +size_t hex_encode(char* buffer, size_t buflen, + const char* source, size_t srclen); + +// hex_encode, but separate each byte representation with a delimiter. +// |delimiter| == 0 means no delimiter +// If the buffer is too short, we return 0 +size_t hex_encode_with_delimiter(char* buffer, size_t buflen, + const char* source, size_t srclen, + char delimiter); + +// Helper functions for hex_encode. +std::string hex_encode(const char* source, size_t srclen); +std::string hex_encode_with_delimiter(const char* source, size_t srclen, + char delimiter); + +// hex_decode converts ascii hex to binary. +size_t hex_decode(char* buffer, size_t buflen, + const char* source, size_t srclen); + +// hex_decode, assuming that there is a delimiter between every byte +// pair. +// |delimiter| == 0 means no delimiter +// If the buffer is too short or the data is invalid, we return 0. +size_t hex_decode_with_delimiter(char* buffer, size_t buflen, + const char* source, size_t srclen, + char delimiter); + +// Helper functions for hex_decode. +size_t hex_decode(char* buffer, size_t buflen, const std::string& source); +size_t hex_decode_with_delimiter(char* buffer, size_t buflen, + const std::string& source, char delimiter); + +// Apply any suitable string transform (including the ones above) to an STL +// string. Stack-allocated temporary space is used for the transformation, +// so value and source may refer to the same string. +typedef size_t (*Transform)(char * buffer, size_t buflen, + const char * source, size_t srclen); +size_t transform(std::string& value, size_t maxlen, const std::string& source, + Transform t); + +// Return the result of applying transform t to source. +std::string s_transform(const std::string& source, Transform t); + +// Convenience wrappers. +inline std::string s_url_encode(const std::string& source) { + return s_transform(source, url_encode); +} +inline std::string s_url_decode(const std::string& source) { + return s_transform(source, url_decode); +} + +// Splits the source string into multiple fields separated by delimiter, +// with duplicates of delimiter creating empty fields. +size_t split(const std::string& source, char delimiter, + std::vector* fields); + +// Splits the source string into multiple fields separated by delimiter, +// with duplicates of delimiter ignored. Trailing delimiter ignored. +size_t tokenize(const std::string& source, char delimiter, + std::vector* fields); + +// Tokenize and append the tokens to fields. Return the new size of fields. +size_t tokenize_append(const std::string& source, char delimiter, + std::vector* fields); + +// Splits the source string into multiple fields separated by delimiter, with +// duplicates of delimiter ignored. Trailing delimiter ignored. A substring in +// between the start_mark and the end_mark is treated as a single field. Return +// the size of fields. For example, if source is "filename +// \"/Library/Application Support/media content.txt\"", delimiter is ' ', and +// the start_mark and end_mark are '"', this method returns two fields: +// "filename" and "/Library/Application Support/media content.txt". +size_t tokenize(const std::string& source, char delimiter, char start_mark, + char end_mark, std::vector* fields); + +// Safe sprintf to std::string +//void sprintf(std::string& value, size_t maxlen, const char * format, ...) +// PRINTF_FORMAT(3); + +// Convert arbitrary values to/from a string. + +template +static bool ToString(const T &t, std::string* s) { + ASSERT(NULL != s); + std::ostringstream oss; + oss << std::boolalpha << t; + *s = oss.str(); + return !oss.fail(); +} + +template +static bool FromString(const std::string& s, T* t) { + ASSERT(NULL != t); + std::istringstream iss(s); + iss >> std::boolalpha >> *t; + return !iss.fail(); +} + +// Inline versions of the string conversion routines. + +template +static inline std::string ToString(const T& val) { + std::string str; ToString(val, &str); return str; +} + +template +static inline T FromString(const std::string& str) { + T val; FromString(str, &val); return val; +} + +template +static inline T FromString(const T& defaultValue, const std::string& str) { + T val(defaultValue); FromString(str, &val); return val; +} + +// simple function to strip out characters which shouldn't be +// used in filenames +char make_char_safe_for_filename(char c); + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_STRINGENCODE_H__ diff --git a/webrtc/base/stringencode_unittest.cc b/webrtc/base/stringencode_unittest.cc new file mode 100644 index 000000000..c9e726ecb --- /dev/null +++ b/webrtc/base/stringencode_unittest.cc @@ -0,0 +1,385 @@ +/* + * Copyright 2004 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 "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +TEST(Utf8EncodeTest, EncodeDecode) { + const struct Utf8Test { + const char* encoded; + size_t encsize, enclen; + unsigned long decoded; + } kTests[] = { + { "a ", 5, 1, 'a' }, + { "\x7F ", 5, 1, 0x7F }, + { "\xC2\x80 ", 5, 2, 0x80 }, + { "\xDF\xBF ", 5, 2, 0x7FF }, + { "\xE0\xA0\x80 ", 5, 3, 0x800 }, + { "\xEF\xBF\xBF ", 5, 3, 0xFFFF }, + { "\xF0\x90\x80\x80 ", 5, 4, 0x10000 }, + { "\xF0\x90\x80\x80 ", 3, 0, 0x10000 }, + { "\xF0\xF0\x80\x80 ", 5, 0, 0 }, + { "\xF0\x90\x80 ", 5, 0, 0 }, + { "\x90\x80\x80 ", 5, 0, 0 }, + { NULL, 0, 0 }, + }; + for (size_t i = 0; kTests[i].encoded; ++i) { + unsigned long val = 0; + ASSERT_EQ(kTests[i].enclen, utf8_decode(kTests[i].encoded, + kTests[i].encsize, + &val)); + unsigned long result = (kTests[i].enclen == 0) ? 0 : kTests[i].decoded; + ASSERT_EQ(result, val); + + if (kTests[i].decoded == 0) { + // Not an interesting encoding test case + continue; + } + + char buffer[5]; + memset(buffer, 0x01, ARRAY_SIZE(buffer)); + ASSERT_EQ(kTests[i].enclen, utf8_encode(buffer, + kTests[i].encsize, + kTests[i].decoded)); + ASSERT_TRUE(memcmp(buffer, kTests[i].encoded, kTests[i].enclen) == 0); + // Make sure remainder of buffer is unchanged + ASSERT_TRUE(memory_check(buffer + kTests[i].enclen, + 0x1, + ARRAY_SIZE(buffer) - kTests[i].enclen)); + } +} + +class HexEncodeTest : public testing::Test { + public: + HexEncodeTest() : enc_res_(0), dec_res_(0) { + for (size_t i = 0; i < sizeof(data_); ++i) { + data_[i] = (i + 128) & 0xff; + } + memset(decoded_, 0x7f, sizeof(decoded_)); + } + + char data_[10]; + char encoded_[31]; + char decoded_[11]; + size_t enc_res_; + size_t dec_res_; +}; + +// Test that we can convert to/from hex with no delimiter. +TEST_F(HexEncodeTest, TestWithNoDelimiter) { + enc_res_ = hex_encode(encoded_, sizeof(encoded_), data_, sizeof(data_)); + ASSERT_EQ(sizeof(data_) * 2, enc_res_); + ASSERT_STREQ("80818283848586878889", encoded_); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test that we can convert to/from hex with a colon delimiter. +TEST_F(HexEncodeTest, TestWithDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), + data_, sizeof(data_), ':'); + ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_); + ASSERT_STREQ("80:81:82:83:84:85:86:87:88:89", encoded_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, ':'); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test that encoding with one delimiter and decoding with another fails. +TEST_F(HexEncodeTest, TestWithWrongDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), + data_, sizeof(data_), ':'); + ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, '/'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that encoding without a delimiter and decoding with one fails. +TEST_F(HexEncodeTest, TestExpectedDelimiter) { + enc_res_ = hex_encode(encoded_, sizeof(encoded_), data_, sizeof(data_)); + ASSERT_EQ(sizeof(data_) * 2, enc_res_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that encoding with a delimiter and decoding without one fails. +TEST_F(HexEncodeTest, TestExpectedNoDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), + data_, sizeof(data_), ':'); + ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_); + ASSERT_EQ(0U, dec_res_); +} + +// Test that we handle a zero-length buffer with no delimiter. +TEST_F(HexEncodeTest, TestZeroLengthNoDelimiter) { + enc_res_ = hex_encode(encoded_, sizeof(encoded_), "", 0); + ASSERT_EQ(0U, enc_res_); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_); + ASSERT_EQ(0U, dec_res_); +} + +// Test that we handle a zero-length buffer with a delimiter. +TEST_F(HexEncodeTest, TestZeroLengthWithDelimiter) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), "", 0, ':'); + ASSERT_EQ(0U, enc_res_); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), + encoded_, enc_res_, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test the std::string variants that take no delimiter. +TEST_F(HexEncodeTest, TestHelpersNoDelimiter) { + std::string result = hex_encode(data_, sizeof(data_)); + ASSERT_EQ("80818283848586878889", result); + dec_res_ = hex_decode(decoded_, sizeof(decoded_), result); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test the std::string variants that use a delimiter. +TEST_F(HexEncodeTest, TestHelpersWithDelimiter) { + std::string result = hex_encode_with_delimiter(data_, sizeof(data_), ':'); + ASSERT_EQ("80:81:82:83:84:85:86:87:88:89", result); + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), result, ':'); + ASSERT_EQ(sizeof(data_), dec_res_); + ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_)); +} + +// Test that encoding into a too-small output buffer (without delimiter) fails. +TEST_F(HexEncodeTest, TestEncodeTooShort) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(data_) * 2, + data_, sizeof(data_), 0); + ASSERT_EQ(0U, enc_res_); +} + +// Test that encoding into a too-small output buffer (with delimiter) fails. +TEST_F(HexEncodeTest, TestEncodeWithDelimiterTooShort) { + enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(data_) * 3 - 1, + data_, sizeof(data_), ':'); + ASSERT_EQ(0U, enc_res_); +} + +// Test that decoding into a too-small output buffer fails. +TEST_F(HexEncodeTest, TestDecodeTooShort) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, "0123456789", 10, 0); + ASSERT_EQ(0U, dec_res_); + ASSERT_EQ(0x7f, decoded_[4]); +} + +// Test that decoding non-hex data fails. +TEST_F(HexEncodeTest, TestDecodeBogusData) { + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), "xyz", 3, 0); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding an odd number of hex characters fails. +TEST_F(HexEncodeTest, TestDecodeOddHexDigits) { + dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), "012", 3, 0); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding a string with too many delimiters fails. +TEST_F(HexEncodeTest, TestDecodeWithDelimiterTooManyDelimiters) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, "01::23::45::67", 14, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding a string with a leading delimiter fails. +TEST_F(HexEncodeTest, TestDecodeWithDelimiterLeadingDelimiter) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, ":01:23:45:67", 12, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Test that decoding a string with a trailing delimiter fails. +TEST_F(HexEncodeTest, TestDecodeWithDelimiterTrailingDelimiter) { + dec_res_ = hex_decode_with_delimiter(decoded_, 4, "01:23:45:67:", 12, ':'); + ASSERT_EQ(0U, dec_res_); +} + +// Tests counting substrings. +TEST(TokenizeTest, CountSubstrings) { + std::vector fields; + + EXPECT_EQ(5ul, tokenize("one two three four five", ' ', &fields)); + fields.clear(); + EXPECT_EQ(1ul, tokenize("one", ' ', &fields)); + + // Extra spaces should be ignored. + fields.clear(); + EXPECT_EQ(5ul, tokenize(" one two three four five ", ' ', &fields)); + fields.clear(); + EXPECT_EQ(1ul, tokenize(" one ", ' ', &fields)); + fields.clear(); + EXPECT_EQ(0ul, tokenize(" ", ' ', &fields)); +} + +// Tests comparing substrings. +TEST(TokenizeTest, CompareSubstrings) { + std::vector fields; + + tokenize("find middle one", ' ', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("middle", fields.at(1).c_str()); + fields.clear(); + + // Extra spaces should be ignored. + tokenize(" find middle one ", ' ', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("middle", fields.at(1).c_str()); + fields.clear(); + tokenize(" ", ' ', &fields); + ASSERT_EQ(0ul, fields.size()); +} + +TEST(TokenizeTest, TokenizeAppend) { + ASSERT_EQ(0ul, tokenize_append("A B C", ' ', NULL)); + + std::vector fields; + + tokenize_append("A B C", ' ', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("B", fields.at(1).c_str()); + + tokenize_append("D E", ' ', &fields); + ASSERT_EQ(5ul, fields.size()); + ASSERT_STREQ("B", fields.at(1).c_str()); + ASSERT_STREQ("E", fields.at(4).c_str()); +} + +TEST(TokenizeTest, TokenizeWithMarks) { + ASSERT_EQ(0ul, tokenize("D \"A B", ' ', '(', ')', NULL)); + + std::vector fields; + tokenize("A B C", ' ', '"', '"', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("C", fields.at(2).c_str()); + + tokenize("\"A B\" C", ' ', '"', '"', &fields); + ASSERT_EQ(2ul, fields.size()); + ASSERT_STREQ("A B", fields.at(0).c_str()); + + tokenize("D \"A B\" C", ' ', '"', '"', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("A B", fields.at(1).c_str()); + + tokenize("D \"A B\" C \"E F\"", ' ', '"', '"', &fields); + ASSERT_EQ(4ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("A B", fields.at(1).c_str()); + ASSERT_STREQ("E F", fields.at(3).c_str()); + + // No matching marks. + tokenize("D \"A B", ' ', '"', '"', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("\"A", fields.at(1).c_str()); + + tokenize("D (A B) C (E F) G", ' ', '(', ')', &fields); + ASSERT_EQ(5ul, fields.size()); + ASSERT_STREQ("D", fields.at(0).c_str()); + ASSERT_STREQ("A B", fields.at(1).c_str()); + ASSERT_STREQ("E F", fields.at(3).c_str()); +} + +// Tests counting substrings. +TEST(SplitTest, CountSubstrings) { + std::vector fields; + + EXPECT_EQ(5ul, split("one,two,three,four,five", ',', &fields)); + fields.clear(); + EXPECT_EQ(1ul, split("one", ',', &fields)); + + // Empty fields between commas count. + fields.clear(); + EXPECT_EQ(5ul, split("one,,three,four,five", ',', &fields)); + fields.clear(); + EXPECT_EQ(3ul, split(",three,", ',', &fields)); + fields.clear(); + EXPECT_EQ(1ul, split("", ',', &fields)); +} + +// Tests comparing substrings. +TEST(SplitTest, CompareSubstrings) { + std::vector fields; + + split("find,middle,one", ',', &fields); + ASSERT_EQ(3ul, fields.size()); + ASSERT_STREQ("middle", fields.at(1).c_str()); + fields.clear(); + + // Empty fields between commas count. + split("find,,middle,one", ',', &fields); + ASSERT_EQ(4ul, fields.size()); + ASSERT_STREQ("middle", fields.at(2).c_str()); + fields.clear(); + split("", ',', &fields); + ASSERT_EQ(1ul, fields.size()); + ASSERT_STREQ("", fields.at(0).c_str()); +} + +TEST(BoolTest, DecodeValid) { + bool value; + EXPECT_TRUE(FromString("true", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString("true,", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString("true , true", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString("true ,\n false", &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString(" true \n", &value)); + EXPECT_TRUE(value); + + EXPECT_TRUE(FromString("false", &value)); + EXPECT_FALSE(value); + EXPECT_TRUE(FromString(" false ", &value)); + EXPECT_FALSE(value); + EXPECT_TRUE(FromString(" false, ", &value)); + EXPECT_FALSE(value); + + EXPECT_TRUE(FromString("true\n")); + EXPECT_FALSE(FromString("false\n")); +} + +TEST(BoolTest, DecodeInvalid) { + bool value; + EXPECT_FALSE(FromString("True", &value)); + EXPECT_FALSE(FromString("TRUE", &value)); + EXPECT_FALSE(FromString("False", &value)); + EXPECT_FALSE(FromString("FALSE", &value)); + EXPECT_FALSE(FromString("0", &value)); + EXPECT_FALSE(FromString("1", &value)); + EXPECT_FALSE(FromString("0,", &value)); + EXPECT_FALSE(FromString("1,", &value)); + EXPECT_FALSE(FromString("1,0", &value)); + EXPECT_FALSE(FromString("1.", &value)); + EXPECT_FALSE(FromString("1.0", &value)); + EXPECT_FALSE(FromString("", &value)); + EXPECT_FALSE(FromString("false\nfalse")); +} + +TEST(BoolTest, RoundTrip) { + bool value; + EXPECT_TRUE(FromString(ToString(true), &value)); + EXPECT_TRUE(value); + EXPECT_TRUE(FromString(ToString(false), &value)); + EXPECT_FALSE(value); +} +} // namespace rtc diff --git a/webrtc/base/stringutils.cc b/webrtc/base/stringutils.cc new file mode 100644 index 000000000..041708d3d --- /dev/null +++ b/webrtc/base/stringutils.cc @@ -0,0 +1,133 @@ +/* + * Copyright 2004 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 "webrtc/base/stringutils.h" +#include "webrtc/base/common.h" + +namespace rtc { + +bool memory_check(const void* memory, int c, size_t count) { + const char* char_memory = static_cast(memory); + char char_c = static_cast(c); + for (size_t i = 0; i < count; ++i) { + if (char_memory[i] != char_c) { + return false; + } + } + return true; +} + +bool string_match(const char* target, const char* pattern) { + while (*pattern) { + if (*pattern == '*') { + if (!*++pattern) { + return true; + } + while (*target) { + if ((toupper(*pattern) == toupper(*target)) + && string_match(target + 1, pattern + 1)) { + return true; + } + ++target; + } + return false; + } else { + if (toupper(*pattern) != toupper(*target)) { + return false; + } + ++target; + ++pattern; + } + } + return !*target; +} + +#if defined(WEBRTC_WIN) +int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n, + CharacterTransformation transformation) { + wchar_t c1, c2; + while (true) { + if (n-- == 0) return 0; + c1 = transformation(*s1); + // Double check that characters are not UTF-8 + ASSERT(static_cast(*s2) < 128); + // Note: *s2 gets implicitly promoted to wchar_t + c2 = transformation(*s2); + if (c1 != c2) return (c1 < c2) ? -1 : 1; + if (!c1) return 0; + ++s1; + ++s2; + } +} + +size_t asccpyn(wchar_t* buffer, size_t buflen, + const char* source, size_t srclen) { + if (buflen <= 0) + return 0; + + if (srclen == SIZE_UNKNOWN) { + srclen = strlenn(source, buflen - 1); + } else if (srclen >= buflen) { + srclen = buflen - 1; + } +#if _DEBUG + // Double check that characters are not UTF-8 + for (size_t pos = 0; pos < srclen; ++pos) + ASSERT(static_cast(source[pos]) < 128); +#endif // _DEBUG + std::copy(source, source + srclen, buffer); + buffer[srclen] = 0; + return srclen; +} + +#endif // WEBRTC_WIN + +void replace_substrs(const char *search, + size_t search_len, + const char *replace, + size_t replace_len, + std::string *s) { + size_t pos = 0; + while ((pos = s->find(search, pos, search_len)) != std::string::npos) { + s->replace(pos, search_len, replace, replace_len); + pos += replace_len; + } +} + +bool starts_with(const char *s1, const char *s2) { + return strncmp(s1, s2, strlen(s2)) == 0; +} + +bool ends_with(const char *s1, const char *s2) { + size_t s1_length = strlen(s1); + size_t s2_length = strlen(s2); + + if (s2_length > s1_length) { + return false; + } + + const char* start = s1 + (s1_length - s2_length); + return strncmp(start, s2, s2_length) == 0; +} + +static const char kWhitespace[] = " \n\r\t"; + +std::string string_trim(const std::string& s) { + std::string::size_type first = s.find_first_not_of(kWhitespace); + std::string::size_type last = s.find_last_not_of(kWhitespace); + + if (first == std::string::npos || last == std::string::npos) { + return std::string(""); + } + + return s.substr(first, last - first + 1); +} + +} // namespace rtc diff --git a/webrtc/base/stringutils.h b/webrtc/base/stringutils.h new file mode 100644 index 000000000..25990e0af --- /dev/null +++ b/webrtc/base/stringutils.h @@ -0,0 +1,318 @@ +/* + * Copyright 2004 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_BASE_STRINGUTILS_H__ +#define WEBRTC_BASE_STRINGUTILS_H__ + +#include +#include +#include +#include + +#if defined(WEBRTC_WIN) +#include +#include +#define alloca _alloca +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) +#ifdef BSD +#include +#else // BSD +#include +#endif // !BSD +#endif // WEBRTC_POSIX + +#include + +#include "webrtc/base/basictypes.h" + +/////////////////////////////////////////////////////////////////////////////// +// Generic string/memory utilities +/////////////////////////////////////////////////////////////////////////////// + +#define STACK_ARRAY(TYPE, LEN) static_cast(::alloca((LEN)*sizeof(TYPE))) + +namespace rtc { + +// Complement to memset. Verifies memory consists of count bytes of value c. +bool memory_check(const void* memory, int c, size_t count); + +// Determines whether the simple wildcard pattern matches target. +// Alpha characters in pattern match case-insensitively. +// Asterisks in pattern match 0 or more characters. +// Ex: string_match("www.TEST.GOOGLE.COM", "www.*.com") -> true +bool string_match(const char* target, const char* pattern); + +} // namespace rtc + +/////////////////////////////////////////////////////////////////////////////// +// Rename a bunch of common string functions so they are consistent across +// platforms and between char and wchar_t variants. +// Here is the full list of functions that are unified: +// strlen, strcmp, stricmp, strncmp, strnicmp +// strchr, vsnprintf, strtoul, tolowercase +// tolowercase is like tolower, but not compatible with end-of-file value +// +// It's not clear if we will ever use wchar_t strings on unix. In theory, +// all strings should be Utf8 all the time, except when interfacing with Win32 +// APIs that require Utf16. +/////////////////////////////////////////////////////////////////////////////// + +inline char tolowercase(char c) { + return static_cast(tolower(c)); +} + +#if defined(WEBRTC_WIN) + +inline size_t strlen(const wchar_t* s) { + return wcslen(s); +} +inline int strcmp(const wchar_t* s1, const wchar_t* s2) { + return wcscmp(s1, s2); +} +inline int stricmp(const wchar_t* s1, const wchar_t* s2) { + return _wcsicmp(s1, s2); +} +inline int strncmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return wcsncmp(s1, s2, n); +} +inline int strnicmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return _wcsnicmp(s1, s2, n); +} +inline const wchar_t* strchr(const wchar_t* s, wchar_t c) { + return wcschr(s, c); +} +inline const wchar_t* strstr(const wchar_t* haystack, const wchar_t* needle) { + return wcsstr(haystack, needle); +} +#ifndef vsnprintf +inline int vsnprintf(wchar_t* buf, size_t n, const wchar_t* fmt, va_list args) { + return _vsnwprintf(buf, n, fmt, args); +} +#endif // !vsnprintf +inline unsigned long strtoul(const wchar_t* snum, wchar_t** end, int base) { + return wcstoul(snum, end, base); +} +inline wchar_t tolowercase(wchar_t c) { + return static_cast(towlower(c)); +} + +#endif // WEBRTC_WIN + +#if defined(WEBRTC_POSIX) + +inline int _stricmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline int _strnicmp(const char* s1, const char* s2, size_t n) { + return strncasecmp(s1, s2, n); +} + +#endif // WEBRTC_POSIX + +/////////////////////////////////////////////////////////////////////////////// +// Traits simplifies porting string functions to be CTYPE-agnostic +/////////////////////////////////////////////////////////////////////////////// + +namespace rtc { + +const size_t SIZE_UNKNOWN = static_cast(-1); + +template +struct Traits { + // STL string type + //typedef XXX string; + // Null-terminated string + //inline static const CTYPE* empty_str(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// String utilities which work with char or wchar_t +/////////////////////////////////////////////////////////////////////////////// + +template +inline const CTYPE* nonnull(const CTYPE* str, const CTYPE* def_str = NULL) { + return str ? str : (def_str ? def_str : Traits::empty_str()); +} + +template +const CTYPE* strchr(const CTYPE* str, const CTYPE* chs) { + for (size_t i=0; str[i]; ++i) { + for (size_t j=0; chs[j]; ++j) { + if (str[i] == chs[j]) { + return str + i; + } + } + } + return 0; +} + +template +const CTYPE* strchrn(const CTYPE* str, size_t slen, CTYPE ch) { + for (size_t i=0; i +size_t strlenn(const CTYPE* buffer, size_t buflen) { + size_t bufpos = 0; + while (buffer[bufpos] && (bufpos < buflen)) { + ++bufpos; + } + return bufpos; +} + +// Safe versions of strncpy, strncat, snprintf and vsnprintf that always +// null-terminate. + +template +size_t strcpyn(CTYPE* buffer, size_t buflen, + const CTYPE* source, size_t srclen = SIZE_UNKNOWN) { + if (buflen <= 0) + return 0; + + if (srclen == SIZE_UNKNOWN) { + srclen = strlenn(source, buflen - 1); + } else if (srclen >= buflen) { + srclen = buflen - 1; + } + memcpy(buffer, source, srclen * sizeof(CTYPE)); + buffer[srclen] = 0; + return srclen; +} + +template +size_t strcatn(CTYPE* buffer, size_t buflen, + const CTYPE* source, size_t srclen = SIZE_UNKNOWN) { + if (buflen <= 0) + return 0; + + size_t bufpos = strlenn(buffer, buflen - 1); + return bufpos + strcpyn(buffer + bufpos, buflen - bufpos, source, srclen); +} + +// Some compilers (clang specifically) require vsprintfn be defined before +// sprintfn. +template +size_t vsprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, + va_list args) { + int len = vsnprintf(buffer, buflen, format, args); + if ((len < 0) || (static_cast(len) >= buflen)) { + len = static_cast(buflen - 1); + buffer[len] = 0; + } + return len; +} + +template +size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...); +template +size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...) { + va_list args; + va_start(args, format); + size_t len = vsprintfn(buffer, buflen, format, args); + va_end(args); + return len; +} + +/////////////////////////////////////////////////////////////////////////////// +// Allow safe comparing and copying ascii (not UTF-8) with both wide and +// non-wide character strings. +/////////////////////////////////////////////////////////////////////////////// + +inline int asccmp(const char* s1, const char* s2) { + return strcmp(s1, s2); +} +inline int ascicmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +inline int ascncmp(const char* s1, const char* s2, size_t n) { + return strncmp(s1, s2, n); +} +inline int ascnicmp(const char* s1, const char* s2, size_t n) { + return _strnicmp(s1, s2, n); +} +inline size_t asccpyn(char* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN) { + return strcpyn(buffer, buflen, source, srclen); +} + +#if defined(WEBRTC_WIN) + +typedef wchar_t(*CharacterTransformation)(wchar_t); +inline wchar_t identity(wchar_t c) { return c; } +int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n, + CharacterTransformation transformation); + +inline int asccmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), identity); +} +inline int ascicmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), tolowercase); +} +inline int ascncmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, identity); +} +inline int ascnicmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, tolowercase); +} +size_t asccpyn(wchar_t* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN); + +#endif // WEBRTC_WIN + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations +/////////////////////////////////////////////////////////////////////////////// + +template<> +struct Traits { + typedef std::string string; + inline static const char* empty_str() { return ""; } +}; + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations (Windows only, currently) +/////////////////////////////////////////////////////////////////////////////// + +#if defined(WEBRTC_WIN) + +template<> +struct Traits { + typedef std::wstring string; + inline static const wchar_t* Traits::empty_str() { return L""; } +}; + +#endif // WEBRTC_WIN + +// Replaces all occurrences of "search" with "replace". +void replace_substrs(const char *search, + size_t search_len, + const char *replace, + size_t replace_len, + std::string *s); + +// True iff s1 starts with s2. +bool starts_with(const char *s1, const char *s2); + +// True iff s1 ends with s2. +bool ends_with(const char *s1, const char *s2); + +// Remove leading and trailing whitespaces. +std::string string_trim(const std::string& s); + +} // namespace rtc + +#endif // WEBRTC_BASE_STRINGUTILS_H__ diff --git a/webrtc/base/stringutils_unittest.cc b/webrtc/base/stringutils_unittest.cc new file mode 100644 index 000000000..b82290d0a --- /dev/null +++ b/webrtc/base/stringutils_unittest.cc @@ -0,0 +1,109 @@ +/* + * Copyright 2004 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 "webrtc/base/gunit.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/common.h" + +namespace rtc { + +// Tests for string_match(). + +TEST(string_matchTest, Matches) { + EXPECT_TRUE( string_match("A.B.C.D", "a.b.c.d")); + EXPECT_TRUE( string_match("www.TEST.GOOGLE.COM", "www.*.com")); + EXPECT_TRUE( string_match("127.0.0.1", "12*.0.*1")); + EXPECT_TRUE( string_match("127.1.0.21", "12*.0.*1")); + EXPECT_FALSE(string_match("127.0.0.0", "12*.0.*1")); + EXPECT_FALSE(string_match("127.0.0.0", "12*.0.*1")); + EXPECT_FALSE(string_match("127.1.1.21", "12*.0.*1")); +} + +// It's not clear if we will ever use wchar_t strings on unix. In theory, +// all strings should be Utf8 all the time, except when interfacing with Win32 +// APIs that require Utf16. + +#if defined(WEBRTC_WIN) + +// Tests for ascii_string_compare(). + +// Tests NULL input. +TEST(ascii_string_compareTest, NullInput) { + // The following results in an access violation in + // ascii_string_compare. Is this a bug or by design? stringutils.h + // should document the expected behavior in this case. + + // EXPECT_EQ(0, ascii_string_compare(NULL, NULL, 1, identity)); +} + +// Tests comparing two strings of different lengths. +TEST(ascii_string_compareTest, DifferentLengths) { + EXPECT_EQ(-1, ascii_string_compare(L"Test", "Test1", 5, identity)); +} + +// Tests the case where the buffer size is smaller than the string +// lengths. +TEST(ascii_string_compareTest, SmallBuffer) { + EXPECT_EQ(0, ascii_string_compare(L"Test", "Test1", 3, identity)); +} + +// Tests the case where the buffer is not full. +TEST(ascii_string_compareTest, LargeBuffer) { + EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 10, identity)); +} + +// Tests comparing two eqaul strings. +TEST(ascii_string_compareTest, Equal) { + EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 5, identity)); + EXPECT_EQ(0, ascii_string_compare(L"TeSt", "tEsT", 5, tolowercase)); +} + +// Tests comparing a smller string to a larger one. +TEST(ascii_string_compareTest, LessThan) { + EXPECT_EQ(-1, ascii_string_compare(L"abc", "abd", 4, identity)); + EXPECT_EQ(-1, ascii_string_compare(L"ABC", "abD", 5, tolowercase)); +} + +// Tests comparing a larger string to a smaller one. +TEST(ascii_string_compareTest, GreaterThan) { + EXPECT_EQ(1, ascii_string_compare(L"xyz", "xy", 5, identity)); + EXPECT_EQ(1, ascii_string_compare(L"abc", "ABB", 5, tolowercase)); +} +#endif // WEBRTC_WIN + +TEST(string_trim_Test, Trimming) { + EXPECT_EQ("temp", string_trim("\n\r\t temp \n\r\t")); + EXPECT_EQ("temp\n\r\t temp", string_trim(" temp\n\r\t temp ")); + EXPECT_EQ("temp temp", string_trim("temp temp")); + EXPECT_EQ("", string_trim(" \r\n\t")); + EXPECT_EQ("", string_trim("")); +} + +TEST(string_startsTest, StartsWith) { + EXPECT_TRUE(starts_with("foobar", "foo")); + EXPECT_TRUE(starts_with("foobar", "foobar")); + EXPECT_TRUE(starts_with("foobar", "")); + EXPECT_TRUE(starts_with("", "")); + EXPECT_FALSE(starts_with("foobar", "bar")); + EXPECT_FALSE(starts_with("foobar", "foobarbaz")); + EXPECT_FALSE(starts_with("", "f")); +} + +TEST(string_endsTest, EndsWith) { + EXPECT_TRUE(ends_with("foobar", "bar")); + EXPECT_TRUE(ends_with("foobar", "foobar")); + EXPECT_TRUE(ends_with("foobar", "")); + EXPECT_TRUE(ends_with("", "")); + EXPECT_FALSE(ends_with("foobar", "foo")); + EXPECT_FALSE(ends_with("foobar", "foobarbaz")); + EXPECT_FALSE(ends_with("", "f")); +} + +} // namespace rtc diff --git a/webrtc/base/systeminfo.cc b/webrtc/base/systeminfo.cc new file mode 100644 index 000000000..213c272b3 --- /dev/null +++ b/webrtc/base/systeminfo.cc @@ -0,0 +1,518 @@ +/* + * Copyright 2008 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 "webrtc/base/systeminfo.h" + +#if defined(WEBRTC_WIN) +#include +#ifndef EXCLUDE_D3D9 +#include +#endif +#include // for __cpuid() +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#elif defined(WEBRTC_LINUX) +#include +#endif +#if defined(WEBRTC_MAC) +#include +#endif + +#if defined(WEBRTC_WIN) +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/win32.h" +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "webrtc/base/macconversion.h" +#elif defined(WEBRTC_LINUX) +#include "webrtc/base/linux.h" +#endif +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +// See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx +#if defined(WEBRTC_WIN) +typedef BOOL (WINAPI *LPFN_GLPI)( + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, + PDWORD); + +static void GetProcessorInformation(int* physical_cpus, int* cache_size) { + // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond. + LPFN_GLPI glpi = reinterpret_cast(GetProcAddress( + GetModuleHandle(L"kernel32"), + "GetLogicalProcessorInformation")); + if (NULL == glpi) { + return; + } + // Determine buffer size, allocate and get processor information. + // Size can change between calls (unlikely), so a loop is done. + DWORD return_length = 0; + scoped_ptr infos; + while (!glpi(infos.get(), &return_length)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + infos.reset(new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[ + return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]); + } else { + return; + } + } + *physical_cpus = 0; + *cache_size = 0; + for (size_t i = 0; + i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) { + if (infos[i].Relationship == RelationProcessorCore) { + ++*physical_cpus; + } else if (infos[i].Relationship == RelationCache) { + int next_cache_size = static_cast(infos[i].Cache.Size); + if (next_cache_size >= *cache_size) { + *cache_size = next_cache_size; + } + } + } + return; +} +#else +// TODO(fbarchard): Use gcc 4.4 provided cpuid intrinsic +// 32 bit fpic requires ebx be preserved +#if (defined(__pic__) || defined(__APPLE__)) && defined(__i386__) +static inline void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile ( // NOLINT + "mov %%ebx, %%edi\n" + "cpuid\n" + "xchg %%edi, %%ebx\n" + : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type) + ); // NOLINT +} +#elif defined(__i386__) || defined(__x86_64__) +static inline void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile ( // NOLINT + "cpuid\n" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type) + ); // NOLINT +} +#endif +#endif // WEBRTC_WIN + +// Note(fbarchard): +// Family and model are extended family and extended model. 8 bits each. +SystemInfo::SystemInfo() + : physical_cpus_(1), logical_cpus_(1), cache_size_(0), + cpu_family_(0), cpu_model_(0), cpu_stepping_(0), + cpu_speed_(0), memory_(0) { + // Initialize the basic information. +#if defined(__arm__) || defined(_M_ARM) + cpu_arch_ = SI_ARCH_ARM; +#elif defined(__x86_64__) || defined(_M_X64) + cpu_arch_ = SI_ARCH_X64; +#elif defined(__i386__) || defined(_M_IX86) + cpu_arch_ = SI_ARCH_X86; +#else + cpu_arch_ = SI_ARCH_UNKNOWN; +#endif + +#if defined(WEBRTC_WIN) + SYSTEM_INFO si; + GetSystemInfo(&si); + logical_cpus_ = si.dwNumberOfProcessors; + GetProcessorInformation(&physical_cpus_, &cache_size_); + if (physical_cpus_ <= 0) { + physical_cpus_ = logical_cpus_; + } + cpu_family_ = si.wProcessorLevel; + cpu_model_ = si.wProcessorRevision >> 8; + cpu_stepping_ = si.wProcessorRevision & 0xFF; +#elif defined(WEBRTC_MAC) + uint32_t sysctl_value; + size_t length = sizeof(sysctl_value); + if (!sysctlbyname("hw.physicalcpu_max", &sysctl_value, &length, NULL, 0)) { + physical_cpus_ = static_cast(sysctl_value); + } + length = sizeof(sysctl_value); + if (!sysctlbyname("hw.logicalcpu_max", &sysctl_value, &length, NULL, 0)) { + logical_cpus_ = static_cast(sysctl_value); + } + uint64_t sysctl_value64; + length = sizeof(sysctl_value64); + if (!sysctlbyname("hw.l3cachesize", &sysctl_value64, &length, NULL, 0)) { + cache_size_ = static_cast(sysctl_value64); + } + if (!cache_size_) { + length = sizeof(sysctl_value64); + if (!sysctlbyname("hw.l2cachesize", &sysctl_value64, &length, NULL, 0)) { + cache_size_ = static_cast(sysctl_value64); + } + } + length = sizeof(sysctl_value); + if (!sysctlbyname("machdep.cpu.family", &sysctl_value, &length, NULL, 0)) { + cpu_family_ = static_cast(sysctl_value); + } + length = sizeof(sysctl_value); + if (!sysctlbyname("machdep.cpu.model", &sysctl_value, &length, NULL, 0)) { + cpu_model_ = static_cast(sysctl_value); + } + length = sizeof(sysctl_value); + if (!sysctlbyname("machdep.cpu.stepping", &sysctl_value, &length, NULL, 0)) { + cpu_stepping_ = static_cast(sysctl_value); + } +#elif defined(__native_client__) + // TODO(ryanpetrie): Implement this via PPAPI when it's available. +#else // WEBRTC_LINUX + ProcCpuInfo proc_info; + if (proc_info.LoadFromSystem()) { + proc_info.GetNumCpus(&logical_cpus_); + proc_info.GetNumPhysicalCpus(&physical_cpus_); + proc_info.GetCpuFamily(&cpu_family_); +#if defined(CPU_X86) + // These values only apply to x86 systems. + proc_info.GetSectionIntValue(0, "model", &cpu_model_); + proc_info.GetSectionIntValue(0, "stepping", &cpu_stepping_); + proc_info.GetSectionIntValue(0, "cpu MHz", &cpu_speed_); + proc_info.GetSectionIntValue(0, "cache size", &cache_size_); + cache_size_ *= 1024; +#endif + } + // ProcCpuInfo reads cpu speed from "cpu MHz" under /proc/cpuinfo. + // But that number is a moving target which can change on-the-fly according to + // many factors including system workload. + // See /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors. + // The one in /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq is more + // accurate. We use it as our cpu speed when it is available. + // cpuinfo_max_freq is measured in KHz and requires conversion to MHz. + int max_freq = rtc::ReadCpuMaxFreq(); + if (max_freq > 0) { + cpu_speed_ = max_freq / 1000; + } +#endif +// For L2 CacheSize see also +// http://www.flounder.com/cpuid_explorer2.htm#CPUID(0x800000006) +#ifdef CPU_X86 + if (cache_size_ == 0) { + int cpu_info[4]; + __cpuid(cpu_info, 0x80000000); // query maximum extended cpuid function. + if (static_cast(cpu_info[0]) >= 0x80000006) { + __cpuid(cpu_info, 0x80000006); + cache_size_ = (cpu_info[2] >> 16) * 1024; + } + } +#endif +} + +// Return the number of cpu threads available to the system. +int SystemInfo::GetMaxCpus() { + return logical_cpus_; +} + +// Return the number of cpu cores available to the system. +int SystemInfo::GetMaxPhysicalCpus() { + return physical_cpus_; +} + +// Return the number of cpus available to the process. Since affinity can be +// changed on the fly, do not cache this value. +// Can be affected by heat. +int SystemInfo::GetCurCpus() { + int cur_cpus; +#if defined(WEBRTC_WIN) + DWORD_PTR process_mask, system_mask; + ::GetProcessAffinityMask(::GetCurrentProcess(), &process_mask, &system_mask); + for (cur_cpus = 0; process_mask; ++cur_cpus) { + // Sparse-ones algorithm. There are slightly faster methods out there but + // they are unintuitive and won't make a difference on a single dword. + process_mask &= (process_mask - 1); + } +#elif defined(WEBRTC_MAC) + uint32_t sysctl_value; + size_t length = sizeof(sysctl_value); + int error = sysctlbyname("hw.ncpu", &sysctl_value, &length, NULL, 0); + cur_cpus = !error ? static_cast(sysctl_value) : 1; +#else + // Linux, Solaris, WEBRTC_ANDROID + cur_cpus = static_cast(sysconf(_SC_NPROCESSORS_ONLN)); +#endif + return cur_cpus; +} + +// Return the type of this CPU. +SystemInfo::Architecture SystemInfo::GetCpuArchitecture() { + return cpu_arch_; +} + +// Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD". +// See "Intel Processor Identification and the CPUID Instruction" +// (Intel document number: 241618) +std::string SystemInfo::GetCpuVendor() { + if (cpu_vendor_.empty()) { +#if defined(CPU_X86) + int cpu_info[4]; + __cpuid(cpu_info, 0); + cpu_info[0] = cpu_info[1]; // Reorder output + cpu_info[1] = cpu_info[3]; + cpu_info[2] = cpu_info[2]; + cpu_info[3] = 0; + cpu_vendor_ = std::string(reinterpret_cast(&cpu_info[0])); +#elif defined(CPU_ARM) + cpu_vendor_ = std::string("ARM"); +#else + cpu_vendor_ = std::string("Undefined"); +#endif + } + return cpu_vendor_; +} + +int SystemInfo::GetCpuCacheSize() { + return cache_size_; +} + +// Return the "family" of this CPU. +int SystemInfo::GetCpuFamily() { + return cpu_family_; +} + +// Return the "model" of this CPU. +int SystemInfo::GetCpuModel() { + return cpu_model_; +} + +// Return the "stepping" of this CPU. +int SystemInfo::GetCpuStepping() { + return cpu_stepping_; +} + +// Return the clockrate of the primary processor in Mhz. This value can be +// cached. Returns -1 on error. +int SystemInfo::GetMaxCpuSpeed() { + if (cpu_speed_) { + return cpu_speed_; + } +#if defined(WEBRTC_WIN) + HKEY key; + static const WCHAR keyName[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key) + == ERROR_SUCCESS) { + DWORD data, len; + len = sizeof(data); + + if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast(&data), + &len) == ERROR_SUCCESS) { + cpu_speed_ = data; + } else { + LOG(LS_WARNING) << "Failed to query registry value HKLM\\" << keyName + << "\\~Mhz"; + cpu_speed_ = -1; + } + + RegCloseKey(key); + } else { + LOG(LS_WARNING) << "Failed to open registry key HKLM\\" << keyName; + cpu_speed_ = -1; + } +#elif defined(WEBRTC_MAC) + uint64_t sysctl_value; + size_t length = sizeof(sysctl_value); + int error = sysctlbyname("hw.cpufrequency_max", &sysctl_value, &length, + NULL, 0); + cpu_speed_ = !error ? static_cast(sysctl_value/1000000) : -1; +#else + // TODO(fbarchard): Implement using proc/cpuinfo + cpu_speed_ = 0; +#endif + return cpu_speed_; +} + +// Dynamically check the current clockrate, which could be reduced because of +// powersaving profiles. Eventually for windows we want to query WMI for +// root\WMI::ProcessorPerformance.InstanceName="Processor_Number_0".frequency +int SystemInfo::GetCurCpuSpeed() { +#if defined(WEBRTC_WIN) + // TODO(fbarchard): Add WMI check, requires COM initialization + // NOTE(fbarchard): Testable on Sandy Bridge. + return GetMaxCpuSpeed(); +#elif defined(WEBRTC_MAC) + uint64_t sysctl_value; + size_t length = sizeof(sysctl_value); + int error = sysctlbyname("hw.cpufrequency", &sysctl_value, &length, NULL, 0); + return !error ? static_cast(sysctl_value/1000000) : GetMaxCpuSpeed(); +#else // WEBRTC_LINUX + // TODO(fbarchard): Use proc/cpuinfo for Cur speed on Linux. + return GetMaxCpuSpeed(); +#endif +} + +// Returns the amount of installed physical memory in Bytes. Cacheable. +// Returns -1 on error. +int64 SystemInfo::GetMemorySize() { + if (memory_) { + return memory_; + } + +#if defined(WEBRTC_WIN) + MEMORYSTATUSEX status = {0}; + status.dwLength = sizeof(status); + + if (GlobalMemoryStatusEx(&status)) { + memory_ = status.ullTotalPhys; + } else { + LOG_GLE(LS_WARNING) << "GlobalMemoryStatusEx failed."; + memory_ = -1; + } + +#elif defined(WEBRTC_MAC) + size_t len = sizeof(memory_); + int error = sysctlbyname("hw.memsize", &memory_, &len, NULL, 0); + if (error || memory_ == 0) { + memory_ = -1; + } +#else // WEBRTC_LINUX + memory_ = static_cast(sysconf(_SC_PHYS_PAGES)) * + static_cast(sysconf(_SC_PAGESIZE)); + if (memory_ < 0) { + LOG(LS_WARNING) << "sysconf(_SC_PHYS_PAGES) failed." + << "sysconf(_SC_PHYS_PAGES) " << sysconf(_SC_PHYS_PAGES) + << "sysconf(_SC_PAGESIZE) " << sysconf(_SC_PAGESIZE); + memory_ = -1; + } +#endif + + return memory_; +} + + +// Return the name of the machine model we are currently running on. +// This is a human readable string that consists of the name and version +// number of the hardware, i.e 'MacBookAir1,1'. Returns an empty string if +// model can not be determined. The string is cached for subsequent calls. +std::string SystemInfo::GetMachineModel() { + if (!machine_model_.empty()) { + return machine_model_; + } + +#if defined(WEBRTC_MAC) + char buffer[128]; + size_t length = sizeof(buffer); + int error = sysctlbyname("hw.model", buffer, &length, NULL, 0); + if (!error) { + machine_model_.assign(buffer, length - 1); + } else { + machine_model_.clear(); + } +#else + machine_model_ = "Not available"; +#endif + + return machine_model_; +} + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +// Helper functions to query IOKit for video hardware properties. +static CFTypeRef SearchForProperty(io_service_t port, CFStringRef name) { + return IORegistryEntrySearchCFProperty(port, kIOServicePlane, + name, kCFAllocatorDefault, + kIORegistryIterateRecursively | kIORegistryIterateParents); +} + +static void GetProperty(io_service_t port, CFStringRef name, int* value) { + if (!value) return; + CFTypeRef ref = SearchForProperty(port, name); + if (ref) { + CFTypeID refType = CFGetTypeID(ref); + if (CFNumberGetTypeID() == refType) { + CFNumberRef number = reinterpret_cast(ref); + p_convertCFNumberToInt(number, value); + } else if (CFDataGetTypeID() == refType) { + CFDataRef data = reinterpret_cast(ref); + if (CFDataGetLength(data) == sizeof(UInt32)) { + *value = *reinterpret_cast(CFDataGetBytePtr(data)); + } + } + CFRelease(ref); + } +} + +static void GetProperty(io_service_t port, CFStringRef name, + std::string* value) { + if (!value) return; + CFTypeRef ref = SearchForProperty(port, name); + if (ref) { + CFTypeID refType = CFGetTypeID(ref); + if (CFStringGetTypeID() == refType) { + CFStringRef stringRef = reinterpret_cast(ref); + p_convertHostCFStringRefToCPPString(stringRef, *value); + } else if (CFDataGetTypeID() == refType) { + CFDataRef dataRef = reinterpret_cast(ref); + *value = std::string(reinterpret_cast( + CFDataGetBytePtr(dataRef)), CFDataGetLength(dataRef)); + } + CFRelease(ref); + } +} +#endif + +// Fills a struct with information on the graphics adapater and returns true +// iff successful. +bool SystemInfo::GetGpuInfo(GpuInfo *info) { + if (!info) return false; +#if defined(WEBRTC_WIN) && !defined(EXCLUDE_D3D9) + D3DADAPTER_IDENTIFIER9 identifier; + HRESULT hr = E_FAIL; + HINSTANCE d3d_lib = LoadLibrary(L"d3d9.dll"); + + if (d3d_lib) { + typedef IDirect3D9* (WINAPI *D3DCreate9Proc)(UINT); + D3DCreate9Proc d3d_create_proc = reinterpret_cast( + GetProcAddress(d3d_lib, "Direct3DCreate9")); + if (d3d_create_proc) { + IDirect3D9* d3d = d3d_create_proc(D3D_SDK_VERSION); + if (d3d) { + hr = d3d->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &identifier); + d3d->Release(); + } + } + FreeLibrary(d3d_lib); + } + + if (hr != D3D_OK) { + LOG(LS_ERROR) << "Failed to access Direct3D9 information."; + return false; + } + + info->device_name = identifier.DeviceName; + info->description = identifier.Description; + info->vendor_id = identifier.VendorId; + info->device_id = identifier.DeviceId; + info->driver = identifier.Driver; + // driver_version format: product.version.subversion.build + std::stringstream ss; + ss << HIWORD(identifier.DriverVersion.HighPart) << "." + << LOWORD(identifier.DriverVersion.HighPart) << "." + << HIWORD(identifier.DriverVersion.LowPart) << "." + << LOWORD(identifier.DriverVersion.LowPart); + info->driver_version = ss.str(); + return true; +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + // We'll query the IOKit for the gpu of the main display. + io_service_t display_service_port = CGDisplayIOServicePort( + kCGDirectMainDisplay); + GetProperty(display_service_port, CFSTR("vendor-id"), &info->vendor_id); + GetProperty(display_service_port, CFSTR("device-id"), &info->device_id); + GetProperty(display_service_port, CFSTR("model"), &info->description); + return true; +#else // WEBRTC_LINUX + // TODO(fbarchard): Implement this on Linux + return false; +#endif +} +} // namespace rtc diff --git a/webrtc/base/systeminfo.h b/webrtc/base/systeminfo.h new file mode 100644 index 000000000..44088629b --- /dev/null +++ b/webrtc/base/systeminfo.h @@ -0,0 +1,81 @@ +/* + * Copyright 2008 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_BASE_SYSTEMINFO_H__ +#define WEBRTC_BASE_SYSTEMINFO_H__ + +#include + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +class SystemInfo { + public: + enum Architecture { + SI_ARCH_UNKNOWN = -1, + SI_ARCH_X86 = 0, + SI_ARCH_X64 = 1, + SI_ARCH_ARM = 2 + }; + + SystemInfo(); + + // The number of CPU Cores in the system. + int GetMaxPhysicalCpus(); + // The number of CPU Threads in the system. + int GetMaxCpus(); + // The number of CPU Threads currently available to this process. + int GetCurCpus(); + // Identity of the CPUs. + Architecture GetCpuArchitecture(); + std::string GetCpuVendor(); + int GetCpuFamily(); + int GetCpuModel(); + int GetCpuStepping(); + // Return size of CPU cache in bytes. Uses largest available cache (L3). + int GetCpuCacheSize(); + // Estimated speed of the CPUs, in MHz. e.g. 2400 for 2.4 GHz + int GetMaxCpuSpeed(); + int GetCurCpuSpeed(); + // Total amount of physical memory, in bytes. + int64 GetMemorySize(); + // The model name of the machine, e.g. "MacBookAir1,1" + std::string GetMachineModel(); + + // The gpu identifier + struct GpuInfo { + GpuInfo() : vendor_id(0), device_id(0) {} + std::string device_name; + std::string description; + int vendor_id; + int device_id; + std::string driver; + std::string driver_version; + }; + bool GetGpuInfo(GpuInfo *info); + + private: + int physical_cpus_; + int logical_cpus_; + int cache_size_; + Architecture cpu_arch_; + std::string cpu_vendor_; + int cpu_family_; + int cpu_model_; + int cpu_stepping_; + int cpu_speed_; + int64 memory_; + std::string machine_model_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_SYSTEMINFO_H__ diff --git a/webrtc/base/systeminfo_unittest.cc b/webrtc/base/systeminfo_unittest.cc new file mode 100644 index 000000000..fec553582 --- /dev/null +++ b/webrtc/base/systeminfo_unittest.cc @@ -0,0 +1,194 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/systeminfo.h" + +#if defined(CPU_X86) || defined(CPU_ARM) +TEST(SystemInfoTest, CpuVendorNonEmpty) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuVendor: " << info.GetCpuVendor(); + EXPECT_FALSE(info.GetCpuVendor().empty()); +} + +// Tests Vendor identification is Intel or AMD. +// See Also http://en.wikipedia.org/wiki/CPUID +TEST(SystemInfoTest, CpuVendorIntelAMDARM) { + rtc::SystemInfo info; +#if defined(CPU_X86) + EXPECT_TRUE(rtc::string_match(info.GetCpuVendor().c_str(), + "GenuineIntel") || + rtc::string_match(info.GetCpuVendor().c_str(), + "AuthenticAMD")); +#elif defined(CPU_ARM) + EXPECT_TRUE(rtc::string_match(info.GetCpuVendor().c_str(), "ARM")); +#endif +} +#endif // defined(CPU_X86) || defined(CPU_ARM) + +// Tests CpuArchitecture matches expectations. +TEST(SystemInfoTest, GetCpuArchitecture) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuArchitecture: " << info.GetCpuArchitecture(); + rtc::SystemInfo::Architecture architecture = info.GetCpuArchitecture(); +#if defined(CPU_X86) || defined(CPU_ARM) + if (sizeof(intptr_t) == 8) { + EXPECT_EQ(rtc::SystemInfo::SI_ARCH_X64, architecture); + } else if (sizeof(intptr_t) == 4) { +#if defined(CPU_ARM) + EXPECT_EQ(rtc::SystemInfo::SI_ARCH_ARM, architecture); +#else + EXPECT_EQ(rtc::SystemInfo::SI_ARCH_X86, architecture); +#endif + } +#endif +} + +// Tests Cpu Cache Size +TEST(SystemInfoTest, CpuCacheSize) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuCacheSize: " << info.GetCpuCacheSize(); + EXPECT_GE(info.GetCpuCacheSize(), 8192); // 8 KB min cache + EXPECT_LE(info.GetCpuCacheSize(), 1024 * 1024 * 1024); // 1 GB max cache +} + +// Tests MachineModel is set. On Mac test machine model is known. +TEST(SystemInfoTest, MachineModelKnown) { + rtc::SystemInfo info; + EXPECT_FALSE(info.GetMachineModel().empty()); + const char *machine_model = info.GetMachineModel().c_str(); + LOG(LS_INFO) << "MachineModel: " << machine_model; + bool known = true; +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + // Full list as of May 2012. Update when new OSX based models are added. + known = rtc::string_match(machine_model, "MacBookPro*") || + rtc::string_match(machine_model, "MacBookAir*") || + rtc::string_match(machine_model, "MacBook*") || + rtc::string_match(machine_model, "MacPro*") || + rtc::string_match(machine_model, "Macmini*") || + rtc::string_match(machine_model, "iMac*") || + rtc::string_match(machine_model, "Xserve*"); +#elif !defined(WEBRTC_IOS) + // All other machines return Not available. + known = rtc::string_match(info.GetMachineModel().c_str(), + "Not available"); +#endif + if (!known) { + LOG(LS_WARNING) << "Machine Model Unknown: " << machine_model; + } +} + +// Tests maximum cpu clockrate. +TEST(SystemInfoTest, CpuMaxCpuSpeed) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxCpuSpeed: " << info.GetMaxCpuSpeed(); + EXPECT_GT(info.GetMaxCpuSpeed(), 0); + EXPECT_LT(info.GetMaxCpuSpeed(), 100000); // 100 Ghz +} + +// Tests current cpu clockrate. +TEST(SystemInfoTest, CpuCurCpuSpeed) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxCurSpeed: " << info.GetCurCpuSpeed(); + EXPECT_GT(info.GetCurCpuSpeed(), 0); + EXPECT_LT(info.GetMaxCpuSpeed(), 100000); +} + +// Tests physical memory size. +TEST(SystemInfoTest, MemorySize) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MemorySize: " << info.GetMemorySize(); + EXPECT_GT(info.GetMemorySize(), -1); +} + +// Tests number of logical cpus available to the system. +TEST(SystemInfoTest, MaxCpus) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxCpus: " << info.GetMaxCpus(); + EXPECT_GT(info.GetMaxCpus(), 0); +} + +// Tests number of physical cpus available to the system. +TEST(SystemInfoTest, MaxPhysicalCpus) { + rtc::SystemInfo info; + LOG(LS_INFO) << "MaxPhysicalCpus: " << info.GetMaxPhysicalCpus(); + EXPECT_GT(info.GetMaxPhysicalCpus(), 0); + EXPECT_LE(info.GetMaxPhysicalCpus(), info.GetMaxCpus()); +} + +// Tests number of logical cpus available to the process. +TEST(SystemInfoTest, CurCpus) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CurCpus: " << info.GetCurCpus(); + EXPECT_GT(info.GetCurCpus(), 0); + EXPECT_LE(info.GetCurCpus(), info.GetMaxCpus()); +} + +#ifdef CPU_X86 +// CPU family/model/stepping is only available on X86. The following tests +// that they are set when running on x86 CPUs. Valid Family/Model/Stepping +// values are non-zero on known CPUs. + +// Tests Intel CPU Family identification. +TEST(SystemInfoTest, CpuFamily) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily(); + EXPECT_GT(info.GetCpuFamily(), 0); +} + +// Tests Intel CPU Model identification. +TEST(SystemInfoTest, CpuModel) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel(); + EXPECT_GT(info.GetCpuModel(), 0); +} + +// Tests Intel CPU Stepping identification. +TEST(SystemInfoTest, CpuStepping) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping(); + EXPECT_GT(info.GetCpuStepping(), 0); +} +#else // CPU_X86 +// If not running on x86 CPU the following tests expect the functions to +// return 0. +TEST(SystemInfoTest, CpuFamily) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily(); + EXPECT_EQ(0, info.GetCpuFamily()); +} + +// Tests Intel CPU Model identification. +TEST(SystemInfoTest, CpuModel) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel(); + EXPECT_EQ(0, info.GetCpuModel()); +} + +// Tests Intel CPU Stepping identification. +TEST(SystemInfoTest, CpuStepping) { + rtc::SystemInfo info; + LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping(); + EXPECT_EQ(0, info.GetCpuStepping()); +} +#endif // CPU_X86 + +#if WEBRTC_WIN && !defined(EXCLUDE_D3D9) +TEST(SystemInfoTest, GpuInfo) { + rtc::SystemInfo info; + rtc::SystemInfo::GpuInfo gi; + EXPECT_TRUE(info.GetGpuInfo(&gi)); + LOG(LS_INFO) << "GpuDriver: " << gi.driver; + EXPECT_FALSE(gi.driver.empty()); + LOG(LS_INFO) << "GpuDriverVersion: " << gi.driver_version; + EXPECT_FALSE(gi.driver_version.empty()); +} +#endif diff --git a/webrtc/base/task.cc b/webrtc/base/task.cc new file mode 100644 index 000000000..ed9f42626 --- /dev/null +++ b/webrtc/base/task.cc @@ -0,0 +1,272 @@ +/* + * Copyright 2004 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 "webrtc/base/task.h" +#include "webrtc/base/common.h" +#include "webrtc/base/taskrunner.h" + +namespace rtc { + +int32 Task::unique_id_seed_ = 0; + +Task::Task(TaskParent *parent) + : TaskParent(this, parent), + state_(STATE_INIT), + blocked_(false), + done_(false), + aborted_(false), + busy_(false), + error_(false), + start_time_(0), + timeout_time_(0), + timeout_seconds_(0), + timeout_suspended_(false) { + unique_id_ = unique_id_seed_++; + + // sanity check that we didn't roll-over our id seed + ASSERT(unique_id_ < unique_id_seed_); +} + +Task::~Task() { + // Is this task being deleted in the correct manner? + ASSERT(!done_ || GetRunner()->is_ok_to_delete(this)); + ASSERT(state_ == STATE_INIT || done_); + ASSERT(state_ == STATE_INIT || blocked_); + + // If the task is being deleted without being done, it + // means that it hasn't been removed from its parent. + // This happens if a task is deleted outside of TaskRunner. + if (!done_) { + Stop(); + } +} + +int64 Task::CurrentTime() { + return GetRunner()->CurrentTime(); +} + +int64 Task::ElapsedTime() { + return CurrentTime() - start_time_; +} + +void Task::Start() { + if (state_ != STATE_INIT) + return; + // Set the start time before starting the task. Otherwise if the task + // finishes quickly and deletes the Task object, setting start_time_ + // will crash. + start_time_ = CurrentTime(); + GetRunner()->StartTask(this); +} + +void Task::Step() { + if (done_) { +#ifdef _DEBUG + // we do not know how !blocked_ happens when done_ - should be impossible. + // But it causes problems, so in retail build, we force blocked_, and + // under debug we assert. + ASSERT(blocked_); +#else + blocked_ = true; +#endif + return; + } + + // Async Error() was called + if (error_) { + done_ = true; + state_ = STATE_ERROR; + blocked_ = true; +// obsolete - an errored task is not considered done now +// SignalDone(); + + Stop(); +#ifdef _DEBUG + // verify that stop removed this from its parent + ASSERT(!parent()->IsChildTask(this)); +#endif + return; + } + + busy_ = true; + int new_state = Process(state_); + busy_ = false; + + if (aborted_) { + Abort(true); // no need to wake because we're awake + return; + } + + if (new_state == STATE_BLOCKED) { + blocked_ = true; + // Let the timeout continue + } else { + state_ = new_state; + blocked_ = false; + ResetTimeout(); + } + + if (new_state == STATE_DONE) { + done_ = true; + } else if (new_state == STATE_ERROR) { + done_ = true; + error_ = true; + } + + if (done_) { +// obsolete - call this yourself +// SignalDone(); + + Stop(); +#if _DEBUG + // verify that stop removed this from its parent + ASSERT(!parent()->IsChildTask(this)); +#endif + blocked_ = true; + } +} + +void Task::Abort(bool nowake) { + // Why only check for done_ (instead of "aborted_ || done_")? + // + // If aborted_ && !done_, it means the logic for aborting still + // needs to be executed (because busy_ must have been true when + // Abort() was previously called). + if (done_) + return; + aborted_ = true; + if (!busy_) { + done_ = true; + blocked_ = true; + error_ = true; + + // "done_" is set before calling "Stop()" to ensure that this code + // doesn't execute more than once (recursively) for the same task. + Stop(); +#ifdef _DEBUG + // verify that stop removed this from its parent + ASSERT(!parent()->IsChildTask(this)); +#endif + if (!nowake) { + // WakeTasks to self-delete. + // Don't call Wake() because it is a no-op after "done_" is set. + // Even if Wake() did run, it clears "blocked_" which isn't desireable. + GetRunner()->WakeTasks(); + } + } +} + +void Task::Wake() { + if (done_) + return; + if (blocked_) { + blocked_ = false; + GetRunner()->WakeTasks(); + } +} + +void Task::Error() { + if (error_ || done_) + return; + error_ = true; + Wake(); +} + +std::string Task::GetStateName(int state) const { + switch (state) { + case STATE_BLOCKED: return "BLOCKED"; + case STATE_INIT: return "INIT"; + case STATE_START: return "START"; + case STATE_DONE: return "DONE"; + case STATE_ERROR: return "ERROR"; + case STATE_RESPONSE: return "RESPONSE"; + } + return "??"; +} + +int Task::Process(int state) { + int newstate = STATE_ERROR; + + if (TimedOut()) { + ClearTimeout(); + newstate = OnTimeout(); + SignalTimeout(); + } else { + switch (state) { + case STATE_INIT: + newstate = STATE_START; + break; + case STATE_START: + newstate = ProcessStart(); + break; + case STATE_RESPONSE: + newstate = ProcessResponse(); + break; + case STATE_DONE: + case STATE_ERROR: + newstate = STATE_BLOCKED; + break; + } + } + + return newstate; +} + +void Task::Stop() { + // No need to wake because we're either awake or in abort + TaskParent::OnStopped(this); +} + +void Task::set_timeout_seconds(const int timeout_seconds) { + timeout_seconds_ = timeout_seconds; + ResetTimeout(); +} + +bool Task::TimedOut() { + return timeout_seconds_ && + timeout_time_ && + CurrentTime() >= timeout_time_; +} + +void Task::ResetTimeout() { + int64 previous_timeout_time = timeout_time_; + bool timeout_allowed = (state_ != STATE_INIT) + && (state_ != STATE_DONE) + && (state_ != STATE_ERROR); + if (timeout_seconds_ && timeout_allowed && !timeout_suspended_) + timeout_time_ = CurrentTime() + + (timeout_seconds_ * kSecToMsec * kMsecTo100ns); + else + timeout_time_ = 0; + + GetRunner()->UpdateTaskTimeout(this, previous_timeout_time); +} + +void Task::ClearTimeout() { + int64 previous_timeout_time = timeout_time_; + timeout_time_ = 0; + GetRunner()->UpdateTaskTimeout(this, previous_timeout_time); +} + +void Task::SuspendTimeout() { + if (!timeout_suspended_) { + timeout_suspended_ = true; + ResetTimeout(); + } +} + +void Task::ResumeTimeout() { + if (timeout_suspended_) { + timeout_suspended_ = false; + ResetTimeout(); + } +} + +} // namespace rtc diff --git a/webrtc/base/task.h b/webrtc/base/task.h new file mode 100644 index 000000000..77d767a78 --- /dev/null +++ b/webrtc/base/task.h @@ -0,0 +1,177 @@ +/* + * Copyright 2004 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_BASE_TASK_H__ +#define WEBRTC_BASE_TASK_H__ + +#include +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/taskparent.h" + +///////////////////////////////////////////////////////////////////// +// +// TASK +// +///////////////////////////////////////////////////////////////////// +// +// Task is a state machine infrastructure. States are pushed forward by +// pushing forwards a TaskRunner that holds on to all Tasks. The purpose +// of Task is threefold: +// +// (1) It manages ongoing work on the UI thread. Multitasking without +// threads, keeping it easy, keeping it real. :-) It does this by +// organizing a set of states for each task. When you return from your +// Process*() function, you return an integer for the next state. You do +// not go onto the next state yourself. Every time you enter a state, +// you check to see if you can do anything yet. If not, you return +// STATE_BLOCKED. If you _could_ do anything, do not return +// STATE_BLOCKED - even if you end up in the same state, return +// STATE_mysamestate. When you are done, return STATE_DONE and then the +// task will self-delete sometime afterwards. +// +// (2) It helps you avoid all those reentrancy problems when you chain +// too many triggers on one thread. Basically if you want to tell a task +// to process something for you, you feed your task some information and +// then you Wake() it. Don't tell it to process it right away. If it +// might be working on something as you send it information, you may want +// to have a queue in the task. +// +// (3) Finally it helps manage parent tasks and children. If a parent +// task gets aborted, all the children tasks are too. The nice thing +// about this, for example, is if you have one parent task that +// represents, say, and Xmpp connection, then you can spawn a whole bunch +// of infinite lifetime child tasks and now worry about cleaning them up. +// When the parent task goes to STATE_DONE, the task engine will make +// sure all those children are aborted and get deleted. +// +// Notice that Task has a few built-in states, e.g., +// +// STATE_INIT - the task isn't running yet +// STATE_START - the task is in its first state +// STATE_RESPONSE - the task is in its second state +// STATE_DONE - the task is done +// +// STATE_ERROR - indicates an error - we should audit the error code in +// light of any usage of it to see if it should be improved. When I +// first put down the task stuff I didn't have a good sense of what was +// needed for Abort and Error, and now the subclasses of Task will ground +// the design in a stronger way. +// +// STATE_NEXT - the first undefined state number. (like WM_USER) - you +// can start defining more task states there. +// +// When you define more task states, just override Process(int state) and +// add your own switch statement. If you want to delegate to +// Task::Process, you can effectively delegate to its switch statement. +// No fancy method pointers or such - this is all just pretty low tech, +// easy to debug, and fast. +// +// Also notice that Task has some primitive built-in timeout functionality. +// +// A timeout is defined as "the task stays in STATE_BLOCKED longer than +// timeout_seconds_." +// +// Descendant classes can override this behavior by calling the +// various protected methods to change the timeout behavior. For +// instance, a descendand might call SuspendTimeout() when it knows +// that it isn't waiting for anything that might timeout, but isn't +// yet in the STATE_DONE state. +// + +namespace rtc { + +// Executes a sequence of steps +class Task : public TaskParent { + public: + Task(TaskParent *parent); + virtual ~Task(); + + int32 unique_id() { return unique_id_; } + + void Start(); + void Step(); + int GetState() const { return state_; } + bool HasError() const { return (GetState() == STATE_ERROR); } + bool Blocked() const { return blocked_; } + bool IsDone() const { return done_; } + int64 ElapsedTime(); + + // Called from outside to stop task without any more callbacks + void Abort(bool nowake = false); + + bool TimedOut(); + + int64 timeout_time() const { return timeout_time_; } + int timeout_seconds() const { return timeout_seconds_; } + void set_timeout_seconds(int timeout_seconds); + + sigslot::signal0<> SignalTimeout; + + // Called inside the task to signal that the task may be unblocked + void Wake(); + + protected: + + enum { + STATE_BLOCKED = -1, + STATE_INIT = 0, + STATE_START = 1, + STATE_DONE = 2, + STATE_ERROR = 3, + STATE_RESPONSE = 4, + STATE_NEXT = 5, // Subclasses which need more states start here and higher + }; + + // Called inside to advise that the task should wake and signal an error + void Error(); + + int64 CurrentTime(); + + virtual std::string GetStateName(int state) const; + virtual int Process(int state); + virtual void Stop(); + virtual int ProcessStart() = 0; + virtual int ProcessResponse() { return STATE_DONE; } + + void ResetTimeout(); + void ClearTimeout(); + + void SuspendTimeout(); + void ResumeTimeout(); + + protected: + virtual int OnTimeout() { + // by default, we are finished after timing out + return STATE_DONE; + } + + private: + void Done(); + + int state_; + bool blocked_; + bool done_; + bool aborted_; + bool busy_; + bool error_; + int64 start_time_; + int64 timeout_time_; + int timeout_seconds_; + bool timeout_suspended_; + int32 unique_id_; + + static int32 unique_id_seed_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TASK_H__ diff --git a/webrtc/base/task_unittest.cc b/webrtc/base/task_unittest.cc new file mode 100644 index 000000000..8831259c6 --- /dev/null +++ b/webrtc/base/task_unittest.cc @@ -0,0 +1,545 @@ +/* + * Copyright 2004 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. + */ + +#if defined(WEBRTC_POSIX) +#include +#endif // WEBRTC_POSIX + +// TODO: Remove this once the cause of sporadic failures in these +// tests is tracked down. +#include + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif // WEBRTC_WIN + +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/task.h" +#include "webrtc/base/taskrunner.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +static int64 GetCurrentTime() { + return static_cast(Time()) * 10000; +} + +// feel free to change these numbers. Note that '0' won't work, though +#define STUCK_TASK_COUNT 5 +#define HAPPY_TASK_COUNT 20 + +// this is a generic timeout task which, when it signals timeout, will +// include the unique ID of the task in the signal (we don't use this +// in production code because we haven't yet had occasion to generate +// an array of the same types of task) + +class IdTimeoutTask : public Task, public sigslot::has_slots<> { + public: + explicit IdTimeoutTask(TaskParent *parent) : Task(parent) { + SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout); + } + + sigslot::signal1 SignalTimeoutId; + sigslot::signal1 SignalDoneId; + + virtual int ProcessStart() { + return STATE_RESPONSE; + } + + void OnLocalTimeout() { + SignalTimeoutId(unique_id()); + } + + protected: + virtual void Stop() { + SignalDoneId(unique_id()); + Task::Stop(); + } +}; + +class StuckTask : public IdTimeoutTask { + public: + explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {} + virtual int ProcessStart() { + return STATE_BLOCKED; + } +}; + +class HappyTask : public IdTimeoutTask { + public: + explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) { + time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2); + } + virtual int ProcessStart() { + if (ElapsedTime() > (time_to_perform_ * 1000 * 10000)) + return STATE_RESPONSE; + else + return STATE_BLOCKED; + } + + private: + int time_to_perform_; +}; + +// simple implementation of a task runner which uses Windows' +// GetSystemTimeAsFileTime() to get the current clock ticks + +class MyTaskRunner : public TaskRunner { + public: + virtual void WakeTasks() { RunTasks(); } + virtual int64 CurrentTime() { + return GetCurrentTime(); + } + + bool timeout_change() const { + return timeout_change_; + } + + void clear_timeout_change() { + timeout_change_ = false; + } + protected: + virtual void OnTimeoutChange() { + timeout_change_ = true; + } + bool timeout_change_; +}; + +// +// this unit test is primarily concerned (for now) with the timeout +// functionality in tasks. It works as follows: +// +// * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout) +// and some "happy" (will immediately finish). +// * Set the timeout on the "stuck" tasks to some number of seconds between +// 1 and the number of stuck tasks +// * Start all the stuck & happy tasks in random order +// * Wait "number of stuck tasks" seconds and make sure everything timed out + +class TaskTest : public sigslot::has_slots<> { + public: + TaskTest() {} + + // no need to delete any tasks; the task runner owns them + ~TaskTest() {} + + void Start() { + // create and configure tasks + for (int i = 0; i < STUCK_TASK_COUNT; ++i) { + stuck_[i].task_ = new StuckTask(&task_runner_); + stuck_[i].task_->SignalTimeoutId.connect(this, + &TaskTest::OnTimeoutStuck); + stuck_[i].timed_out_ = false; + stuck_[i].xlat_ = stuck_[i].task_->unique_id(); + stuck_[i].task_->set_timeout_seconds(i + 1); + LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout " + << stuck_[i].task_->timeout_seconds(); + } + + for (int i = 0; i < HAPPY_TASK_COUNT; ++i) { + happy_[i].task_ = new HappyTask(&task_runner_); + happy_[i].task_->SignalTimeoutId.connect(this, + &TaskTest::OnTimeoutHappy); + happy_[i].task_->SignalDoneId.connect(this, + &TaskTest::OnDoneHappy); + happy_[i].timed_out_ = false; + happy_[i].xlat_ = happy_[i].task_->unique_id(); + } + + // start all the tasks in random order + int stuck_index = 0; + int happy_index = 0; + for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) { + if ((stuck_index < STUCK_TASK_COUNT) && + (happy_index < HAPPY_TASK_COUNT)) { + if (rand() % 2 == 1) { + stuck_[stuck_index++].task_->Start(); + } else { + happy_[happy_index++].task_->Start(); + } + } else if (stuck_index < STUCK_TASK_COUNT) { + stuck_[stuck_index++].task_->Start(); + } else { + happy_[happy_index++].task_->Start(); + } + } + + for (int i = 0; i < STUCK_TASK_COUNT; ++i) { + std::cout << "Stuck task #" << i << " timeout is " << + stuck_[i].task_->timeout_seconds() << " at " << + stuck_[i].task_->timeout_time() << std::endl; + } + + // just a little self-check to make sure we started all the tasks + ASSERT_EQ(STUCK_TASK_COUNT, stuck_index); + ASSERT_EQ(HAPPY_TASK_COUNT, happy_index); + + // run the unblocked tasks + LOG(LS_INFO) << "Running tasks"; + task_runner_.RunTasks(); + + std::cout << "Start time is " << GetCurrentTime() << std::endl; + + // give all the stuck tasks time to timeout + for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT; + ++i) { + Thread::Current()->ProcessMessages(1000); + for (int j = 0; j < HAPPY_TASK_COUNT; ++j) { + if (happy_[j].task_) { + happy_[j].task_->Wake(); + } + } + LOG(LS_INFO) << "Polling tasks"; + task_runner_.PollTasks(); + } + + // We see occasional test failures here due to the stuck tasks not having + // timed-out yet, which seems like it should be impossible. To help track + // this down we have added logging of the timing information, which we send + // directly to stdout so that we get it in opt builds too. + std::cout << "End time is " << GetCurrentTime() << std::endl; + } + + void OnTimeoutStuck(const int id) { + LOG(LS_INFO) << "Timed out task " << id; + + int i; + for (i = 0; i < STUCK_TASK_COUNT; ++i) { + if (stuck_[i].xlat_ == id) { + stuck_[i].timed_out_ = true; + stuck_[i].task_ = NULL; + break; + } + } + + // getting a bad ID here is a failure, but let's continue + // running to see what else might go wrong + EXPECT_LT(i, STUCK_TASK_COUNT); + } + + void OnTimeoutHappy(const int id) { + int i; + for (i = 0; i < HAPPY_TASK_COUNT; ++i) { + if (happy_[i].xlat_ == id) { + happy_[i].timed_out_ = true; + happy_[i].task_ = NULL; + break; + } + } + + // getting a bad ID here is a failure, but let's continue + // running to see what else might go wrong + EXPECT_LT(i, HAPPY_TASK_COUNT); + } + + void OnDoneHappy(const int id) { + int i; + for (i = 0; i < HAPPY_TASK_COUNT; ++i) { + if (happy_[i].xlat_ == id) { + happy_[i].task_ = NULL; + break; + } + } + + // getting a bad ID here is a failure, but let's continue + // running to see what else might go wrong + EXPECT_LT(i, HAPPY_TASK_COUNT); + } + + void check_passed() { + EXPECT_TRUE(task_runner_.AllChildrenDone()); + + // make sure none of our happy tasks timed out + for (int i = 0; i < HAPPY_TASK_COUNT; ++i) { + EXPECT_FALSE(happy_[i].timed_out_); + } + + // make sure all of our stuck tasks timed out + for (int i = 0; i < STUCK_TASK_COUNT; ++i) { + EXPECT_TRUE(stuck_[i].timed_out_); + if (!stuck_[i].timed_out_) { + std::cout << "Stuck task #" << i << " timeout is at " + << stuck_[i].task_->timeout_time() << std::endl; + } + } + + std::cout.flush(); + } + + private: + struct TaskInfo { + IdTimeoutTask *task_; + bool timed_out_; + int xlat_; + }; + + MyTaskRunner task_runner_; + TaskInfo stuck_[STUCK_TASK_COUNT]; + TaskInfo happy_[HAPPY_TASK_COUNT]; +}; + +TEST(start_task_test, Timeout) { + TaskTest task_test; + task_test.Start(); + task_test.check_passed(); +} + +// Test for aborting the task while it is running + +class AbortTask : public Task { + public: + explicit AbortTask(TaskParent *parent) : Task(parent) { + set_timeout_seconds(1); + } + + virtual int ProcessStart() { + Abort(); + return STATE_NEXT; + } + private: + DISALLOW_EVIL_CONSTRUCTORS(AbortTask); +}; + +class TaskAbortTest : public sigslot::has_slots<> { + public: + TaskAbortTest() {} + + // no need to delete any tasks; the task runner owns them + ~TaskAbortTest() {} + + void Start() { + Task *abort_task = new AbortTask(&task_runner_); + abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout); + abort_task->Start(); + + // run the task + task_runner_.RunTasks(); + } + + private: + void OnTimeout() { + FAIL() << "Task timed out instead of aborting."; + } + + MyTaskRunner task_runner_; + DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest); +}; + +TEST(start_task_test, Abort) { + TaskAbortTest abort_test; + abort_test.Start(); +} + +// Test for aborting a task to verify that it does the Wake operation +// which gets it deleted. + +class SetBoolOnDeleteTask : public Task { + public: + SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted) + : Task(parent), + set_when_deleted_(set_when_deleted) { + EXPECT_TRUE(NULL != set_when_deleted); + EXPECT_FALSE(*set_when_deleted); + } + + virtual ~SetBoolOnDeleteTask() { + *set_when_deleted_ = true; + } + + virtual int ProcessStart() { + return STATE_BLOCKED; + } + + private: + bool* set_when_deleted_; + DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask); +}; + +class AbortShouldWakeTest : public sigslot::has_slots<> { + public: + AbortShouldWakeTest() {} + + // no need to delete any tasks; the task runner owns them + ~AbortShouldWakeTest() {} + + void Start() { + bool task_deleted = false; + Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted); + task_to_abort->Start(); + + // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls + // TaskRunner::RunTasks() immediately which should delete the task. + task_to_abort->Abort(); + EXPECT_TRUE(task_deleted); + + if (!task_deleted) { + // avoid a crash (due to referencing a local variable) + // if the test fails. + task_runner_.RunTasks(); + } + } + + private: + void OnTimeout() { + FAIL() << "Task timed out instead of aborting."; + } + + MyTaskRunner task_runner_; + DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest); +}; + +TEST(start_task_test, AbortShouldWake) { + AbortShouldWakeTest abort_should_wake_test; + abort_should_wake_test.Start(); +} + +// Validate that TaskRunner's OnTimeoutChange gets called appropriately +// * When a task calls UpdateTaskTimeout +// * When the next timeout task time, times out +class TimeoutChangeTest : public sigslot::has_slots<> { + public: + TimeoutChangeTest() + : task_count_(ARRAY_SIZE(stuck_tasks_)) {} + + // no need to delete any tasks; the task runner owns them + ~TimeoutChangeTest() {} + + void Start() { + for (int i = 0; i < task_count_; ++i) { + stuck_tasks_[i] = new StuckTask(&task_runner_); + stuck_tasks_[i]->set_timeout_seconds(i + 2); + stuck_tasks_[i]->SignalTimeoutId.connect(this, + &TimeoutChangeTest::OnTimeoutId); + } + + for (int i = task_count_ - 1; i >= 0; --i) { + stuck_tasks_[i]->Start(); + } + task_runner_.clear_timeout_change(); + + // At this point, our timeouts are set as follows + // task[0] is 2 seconds, task[1] at 3 seconds, etc. + + stuck_tasks_[0]->set_timeout_seconds(2); + // Now, task[0] is 2 seconds, task[1] at 3 seconds... + // so timeout change shouldn't be called. + EXPECT_FALSE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + + stuck_tasks_[0]->set_timeout_seconds(1); + // task[0] is 1 seconds, task[1] at 3 seconds... + // The smallest timeout got smaller so timeout change be called. + EXPECT_TRUE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + + stuck_tasks_[1]->set_timeout_seconds(2); + // task[0] is 1 seconds, task[1] at 2 seconds... + // The smallest timeout is still 1 second so no timeout change. + EXPECT_FALSE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + + while (task_count_ > 0) { + int previous_count = task_count_; + task_runner_.PollTasks(); + if (previous_count != task_count_) { + // We only get here when a task times out. When that + // happens, the timeout change should get called because + // the smallest timeout is now in the past. + EXPECT_TRUE(task_runner_.timeout_change()); + task_runner_.clear_timeout_change(); + } + Thread::Current()->socketserver()->Wait(500, false); + } + } + + private: + void OnTimeoutId(const int id) { + for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) { + if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) { + task_count_--; + stuck_tasks_[i] = NULL; + break; + } + } + } + + MyTaskRunner task_runner_; + StuckTask* (stuck_tasks_[3]); + int task_count_; + DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest); +}; + +TEST(start_task_test, TimeoutChange) { + TimeoutChangeTest timeout_change_test; + timeout_change_test.Start(); +} + +class DeleteTestTaskRunner : public TaskRunner { + public: + DeleteTestTaskRunner() { + } + virtual void WakeTasks() { } + virtual int64 CurrentTime() { + return GetCurrentTime(); + } + private: + DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner); +}; + +TEST(unstarted_task_test, DeleteTask) { + // This test ensures that we don't + // crash if a task is deleted without running it. + DeleteTestTaskRunner task_runner; + HappyTask* happy_task = new HappyTask(&task_runner); + happy_task->Start(); + + // try deleting the task directly + HappyTask* child_happy_task = new HappyTask(happy_task); + delete child_happy_task; + + // run the unblocked tasks + task_runner.RunTasks(); +} + +TEST(unstarted_task_test, DoNotDeleteTask1) { + // This test ensures that we don't + // crash if a task runner is deleted without + // running a certain task. + DeleteTestTaskRunner task_runner; + HappyTask* happy_task = new HappyTask(&task_runner); + happy_task->Start(); + + HappyTask* child_happy_task = new HappyTask(happy_task); + child_happy_task->Start(); + + // Never run the tasks +} + +TEST(unstarted_task_test, DoNotDeleteTask2) { + // This test ensures that we don't + // crash if a taskrunner is delete with a + // task that has never been started. + DeleteTestTaskRunner task_runner; + HappyTask* happy_task = new HappyTask(&task_runner); + happy_task->Start(); + + // Do not start the task. + // Note: this leaks memory, so don't do this. + // Instead, always run your tasks or delete them. + new HappyTask(happy_task); + + // run the unblocked tasks + task_runner.RunTasks(); +} + +} // namespace rtc diff --git a/webrtc/base/taskparent.cc b/webrtc/base/taskparent.cc new file mode 100644 index 000000000..edc146fd2 --- /dev/null +++ b/webrtc/base/taskparent.cc @@ -0,0 +1,95 @@ +/* + * Copyright 2004 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 "webrtc/base/taskparent.h" + +#include "webrtc/base/task.h" +#include "webrtc/base/taskrunner.h" + +namespace rtc { + +TaskParent::TaskParent(Task* derived_instance, TaskParent *parent) + : parent_(parent) { + ASSERT(derived_instance != NULL); + ASSERT(parent != NULL); + runner_ = parent->GetRunner(); + parent_->AddChild(derived_instance); + Initialize(); +} + +TaskParent::TaskParent(TaskRunner *derived_instance) + : parent_(NULL), + runner_(derived_instance) { + ASSERT(derived_instance != NULL); + Initialize(); +} + +// Does common initialization of member variables +void TaskParent::Initialize() { + children_.reset(new ChildSet()); + child_error_ = false; +} + +void TaskParent::AddChild(Task *child) { + children_->insert(child); +} + +#ifdef _DEBUG +bool TaskParent::IsChildTask(Task *task) { + ASSERT(task != NULL); + return task->parent_ == this && children_->find(task) != children_->end(); +} +#endif + +bool TaskParent::AllChildrenDone() { + for (ChildSet::iterator it = children_->begin(); + it != children_->end(); + ++it) { + if (!(*it)->IsDone()) + return false; + } + return true; +} + +bool TaskParent::AnyChildError() { + return child_error_; +} + +void TaskParent::AbortAllChildren() { + if (children_->size() > 0) { +#ifdef _DEBUG + runner_->IncrementAbortCount(); +#endif + + ChildSet copy = *children_; + for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) { + (*it)->Abort(true); // Note we do not wake + } + +#ifdef _DEBUG + runner_->DecrementAbortCount(); +#endif + } +} + +void TaskParent::OnStopped(Task *task) { + AbortAllChildren(); + parent_->OnChildStopped(task); +} + +void TaskParent::OnChildStopped(Task *child) { + if (child->HasError()) + child_error_ = true; + children_->erase(child); +} + +} // namespace rtc diff --git a/webrtc/base/taskparent.h b/webrtc/base/taskparent.h new file mode 100644 index 000000000..a3832024e --- /dev/null +++ b/webrtc/base/taskparent.h @@ -0,0 +1,62 @@ +/* + * Copyright 2004 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_BASE_TASKPARENT_H__ +#define WEBRTC_BASE_TASKPARENT_H__ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +class Task; +class TaskRunner; + +class TaskParent { + public: + TaskParent(Task *derived_instance, TaskParent *parent); + explicit TaskParent(TaskRunner *derived_instance); + virtual ~TaskParent() { } + + TaskParent *GetParent() { return parent_; } + TaskRunner *GetRunner() { return runner_; } + + bool AllChildrenDone(); + bool AnyChildError(); +#ifdef _DEBUG + bool IsChildTask(Task *task); +#endif + + protected: + void OnStopped(Task *task); + void AbortAllChildren(); + TaskParent *parent() { + return parent_; + } + + private: + void Initialize(); + void OnChildStopped(Task *child); + void AddChild(Task *child); + + TaskParent *parent_; + TaskRunner *runner_; + bool child_error_; + typedef std::set ChildSet; + scoped_ptr children_; + DISALLOW_EVIL_CONSTRUCTORS(TaskParent); +}; + + +} // namespace rtc + +#endif // WEBRTC_BASE_TASKPARENT_H__ diff --git a/webrtc/base/taskrunner.cc b/webrtc/base/taskrunner.cc new file mode 100644 index 000000000..bc4ab5e44 --- /dev/null +++ b/webrtc/base/taskrunner.cc @@ -0,0 +1,224 @@ +/* + * Copyright 2004 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 "webrtc/base/taskrunner.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/task.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +TaskRunner::TaskRunner() + : TaskParent(this), + next_timeout_task_(NULL), + tasks_running_(false) +#ifdef _DEBUG + , abort_count_(0), + deleting_task_(NULL) +#endif +{ +} + +TaskRunner::~TaskRunner() { + // this kills and deletes children silently! + AbortAllChildren(); + InternalRunTasks(true); +} + +void TaskRunner::StartTask(Task * task) { + tasks_.push_back(task); + + // the task we just started could be about to timeout -- + // make sure our "next timeout task" is correct + UpdateTaskTimeout(task, 0); + + WakeTasks(); +} + +void TaskRunner::RunTasks() { + InternalRunTasks(false); +} + +void TaskRunner::InternalRunTasks(bool in_destructor) { + // This shouldn't run while an abort is happening. + // If that occurs, then tasks may be deleted in this method, + // but pointers to them will still be in the + // "ChildSet copy" in TaskParent::AbortAllChildren. + // Subsequent use of those task may cause data corruption or crashes. + ASSERT(!abort_count_); + // Running continues until all tasks are Blocked (ok for a small # of tasks) + if (tasks_running_) { + return; // don't reenter + } + + tasks_running_ = true; + + int64 previous_timeout_time = next_task_timeout(); + + int did_run = true; + while (did_run) { + did_run = false; + // use indexing instead of iterators because tasks_ may grow + for (size_t i = 0; i < tasks_.size(); ++i) { + while (!tasks_[i]->Blocked()) { + tasks_[i]->Step(); + did_run = true; + } + } + } + // Tasks are deleted when running has paused + bool need_timeout_recalc = false; + for (size_t i = 0; i < tasks_.size(); ++i) { + if (tasks_[i]->IsDone()) { + Task* task = tasks_[i]; + if (next_timeout_task_ && + task->unique_id() == next_timeout_task_->unique_id()) { + next_timeout_task_ = NULL; + need_timeout_recalc = true; + } + +#ifdef _DEBUG + deleting_task_ = task; +#endif + delete task; +#ifdef _DEBUG + deleting_task_ = NULL; +#endif + tasks_[i] = NULL; + } + } + // Finally, remove nulls + std::vector::iterator it; + it = std::remove(tasks_.begin(), + tasks_.end(), + reinterpret_cast(NULL)); + + tasks_.erase(it, tasks_.end()); + + if (need_timeout_recalc) + RecalcNextTimeout(NULL); + + // Make sure that adjustments are done to account + // for any timeout changes (but don't call this + // while being destroyed since it calls a pure virtual function). + if (!in_destructor) + CheckForTimeoutChange(previous_timeout_time); + + tasks_running_ = false; +} + +void TaskRunner::PollTasks() { + // see if our "next potentially timed-out task" has indeed timed out. + // If it has, wake it up, then queue up the next task in line + // Repeat while we have new timed-out tasks. + // TODO: We need to guard against WakeTasks not updating + // next_timeout_task_. Maybe also add documentation in the header file once + // we understand this code better. + Task* old_timeout_task = NULL; + while (next_timeout_task_ && + old_timeout_task != next_timeout_task_ && + next_timeout_task_->TimedOut()) { + old_timeout_task = next_timeout_task_; + next_timeout_task_->Wake(); + WakeTasks(); + } +} + +int64 TaskRunner::next_task_timeout() const { + if (next_timeout_task_) { + return next_timeout_task_->timeout_time(); + } + return 0; +} + +// this function gets called frequently -- when each task changes +// state to something other than DONE, ERROR or BLOCKED, it calls +// ResetTimeout(), which will call this function to make sure that +// the next timeout-able task hasn't changed. The logic in this function +// prevents RecalcNextTimeout() from getting called in most cases, +// effectively making the task scheduler O-1 instead of O-N + +void TaskRunner::UpdateTaskTimeout(Task* task, + int64 previous_task_timeout_time) { + ASSERT(task != NULL); + int64 previous_timeout_time = next_task_timeout(); + bool task_is_timeout_task = next_timeout_task_ != NULL && + task->unique_id() == next_timeout_task_->unique_id(); + if (task_is_timeout_task) { + previous_timeout_time = previous_task_timeout_time; + } + + // if the relevant task has a timeout, then + // check to see if it's closer than the current + // "about to timeout" task + if (task->timeout_time()) { + if (next_timeout_task_ == NULL || + (task->timeout_time() <= next_timeout_task_->timeout_time())) { + next_timeout_task_ = task; + } + } else if (task_is_timeout_task) { + // otherwise, if the task doesn't have a timeout, + // and it used to be our "about to timeout" task, + // walk through all the tasks looking for the real + // "about to timeout" task + RecalcNextTimeout(task); + } + + // Note when task_running_, then the running routine + // (TaskRunner::InternalRunTasks) is responsible for calling + // CheckForTimeoutChange. + if (!tasks_running_) { + CheckForTimeoutChange(previous_timeout_time); + } +} + +void TaskRunner::RecalcNextTimeout(Task *exclude_task) { + // walk through all the tasks looking for the one + // which satisfies the following: + // it's not finished already + // we're not excluding it + // it has the closest timeout time + + int64 next_timeout_time = 0; + next_timeout_task_ = NULL; + + for (size_t i = 0; i < tasks_.size(); ++i) { + Task *task = tasks_[i]; + // if the task isn't complete, and it actually has a timeout time + if (!task->IsDone() && (task->timeout_time() > 0)) + // if it doesn't match our "exclude" task + if (exclude_task == NULL || + exclude_task->unique_id() != task->unique_id()) + // if its timeout time is sooner than our current timeout time + if (next_timeout_time == 0 || + task->timeout_time() <= next_timeout_time) { + // set this task as our next-to-timeout + next_timeout_time = task->timeout_time(); + next_timeout_task_ = task; + } + } +} + +void TaskRunner::CheckForTimeoutChange(int64 previous_timeout_time) { + int64 next_timeout = next_task_timeout(); + bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) || + next_timeout < previous_timeout_time || + (previous_timeout_time <= CurrentTime() && + previous_timeout_time != next_timeout); + if (timeout_change) { + OnTimeoutChange(); + } +} + +} // namespace rtc diff --git a/webrtc/base/taskrunner.h b/webrtc/base/taskrunner.h new file mode 100644 index 000000000..629c2d3ac --- /dev/null +++ b/webrtc/base/taskrunner.h @@ -0,0 +1,100 @@ +/* + * Copyright 2004 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_BASE_TASKRUNNER_H__ +#define WEBRTC_BASE_TASKRUNNER_H__ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/taskparent.h" + +namespace rtc { +class Task; + +const int64 kSecToMsec = 1000; +const int64 kMsecTo100ns = 10000; +const int64 kSecTo100ns = kSecToMsec * kMsecTo100ns; + +class TaskRunner : public TaskParent, public sigslot::has_slots<> { + public: + TaskRunner(); + virtual ~TaskRunner(); + + virtual void WakeTasks() = 0; + + // Returns the current time in 100ns units. It is used for + // determining timeouts. The origin is not important, only + // the units and that rollover while the computer is running. + // + // On Windows, GetSystemTimeAsFileTime is the typical implementation. + virtual int64 CurrentTime() = 0 ; + + void StartTask(Task *task); + void RunTasks(); + void PollTasks(); + + void UpdateTaskTimeout(Task *task, int64 previous_task_timeout_time); + +#ifdef _DEBUG + bool is_ok_to_delete(Task* task) { + return task == deleting_task_; + } + + void IncrementAbortCount() { + ++abort_count_; + } + + void DecrementAbortCount() { + --abort_count_; + } +#endif + + // Returns the next absolute time when a task times out + // OR "0" if there is no next timeout. + int64 next_task_timeout() const; + + protected: + // The primary usage of this method is to know if + // a callback timer needs to be set-up or adjusted. + // This method will be called + // * when the next_task_timeout() becomes a smaller value OR + // * when next_task_timeout() has changed values and the previous + // value is in the past. + // + // If the next_task_timeout moves to the future, this method will *not* + // get called (because it subclass should check next_task_timeout() + // when its timer goes off up to see if it needs to set-up a new timer). + // + // Note that this maybe called conservatively. In that it may be + // called when no time change has happened. + virtual void OnTimeoutChange() { + // by default, do nothing. + } + + private: + void InternalRunTasks(bool in_destructor); + void CheckForTimeoutChange(int64 previous_timeout_time); + + std::vector tasks_; + Task *next_timeout_task_; + bool tasks_running_; +#ifdef _DEBUG + int abort_count_; + Task* deleting_task_; +#endif + + void RecalcNextTimeout(Task *exclude_task); +}; + +} // namespace rtc + +#endif // TASK_BASE_TASKRUNNER_H__ diff --git a/webrtc/base/template_util.h b/webrtc/base/template_util.h new file mode 100644 index 000000000..f0bf39c5f --- /dev/null +++ b/webrtc/base/template_util.h @@ -0,0 +1,112 @@ +/* + * Copyright 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_BASE_TEMPLATE_UTIL_H_ +#define WEBRTC_BASE_TEMPLATE_UTIL_H_ + +#include // For size_t. + +namespace rtc { + +// template definitions from tr1 + +template +struct integral_constant { + static const T value = v; + typedef T value_type; + typedef integral_constant type; +}; + +template const T integral_constant::value; + +typedef integral_constant true_type; +typedef integral_constant false_type; + +template struct is_pointer : false_type {}; +template struct is_pointer : true_type {}; + +template struct is_same : public false_type {}; +template struct is_same : true_type {}; + +template struct is_array : public false_type {}; +template struct is_array : public true_type {}; +template struct is_array : public true_type {}; + +template struct is_non_const_reference : false_type {}; +template struct is_non_const_reference : true_type {}; +template struct is_non_const_reference : false_type {}; + +template struct is_void : false_type {}; +template <> struct is_void : true_type {}; + +namespace internal { + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// This class is an implementation detail for is_convertible, and you +// don't need to know how it works to use is_convertible. For those +// who care: we declare two different functions, one whose argument is +// of type To and one with a variadic argument list. We give them +// return types of different size, so we can use sizeof to trick the +// compiler into telling us which function it would have chosen if we +// had called it with an argument of type From. See Alexandrescu's +// _Modern C++ Design_ for more details on this sort of trick. + +struct ConvertHelper { + template + static YesType Test(To); + + template + static NoType Test(...); + + template + static From& Create(); +}; + +// Used to determine if a type is a struct/union/class. Inspired by Boost's +// is_class type_trait implementation. +struct IsClassHelper { + template + static YesType Test(void(C::*)(void)); + + template + static NoType Test(...); +}; + +} // namespace internal + +// Inherits from true_type if From is convertible to To, false_type otherwise. +// +// Note that if the type is convertible, this will be a true_type REGARDLESS +// of whether or not the conversion would emit a warning. +template +struct is_convertible + : integral_constant( + internal::ConvertHelper::Create())) == + sizeof(internal::YesType)> { +}; + +template +struct is_class + : integral_constant(0)) == + sizeof(internal::YesType)> { +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TEMPLATE_UTIL_H_ diff --git a/webrtc/base/testbase64.h b/webrtc/base/testbase64.h new file mode 100644 index 000000000..39dd00ce3 --- /dev/null +++ b/webrtc/base/testbase64.h @@ -0,0 +1,5 @@ +/* This file was generated by googleclient/talk/binary2header.sh */ + +static unsigned char testbase64[] = { +0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xe1, 0x0d, 0x07, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x9e, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xbe, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0xc3, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd4, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xdc, 0x01, 0x32, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x3c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x04, 0x02, 0x13, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x02, 0xc4, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x53, 0x4f, 0x4e, 0x59, 0x00, 0x44, 0x53, 0x43, 0x2d, 0x50, 0x32, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x37, 0x2e, 0x30, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x33, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x31, 0x30, 0x3a, 0x30, 0x34, 0x00, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58, 0x20, 0x31, 0x30, 0x2e, 0x34, 0x2e, 0x38, 0x00, 0x00, 0x1c, 0x82, 0x9a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x6a, 0x82, 0x9d, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x72, 0x88, 0x22, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x88, 0x27, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x64, 0x00, 0x00, 0x90, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x7a, 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xa2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xaa, 0x92, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xb2, 0x92, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x92, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x92, 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xba, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa0, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0a, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x12, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x1a, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x22, 0x02, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0xff, 0xed, 0x2e, 0x1c, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x33, 0x2e, 0x30, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x1c, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x1c, 0x02, 0x78, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfb, 0x09, 0xa6, 0xbd, 0x07, 0x4c, 0x2a, 0x36, 0x9d, 0x8f, 0xe2, 0xcc, 0x57, 0xa9, 0xac, 0x85, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xea, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xb0, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x22, 0x2d, 0x2f, 0x2f, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x2f, 0x44, 0x54, 0x44, 0x20, 0x50, 0x4c, 0x49, 0x53, 0x54, 0x20, 0x31, 0x2e, 0x30, 0x2f, 0x2f, 0x45, 0x4e, 0x22, 0x20, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x54, 0x44, 0x73, 0x2f, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x2d, 0x31, 0x2e, 0x30, 0x2e, 0x64, 0x74, 0x64, 0x22, 0x3e, 0x0a, 0x3c, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x3e, 0x0a, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x73, 0x75, 0x62, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x70, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x6e, 0x61, 0x2d, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x55, 0x53, 0x20, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x3c, 0x2f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x3e, 0x0a, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xde, 0x02, 0x40, 0xff, 0xee, 0xff, 0xee, 0x03, 0x06, 0x02, 0x52, 0x03, 0x67, 0x05, 0x28, 0x03, 0xfc, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd8, 0x02, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x08, 0x00, 0x19, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x27, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0x6c, 0x66, 0x66, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0xa1, 0x99, 0x9a, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x03, 0x45, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x08, 0x00, 0x44, 0x00, 0x53, 0x00, 0x43, 0x00, 0x30, 0x00, 0x32, 0x00, 0x33, 0x00, 0x32, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x06, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x56, 0x6c, 0x4c, 0x73, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x07, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0c, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x6f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0a, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x49, 0x6d, 0x67, 0x20, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x73, 0x67, 0x65, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x61, 0x6c, 0x74, 0x54, 0x61, 0x67, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x49, 0x73, 0x48, 0x54, 0x4d, 0x4c, 0x62, 0x6f, 0x6f, 0x6c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x68, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x48, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x09, 0x76, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x0b, 0x62, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x11, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x42, 0x47, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x09, 0x74, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x6c, 0x65, 0x66, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x72, 0x69, 0x67, 0x68, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf9, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x18, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x00, 0x00, 0x13, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x20, 0x00, 0x37, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xff, 0xe1, 0x15, 0x67, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x00, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x27, 0xef, 0xbb, 0xbf, 0x27, 0x20, 0x69, 0x64, 0x3d, 0x27, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x27, 0x3f, 0x3e, 0x0a, 0x3c, 0x3f, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2d, 0x78, 0x61, 0x70, 0x2d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x20, 0x65, 0x73, 0x63, 0x3d, 0x22, 0x43, 0x52, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x27, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x27, 0x20, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x74, 0x6b, 0x3d, 0x27, 0x58, 0x4d, 0x50, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69, 0x74, 0x20, 0x32, 0x2e, 0x38, 0x2e, 0x32, 0x2d, 0x33, 0x33, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x31, 0x2e, 0x35, 0x27, 0x3e, 0x0a, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x27, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x69, 0x58, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x58, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x64, 0x66, 0x2f, 0x31, 0x2e, 0x33, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x64, 0x66, 0x3a, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x43, 0x61, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x78, 0x61, 0x70, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x64, 0x6f, 0x63, 0x69, 0x64, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x36, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x3c, 0x2f, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x64, 0x63, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x72, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x63, 0x2f, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x31, 0x2e, 0x31, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x20, 0x78, 0x6d, 0x6c, 0x3a, 0x6c, 0x61, 0x6e, 0x67, 0x3d, 0x27, 0x78, 0x2d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x27, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x27, 0x77, 0x27, 0x3f, 0x3e, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x40, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x06, 0x04, 0x03, 0x04, 0x06, 0x07, 0x05, 0x04, 0x04, 0x05, 0x07, 0x08, 0x06, 0x06, 0x07, 0x06, 0x06, 0x08, 0x0a, 0x08, 0x09, 0x09, 0x09, 0x09, 0x08, 0x0a, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x04, 0x05, 0x05, 0x08, 0x07, 0x08, 0x0f, 0x0a, 0x0a, 0x0f, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x0d, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x00, 0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x03, 0x02, 0x06, 0x01, 0x00, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x02, 0x02, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x02, 0x06, 0x07, 0x03, 0x04, 0x02, 0x06, 0x02, 0x73, 0x01, 0x02, 0x03, 0x11, 0x04, 0x00, 0x05, 0x21, 0x12, 0x31, 0x41, 0x51, 0x06, 0x13, 0x61, 0x22, 0x71, 0x81, 0x14, 0x32, 0x91, 0xa1, 0x07, 0x15, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xe1, 0x33, 0x16, 0x62, 0xf0, 0x24, 0x72, 0x82, 0xf1, 0x25, 0x43, 0x34, 0x53, 0x92, 0xa2, 0xb2, 0x63, 0x73, 0xc2, 0x35, 0x44, 0x27, 0x93, 0xa3, 0xb3, 0x36, 0x17, 0x54, 0x64, 0x74, 0xc3, 0xd2, 0xe2, 0x08, 0x26, 0x83, 0x09, 0x0a, 0x18, 0x19, 0x84, 0x94, 0x45, 0x46, 0xa4, 0xb4, 0x56, 0xd3, 0x55, 0x28, 0x1a, 0xf2, 0xe3, 0xf3, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x03, 0x05, 0x05, 0x04, 0x05, 0x06, 0x04, 0x08, 0x03, 0x03, 0x6d, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x41, 0x05, 0x51, 0x13, 0x61, 0x22, 0x06, 0x71, 0x81, 0x91, 0x32, 0xa1, 0xb1, 0xf0, 0x14, 0xc1, 0xd1, 0xe1, 0x23, 0x42, 0x15, 0x52, 0x62, 0x72, 0xf1, 0x33, 0x24, 0x34, 0x43, 0x82, 0x16, 0x92, 0x53, 0x25, 0xa2, 0x63, 0xb2, 0xc2, 0x07, 0x73, 0xd2, 0x35, 0xe2, 0x44, 0x83, 0x17, 0x54, 0x93, 0x08, 0x09, 0x0a, 0x18, 0x19, 0x26, 0x36, 0x45, 0x1a, 0x27, 0x64, 0x74, 0x55, 0x37, 0xf2, 0xa3, 0xb3, 0xc3, 0x28, 0x29, 0xd3, 0xe3, 0xf3, 0x84, 0x94, 0xa4, 0xb4, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x46, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf0, 0x67, 0xa6, 0x5c, 0x0f, 0x01, 0xd4, 0x7e, 0x18, 0x12, 0x98, 0xe9, 0xd6, 0x2d, 0x34, 0x6d, 0x70, 0xdf, 0xdc, 0xa1, 0xe3, 0xec, 0x5b, 0xfb, 0x32, 0x24, 0xb2, 0x01, 0x1f, 0x15, 0xa4, 0x52, 0x4a, 0x82, 0x31, 0xf1, 0xfe, 0xd1, 0x3d, 0x14, 0x64, 0x49, 0x64, 0x22, 0x98, 0xcf, 0xa5, 0x46, 0x6c, 0x16, 0x55, 0x71, 0x56, 0x62, 0x28, 0x07, 0xc5, 0x45, 0x15, 0xa0, 0xc8, 0x89, 0x33, 0xe1, 0x63, 0xd2, 0xd8, 0x34, 0x44, 0x17, 0xa0, 0x2c, 0x4d, 0x16, 0xbb, 0xed, 0xdc, 0xf8, 0x64, 0xc1, 0x6b, 0x31, 0x42, 0x18, 0x8e, 0xc7, 0xb5, 0x2a, 0x7d, 0xb2, 0x56, 0xc5, 0x61, 0x8c, 0xf2, 0xa0, 0x1b, 0x1e, 0x83, 0x0d, 0xa1, 0x63, 0x50, 0x1f, 0x97, 0x7c, 0x2a, 0xa9, 0x1a, 0x9a, 0x86, 0x4f, 0xb4, 0xb4, 0x38, 0x0a, 0xa6, 0x0b, 0xb8, 0x0c, 0x05, 0x14, 0xf8, 0x76, 0x3e, 0x19, 0x14, 0xb6, 0x78, 0xf8, 0x8c, 0x2a, 0xd5, 0x01, 0xdc, 0x6f, 0x8a, 0x1a, 0xe3, 0x8d, 0xab, 0xff, 0xd0, 0xf0, 0xec, 0xe9, 0x15, 0xb5, 0xb9, 0x5a, 0x7c, 0x4c, 0xa2, 0x9e, 0x24, 0xf5, 0xca, 0xc6, 0xe5, 0x99, 0xd9, 0x34, 0x99, 0x04, 0x3a, 0x7d, 0xb5, 0xba, 0xd5, 0x51, 0x63, 0x0e, 0xc7, 0xc5, 0x9b, 0x73, 0xf8, 0xe4, 0x6f, 0x76, 0xca, 0xd9, 0xda, 0x54, 0x6d, 0x72, 0x2e, 0x1a, 0x57, 0x11, 0x44, 0x40, 0x0d, 0x27, 0x7a, 0x0f, 0xd9, 0x5f, 0x12, 0x69, 0x4c, 0x84, 0xcd, 0x36, 0xe3, 0x85, 0xb2, 0xcd, 0x2f, 0x4a, 0x8b, 0x58, 0x36, 0xf6, 0x76, 0xa8, 0x64, 0x64, 0x3c, 0xa4, 0x93, 0xaa, 0x25, 0x3c, 0x49, 0xda, 0xa4, 0xe5, 0x26, 0x54, 0xe4, 0x8c, 0x7c, 0x5c, 0x93, 0x4d, 0x67, 0xc9, 0x3a, 0x6e, 0x9f, 0x13, 0xb4, 0xce, 0xf7, 0x3a, 0x9b, 0xad, 0x52, 0xd6, 0x2a, 0xd1, 0x49, 0xee, 0xc7, 0xf8, 0x64, 0x46, 0x42, 0x4e, 0xcd, 0x92, 0xc2, 0x00, 0xdd, 0x8a, 0x47, 0xe5, 0x69, 0x6e, 0xd4, 0xa4, 0x08, 0x16, 0x83, 0x9c, 0x8c, 0xdd, 0x95, 0x6b, 0xb9, 0xf6, 0xef, 0x97, 0x78, 0x94, 0xe3, 0x78, 0x04, 0xa4, 0xf3, 0xe8, 0xee, 0x64, 0xe1, 0x12, 0x10, 0x05, 0x6a, 0xc7, 0xc0, 0x6f, 0x53, 0xf3, 0xc9, 0x89, 0xb4, 0x9c, 0x4e, 0xb4, 0xf2, 0xd3, 0xde, 0x7a, 0xd2, 0x19, 0x16, 0x38, 0x61, 0x5d, 0xd9, 0x88, 0x05, 0x9c, 0xf4, 0x0a, 0x0f, 0x5f, 0x73, 0x84, 0xe4, 0xa4, 0xc7, 0x0d, 0xa5, 0xf1, 0x59, 0xba, 0x5c, 0x08, 0x98, 0x6f, 0xc8, 0x20, 0xfa, 0x4e, 0x4e, 0xf6, 0x69, 0xe1, 0xa2, 0x89, 0xfd, 0x1f, 0x77, 0x2c, 0xe6, 0xce, 0xd6, 0x17, 0x9a, 0x69, 0xdb, 0xd3, 0x86, 0x18, 0xc1, 0x67, 0x77, 0x26, 0x80, 0x28, 0x1b, 0x93, 0x88, 0x41, 0x0f, 0x40, 0xb0, 0xfc, 0x87, 0xf3, 0x43, 0x98, 0xd7, 0x58, 0x96, 0xdb, 0x4d, 0x91, 0x88, 0xe5, 0x6c, 0x58, 0xdc, 0x5c, 0x2a, 0xf7, 0x2c, 0xb1, 0xfc, 0x20, 0x8f, 0x02, 0xd9, 0x65, 0x06, 0xbe, 0x26, 0x6f, 0xa2, 0x7f, 0xce, 0x3d, 0x69, 0x26, 0xdd, 0x13, 0x52, 0xbf, 0xbd, 0x92, 0x62, 0x59, 0x4c, 0x90, 0xac, 0x50, 0x45, 0x5e, 0xbb, 0x09, 0x03, 0x12, 0x29, 0x84, 0x00, 0xc4, 0xc9, 0x11, 0xff, 0x00, 0x42, 0xe7, 0xa7, 0x7a, 0xd4, 0xfd, 0x21, 0x79, 0xe9, 0x78, 0x71, 0x8b, 0x95, 0x39, 0x75, 0xaf, 0x4e, 0x98, 0x78, 0x42, 0x38, 0xdf, 0xff, 0xd1, 0xf0, 0xe6, 0xa0, 0x58, 0xc8, 0x84, 0x9a, 0xaa, 0x30, 0x55, 0xf9, 0x0a, 0x6f, 0x90, 0x0c, 0xca, 0x72, 0x48, 0xb8, 0x1e, 0x89, 0xa7, 0x23, 0x17, 0x24, 0xff, 0x00, 0x61, 0xb6, 0x54, 0x76, 0x6e, 0x1b, 0xa7, 0xbe, 0x50, 0xf2, 0xc1, 0xd7, 0x4c, 0x52, 0x5e, 0x33, 0x5b, 0xe9, 0x10, 0xf4, 0x54, 0x3c, 0x5e, 0x77, 0xee, 0x49, 0xec, 0x2b, 0xb6, 0x63, 0xe4, 0xc9, 0xc3, 0xef, 0x73, 0xf0, 0xe1, 0x32, 0x1b, 0xf2, 0x7a, 0x05, 0xce, 0xad, 0x65, 0xa1, 0x98, 0xb4, 0x0f, 0x2a, 0x5b, 0x23, 0xeb, 0x12, 0x00, 0x88, 0xb0, 0xa8, 0x66, 0x46, 0x3d, 0xea, 0x7b, 0xfb, 0x9e, 0x99, 0x89, 0xbc, 0x8d, 0x97, 0x3a, 0x34, 0x05, 0x32, 0x5d, 0x1f, 0xc9, 0x1a, 0x8c, 0x36, 0x8c, 0x6f, 0x66, 0xfa, 0xc6, 0xb7, 0x7d, 0xf0, 0x94, 0x04, 0xf0, 0x88, 0xc9, 0xd5, 0x9d, 0x8d, 0x4b, 0x11, 0xd4, 0x9f, 0xbb, 0x25, 0xc5, 0xdc, 0xa2, 0x03, 0x99, 0x4b, 0xbc, 0xf3, 0x0d, 0x97, 0x96, 0x74, 0xe5, 0xf2, 0xb6, 0x80, 0x95, 0xbd, 0x99, 0x15, 0xf5, 0x4b, 0xd2, 0x37, 0x58, 0x46, 0xd4, 0x27, 0xc5, 0xce, 0xc1, 0x7c, 0x30, 0x8e, 0x68, 0x94, 0x7b, 0x9e, 0x6d, 0xe6, 0x7b, 0x9b, 0x5d, 0x3a, 0xd8, 0xdb, 0x32, 0xfa, 0x77, 0x65, 0x15, 0xe4, 0x57, 0xa7, 0x21, 0x55, 0x04, 0x57, 0xef, 0xd8, 0x66, 0x56, 0x38, 0x19, 0x1b, 0xe8, 0xe0, 0x67, 0x98, 0xc7, 0x1a, 0x1c, 0xde, 0x71, 0x71, 0x79, 0x2c, 0xf2, 0xfa, 0x8c, 0x48, 0xec, 0xb5, 0x24, 0x9a, 0x0c, 0xce, 0x75, 0x29, 0xae, 0x8c, 0x67, 0xd4, 0xb5, 0x0b, 0x4b, 0x04, 0x05, 0xef, 0x2e, 0x66, 0x8e, 0x18, 0x08, 0x15, 0xdd, 0x8f, 0x11, 0xb0, 0xeb, 0x4c, 0x04, 0x5b, 0x21, 0x2a, 0x7d, 0x41, 0xe4, 0x4f, 0xcb, 0xcb, 0x5d, 0x12, 0x45, 0xb8, 0xb7, 0x53, 0x71, 0xaa, 0x9f, 0x86, 0x5b, 0xd6, 0x50, 0x4a, 0xed, 0xba, 0x46, 0x77, 0x00, 0x13, 0xd4, 0x8c, 0x85, 0xd3, 0x12, 0x6d, 0xeb, 0x1a, 0x67, 0x95, 0xd9, 0x39, 0x39, 0x50, 0xac, 0xff, 0x00, 0x6f, 0xc4, 0xff, 0x00, 0x1c, 0x81, 0x92, 0xb2, 0x6b, 0x6d, 0x02, 0xdd, 0xbd, 0x36, 0x92, 0x36, 0x2d, 0x1f, 0xc0, 0x2a, 0x0b, 0x28, 0x1b, 0x91, 0x41, 0xf4, 0x9c, 0xb6, 0x25, 0x81, 0x46, 0xfe, 0x81, 0xb5, 0xad, 0x3d, 0xba, 0x57, 0xb7, 0xf9, 0xf6, 0xc9, 0xb0, 0x7f, 0xff, 0xd2, 0xf0, 0xe2, 0x86, 0x95, 0xc4, 0x67, 0x7e, 0x3f, 0x11, 0xf7, 0xa8, 0x19, 0x06, 0x69, 0x8d, 0xca, 0xca, 0x24, 0x8f, 0xd3, 0x52, 0x24, 0x89, 0x47, 0x25, 0x1f, 0xcb, 0x20, 0xf8, 0xb2, 0xb2, 0x76, 0x6e, 0x88, 0x36, 0xf6, 0x6f, 0x2a, 0xc1, 0x6e, 0xfa, 0x45, 0xad, 0xbc, 0x3f, 0x0b, 0x46, 0x81, 0x4d, 0x46, 0xea, 0x7a, 0x9a, 0x83, 0x9a, 0xa9, 0xdd, 0xbb, 0xec, 0x7b, 0x06, 0x5b, 0xe5, 0xcf, 0x2e, 0x69, 0xfa, 0x5c, 0xcd, 0x7b, 0x14, 0x5e, 0xa5, 0xee, 0xf5, 0xb8, 0x7d, 0xdd, 0x99, 0xba, 0xef, 0x91, 0x16, 0x5b, 0x36, 0xb6, 0x65, 0x0d, 0xac, 0xb2, 0x5b, 0xed, 0x34, 0x81, 0x7a, 0xbb, 0x46, 0x40, 0x6a, 0x9e, 0xb4, 0x39, 0x31, 0x13, 0x49, 0xda, 0xd2, 0x9b, 0xed, 0x1e, 0xc4, 0x24, 0xb3, 0x35, 0xb2, 0x88, 0x60, 0x06, 0xe6, 0x56, 0x98, 0x96, 0x79, 0x1e, 0x31, 0x51, 0xc9, 0x8f, 0xcb, 0x00, 0xe6, 0xb3, 0xe4, 0xf9, 0x2b, 0xcc, 0x7a, 0x94, 0xda, 0x96, 0xa9, 0x71, 0x77, 0x70, 0x79, 0xcd, 0x33, 0x97, 0x76, 0x3f, 0xcc, 0xc6, 0xa6, 0x9f, 0x2e, 0x99, 0xb9, 0xc6, 0x2a, 0x21, 0xe6, 0x73, 0xca, 0xe6, 0x4a, 0x51, 0x1a, 0x99, 0x1c, 0x28, 0x04, 0x93, 0xd0, 0x0e, 0xa4, 0xe4, 0xda, 0x5f, 0x50, 0xfe, 0x4a, 0xfe, 0x48, 0xb5, 0xb2, 0xc1, 0xe6, 0x1f, 0x31, 0x7e, 0xef, 0x52, 0x91, 0x43, 0xc3, 0x6e, 0x77, 0xf4, 0x22, 0x6d, 0xbf, 0xe4, 0x63, 0x0e, 0xbf, 0xca, 0x36, 0xeb, 0x5c, 0x84, 0xa5, 0x48, 0x7d, 0x3b, 0x61, 0xa1, 0xdb, 0x5b, 0x2c, 0x71, 0xda, 0x45, 0xc4, 0x28, 0x00, 0x81, 0xdb, 0x31, 0xc9, 0xb4, 0xb2, 0x3b, 0x5d, 0x27, 0xa5, 0x05, 0x1b, 0xc7, 0xdb, 0x10, 0xa9, 0xbd, 0xa6, 0x93, 0x0c, 0x75, 0xe4, 0x39, 0x35, 0x41, 0x3d, 0xc5, 0x06, 0xdb, 0x8e, 0xfd, 0x46, 0x5b, 0x1d, 0x98, 0x95, 0x4f, 0x46, 0xdb, 0xd5, 0xfb, 0x29, 0x5e, 0x9d, 0x0d, 0x32, 0xeb, 0x61, 0x4f, 0xff, 0xd3, 0xf1, 0x46, 0x9a, 0x16, 0x1b, 0x91, 0x71, 0x28, 0xac, 0x4a, 0x14, 0x30, 0x3e, 0x19, 0x54, 0xb9, 0x36, 0xc7, 0x9b, 0x2d, 0xd1, 0x6c, 0x45, 0xe3, 0xdc, 0xde, 0xc8, 0x95, 0x5b, 0x87, 0xf8, 0x41, 0x1d, 0x10, 0x54, 0x01, 0x98, 0x79, 0x25, 0xd1, 0xda, 0xe9, 0xe1, 0xb5, 0x9e, 0xac, 0xeb, 0x42, 0xba, 0x8e, 0xdf, 0x8c, 0x31, 0x21, 0x70, 0xb4, 0x5d, 0xbe, 0xc5, 0x7c, 0x2b, 0xed, 0xe1, 0x94, 0x18, 0xb9, 0x51, 0x3d, 0x03, 0x2c, 0x13, 0x6b, 0xf1, 0x42, 0x6e, 0xe2, 0xb7, 0x12, 0xa0, 0xdd, 0x50, 0x9f, 0x4f, 0x6f, 0xa7, 0x6f, 0xc7, 0x03, 0x61, 0xa0, 0x83, 0xb5, 0xf3, 0x97, 0x98, 0x20, 0x9c, 0x44, 0xea, 0xd0, 0xad, 0x48, 0x64, 0x90, 0x21, 0xd8, 0x9f, 0xa7, 0xa6, 0x44, 0xca, 0x99, 0xc6, 0x36, 0xcb, 0x74, 0x5d, 0x7e, 0x5b, 0xfe, 0x31, 0x6a, 0x31, 0xf3, 0x8c, 0xd0, 0xad, 0x40, 0xa3, 0x1f, 0x7c, 0x44, 0xd6, 0x51, 0xd9, 0xe0, 0x5f, 0x9a, 0x7e, 0x41, 0x9f, 0x40, 0xf3, 0x14, 0xba, 0x85, 0xba, 0x34, 0xba, 0x2d, 0xfb, 0x34, 0xd0, 0xcf, 0x4f, 0xb0, 0xce, 0x6a, 0x51, 0xe9, 0xb0, 0x20, 0xf4, 0xf1, 0x19, 0xb2, 0xc3, 0x90, 0x11, 0x4e, 0x97, 0x55, 0x80, 0x83, 0xc4, 0x17, 0x7e, 0x4c, 0x79, 0x19, 0xfc, 0xd1, 0xe7, 0x78, 0x4b, 0x91, 0x1d, 0xae, 0x92, 0xa6, 0xf6, 0x46, 0x75, 0xe4, 0xad, 0x22, 0x1f, 0xdd, 0xa1, 0x07, 0xb3, 0x1e, 0xfe, 0xd9, 0x92, 0xeb, 0x4b, 0xed, 0xfd, 0x0a, 0xc2, 0x63, 0x27, 0xa4, 0x88, 0x17, 0x60, 0x49, 0x35, 0xdc, 0x8e, 0xa5, 0x7d, 0xab, 0xd3, 0x28, 0x90, 0x50, 0xcd, 0xed, 0x2d, 0xda, 0x15, 0x55, 0x51, 0xf1, 0x1a, 0x0a, 0xf7, 0x39, 0x5d, 0xaa, 0x77, 0x6f, 0x01, 0x8e, 0xa7, 0x7d, 0xfa, 0xff, 0x00, 0x66, 0x10, 0xa8, 0xb8, 0x63, 0x76, 0x90, 0xa8, 0x20, 0x06, 0x56, 0xdb, 0x61, 0xda, 0xbd, 0x4f, 0xcb, 0x24, 0x15, 0x0f, 0xf5, 0x66, 0xe5, 0x5f, 0x4c, 0x53, 0xc3, 0xb7, 0xce, 0x99, 0x6b, 0x17, 0xff, 0xd4, 0xf0, 0xec, 0x57, 0x6f, 0x32, 0xa5, 0xa4, 0x43, 0x76, 0x75, 0xa9, 0xf1, 0x03, 0xfa, 0x64, 0x08, 0x6c, 0x8e, 0xfb, 0x3d, 0x7f, 0xcb, 0x16, 0x2b, 0x3d, 0xbc, 0x16, 0xa3, 0x66, 0x6d, 0x98, 0xfb, 0x1e, 0xb9, 0xac, 0xc8, 0x77, 0xb7, 0x7d, 0x01, 0xb3, 0x37, 0xb8, 0xd3, 0x46, 0x95, 0x68, 0x86, 0xd2, 0x2e, 0x4e, 0xab, 0xf0, 0x23, 0x11, 0x4e, 0x5f, 0xcd, 0x98, 0xe7, 0x25, 0x96, 0x71, 0x83, 0x0f, 0xd6, 0x3c, 0xb9, 0xe7, 0x0d, 0x7c, 0x41, 0x22, 0x5e, 0xb3, 0x20, 0x0c, 0x65, 0x80, 0xc8, 0x63, 0x8e, 0xbb, 0x95, 0xa5, 0x07, 0xeb, 0xcc, 0xac, 0x73, 0x83, 0x4e, 0x5c, 0x59, 0x09, 0xd8, 0xec, 0xc8, 0x57, 0x41, 0xd3, 0x4e, 0x95, 0xa5, 0x5b, 0x4b, 0x6a, 0xcb, 0xab, 0x43, 0x10, 0x4b, 0xeb, 0x85, 0xa2, 0x2c, 0x8e, 0x3f, 0x68, 0x54, 0xf5, 0x00, 0xd3, 0x97, 0x7a, 0x65, 0x79, 0xa6, 0x24, 0x76, 0x6f, 0xd3, 0x62, 0x96, 0x30, 0x78, 0xcb, 0x21, 0xf2, 0xf4, 0x22, 0xce, 0x54, 0x8e, 0x46, 0x26, 0x10, 0x7e, 0x0a, 0xf5, 0xd8, 0xf5, 0x1f, 0x31, 0x98, 0x83, 0x73, 0xb3, 0x91, 0xcd, 0x67, 0xe6, 0x7d, 0xe8, 0x16, 0x69, 0x6f, 0x10, 0x1f, 0x54, 0x9a, 0x37, 0xf5, 0x41, 0x5e, 0x7f, 0x0a, 0x29, 0x62, 0x02, 0xf8, 0x9c, 0xc8, 0x8c, 0x77, 0x6a, 0x99, 0xa0, 0x89, 0xff, 0x00, 0x9c, 0x74, 0xd2, 0xed, 0xed, 0xfc, 0xbb, 0x7b, 0xaa, 0x9a, 0x7d, 0x62, 0xfe, 0x46, 0x2d, 0xfe, 0x4c, 0x51, 0x31, 0x11, 0xa9, 0xf6, 0xef, 0x9b, 0x30, 0x5e, 0x7b, 0x38, 0xdd, 0xf4, 0x7f, 0x95, 0x94, 0xbc, 0x12, 0x43, 0x30, 0x6a, 0xb2, 0xf3, 0x86, 0x40, 0x3e, 0xcb, 0xd7, 0x6a, 0xd7, 0xb1, 0xe9, 0x8f, 0x37, 0x19, 0x97, 0x41, 0x2c, 0x71, 0x20, 0xf5, 0x36, 0x9c, 0x55, 0x78, 0x1d, 0x8a, 0x91, 0xd7, 0x11, 0x14, 0x5a, 0x3e, 0x19, 0x03, 0x10, 0x6b, 0xca, 0xbd, 0x86, 0xf8, 0x9d, 0x95, 0x18, 0x36, 0x65, 0x2e, 0xbc, 0x54, 0x1f, 0xa2, 0x99, 0x00, 0x59, 0x2a, 0x6f, 0x5e, 0x55, 0x15, 0xe9, 0x5f, 0xc3, 0x2f, 0xb6, 0x14, 0xff, 0x00, 0xff, 0xd5, 0xf1, 0x95, 0xfe, 0x80, 0x74, 0x0d, 0x7c, 0xd9, 0x89, 0x3d, 0x78, 0x57, 0x8b, 0xc5, 0x28, 0xe8, 0x55, 0xf7, 0x1f, 0x48, 0xca, 0x38, 0xb8, 0x83, 0x9f, 0x93, 0x07, 0x85, 0x3a, 0x7a, 0x6f, 0x95, 0x66, 0x2b, 0x2c, 0x4c, 0x0d, 0x14, 0x00, 0x3e, 0x9c, 0xc3, 0x98, 0x76, 0xb8, 0x45, 0xbd, 0x02, 0xde, 0x48, 0xee, 0xdc, 0xa0, 0x15, 0xe2, 0x2b, 0xc8, 0x8a, 0x8a, 0xfd, 0x3b, 0x66, 0x3f, 0x00, 0x73, 0x84, 0x2d, 0x36, 0xb5, 0xb5, 0x9e, 0x35, 0x1c, 0x29, 0xc4, 0xfe, 0xc8, 0x04, 0x7f, 0xc4, 0x69, 0x91, 0xe1, 0x67, 0x2c, 0x4a, 0xd2, 0xe9, 0x4e, 0xe3, 0xd4, 0xf4, 0x81, 0x5a, 0x12, 0xc5, 0x41, 0x3f, 0x79, 0x38, 0x9b, 0x60, 0x20, 0x07, 0x34, 0xb0, 0xc9, 0x03, 0x5c, 0x23, 0x03, 0x53, 0x13, 0x56, 0x88, 0xdf, 0x09, 0xda, 0x9b, 0xd3, 0xb6, 0x52, 0x0e, 0xec, 0xe4, 0x29, 0x24, 0xfc, 0xd0, 0xe7, 0x75, 0xe5, 0x57, 0x6b, 0x61, 0xfb, 0xf0, 0xca, 0xaa, 0x57, 0xa8, 0xe6, 0x78, 0x1a, 0x7d, 0xf9, 0x95, 0x8a, 0x5e, 0xa0, 0xe3, 0x67, 0x8f, 0xa0, 0xbd, 0x5b, 0xf2, 0xdf, 0x4a, 0x82, 0xcb, 0x4a, 0xb3, 0xb0, 0xb4, 0x41, 0x0a, 0x70, 0x48, 0xd9, 0x57, 0x60, 0x51, 0x3a, 0x8f, 0xbc, 0xe6, 0x7b, 0xcb, 0xe4, 0x3b, 0xa7, 0x3f, 0x9b, 0x9f, 0x9a, 0xba, 0x77, 0xe5, 0x5f, 0x95, 0x9c, 0x59, 0x94, 0x9f, 0xcd, 0x37, 0x8c, 0xa9, 0xa6, 0xd9, 0x39, 0xaa, 0xd0, 0x7d, 0xa9, 0x1c, 0x03, 0x5e, 0x09, 0xff, 0x00, 0x0c, 0x76, 0xcb, 0x62, 0x2d, 0xa5, 0xf2, 0x85, 0xbf, 0xe7, 0x87, 0xe6, 0xa3, 0x5e, 0x4d, 0xa8, 0xc9, 0xe6, 0x8b, 0xd5, 0x69, 0x5c, 0xb0, 0x4a, 0xab, 0xc4, 0xb5, 0x35, 0x0a, 0xaa, 0xea, 0x40, 0x03, 0xa0, 0xf6, 0xcb, 0x40, 0x4d, 0x3e, 0xdb, 0xff, 0x00, 0x9c, 0x7f, 0xfc, 0xce, 0x4f, 0xcc, 0xbf, 0x26, 0x25, 0xe5, 0xd3, 0x2f, 0xe9, 0xdd, 0x3d, 0xfe, 0xab, 0xa9, 0xaa, 0xd2, 0xa6, 0x40, 0x2a, 0xb2, 0x71, 0x00, 0x01, 0xea, 0x0d, 0xe8, 0x3a, 0x64, 0x25, 0x16, 0x1c, 0x8b, 0xd9, 0x51, 0x39, 0x28, 0x12, 0x51, 0x41, 0xfd, 0xa3, 0xd2, 0xb9, 0x4f, 0x0d, 0x33, 0xb5, 0xf4, 0x87, 0x9d, 0x79, 0x0e, 0xb4, 0xaf, 0x6a, 0xf8, 0xf1, 0xf0, 0xc9, 0xda, 0xbf, 0xff, 0xd6, 0xf2, 0xc6, 0xb5, 0x68, 0x64, 0xd0, 0x6d, 0x35, 0x20, 0x39, 0xcd, 0x13, 0x0f, 0x5e, 0x61, 0xfc, 0x8f, 0x40, 0x8b, 0x5e, 0xe0, 0x66, 0x1c, 0x4f, 0xaa, 0x9d, 0xe6, 0xa6, 0x1e, 0x91, 0x2e, 0xa9, 0x87, 0x95, 0xee, 0x9c, 0xc5, 0x55, 0x34, 0x60, 0x40, 0xae, 0x57, 0x30, 0xd9, 0xa7, 0x95, 0xbd, 0x6f, 0xcb, 0x26, 0x39, 0x40, 0x0d, 0x4e, 0xc0, 0x9f, 0x9e, 0x50, 0x5d, 0xac, 0x79, 0x33, 0x8b, 0xbb, 0x9b, 0x3b, 0x6b, 0x35, 0x48, 0x54, 0x09, 0x29, 0x56, 0x7f, 0xe1, 0x86, 0x72, 0x00, 0x2c, 0x6e, 0xf7, 0x63, 0x3e, 0x63, 0xbd, 0xbd, 0x5d, 0x20, 0x2a, 0xb3, 0xa4, 0x33, 0x48, 0xab, 0x21, 0x43, 0xf1, 0x2c, 0x47, 0xed, 0x1d, 0xbc, 0x73, 0x18, 0x9b, 0x64, 0x28, 0x96, 0x3a, 0xc7, 0x49, 0xb0, 0xf4, 0xcc, 0xe9, 0x73, 0x6c, 0xb4, 0xf8, 0x67, 0x92, 0x32, 0x21, 0x70, 0x7b, 0x89, 0x05, 0x57, 0xef, 0x38, 0x28, 0x94, 0x4a, 0x7d, 0x13, 0x7d, 0x6a, 0xd3, 0x4c, 0xb8, 0xf2, 0xc3, 0xc8, 0x2e, 0x03, 0xf3, 0xe2, 0x7d, 0x33, 0xb7, 0xc5, 0xcc, 0x71, 0x03, 0xc6, 0xb9, 0x64, 0x06, 0xe2, 0x9a, 0xf2, 0x4f, 0xd2, 0x6d, 0xe9, 0xfe, 0x41, 0x45, 0x5b, 0x18, 0x66, 0xa5, 0x64, 0x09, 0xf4, 0xd5, 0xb7, 0xcd, 0x93, 0xc7, 0xcf, 0x9b, 0xe5, 0x6f, 0xf9, 0xc8, 0x0d, 0x56, 0xeb, 0x59, 0xfc, 0xce, 0xd5, 0x12, 0x61, 0xc4, 0x69, 0xe9, 0x0d, 0xa4, 0x4b, 0xfe, 0x48, 0x40, 0xd5, 0x3e, 0xe4, 0xb6, 0x64, 0x8e, 0x4c, 0x02, 0x61, 0x65, 0xa0, 0x14, 0xb4, 0xb6, 0xb0, 0xb1, 0xb6, 0xb2, 0x97, 0xcb, 0xf1, 0x5a, 0x2d, 0xc6, 0xa5, 0xac, 0xb4, 0x70, 0x5d, 0xc7, 0x3d, 0xc1, 0x51, 0x24, 0x91, 0xc9, 0x31, 0x75, 0x6b, 0x70, 0x9f, 0x14, 0x68, 0x01, 0x46, 0xe4, 0xb5, 0xa3, 0x17, 0xcb, 0x40, 0x61, 0x6f, 0x47, 0xff, 0x00, 0x9c, 0x3a, 0x8f, 0x5b, 0x4f, 0x3c, 0x6b, 0xb7, 0xfa, 0x30, 0x91, 0x3c, 0xa4, 0xb1, 0x95, 0xb9, 0x82, 0x42, 0x0a, 0xbc, 0x8e, 0xe4, 0xdb, 0xa9, 0xef, 0xc9, 0x17, 0x91, 0x24, 0x7c, 0xb2, 0x05, 0x64, 0xfb, 0x75, 0x64, 0x32, 0x39, 0x69, 0x5b, 0x9c, 0xad, 0xb9, 0xdb, 0xa7, 0xb5, 0x3b, 0x53, 0x2a, 0x21, 0x41, 0x44, 0xf3, 0x8b, 0x8f, 0x2e, 0x43, 0x9d, 0x2b, 0xd4, 0x57, 0x23, 0x41, 0x36, 0xff, 0x00, 0xff, 0xd7, 0xf0, 0xc0, 0xd5, 0xb5, 0x11, 0x64, 0xb6, 0x3f, 0x59, 0x90, 0xd9, 0xab, 0x06, 0xf4, 0x79, 0x7c, 0x3b, 0x74, 0xc8, 0x08, 0x8b, 0xb6, 0xe3, 0x96, 0x55, 0x57, 0xb3, 0x3e, 0xf2, 0x35, 0xc7, 0xd6, 0x0b, 0x45, 0x5d, 0xdc, 0x8a, 0x7d, 0xd9, 0x8d, 0x94, 0x3b, 0x3d, 0x1c, 0x9e, 0xc3, 0xe5, 0xc3, 0x2c, 0x7c, 0xc5, 0x0f, 0xee, 0xdb, 0x8b, 0x0c, 0xc4, 0x26, 0x9d, 0xa0, 0x9a, 0x7d, 0x2c, 0xe5, 0xe4, 0x55, 0x7f, 0xee, 0xc1, 0x15, 0x04, 0xd0, 0x12, 0x3c, 0x72, 0x89, 0x1b, 0x2c, 0xcc, 0xa8, 0x2a, 0x8b, 0x87, 0xbb, 0x63, 0x1a, 0x28, 0x65, 0xf0, 0xed, 0xf2, 0xc3, 0xc2, 0x0a, 0x06, 0x4a, 0x46, 0xc7, 0xa5, 0xa3, 0x59, 0xc8, 0xb2, 0xc7, 0x45, 0x22, 0x9c, 0x14, 0x54, 0x10, 0x46, 0xf5, 0x1d, 0x32, 0x5c, 0x14, 0x14, 0xe4, 0x32, 0x2f, 0x3a, 0xf3, 0xb6, 0x90, 0x9a, 0x6d, 0xae, 0x9f, 0x3d, 0xab, 0xb8, 0x8a, 0x3b, 0xf8, 0x39, 0x44, 0x58, 0xf0, 0x08, 0xd5, 0x14, 0xa5, 0x7b, 0x65, 0x98, 0x8e, 0xfb, 0xb5, 0x67, 0x87, 0xa5, 0xef, 0x5e, 0x44, 0x96, 0x35, 0xb5, 0xb6, 0x59, 0x36, 0xfd, 0xd8, 0xa0, 0xf1, 0x20, 0x53, 0x33, 0xc0, 0x79, 0x59, 0x73, 0x7c, 0xd7, 0xf9, 0xfb, 0xa2, 0xcd, 0x67, 0xf9, 0xa7, 0x7b, 0x72, 0xf1, 0x71, 0x83, 0x53, 0x86, 0x0b, 0x98, 0x24, 0x22, 0x8a, 0xcc, 0x88, 0x23, 0x7f, 0xb8, 0xae, 0xf9, 0x7c, 0x50, 0x1e, 0x5f, 0x7c, 0x48, 0x21, 0x44, 0x6b, 0xce, 0x9b, 0xb0, 0x1b, 0x9e, 0xf5, 0xaf, 0x8e, 0x4d, 0x5f, 0x7a, 0x7f, 0xce, 0x34, 0xf9, 0x5d, 0x3c, 0xa3, 0xf9, 0x69, 0x63, 0xa9, 0x3c, 0x27, 0xeb, 0xda, 0xe1, 0x37, 0xd7, 0x2e, 0xaa, 0xdb, 0x06, 0xda, 0x30, 0x49, 0xfe, 0x54, 0x03, 0x03, 0x49, 0xdc, 0xb3, 0xaf, 0x38, 0xfe, 0x6a, 0xf9, 0x47, 0xc9, 0x3a, 0x74, 0x97, 0xfa, 0xf6, 0xaf, 0x15, 0x85, 0xb8, 0x75, 0x89, 0xb8, 0x87, 0x9a, 0x72, 0xee, 0x2a, 0x14, 0x24, 0x60, 0xb1, 0xa8, 0xdf, 0x07, 0x0b, 0x2d, 0xcb, 0xcf, 0x7f, 0xe8, 0x6a, 0xff, 0x00, 0x26, 0xbd, 0x6a, 0x7f, 0x89, 0x2f, 0xf8, 0x52, 0x9e, 0xb7, 0xe8, 0xb9, 0xb8, 0x57, 0xc2, 0x95, 0xe9, 0x8f, 0x08, 0x5a, 0x2f, 0xff, 0xd0, 0xf0, 0x4d, 0x40, 0xaa, 0xd7, 0x00, 0x64, 0xcb, 0x3c, 0x97, 0xa8, 0xb5, 0x9e, 0xa3, 0x1a, 0xd6, 0x84, 0x95, 0x3f, 0x45, 0x72, 0x9c, 0xa2, 0xc3, 0x99, 0xa5, 0x9d, 0x49, 0xf4, 0x17, 0x97, 0xaf, 0x63, 0x17, 0x52, 0x6f, 0xf0, 0xc8, 0x43, 0x6f, 0x9a, 0xe9, 0x07, 0x70, 0x0e, 0xec, 0x83, 0x51, 0x44, 0xb8, 0x61, 0x1a, 0x9e, 0x11, 0xd3, 0x91, 0x60, 0x68, 0x6b, 0xd3, 0x31, 0x4f, 0x36, 0xd3, 0x4c, 0x52, 0xef, 0x4c, 0xd5, 0x0c, 0xc4, 0x69, 0xda, 0x94, 0xc8, 0x3a, 0xf0, 0x66, 0x07, 0x73, 0xe0, 0x40, 0xfd, 0x79, 0x93, 0x12, 0x1c, 0x9c, 0x32, 0xc7, 0xfc, 0x41, 0x33, 0xd2, 0xb4, 0x6f, 0x38, 0x98, 0x65, 0x76, 0xbf, 0x69, 0x42, 0xd0, 0xaa, 0xc9, 0xde, 0x95, 0xad, 0x28, 0x46, 0x4e, 0xac, 0x39, 0x77, 0x80, 0x11, 0xbf, 0xd8, 0xc7, 0x7c, 0xe1, 0xa5, 0xf9, 0x92, 0x4d, 0x32, 0x5b, 0x8b, 0x93, 0x27, 0xa7, 0x68, 0x56, 0xe2, 0x45, 0xda, 0x85, 0x61, 0x6e, 0x67, 0xad, 0x6b, 0xb0, 0x38, 0xc2, 0x81, 0xe4, 0xc7, 0x52, 0x31, 0x1c, 0x67, 0x86, 0x5b, 0xbd, 0x37, 0xca, 0x7a, 0x94, 0xb1, 0x69, 0xb6, 0x2e, 0xb7, 0x15, 0x48, 0xc2, 0xb4, 0x52, 0x53, 0xac, 0x32, 0xaf, 0xb1, 0xed, 0x9b, 0x10, 0x36, 0x78, 0x5c, 0x9f, 0x51, 0x64, 0x1f, 0x98, 0x3e, 0x58, 0xb6, 0xfc, 0xc8, 0xf2, 0xe5, 0xbc, 0x68, 0x52, 0x2d, 0x5a, 0xd1, 0x84, 0xb6, 0xf3, 0x95, 0x0e, 0xc0, 0x85, 0xe2, 0xcb, 0xd8, 0xd1, 0xbb, 0xe4, 0xc1, 0xa6, 0x97, 0xce, 0x17, 0x5f, 0x95, 0xde, 0x6d, 0xb6, 0xbe, 0xb7, 0x69, 0x34, 0xf3, 0x3c, 0x72, 0xcf, 0xe8, 0xa3, 0x45, 0x49, 0x95, 0x4a, 0x90, 0x3e, 0x35, 0x5a, 0x95, 0x1d, 0xfe, 0x21, 0x93, 0x4d, 0xbe, 0xd2, 0xd2, 0xf5, 0x8b, 0xbd, 0x32, 0x2d, 0x3f, 0x4c, 0x9a, 0xe4, 0xca, 0x9e, 0x90, 0x85, 0x65, 0x55, 0x08, 0x85, 0x91, 0x01, 0x3b, 0x0a, 0x05, 0xe9, 0xb0, 0xc0, 0x5a, 0xc3, 0xcd, 0x3f, 0x3b, 0x7f, 0x26, 0xec, 0xff, 0x00, 0x35, 0x6d, 0x6d, 0xb5, 0x3d, 0x16, 0xfe, 0x0d, 0x3b, 0xcd, 0x96, 0x01, 0x92, 0x46, 0x9e, 0xa2, 0x0b, 0xc8, 0xb7, 0x28, 0x92, 0x71, 0xfb, 0x2e, 0xa7, 0xec, 0x3d, 0x0f, 0xc2, 0x68, 0x71, 0x05, 0x95, 0xd3, 0xe7, 0x9f, 0xfa, 0x16, 0x2f, 0xcd, 0x7f, 0x43, 0xd6, 0xfa, 0xa5, 0x97, 0xab, 0xeb, 0x7a, 0x5f, 0x55, 0xfa, 0xec, 0x5e, 0xaf, 0x0f, 0xf7, 0xed, 0x2b, 0x4e, 0x15, 0xff, 0x00, 0x65, 0xdf, 0x8e, 0x14, 0xf1, 0xbf, 0xff, 0xd1, 0xf0, 0x5a, 0xa7, 0x18, 0x5e, 0x56, 0x1f, 0x68, 0x71, 0x5f, 0xa7, 0xbe, 0x2a, 0x98, 0xdb, 0xfa, 0x90, 0x24, 0x37, 0xb0, 0xfd, 0xb8, 0xa8, 0x58, 0x78, 0xae, 0x43, 0xc9, 0xb4, 0x6d, 0xbb, 0xda, 0x3c, 0xa1, 0xad, 0x43, 0xa8, 0xda, 0xc5, 0x2a, 0x3d, 0x26, 0x5a, 0x02, 0x2b, 0xbe, 0x60, 0x64, 0x8d, 0x17, 0x6f, 0x8b, 0x20, 0x90, 0x7a, 0x3c, 0x32, 0x8b, 0xa8, 0x02, 0xf3, 0xfd, 0xe0, 0x1b, 0x11, 0x98, 0x66, 0x3b, 0xb9, 0x62, 0x54, 0x83, 0x36, 0xf2, 0xa4, 0xe4, 0x29, 0x34, 0xeb, 0xc8, 0x74, 0xae, 0x0d, 0xc3, 0x65, 0x82, 0x13, 0x6b, 0x57, 0xba, 0x54, 0xe4, 0x8c, 0x41, 0x1b, 0x75, 0xa7, 0xe0, 0x72, 0x5c, 0x4c, 0x84, 0x50, 0x5a, 0xb3, 0xdd, 0xdd, 0xc3, 0x24, 0x33, 0xb1, 0x60, 0xe0, 0x86, 0x52, 0x45, 0x38, 0xd2, 0x87, 0x24, 0x26, 0x6d, 0x8c, 0xe1, 0x41, 0x25, 0xfc, 0xa3, 0xd7, 0x2f, 0x6f, 0x3c, 0xbf, 0x73, 0xa5, 0xb2, 0x2c, 0xd1, 0x69, 0x17, 0x2f, 0x6b, 0x14, 0x8c, 0x0f, 0x21, 0x0d, 0x79, 0x46, 0x09, 0x15, 0xed, 0xb7, 0x4e, 0xd9, 0xb9, 0x8b, 0xcb, 0xe4, 0xa2, 0x5e, 0xa3, 0xa6, 0xdf, 0x6a, 0x36, 0xe4, 0xcd, 0x69, 0x1c, 0x4e, 0x84, 0x7c, 0x76, 0xab, 0x21, 0x67, 0xa8, 0xa7, 0xd9, 0xf8, 0x4d, 0x2b, 0xf3, 0xc3, 0x4d, 0x49, 0x57, 0x98, 0x75, 0x6f, 0x31, 0xda, 0xf9, 0xa3, 0x4b, 0xfd, 0x1f, 0x69, 0x1d, 0xae, 0xa1, 0xa9, 0x7e, 0xee, 0xe6, 0xd2, 0x79, 0x18, 0xf3, 0xb5, 0x1f, 0xee, 0xd9, 0x0a, 0x01, 0x4e, 0x3f, 0xb3, 0x4d, 0xf2, 0x9c, 0xb9, 0x04, 0x05, 0xb7, 0xe2, 0x87, 0x1e, 0xdd, 0x19, 0x3e, 0xaf, 0x6b, 0xae, 0xcb, 0x6d, 0x13, 0x0d, 0x45, 0xa2, 0x8e, 0x06, 0xe5, 0x13, 0x2a, 0x02, 0x01, 0x5e, 0x82, 0xb5, 0x04, 0xe6, 0x11, 0xd4, 0xcd, 0xda, 0x43, 0x49, 0x8e, 0xb7, 0xdc, 0xb1, 0x51, 0xe6, 0x4d, 0x76, 0xd2, 0x61, 0x15, 0xaa, 0x4b, 0xa8, 0xc9, 0x6e, 0x49, 0x79, 0x20, 0xe6, 0x8c, 0x49, 0xad, 0x43, 0x16, 0xe4, 0xa7, 0xaf, 0x43, 0xd3, 0x26, 0x35, 0x75, 0xcd, 0xa8, 0xe8, 0x87, 0x46, 0xbf, 0xc7, 0x9a, 0xff, 0x00, 0xd6, 0xbf, 0x48, 0xfe, 0x88, 0xfd, 0xe7, 0x0f, 0xab, 0xfa, 0x3f, 0x58, 0x7f, 0x5f, 0x8d, 0x3f, 0x9f, 0xa7, 0x5e, 0xd4, 0xc3, 0xf9, 0xd1, 0x7c, 0xb6, 0x47, 0xe4, 0x3a, 0x5b, 0xff, 0xd2, 0xf0, 0xb7, 0xa6, 0x1e, 0xdf, 0xd3, 0xf6, 0xa5, 0x71, 0x54, 0xdb, 0x4b, 0x80, 0x3c, 0x42, 0x26, 0xee, 0x29, 0xbe, 0x51, 0x23, 0x4e, 0x44, 0x05, 0x84, 0x45, 0xa5, 0xd5, 0xf7, 0x97, 0x2e, 0xfd, 0x6b, 0x6a, 0x98, 0x09, 0xab, 0xc7, 0xfc, 0x46, 0x3b, 0x4c, 0x26, 0x32, 0x30, 0x3e, 0x4f, 0x49, 0xd0, 0xfc, 0xfb, 0x05, 0xd4, 0x4a, 0x7d, 0x40, 0xac, 0x3a, 0x8e, 0x84, 0x1c, 0xc5, 0x96, 0x2a, 0x73, 0xe1, 0x9c, 0x16, 0x6d, 0xa5, 0x79, 0x86, 0xd6, 0xec, 0x80, 0x5a, 0xa0, 0xf5, 0xca, 0xcc, 0x5c, 0xa1, 0x2b, 0x1b, 0x26, 0x30, 0x6a, 0x31, 0x46, 0xcf, 0x1c, 0x87, 0x94, 0x64, 0x9e, 0x3d, 0xb6, 0xf0, 0xca, 0xa8, 0x39, 0x51, 0x99, 0x42, 0x6b, 0x1a, 0xc5, 0xa5, 0xa5, 0x94, 0xf7, 0x92, 0xc8, 0xaa, 0xb1, 0x23, 0x30, 0x04, 0xf8, 0x0e, 0x9f, 0x4e, 0x4a, 0x11, 0xb2, 0xd5, 0x9b, 0x25, 0x06, 0x1b, 0xff, 0x00, 0x38, 0xfd, 0xad, 0xdf, 0xda, 0xf9, 0xa2, 0xfe, 0xc5, 0x42, 0xbe, 0x9b, 0x7f, 0x0b, 0xdd, 0xdd, 0x07, 0xaf, 0x14, 0x68, 0xd8, 0x71, 0x6d, 0xbb, 0x90, 0xfc, 0x73, 0x6e, 0xf2, 0xf2, 0xdd, 0xf4, 0xad, 0xa6, 0xab, 0x6d, 0x69, 0x14, 0xfa, 0xee, 0xa0, 0xe2, 0x0b, 0x0d, 0x39, 0x19, 0xfe, 0x11, 0xc5, 0x1a, 0x4a, 0x1d, 0x8f, 0x73, 0x4f, 0xf8, 0x96, 0x0b, 0x40, 0x8d, 0xec, 0xf3, 0x6d, 0x3f, 0x52, 0xba, 0xd6, 0x35, 0x8b, 0xbf, 0x36, 0x6a, 0x5f, 0x0d, 0xc5, 0xdc, 0xa8, 0xb6, 0xa8, 0x7a, 0xc5, 0x6c, 0x9b, 0x22, 0x0f, 0xa3, 0x73, 0x9a, 0xbc, 0xb3, 0xe2, 0x36, 0xed, 0xb1, 0x43, 0x80, 0x53, 0xd0, 0xa7, 0xd4, 0x44, 0xfa, 0x7a, 0xda, 0x83, 0xbd, 0x3e, 0x2f, 0xa7, 0x2b, 0xad, 0x9b, 0xb8, 0x8d, 0xa8, 0xe8, 0x91, 0xdb, 0xfa, 0x2d, 0x6f, 0xc3, 0x8a, 0x2d, 0x56, 0xa3, 0xad, 0x4f, 0x5c, 0xa4, 0x0d, 0xdc, 0xa3, 0xca, 0xd0, 0xbf, 0xa1, 0xe3, 0xfa, 0xe7, 0x0f, 0xf2, 0xb9, 0x57, 0xbf, 0x1a, 0xe4, 0xb8, 0x57, 0xc5, 0xdd, 0xff, 0xd3, 0xf0, 0xcc, 0x5d, 0x7b, 0x70, 0xc5, 0x53, 0x6d, 0x2f, 0xd5, 0xe4, 0x69, 0xfd, 0xdf, 0xec, 0xd7, 0xad, 0x7d, 0xb2, 0x8c, 0x8d, 0xd8, 0xed, 0x91, 0x9f, 0x43, 0xea, 0xe7, 0xeb, 0x94, 0xad, 0x3e, 0x1e, 0x95, 0xfc, 0x72, 0x81, 0x7d, 0x1c, 0x9d, 0xba, 0xb1, 0x7b, 0xdf, 0xa9, 0x7a, 0xdf, 0xee, 0x2f, 0xd4, 0xfa, 0xe7, 0xed, 0x7a, 0x7f, 0xdd, 0xff, 0x00, 0xb2, 0xae, 0x64, 0x0b, 0xea, 0xe3, 0x9a, 0xbf, 0x4a, 0x6f, 0xa4, 0xff, 0x00, 0x89, 0xbd, 0x45, 0xfa, 0xb5, 0x79, 0xf7, 0xeb, 0xc7, 0xe9, 0xae, 0x57, 0x2e, 0x17, 0x23, 0x1f, 0x89, 0xd1, 0x99, 0x8f, 0xf1, 0xa7, 0x11, 0xcf, 0xd3, 0xf5, 0x29, 0xb5, 0x6b, 0xd3, 0xe8, 0xcc, 0x7f, 0x45, 0xb9, 0xa3, 0xc5, 0x62, 0xbe, 0x68, 0xff, 0x00, 0x15, 0xfd, 0x4c, 0xfe, 0x90, 0xaf, 0xd4, 0xab, 0xf1, 0x7a, 0x7f, 0x62, 0x9d, 0xab, 0xdf, 0x32, 0xb1, 0x70, 0x5e, 0xdc, 0xdc, 0x2d, 0x47, 0x8b, 0x5e, 0xae, 0x4c, 0xbf, 0xf2, 0x37, 0x9f, 0x3d, 0x5b, 0xd2, 0xff, 0x00, 0x8e, 0x87, 0xee, 0x29, 0x5a, 0xf2, 0xf4, 0xaa, 0xd4, 0xa5, 0x36, 0xa7, 0x3a, 0x57, 0xfd, 0x8e, 0x64, 0x3a, 0xf2, 0xf6, 0xbf, 0xcc, 0x7f, 0x5b, 0xfc, 0x23, 0xa7, 0xfe, 0x8e, 0xff, 0x00, 0x8e, 0x37, 0xd6, 0x63, 0xfa, 0xe5, 0x2b, 0xcb, 0x87, 0xec, 0xd6, 0xbd, 0xb9, 0x7d, 0xac, 0xc7, 0xcd, 0x7c, 0x2d, 0xf8, 0x2b, 0x89, 0x26, 0x8f, 0xd4, 0xfa, 0x94, 0x3e, 0x85, 0x29, 0xc9, 0x69, 0xfc, 0x33, 0x58, 0x5d, 0x9c, 0x79, 0xb2, 0xbb, 0x0f, 0xac, 0x7a, 0x2b, 0xea, 0x75, 0xef, 0x92, 0x0c, 0x53, 0x3d, 0x2f, 0xd4, 0xfa, 0xbb, 0xfa, 0x74, 0xf5, 0x39, 0x9a, 0xd7, 0xe7, 0x80, 0x53, 0x79, 0xba, 0x5b, 0xfe, 0x97, 0xfa, 0x4b, 0xfc, 0xba, 0x7f, 0xb1, 0xc7, 0xab, 0x1e, 0x8f, 0xff, 0xd9 +}; diff --git a/webrtc/base/testclient.cc b/webrtc/base/testclient.cc new file mode 100644 index 000000000..32670e21a --- /dev/null +++ b/webrtc/base/testclient.cc @@ -0,0 +1,148 @@ +/* + * Copyright 2004 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 "webrtc/base/testclient.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +// DESIGN: Each packet received is put it into a list of packets. +// Callers can retrieve received packets from any thread by calling +// NextPacket. + +TestClient::TestClient(AsyncPacketSocket* socket) + : socket_(socket), ready_to_send_(false) { + packets_ = new std::vector(); + socket_->SignalReadPacket.connect(this, &TestClient::OnPacket); + socket_->SignalReadyToSend.connect(this, &TestClient::OnReadyToSend); +} + +TestClient::~TestClient() { + delete socket_; + for (unsigned i = 0; i < packets_->size(); i++) + delete (*packets_)[i]; + delete packets_; +} + +bool TestClient::CheckConnState(AsyncPacketSocket::State state) { + // Wait for our timeout value until the socket reaches the desired state. + uint32 end = TimeAfter(kTimeout); + while (socket_->GetState() != state && TimeUntil(end) > 0) + Thread::Current()->ProcessMessages(1); + return (socket_->GetState() == state); +} + +int TestClient::Send(const char* buf, size_t size) { + rtc::PacketOptions options; + return socket_->Send(buf, size, options); +} + +int TestClient::SendTo(const char* buf, size_t size, + const SocketAddress& dest) { + rtc::PacketOptions options; + return socket_->SendTo(buf, size, dest, options); +} + +TestClient::Packet* TestClient::NextPacket() { + // If no packets are currently available, we go into a get/dispatch loop for + // at most 1 second. If, during the loop, a packet arrives, then we can stop + // early and return it. + + // Note that the case where no packet arrives is important. We often want to + // test that a packet does not arrive. + + // Note also that we only try to pump our current thread's message queue. + // Pumping another thread's queue could lead to messages being dispatched from + // the wrong thread to non-thread-safe objects. + + uint32 end = TimeAfter(kTimeout); + while (TimeUntil(end) > 0) { + { + CritScope cs(&crit_); + if (packets_->size() != 0) { + break; + } + } + Thread::Current()->ProcessMessages(1); + } + + // Return the first packet placed in the queue. + Packet* packet = NULL; + CritScope cs(&crit_); + if (packets_->size() > 0) { + packet = packets_->front(); + packets_->erase(packets_->begin()); + } + + return packet; +} + +bool TestClient::CheckNextPacket(const char* buf, size_t size, + SocketAddress* addr) { + bool res = false; + Packet* packet = NextPacket(); + if (packet) { + res = (packet->size == size && memcmp(packet->buf, buf, size) == 0); + if (addr) + *addr = packet->addr; + delete packet; + } + return res; +} + +bool TestClient::CheckNoPacket() { + bool res; + Packet* packet = NextPacket(); + res = (packet == NULL); + delete packet; + return res; +} + +int TestClient::GetError() { + return socket_->GetError(); +} + +int TestClient::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +bool TestClient::ready_to_send() const { + return ready_to_send_; +} + +void TestClient::OnPacket(AsyncPacketSocket* socket, const char* buf, + size_t size, const SocketAddress& remote_addr, + const PacketTime& packet_time) { + CritScope cs(&crit_); + packets_->push_back(new Packet(remote_addr, buf, size)); +} + +void TestClient::OnReadyToSend(AsyncPacketSocket* socket) { + ready_to_send_ = true; +} + +TestClient::Packet::Packet(const SocketAddress& a, const char* b, size_t s) + : addr(a), buf(0), size(s) { + buf = new char[size]; + memcpy(buf, b, size); +} + +TestClient::Packet::Packet(const Packet& p) + : addr(p.addr), buf(0), size(p.size) { + buf = new char[size]; + memcpy(buf, p.buf, size); +} + +TestClient::Packet::~Packet() { + delete[] buf; +} + +} // namespace rtc diff --git a/webrtc/base/testclient.h b/webrtc/base/testclient.h new file mode 100644 index 000000000..d56f948b0 --- /dev/null +++ b/webrtc/base/testclient.h @@ -0,0 +1,93 @@ +/* + * Copyright 2004 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_BASE_TESTCLIENT_H_ +#define WEBRTC_BASE_TESTCLIENT_H_ + +#include +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +// A simple client that can send TCP or UDP data and check that it receives +// what it expects to receive. Useful for testing server functionality. +class TestClient : public sigslot::has_slots<> { + public: + // Records the contents of a packet that was received. + struct Packet { + Packet(const SocketAddress& a, const char* b, size_t s); + Packet(const Packet& p); + virtual ~Packet(); + + SocketAddress addr; + char* buf; + size_t size; + }; + + // Creates a client that will send and receive with the given socket and + // will post itself messages with the given thread. + explicit TestClient(AsyncPacketSocket* socket); + ~TestClient(); + + SocketAddress address() const { return socket_->GetLocalAddress(); } + SocketAddress remote_address() const { return socket_->GetRemoteAddress(); } + + // Checks that the socket moves to the specified connect state. + bool CheckConnState(AsyncPacketSocket::State state); + + // Checks that the socket is connected to the remote side. + bool CheckConnected() { + return CheckConnState(AsyncPacketSocket::STATE_CONNECTED); + } + + // Sends using the clients socket. + int Send(const char* buf, size_t size); + + // Sends using the clients socket to the given destination. + int SendTo(const char* buf, size_t size, const SocketAddress& dest); + + // Returns the next packet received by the client or 0 if none is received + // within a reasonable amount of time. The caller must delete the packet + // when done with it. + Packet* NextPacket(); + + // Checks that the next packet has the given contents. Returns the remote + // address that the packet was sent from. + bool CheckNextPacket(const char* buf, size_t len, SocketAddress* addr); + + // Checks that no packets have arrived or will arrive in the next second. + bool CheckNoPacket(); + + int GetError(); + int SetOption(Socket::Option opt, int value); + + bool ready_to_send() const; + + private: + static const int kTimeout = 1000; + // Workaround for the fact that AsyncPacketSocket::GetConnState doesn't exist. + Socket::ConnState GetState(); + // Slot for packets read on the socket. + void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t len, + const SocketAddress& remote_addr, + const PacketTime& packet_time); + void OnReadyToSend(AsyncPacketSocket* socket); + + CriticalSection crit_; + AsyncPacketSocket* socket_; + std::vector* packets_; + bool ready_to_send_; + DISALLOW_EVIL_CONSTRUCTORS(TestClient); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TESTCLIENT_H_ diff --git a/webrtc/base/testclient_unittest.cc b/webrtc/base/testclient_unittest.cc new file mode 100644 index 000000000..c28266867 --- /dev/null +++ b/webrtc/base/testclient_unittest.cc @@ -0,0 +1,77 @@ +/* + * Copyright 2006 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 "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testechoserver.h" +#include "webrtc/base/thread.h" + +using namespace rtc; + +void TestUdpInternal(const SocketAddress& loopback) { + Thread *main = Thread::Current(); + AsyncSocket* socket = main->socketserver() + ->CreateAsyncSocket(loopback.family(), SOCK_DGRAM); + socket->Bind(loopback); + + TestClient client(new AsyncUDPSocket(socket)); + SocketAddress addr = client.address(), from; + EXPECT_EQ(3, client.SendTo("foo", 3, addr)); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from)); + EXPECT_EQ(from, addr); + EXPECT_TRUE(client.CheckNoPacket()); +} + +void TestTcpInternal(const SocketAddress& loopback) { + Thread *main = Thread::Current(); + TestEchoServer server(main, loopback); + + AsyncSocket* socket = main->socketserver() + ->CreateAsyncSocket(loopback.family(), SOCK_STREAM); + AsyncTCPSocket* tcp_socket = AsyncTCPSocket::Create( + socket, loopback, server.address()); + ASSERT_TRUE(tcp_socket != NULL); + + TestClient client(tcp_socket); + SocketAddress addr = client.address(), from; + EXPECT_TRUE(client.CheckConnected()); + EXPECT_EQ(3, client.Send("foo", 3)); + EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from)); + EXPECT_EQ(from, server.address()); + EXPECT_TRUE(client.CheckNoPacket()); +} + +// Tests whether the TestClient can send UDP to itself. +TEST(TestClientTest, TestUdpIPv4) { + TestUdpInternal(SocketAddress("127.0.0.1", 0)); +} + +TEST(TestClientTest, TestUdpIPv6) { + if (HasIPv6Enabled()) { + TestUdpInternal(SocketAddress("::1", 0)); + } else { + LOG(LS_INFO) << "Skipping IPv6 test."; + } +} + +// Tests whether the TestClient can connect to a server and exchange data. +TEST(TestClientTest, TestTcpIPv4) { + TestTcpInternal(SocketAddress("127.0.0.1", 0)); +} + +TEST(TestClientTest, TestTcpIPv6) { + if (HasIPv6Enabled()) { + TestTcpInternal(SocketAddress("::1", 0)); + } else { + LOG(LS_INFO) << "Skipping IPv6 test."; + } +} diff --git a/webrtc/base/testechoserver.h b/webrtc/base/testechoserver.h new file mode 100644 index 000000000..733b320dd --- /dev/null +++ b/webrtc/base/testechoserver.h @@ -0,0 +1,73 @@ +/* + * Copyright 2004 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_BASE_TESTECHOSERVER_H_ +#define WEBRTC_BASE_TESTECHOSERVER_H_ + +#include +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +// A test echo server, echoes back any packets sent to it. +// Useful for unit tests. +class TestEchoServer : public sigslot::has_slots<> { + public: + TestEchoServer(Thread* thread, const SocketAddress& addr) + : server_socket_(thread->socketserver()->CreateAsyncSocket(addr.family(), + SOCK_STREAM)) { + server_socket_->Bind(addr); + server_socket_->Listen(5); + server_socket_->SignalReadEvent.connect(this, &TestEchoServer::OnAccept); + } + ~TestEchoServer() { + for (ClientList::iterator it = client_sockets_.begin(); + it != client_sockets_.end(); ++it) { + delete *it; + } + } + + SocketAddress address() const { return server_socket_->GetLocalAddress(); } + + private: + void OnAccept(AsyncSocket* socket) { + AsyncSocket* raw_socket = socket->Accept(NULL); + if (raw_socket) { + AsyncTCPSocket* packet_socket = new AsyncTCPSocket(raw_socket, false); + packet_socket->SignalReadPacket.connect(this, &TestEchoServer::OnPacket); + packet_socket->SignalClose.connect(this, &TestEchoServer::OnClose); + client_sockets_.push_back(packet_socket); + } + } + void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& remote_addr, + const PacketTime& packet_time) { + rtc::PacketOptions options; + socket->Send(buf, size, options); + } + void OnClose(AsyncPacketSocket* socket, int err) { + ClientList::iterator it = + std::find(client_sockets_.begin(), client_sockets_.end(), socket); + client_sockets_.erase(it); + Thread::Current()->Dispose(socket); + } + + typedef std::list ClientList; + scoped_ptr server_socket_; + ClientList client_sockets_; + DISALLOW_EVIL_CONSTRUCTORS(TestEchoServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TESTECHOSERVER_H_ diff --git a/webrtc/base/testutils.h b/webrtc/base/testutils.h new file mode 100644 index 000000000..74fed45cd --- /dev/null +++ b/webrtc/base/testutils.h @@ -0,0 +1,629 @@ +/* + * Copyright 2004 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_BASE_TESTUTILS_H__ +#define WEBRTC_BASE_TESTUTILS_H__ + +// Utilities for testing rtc infrastructure in unittests + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include +#include + +// X defines a few macros that stomp on types that gunit.h uses. +#undef None +#undef Bool +#endif + +#include +#include +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +namespace testing { + +using namespace rtc; + +/////////////////////////////////////////////////////////////////////////////// +// StreamSink - Monitor asynchronously signalled events from StreamInterface +// or AsyncSocket (which should probably be a StreamInterface. +/////////////////////////////////////////////////////////////////////////////// + +// Note: Any event that is an error is treaded as SSE_ERROR instead of that +// event. + +enum StreamSinkEvent { + SSE_OPEN = SE_OPEN, + SSE_READ = SE_READ, + SSE_WRITE = SE_WRITE, + SSE_CLOSE = SE_CLOSE, + SSE_ERROR = 16 +}; + +class StreamSink : public sigslot::has_slots<> { + public: + void Monitor(StreamInterface* stream) { + stream->SignalEvent.connect(this, &StreamSink::OnEvent); + events_.erase(stream); + } + void Unmonitor(StreamInterface* stream) { + stream->SignalEvent.disconnect(this); + // In case you forgot to unmonitor a previous object with this address + events_.erase(stream); + } + bool Check(StreamInterface* stream, StreamSinkEvent event, bool reset = true) { + return DoCheck(stream, event, reset); + } + int Events(StreamInterface* stream, bool reset = true) { + return DoEvents(stream, reset); + } + + void Monitor(AsyncSocket* socket) { + socket->SignalConnectEvent.connect(this, &StreamSink::OnConnectEvent); + socket->SignalReadEvent.connect(this, &StreamSink::OnReadEvent); + socket->SignalWriteEvent.connect(this, &StreamSink::OnWriteEvent); + socket->SignalCloseEvent.connect(this, &StreamSink::OnCloseEvent); + // In case you forgot to unmonitor a previous object with this address + events_.erase(socket); + } + void Unmonitor(AsyncSocket* socket) { + socket->SignalConnectEvent.disconnect(this); + socket->SignalReadEvent.disconnect(this); + socket->SignalWriteEvent.disconnect(this); + socket->SignalCloseEvent.disconnect(this); + events_.erase(socket); + } + bool Check(AsyncSocket* socket, StreamSinkEvent event, bool reset = true) { + return DoCheck(socket, event, reset); + } + int Events(AsyncSocket* socket, bool reset = true) { + return DoEvents(socket, reset); + } + + private: + typedef std::map EventMap; + + void OnEvent(StreamInterface* stream, int events, int error) { + if (error) { + events = SSE_ERROR; + } + AddEvents(stream, events); + } + void OnConnectEvent(AsyncSocket* socket) { + AddEvents(socket, SSE_OPEN); + } + void OnReadEvent(AsyncSocket* socket) { + AddEvents(socket, SSE_READ); + } + void OnWriteEvent(AsyncSocket* socket) { + AddEvents(socket, SSE_WRITE); + } + void OnCloseEvent(AsyncSocket* socket, int error) { + AddEvents(socket, (0 == error) ? SSE_CLOSE : SSE_ERROR); + } + + void AddEvents(void* obj, int events) { + EventMap::iterator it = events_.find(obj); + if (events_.end() == it) { + events_.insert(EventMap::value_type(obj, events)); + } else { + it->second |= events; + } + } + bool DoCheck(void* obj, StreamSinkEvent event, bool reset) { + EventMap::iterator it = events_.find(obj); + if ((events_.end() == it) || (0 == (it->second & event))) { + return false; + } + if (reset) { + it->second &= ~event; + } + return true; + } + int DoEvents(void* obj, bool reset) { + EventMap::iterator it = events_.find(obj); + if (events_.end() == it) + return 0; + int events = it->second; + if (reset) { + it->second = 0; + } + return events; + } + + EventMap events_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamSource - Implements stream interface and simulates asynchronous +// events on the stream, without a network. Also buffers written data. +/////////////////////////////////////////////////////////////////////////////// + +class StreamSource : public StreamInterface { +public: + StreamSource() { + Clear(); + } + + void Clear() { + readable_data_.clear(); + written_data_.clear(); + state_ = SS_CLOSED; + read_block_ = 0; + write_block_ = SIZE_UNKNOWN; + } + void QueueString(const char* data) { + QueueData(data, strlen(data)); + } + void QueueStringF(const char* format, ...) { + va_list args; + va_start(args, format); + char buffer[1024]; + size_t len = vsprintfn(buffer, sizeof(buffer), format, args); + ASSERT(len < sizeof(buffer) - 1); + va_end(args); + QueueData(buffer, len); + } + void QueueData(const char* data, size_t len) { + readable_data_.insert(readable_data_.end(), data, data + len); + if ((SS_OPEN == state_) && (readable_data_.size() == len)) { + SignalEvent(this, SE_READ, 0); + } + } + std::string ReadData() { + std::string data; + // avoid accessing written_data_[0] if it is undefined + if (written_data_.size() > 0) { + data.insert(0, &written_data_[0], written_data_.size()); + } + written_data_.clear(); + return data; + } + void SetState(StreamState state) { + int events = 0; + if ((SS_OPENING == state_) && (SS_OPEN == state)) { + events |= SE_OPEN; + if (!readable_data_.empty()) { + events |= SE_READ; + } + } else if ((SS_CLOSED != state_) && (SS_CLOSED == state)) { + events |= SE_CLOSE; + } + state_ = state; + if (events) { + SignalEvent(this, events, 0); + } + } + // Will cause Read to block when there are pos bytes in the read queue. + void SetReadBlock(size_t pos) { read_block_ = pos; } + // Will cause Write to block when there are pos bytes in the write queue. + void SetWriteBlock(size_t pos) { write_block_ = pos; } + + virtual StreamState GetState() const { return state_; } + virtual StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (SS_CLOSED == state_) { + if (error) *error = -1; + return SR_ERROR; + } + if ((SS_OPENING == state_) || (readable_data_.size() <= read_block_)) { + return SR_BLOCK; + } + size_t count = _min(buffer_len, readable_data_.size() - read_block_); + memcpy(buffer, &readable_data_[0], count); + size_t new_size = readable_data_.size() - count; + // Avoid undefined access beyond the last element of the vector. + // This only happens when new_size is 0. + if (count < readable_data_.size()) { + memmove(&readable_data_[0], &readable_data_[count], new_size); + } + readable_data_.resize(new_size); + if (read) *read = count; + return SR_SUCCESS; + } + virtual StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (SS_CLOSED == state_) { + if (error) *error = -1; + return SR_ERROR; + } + if (SS_OPENING == state_) { + return SR_BLOCK; + } + if (SIZE_UNKNOWN != write_block_) { + if (written_data_.size() >= write_block_) { + return SR_BLOCK; + } + if (data_len > (write_block_ - written_data_.size())) { + data_len = write_block_ - written_data_.size(); + } + } + if (written) *written = data_len; + const char* cdata = static_cast(data); + written_data_.insert(written_data_.end(), cdata, cdata + data_len); + return SR_SUCCESS; + } + virtual void Close() { state_ = SS_CLOSED; } + +private: + typedef std::vector Buffer; + Buffer readable_data_, written_data_; + StreamState state_; + size_t read_block_, write_block_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SocketTestClient +// Creates a simulated client for testing. Works on real and virtual networks. +/////////////////////////////////////////////////////////////////////////////// + +class SocketTestClient : public sigslot::has_slots<> { +public: + SocketTestClient() { + Init(NULL, AF_INET); + } + SocketTestClient(AsyncSocket* socket) { + Init(socket, socket->GetLocalAddress().family()); + } + SocketTestClient(const SocketAddress& address) { + Init(NULL, address.family()); + socket_->Connect(address); + } + + AsyncSocket* socket() { return socket_.get(); } + + void QueueString(const char* data) { + QueueData(data, strlen(data)); + } + void QueueStringF(const char* format, ...) { + va_list args; + va_start(args, format); + char buffer[1024]; + size_t len = vsprintfn(buffer, sizeof(buffer), format, args); + ASSERT(len < sizeof(buffer) - 1); + va_end(args); + QueueData(buffer, len); + } + void QueueData(const char* data, size_t len) { + send_buffer_.insert(send_buffer_.end(), data, data + len); + if (Socket::CS_CONNECTED == socket_->GetState()) { + Flush(); + } + } + std::string ReadData() { + std::string data(&recv_buffer_[0], recv_buffer_.size()); + recv_buffer_.clear(); + return data; + } + + bool IsConnected() const { + return (Socket::CS_CONNECTED == socket_->GetState()); + } + bool IsClosed() const { + return (Socket::CS_CLOSED == socket_->GetState()); + } + +private: + typedef std::vector Buffer; + + void Init(AsyncSocket* socket, int family) { + if (!socket) { + socket = Thread::Current()->socketserver() + ->CreateAsyncSocket(family, SOCK_STREAM); + } + socket_.reset(socket); + socket_->SignalConnectEvent.connect(this, + &SocketTestClient::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &SocketTestClient::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &SocketTestClient::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &SocketTestClient::OnCloseEvent); + } + + void Flush() { + size_t sent = 0; + while (sent < send_buffer_.size()) { + int result = socket_->Send(&send_buffer_[sent], + send_buffer_.size() - sent); + if (result > 0) { + sent += result; + } else { + break; + } + } + size_t new_size = send_buffer_.size() - sent; + memmove(&send_buffer_[0], &send_buffer_[sent], new_size); + send_buffer_.resize(new_size); + } + + void OnConnectEvent(AsyncSocket* socket) { + if (!send_buffer_.empty()) { + Flush(); + } + } + void OnReadEvent(AsyncSocket* socket) { + char data[64 * 1024]; + int result = socket_->Recv(data, ARRAY_SIZE(data)); + if (result > 0) { + recv_buffer_.insert(recv_buffer_.end(), data, data + result); + } + } + void OnWriteEvent(AsyncSocket* socket) { + if (!send_buffer_.empty()) { + Flush(); + } + } + void OnCloseEvent(AsyncSocket* socket, int error) { + } + + scoped_ptr socket_; + Buffer send_buffer_, recv_buffer_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SocketTestServer +// Creates a simulated server for testing. Works on real and virtual networks. +/////////////////////////////////////////////////////////////////////////////// + +class SocketTestServer : public sigslot::has_slots<> { + public: + SocketTestServer(const SocketAddress& address) + : socket_(Thread::Current()->socketserver() + ->CreateAsyncSocket(address.family(), SOCK_STREAM)) + { + socket_->SignalReadEvent.connect(this, &SocketTestServer::OnReadEvent); + socket_->Bind(address); + socket_->Listen(5); + } + virtual ~SocketTestServer() { + clear(); + } + + size_t size() const { return clients_.size(); } + SocketTestClient* client(size_t index) const { return clients_[index]; } + SocketTestClient* operator[](size_t index) const { return client(index); } + + void clear() { + for (size_t i=0; i(socket_->Accept(NULL)); + if (!accepted) + return; + clients_.push_back(new SocketTestClient(accepted)); + } + + scoped_ptr socket_; + std::vector clients_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Generic Utilities +/////////////////////////////////////////////////////////////////////////////// + +inline bool ReadFile(const char* filename, std::string* contents) { + FILE* fp = fopen(filename, "rb"); + if (!fp) + return false; + char buffer[1024*64]; + size_t read; + contents->clear(); + while ((read = fread(buffer, 1, sizeof(buffer), fp))) { + contents->append(buffer, read); + } + bool success = (0 != feof(fp)); + fclose(fp); + return success; +} + +// Look in parent dir for parallel directory. +inline rtc::Pathname GetSiblingDirectory( + const std::string& parallel_dir) { + rtc::Pathname path = rtc::Filesystem::GetCurrentDirectory(); + while (!path.empty()) { + rtc::Pathname potential_parallel_dir = path; + potential_parallel_dir.AppendFolder(parallel_dir); + if (rtc::Filesystem::IsFolder(potential_parallel_dir)) { + return potential_parallel_dir; + } + + path.SetFolder(path.parent_folder()); + } + return path; +} + +inline rtc::Pathname GetGoogle3Directory() { + return GetSiblingDirectory("google3"); +} + +inline rtc::Pathname GetTalkDirectory() { + return GetSiblingDirectory("talk"); +} + +/////////////////////////////////////////////////////////////////////////////// +// Unittest predicates which are similar to STREQ, but for raw memory +/////////////////////////////////////////////////////////////////////////////// + +inline AssertionResult CmpHelperMemEq(const char* expected_expression, + const char* expected_length_expression, + const char* actual_expression, + const char* actual_length_expression, + const void* expected, + size_t expected_length, + const void* actual, + size_t actual_length) +{ + if ((expected_length == actual_length) + && (0 == memcmp(expected, actual, expected_length))) { + return AssertionSuccess(); + } + + Message msg; + msg << "Value of: " << actual_expression + << " [" << actual_length_expression << "]"; + if (true) { //!actual_value.Equals(actual_expression)) { + size_t buffer_size = actual_length * 2 + 1; + char* buffer = STACK_ARRAY(char, buffer_size); + hex_encode(buffer, buffer_size, + reinterpret_cast(actual), actual_length); + msg << "\n Actual: " << buffer << " [" << actual_length << "]"; + } + + msg << "\nExpected: " << expected_expression + << " [" << expected_length_expression << "]"; + if (true) { //!expected_value.Equals(expected_expression)) { + size_t buffer_size = expected_length * 2 + 1; + char* buffer = STACK_ARRAY(char, buffer_size); + hex_encode(buffer, buffer_size, + reinterpret_cast(expected), expected_length); + msg << "\nWhich is: " << buffer << " [" << expected_length << "]"; + } + + return AssertionFailure(msg); +} + +inline AssertionResult CmpHelperFileEq(const char* expected_expression, + const char* expected_length_expression, + const char* actual_filename, + const void* expected, + size_t expected_length, + const char* filename) +{ + std::string contents; + if (!ReadFile(filename, &contents)) { + Message msg; + msg << "File '" << filename << "' could not be read."; + return AssertionFailure(msg); + } + return CmpHelperMemEq(expected_expression, expected_length_expression, + actual_filename, "", + expected, expected_length, + contents.c_str(), contents.size()); +} + +#define EXPECT_MEMEQ(expected, expected_length, actual, actual_length) \ + EXPECT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \ + actual, actual_length) + +#define ASSERT_MEMEQ(expected, expected_length, actual, actual_length) \ + ASSERT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \ + actual, actual_length) + +#define EXPECT_FILEEQ(expected, expected_length, filename) \ + EXPECT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \ + filename) + +#define ASSERT_FILEEQ(expected, expected_length, filename) \ + ASSERT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \ + filename) + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for initializing constant memory with integers in a particular byte +// order +/////////////////////////////////////////////////////////////////////////////// + +#define BYTE_CAST(x) static_cast((x) & 0xFF) + +// Declare a N-bit integer as a little-endian sequence of bytes +#define LE16(x) BYTE_CAST(((uint16)x) >> 0), BYTE_CAST(((uint16)x) >> 8) + +#define LE32(x) BYTE_CAST(((uint32)x) >> 0), BYTE_CAST(((uint32)x) >> 8), \ + BYTE_CAST(((uint32)x) >> 16), BYTE_CAST(((uint32)x) >> 24) + +#define LE64(x) BYTE_CAST(((uint64)x) >> 0), BYTE_CAST(((uint64)x) >> 8), \ + BYTE_CAST(((uint64)x) >> 16), BYTE_CAST(((uint64)x) >> 24), \ + BYTE_CAST(((uint64)x) >> 32), BYTE_CAST(((uint64)x) >> 40), \ + BYTE_CAST(((uint64)x) >> 48), BYTE_CAST(((uint64)x) >> 56) + +// Declare a N-bit integer as a big-endian (Internet) sequence of bytes +#define BE16(x) BYTE_CAST(((uint16)x) >> 8), BYTE_CAST(((uint16)x) >> 0) + +#define BE32(x) BYTE_CAST(((uint32)x) >> 24), BYTE_CAST(((uint32)x) >> 16), \ + BYTE_CAST(((uint32)x) >> 8), BYTE_CAST(((uint32)x) >> 0) + +#define BE64(x) BYTE_CAST(((uint64)x) >> 56), BYTE_CAST(((uint64)x) >> 48), \ + BYTE_CAST(((uint64)x) >> 40), BYTE_CAST(((uint64)x) >> 32), \ + BYTE_CAST(((uint64)x) >> 24), BYTE_CAST(((uint64)x) >> 16), \ + BYTE_CAST(((uint64)x) >> 8), BYTE_CAST(((uint64)x) >> 0) + +// Declare a N-bit integer as a this-endian (local machine) sequence of bytes +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 1 +#endif // BIG_ENDIAN + +#if BIG_ENDIAN +#define TE16 BE16 +#define TE32 BE32 +#define TE64 BE64 +#else // !BIG_ENDIAN +#define TE16 LE16 +#define TE32 LE32 +#define TE64 LE64 +#endif // !BIG_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// + +// Helpers for determining if X/screencasting is available (on linux). + +#define MAYBE_SKIP_SCREENCAST_TEST() \ + if (!testing::IsScreencastingAvailable()) { \ + LOG(LS_WARNING) << "Skipping test, since it doesn't have the requisite " \ + << "X environment for screen capture."; \ + return; \ + } \ + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +struct XDisplay { + XDisplay() : display_(XOpenDisplay(NULL)) { } + ~XDisplay() { if (display_) XCloseDisplay(display_); } + bool IsValid() const { return display_ != NULL; } + operator Display*() { return display_; } + private: + Display* display_; +}; +#endif + +// Returns true if screencasting is available. When false, anything that uses +// screencasting features may fail. +inline bool IsScreencastingAvailable() { +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + XDisplay display; + if (!display.IsValid()) { + LOG(LS_WARNING) << "No X Display available."; + return false; + } + int ignored_int, major_version, minor_version; + if (!XRRQueryExtension(display, &ignored_int, &ignored_int) || + !XRRQueryVersion(display, &major_version, &minor_version) || + major_version < 1 || + (major_version < 2 && minor_version < 3)) { + LOG(LS_WARNING) << "XRandr version: " << major_version << "." + << minor_version; + LOG(LS_WARNING) << "XRandr is not supported or is too old (pre 1.3)."; + return false; + } +#endif + return true; +} +} // namespace testing + +#endif // WEBRTC_BASE_TESTUTILS_H__ diff --git a/webrtc/base/thread.cc b/webrtc/base/thread.cc new file mode 100644 index 000000000..49a299d65 --- /dev/null +++ b/webrtc/base/thread.cc @@ -0,0 +1,560 @@ +/* + * Copyright 2004 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 "webrtc/base/thread.h" + +#ifndef __has_feature +#define __has_feature(x) 0 // Compatibility with non-clang or LLVM compilers. +#endif // __has_feature + +#if defined(WEBRTC_WIN) +#include +#elif defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/timeutils.h" + +#if !__has_feature(objc_arc) && (defined(WEBRTC_MAC)) +#include "webrtc/base/maccocoathreadhelper.h" +#include "webrtc/base/scoped_autorelease_pool.h" +#endif + +namespace rtc { + +ThreadManager* ThreadManager::Instance() { + LIBJINGLE_DEFINE_STATIC_LOCAL(ThreadManager, thread_manager, ()); + return &thread_manager; +} + +// static +Thread* Thread::Current() { + return ThreadManager::Instance()->CurrentThread(); +} + +#if defined(WEBRTC_POSIX) +ThreadManager::ThreadManager() { + pthread_key_create(&key_, NULL); +#ifndef NO_MAIN_THREAD_WRAPPING + WrapCurrentThread(); +#endif +#if !__has_feature(objc_arc) && (defined(WEBRTC_MAC)) + // Under Automatic Reference Counting (ARC), you cannot use autorelease pools + // directly. Instead, you use @autoreleasepool blocks instead. Also, we are + // maintaining thread safety using immutability within context of GCD dispatch + // queues in this case. + InitCocoaMultiThreading(); +#endif +} + +ThreadManager::~ThreadManager() { +#if __has_feature(objc_arc) + @autoreleasepool +#elif defined(WEBRTC_MAC) + // This is called during exit, at which point apparently no NSAutoreleasePools + // are available; but we might still need them to do cleanup (or we get the + // "no autoreleasepool in place, just leaking" warning when exiting). + ScopedAutoreleasePool pool; +#endif + { + UnwrapCurrentThread(); + pthread_key_delete(key_); + } +} + +Thread *ThreadManager::CurrentThread() { + return static_cast(pthread_getspecific(key_)); +} + +void ThreadManager::SetCurrentThread(Thread *thread) { + pthread_setspecific(key_, thread); +} +#endif + +#if defined(WEBRTC_WIN) +ThreadManager::ThreadManager() { + key_ = TlsAlloc(); +#ifndef NO_MAIN_THREAD_WRAPPING + WrapCurrentThread(); +#endif +} + +ThreadManager::~ThreadManager() { + UnwrapCurrentThread(); + TlsFree(key_); +} + +Thread *ThreadManager::CurrentThread() { + return static_cast(TlsGetValue(key_)); +} + +void ThreadManager::SetCurrentThread(Thread *thread) { + TlsSetValue(key_, thread); +} +#endif + +Thread *ThreadManager::WrapCurrentThread() { + Thread* result = CurrentThread(); + if (NULL == result) { + result = new Thread(); + result->WrapCurrentWithThreadManager(this); + } + return result; +} + +void ThreadManager::UnwrapCurrentThread() { + Thread* t = CurrentThread(); + if (t && !(t->IsOwned())) { + t->UnwrapCurrent(); + delete t; + } +} + +struct ThreadInit { + Thread* thread; + Runnable* runnable; +}; + +Thread::Thread(SocketServer* ss) + : MessageQueue(ss), + priority_(PRIORITY_NORMAL), + running_(true, false), +#if defined(WEBRTC_WIN) + thread_(NULL), + thread_id_(0), +#endif + owned_(true) { + SetName("Thread", this); // default name +} + +Thread::~Thread() { + Stop(); + Clear(NULL); +} + +bool Thread::SleepMs(int milliseconds) { +#if defined(WEBRTC_WIN) + ::Sleep(milliseconds); + return true; +#else + // POSIX has both a usleep() and a nanosleep(), but the former is deprecated, + // so we use nanosleep() even though it has greater precision than necessary. + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + int ret = nanosleep(&ts, NULL); + if (ret != 0) { + LOG_ERR(LS_WARNING) << "nanosleep() returning early"; + return false; + } + return true; +#endif +} + +bool Thread::SetName(const std::string& name, const void* obj) { + if (running()) return false; + name_ = name; + if (obj) { + char buf[16]; + sprintfn(buf, sizeof(buf), " 0x%p", obj); + name_ += buf; + } + return true; +} + +bool Thread::SetPriority(ThreadPriority priority) { +#if defined(WEBRTC_WIN) + if (running()) { + BOOL ret = FALSE; + if (priority == PRIORITY_NORMAL) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_NORMAL); + } else if (priority == PRIORITY_HIGH) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_HIGHEST); + } else if (priority == PRIORITY_ABOVE_NORMAL) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL); + } else if (priority == PRIORITY_IDLE) { + ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_IDLE); + } + if (!ret) { + return false; + } + } + priority_ = priority; + return true; +#else + // TODO: Implement for Linux/Mac if possible. + if (running()) return false; + priority_ = priority; + return true; +#endif +} + +bool Thread::Start(Runnable* runnable) { + ASSERT(owned_); + if (!owned_) return false; + ASSERT(!running()); + if (running()) return false; + + Restart(); // reset fStop_ if the thread is being restarted + + // Make sure that ThreadManager is created on the main thread before + // we start a new thread. + ThreadManager::Instance(); + + ThreadInit* init = new ThreadInit; + init->thread = this; + init->runnable = runnable; +#if defined(WEBRTC_WIN) + DWORD flags = 0; + if (priority_ != PRIORITY_NORMAL) { + flags = CREATE_SUSPENDED; + } + thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreRun, init, flags, + &thread_id_); + if (thread_) { + running_.Set(); + if (priority_ != PRIORITY_NORMAL) { + SetPriority(priority_); + ::ResumeThread(thread_); + } + } else { + return false; + } +#elif defined(WEBRTC_POSIX) + pthread_attr_t attr; + pthread_attr_init(&attr); + + // Thread priorities are not supported in NaCl. +#if !defined(__native_client__) + if (priority_ != PRIORITY_NORMAL) { + if (priority_ == PRIORITY_IDLE) { + // There is no POSIX-standard way to set a below-normal priority for an + // individual thread (only whole process), so let's not support it. + LOG(LS_WARNING) << "PRIORITY_IDLE not supported"; + } else { + // Set real-time round-robin policy. + if (pthread_attr_setschedpolicy(&attr, SCHED_RR) != 0) { + LOG(LS_ERROR) << "pthread_attr_setschedpolicy"; + } + struct sched_param param; + if (pthread_attr_getschedparam(&attr, ¶m) != 0) { + LOG(LS_ERROR) << "pthread_attr_getschedparam"; + } else { + // The numbers here are arbitrary. + if (priority_ == PRIORITY_HIGH) { + param.sched_priority = 6; // 6 = HIGH + } else { + ASSERT(priority_ == PRIORITY_ABOVE_NORMAL); + param.sched_priority = 4; // 4 = ABOVE_NORMAL + } + if (pthread_attr_setschedparam(&attr, ¶m) != 0) { + LOG(LS_ERROR) << "pthread_attr_setschedparam"; + } + } + } + } +#endif // !defined(__native_client__) + + int error_code = pthread_create(&thread_, &attr, PreRun, init); + if (0 != error_code) { + LOG(LS_ERROR) << "Unable to create pthread, error " << error_code; + return false; + } + running_.Set(); +#endif + return true; +} + +void Thread::Join() { + if (running()) { + ASSERT(!IsCurrent()); +#if defined(WEBRTC_WIN) + WaitForSingleObject(thread_, INFINITE); + CloseHandle(thread_); + thread_ = NULL; + thread_id_ = 0; +#elif defined(WEBRTC_POSIX) + void *pv; + pthread_join(thread_, &pv); +#endif + running_.Reset(); + } +} + +#if defined(WEBRTC_WIN) +// As seen on MSDN. +// http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.71).aspx +#define MSDEV_SET_THREAD_NAME 0x406D1388 +typedef struct tagTHREADNAME_INFO { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; +} THREADNAME_INFO; + +void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = szThreadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try { + RaiseException(MSDEV_SET_THREAD_NAME, 0, sizeof(info) / sizeof(DWORD), + reinterpret_cast(&info)); + } + __except(EXCEPTION_CONTINUE_EXECUTION) { + } +} +#endif // WEBRTC_WIN + +void* Thread::PreRun(void* pv) { + ThreadInit* init = static_cast(pv); + ThreadManager::Instance()->SetCurrentThread(init->thread); +#if defined(WEBRTC_WIN) + SetThreadName(GetCurrentThreadId(), init->thread->name_.c_str()); +#elif defined(WEBRTC_POSIX) + // TODO: See if naming exists for pthreads. +#endif +#if __has_feature(objc_arc) + @autoreleasepool +#elif defined(WEBRTC_MAC) + // Make sure the new thread has an autoreleasepool + ScopedAutoreleasePool pool; +#endif + { + if (init->runnable) { + init->runnable->Run(init->thread); + } else { + init->thread->Run(); + } + delete init; + return NULL; + } +} + +void Thread::Run() { + ProcessMessages(kForever); +} + +bool Thread::IsOwned() { + return owned_; +} + +void Thread::Stop() { + MessageQueue::Quit(); + Join(); +} + +void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { + if (fStop_) + return; + + // Sent messages are sent to the MessageHandler directly, in the context + // of "thread", like Win32 SendMessage. If in the right context, + // call the handler directly. + + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + if (IsCurrent()) { + phandler->OnMessage(&msg); + return; + } + + AutoThread thread; + Thread *current_thread = Thread::Current(); + ASSERT(current_thread != NULL); // AutoThread ensures this + + bool ready = false; + { + CritScope cs(&crit_); + _SendMessage smsg; + smsg.thread = current_thread; + smsg.msg = msg; + smsg.ready = &ready; + sendlist_.push_back(smsg); + } + + // Wait for a reply + + ss_->WakeUp(); + + bool waited = false; + crit_.Enter(); + while (!ready) { + crit_.Leave(); + current_thread->ReceiveSends(); + current_thread->socketserver()->Wait(kForever, false); + waited = true; + crit_.Enter(); + } + crit_.Leave(); + + // Our Wait loop above may have consumed some WakeUp events for this + // MessageQueue, that weren't relevant to this Send. Losing these WakeUps can + // cause problems for some SocketServers. + // + // Concrete example: + // Win32SocketServer on thread A calls Send on thread B. While processing the + // message, thread B Posts a message to A. We consume the wakeup for that + // Post while waiting for the Send to complete, which means that when we exit + // this loop, we need to issue another WakeUp, or else the Posted message + // won't be processed in a timely manner. + + if (waited) { + current_thread->socketserver()->WakeUp(); + } +} + +void Thread::ReceiveSends() { + // Receive a sent message. Cleanup scenarios: + // - thread sending exits: We don't allow this, since thread can exit + // only via Join, so Send must complete. + // - thread receiving exits: Wakeup/set ready in Thread::Clear() + // - object target cleared: Wakeup/set ready in Thread::Clear() + crit_.Enter(); + while (!sendlist_.empty()) { + _SendMessage smsg = sendlist_.front(); + sendlist_.pop_front(); + crit_.Leave(); + smsg.msg.phandler->OnMessage(&smsg.msg); + crit_.Enter(); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + } + crit_.Leave(); +} + +void Thread::Clear(MessageHandler *phandler, uint32 id, + MessageList* removed) { + CritScope cs(&crit_); + + // Remove messages on sendlist_ with phandler + // Object target cleared: remove from send list, wakeup/set ready + // if sender not NULL. + + std::list<_SendMessage>::iterator iter = sendlist_.begin(); + while (iter != sendlist_.end()) { + _SendMessage smsg = *iter; + if (smsg.msg.Match(phandler, id)) { + if (removed) { + removed->push_back(smsg.msg); + } else { + delete smsg.msg.pdata; + } + iter = sendlist_.erase(iter); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + continue; + } + ++iter; + } + + MessageQueue::Clear(phandler, id, removed); +} + +bool Thread::ProcessMessages(int cmsLoop) { + uint32 msEnd = (kForever == cmsLoop) ? 0 : TimeAfter(cmsLoop); + int cmsNext = cmsLoop; + + while (true) { +#if __has_feature(objc_arc) + @autoreleasepool +#elif defined(WEBRTC_MAC) + // see: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html + // Each thread is supposed to have an autorelease pool. Also for event loops + // like this, autorelease pool needs to be created and drained/released + // for each cycle. + ScopedAutoreleasePool pool; +#endif + { + Message msg; + if (!Get(&msg, cmsNext)) + return !IsQuitting(); + Dispatch(&msg); + + if (cmsLoop != kForever) { + cmsNext = TimeUntil(msEnd); + if (cmsNext < 0) + return true; + } + } + } +} + +bool Thread::WrapCurrent() { + return WrapCurrentWithThreadManager(ThreadManager::Instance()); +} + +bool Thread::WrapCurrentWithThreadManager(ThreadManager* thread_manager) { + if (running()) + return false; +#if defined(WEBRTC_WIN) + // We explicitly ask for no rights other than synchronization. + // This gives us the best chance of succeeding. + thread_ = OpenThread(SYNCHRONIZE, FALSE, GetCurrentThreadId()); + if (!thread_) { + LOG_GLE(LS_ERROR) << "Unable to get handle to thread."; + return false; + } + thread_id_ = GetCurrentThreadId(); +#elif defined(WEBRTC_POSIX) + thread_ = pthread_self(); +#endif + owned_ = false; + running_.Set(); + thread_manager->SetCurrentThread(this); + return true; +} + +void Thread::UnwrapCurrent() { + // Clears the platform-specific thread-specific storage. + ThreadManager::Instance()->SetCurrentThread(NULL); +#if defined(WEBRTC_WIN) + if (!CloseHandle(thread_)) { + LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle."; + } +#endif + running_.Reset(); +} + + +AutoThread::AutoThread(SocketServer* ss) : Thread(ss) { + if (!ThreadManager::Instance()->CurrentThread()) { + ThreadManager::Instance()->SetCurrentThread(this); + } +} + +AutoThread::~AutoThread() { + Stop(); + if (ThreadManager::Instance()->CurrentThread() == this) { + ThreadManager::Instance()->SetCurrentThread(NULL); + } +} + +#if defined(WEBRTC_WIN) +void ComThread::Run() { + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + ASSERT(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) { + Thread::Run(); + CoUninitialize(); + } else { + LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr; + } +} +#endif + +} // namespace rtc diff --git a/webrtc/base/thread.h b/webrtc/base/thread.h new file mode 100644 index 000000000..38727464b --- /dev/null +++ b/webrtc/base/thread.h @@ -0,0 +1,294 @@ +/* + * Copyright 2004 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_BASE_THREAD_H_ +#define WEBRTC_BASE_THREAD_H_ + +#include +#include +#include +#include + +#if defined(WEBRTC_POSIX) +#include +#endif +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/event.h" +#include "webrtc/base/messagequeue.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +class Thread; + +class ThreadManager { + public: + ThreadManager(); + ~ThreadManager(); + + static ThreadManager* Instance(); + + Thread* CurrentThread(); + void SetCurrentThread(Thread* thread); + + // Returns a thread object with its thread_ ivar set + // to whatever the OS uses to represent the thread. + // If there already *is* a Thread object corresponding to this thread, + // this method will return that. Otherwise it creates a new Thread + // object whose wrapped() method will return true, and whose + // handle will, on Win32, be opened with only synchronization privileges - + // if you need more privilegs, rather than changing this method, please + // write additional code to adjust the privileges, or call a different + // factory method of your own devising, because this one gets used in + // unexpected contexts (like inside browser plugins) and it would be a + // shame to break it. It is also conceivable on Win32 that we won't even + // be able to get synchronization privileges, in which case the result + // will have a NULL handle. + Thread *WrapCurrentThread(); + void UnwrapCurrentThread(); + + private: +#if defined(WEBRTC_POSIX) + pthread_key_t key_; +#endif + +#if defined(WEBRTC_WIN) + DWORD key_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ThreadManager); +}; + +struct _SendMessage { + _SendMessage() {} + Thread *thread; + Message msg; + bool *ready; +}; + +enum ThreadPriority { + PRIORITY_IDLE = -1, + PRIORITY_NORMAL = 0, + PRIORITY_ABOVE_NORMAL = 1, + PRIORITY_HIGH = 2, +}; + +class Runnable { + public: + virtual ~Runnable() {} + virtual void Run(Thread* thread) = 0; + + protected: + Runnable() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Runnable); +}; + +// WARNING! SUBCLASSES MUST CALL Stop() IN THEIR DESTRUCTORS! See ~Thread(). + +class Thread : public MessageQueue { + public: + explicit Thread(SocketServer* ss = NULL); + // NOTE: ALL SUBCLASSES OF Thread MUST CALL Stop() IN THEIR DESTRUCTORS (or + // guarantee Stop() is explicitly called before the subclass is destroyed). + // This is required to avoid a data race between the destructor modifying the + // vtable, and the Thread::PreRun calling the virtual method Run(). + virtual ~Thread(); + + static Thread* Current(); + + bool IsCurrent() const { + return Current() == this; + } + + // Sleeps the calling thread for the specified number of milliseconds, during + // which time no processing is performed. Returns false if sleeping was + // interrupted by a signal (POSIX only). + static bool SleepMs(int millis); + + // Sets the thread's name, for debugging. Must be called before Start(). + // If |obj| is non-NULL, its value is appended to |name|. + const std::string& name() const { return name_; } + bool SetName(const std::string& name, const void* obj); + + // Sets the thread's priority. Must be called before Start(). + ThreadPriority priority() const { return priority_; } + bool SetPriority(ThreadPriority priority); + + // Starts the execution of the thread. + bool Start(Runnable* runnable = NULL); + + // Tells the thread to stop and waits until it is joined. + // Never call Stop on the current thread. Instead use the inherited Quit + // function which will exit the base MessageQueue without terminating the + // underlying OS thread. + virtual void Stop(); + + // By default, Thread::Run() calls ProcessMessages(kForever). To do other + // work, override Run(). To receive and dispatch messages, call + // ProcessMessages occasionally. + virtual void Run(); + + virtual void Send(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL); + + // Convenience method to invoke a functor on another thread. Caller must + // provide the |ReturnT| template argument, which cannot (easily) be deduced. + // Uses Send() internally, which blocks the current thread until execution + // is complete. + // Ex: bool result = thread.Invoke(&MyFunctionReturningBool); + template + ReturnT Invoke(const FunctorT& functor) { + FunctorMessageHandler handler(functor); + Send(&handler); + return handler.result(); + } + + // From MessageQueue + virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY, + MessageList* removed = NULL); + virtual void ReceiveSends(); + + // ProcessMessages will process I/O and dispatch messages until: + // 1) cms milliseconds have elapsed (returns true) + // 2) Stop() is called (returns false) + bool ProcessMessages(int cms); + + // Returns true if this is a thread that we created using the standard + // constructor, false if it was created by a call to + // ThreadManager::WrapCurrentThread(). The main thread of an application + // is generally not owned, since the OS representation of the thread + // obviously exists before we can get to it. + // You cannot call Start on non-owned threads. + bool IsOwned(); + +#if defined(WEBRTC_WIN) + HANDLE GetHandle() const { + return thread_; + } + DWORD GetId() const { + return thread_id_; + } +#elif defined(WEBRTC_POSIX) + pthread_t GetPThread() { + return thread_; + } +#endif + + // This method should be called when thread is created using non standard + // method, like derived implementation of rtc::Thread and it can not be + // started by calling Start(). This will set started flag to true and + // owned to false. This must be called from the current thread. + // NOTE: These methods should be used by the derived classes only, added here + // only for testing. + bool WrapCurrent(); + void UnwrapCurrent(); + + // Expose private method running() for tests. + // + // DANGER: this is a terrible public API. Most callers that might want to + // call this likely do not have enough control/knowledge of the Thread in + // question to guarantee that the returned value remains true for the duration + // of whatever code is conditionally executing because of the return value! + bool RunningForTest() { return running(); } + // This is a legacy call-site that probably doesn't need to exist in the first + // place. + // TODO(fischman): delete once the ASSERT added in channelmanager.cc sticks + // for a month (ETA 2014/06/22). + bool RunningForChannelManager() { return running(); } + + protected: + // Blocks the calling thread until this thread has terminated. + void Join(); + + private: + static void *PreRun(void *pv); + + // ThreadManager calls this instead WrapCurrent() because + // ThreadManager::Instance() cannot be used while ThreadManager is + // being created. + bool WrapCurrentWithThreadManager(ThreadManager* thread_manager); + + // Return true if the thread was started and hasn't yet stopped. + bool running() { return running_.Wait(0); } + + std::list<_SendMessage> sendlist_; + std::string name_; + ThreadPriority priority_; + Event running_; // Signalled means running. + +#if defined(WEBRTC_POSIX) + pthread_t thread_; +#endif + +#if defined(WEBRTC_WIN) + HANDLE thread_; + DWORD thread_id_; +#endif + + bool owned_; + + friend class ThreadManager; + + DISALLOW_COPY_AND_ASSIGN(Thread); +}; + +// AutoThread automatically installs itself at construction +// uninstalls at destruction, if a Thread object is +// _not already_ associated with the current OS thread. + +class AutoThread : public Thread { + public: + explicit AutoThread(SocketServer* ss = 0); + virtual ~AutoThread(); + + private: + DISALLOW_COPY_AND_ASSIGN(AutoThread); +}; + +// Win32 extension for threads that need to use COM +#if defined(WEBRTC_WIN) +class ComThread : public Thread { + public: + ComThread() {} + virtual ~ComThread() { Stop(); } + + protected: + virtual void Run(); + + private: + DISALLOW_COPY_AND_ASSIGN(ComThread); +}; +#endif + +// Provides an easy way to install/uninstall a socketserver on a thread. +class SocketServerScope { + public: + explicit SocketServerScope(SocketServer* ss) { + old_ss_ = Thread::Current()->socketserver(); + Thread::Current()->set_socketserver(ss); + } + ~SocketServerScope() { + Thread::Current()->set_socketserver(old_ss_); + } + + private: + SocketServer* old_ss_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(SocketServerScope); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_THREAD_H_ diff --git a/webrtc/base/thread_checker.h b/webrtc/base/thread_checker.h new file mode 100644 index 000000000..eee931553 --- /dev/null +++ b/webrtc/base/thread_checker.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/threading/thread_checker.h. + +#ifndef WEBRTC_BASE_THREAD_CHECKER_H_ +#define WEBRTC_BASE_THREAD_CHECKER_H_ + +// Apart from debug builds, we also enable the thread checker in +// builds with DCHECK_ALWAYS_ON so that trybots and waterfall bots +// with this define will get the same level of thread checking as +// debug bots. +// +// Note that this does not perfectly match situations where DCHECK is +// enabled. For example a non-official release build may have +// DCHECK_ALWAYS_ON undefined (and therefore ThreadChecker would be +// disabled) but have DCHECKs enabled at runtime. +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_THREAD_CHECKER 1 +#else +#define ENABLE_THREAD_CHECKER 0 +#endif + +#include "webrtc/base/thread_checker_impl.h" + +namespace rtc { + +// Do nothing implementation, for use in release mode. +// +// Note: You should almost always use the ThreadChecker class to get the +// right version for your build configuration. +class ThreadCheckerDoNothing { + public: + bool CalledOnValidThread() const { + return true; + } + + void DetachFromThread() {} +}; + +// ThreadChecker is a helper class used to help verify that some methods of a +// class are called from the same thread. It provides identical functionality to +// base::NonThreadSafe, but it is meant to be held as a member variable, rather +// than inherited from base::NonThreadSafe. +// +// While inheriting from base::NonThreadSafe may give a clear indication about +// the thread-safety of a class, it may also lead to violations of the style +// guide with regard to multiple inheritance. The choice between having a +// ThreadChecker member and inheriting from base::NonThreadSafe should be based +// on whether: +// - Derived classes need to know the thread they belong to, as opposed to +// having that functionality fully encapsulated in the base class. +// - Derived classes should be able to reassign the base class to another +// thread, via DetachFromThread. +// +// If neither of these are true, then having a ThreadChecker member and calling +// CalledOnValidThread is the preferable solution. +// +// Example: +// class MyClass { +// public: +// void Foo() { +// DCHECK(thread_checker_.CalledOnValidThread()); +// ... (do stuff) ... +// } +// +// private: +// ThreadChecker thread_checker_; +// } +// +// In Release mode, CalledOnValidThread will always return true. +#if ENABLE_THREAD_CHECKER +class ThreadChecker : public ThreadCheckerImpl { +}; +#else +class ThreadChecker : public ThreadCheckerDoNothing { +}; +#endif // ENABLE_THREAD_CHECKER + +#undef ENABLE_THREAD_CHECKER + +} // namespace rtc + +#endif // WEBRTC_BASE_THREAD_CHECKER_H_ diff --git a/webrtc/base/thread_checker_impl.cc b/webrtc/base/thread_checker_impl.cc new file mode 100644 index 000000000..4a7455d30 --- /dev/null +++ b/webrtc/base/thread_checker_impl.cc @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/threading/thread_checker_impl.cc. + +#include "webrtc/base/thread_checker_impl.h" + +#include "webrtc/base/thread.h" + +namespace rtc { + +ThreadCheckerImpl::ThreadCheckerImpl() + : valid_thread_() { + EnsureThreadIdAssigned(); +} + +ThreadCheckerImpl::~ThreadCheckerImpl() { +} + +bool ThreadCheckerImpl::CalledOnValidThread() const { + CritScope scoped_lock(&lock_); + EnsureThreadIdAssigned(); + return valid_thread_->IsCurrent(); +} + +void ThreadCheckerImpl::DetachFromThread() { + CritScope scoped_lock(&lock_); + valid_thread_ = NULL; +} + +void ThreadCheckerImpl::EnsureThreadIdAssigned() const { + if (!valid_thread_) { + valid_thread_ = Thread::Current(); + } +} + +} // namespace rtc diff --git a/webrtc/base/thread_checker_impl.h b/webrtc/base/thread_checker_impl.h new file mode 100644 index 000000000..1d776b5eb --- /dev/null +++ b/webrtc/base/thread_checker_impl.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/threading/thread_checker_impl.h. + +#ifndef WEBRTC_BASE_THREAD_CHECKER_IMPL_H_ +#define WEBRTC_BASE_THREAD_CHECKER_IMPL_H_ + +#include "webrtc/base/criticalsection.h" + +namespace rtc { + +class Thread; + +// Real implementation of ThreadChecker, for use in debug mode, or +// for temporary use in release mode (e.g. to CHECK on a threading issue +// seen only in the wild). +// +// Note: You should almost always use the ThreadChecker class to get the +// right version for your build configuration. +class ThreadCheckerImpl { + public: + ThreadCheckerImpl(); + ~ThreadCheckerImpl(); + + bool CalledOnValidThread() const; + + // Changes the thread that is checked for in CalledOnValidThread. This may + // be useful when an object may be created on one thread and then used + // exclusively on another thread. + void DetachFromThread(); + + private: + void EnsureThreadIdAssigned() const; + + mutable CriticalSection lock_; + // This is mutable so that CalledOnValidThread can set it. + // It's guarded by |lock_|. + mutable const Thread* valid_thread_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_THREAD_CHECKER_IMPL_H_ diff --git a/webrtc/base/thread_checker_unittest.cc b/webrtc/base/thread_checker_unittest.cc new file mode 100644 index 000000000..13c1da5e2 --- /dev/null +++ b/webrtc/base/thread_checker_unittest.cc @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/threading/thread_checker_unittest.cc. + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/thread_checker.h" +#include "webrtc/base/scoped_ptr.h" + +// Duplicated from base/threading/thread_checker.h so that we can be +// good citizens there and undef the macro. +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) +#define ENABLE_THREAD_CHECKER 1 +#else +#define ENABLE_THREAD_CHECKER 0 +#endif + +namespace rtc { + +namespace { + +// Simple class to exercise the basics of ThreadChecker. +// Both the destructor and DoStuff should verify that they were +// called on the same thread as the constructor. +class ThreadCheckerClass : public ThreadChecker { + public: + ThreadCheckerClass() {} + + // Verifies that it was called on the same thread as the constructor. + void DoStuff() { + assert(CalledOnValidThread()); + } + + void DetachFromThread() { + ThreadChecker::DetachFromThread(); + } + + static void MethodOnDifferentThreadImpl(); + static void DetachThenCallFromDifferentThreadImpl(); + + private: + DISALLOW_COPY_AND_ASSIGN(ThreadCheckerClass); +}; + +// Calls ThreadCheckerClass::DoStuff on another thread. +class CallDoStuffOnThread : public Thread { + public: + explicit CallDoStuffOnThread(ThreadCheckerClass* thread_checker_class) + : Thread(), + thread_checker_class_(thread_checker_class) { + SetName("call_do_stuff_on_thread", NULL); + } + + virtual void Run() OVERRIDE { + thread_checker_class_->DoStuff(); + } + + // New method. Needed since Thread::Join is protected, and it is called by + // the TEST. + void Join() { + Thread::Join(); + } + + private: + ThreadCheckerClass* thread_checker_class_; + + DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread); +}; + +// Deletes ThreadCheckerClass on a different thread. +class DeleteThreadCheckerClassOnThread : public Thread { + public: + explicit DeleteThreadCheckerClassOnThread( + ThreadCheckerClass* thread_checker_class) + : Thread(), + thread_checker_class_(thread_checker_class) { + SetName("delete_thread_checker_class_on_thread", NULL); + } + + virtual void Run() OVERRIDE { + thread_checker_class_.reset(); + } + + // New method. Needed since Thread::Join is protected, and it is called by + // the TEST. + void Join() { + Thread::Join(); + } + + private: + scoped_ptr thread_checker_class_; + + DISALLOW_COPY_AND_ASSIGN(DeleteThreadCheckerClassOnThread); +}; + +} // namespace + +TEST(ThreadCheckerTest, CallsAllowedOnSameThread) { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // Verify that DoStuff doesn't assert. + thread_checker_class->DoStuff(); + + // Verify that the destructor doesn't assert. + thread_checker_class.reset(); +} + +TEST(ThreadCheckerTest, DestructorAllowedOnDifferentThread) { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // Verify that the destructor doesn't assert + // when called on a different thread. + DeleteThreadCheckerClassOnThread delete_on_thread( + thread_checker_class.release()); + + delete_on_thread.Start(); + delete_on_thread.Join(); +} + +TEST(ThreadCheckerTest, DetachFromThread) { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // Verify that DoStuff doesn't assert when called on a different thread after + // a call to DetachFromThread. + thread_checker_class->DetachFromThread(); + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER + +void ThreadCheckerClass::MethodOnDifferentThreadImpl() { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // DoStuff should assert in debug builds only when called on a + // different thread. + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); +} + +#if ENABLE_THREAD_CHECKER +TEST(ThreadCheckerDeathTest, MethodNotAllowedOnDifferentThreadInDebug) { + ASSERT_DEATH({ + ThreadCheckerClass::MethodOnDifferentThreadImpl(); + }, ""); +} +#else +TEST(ThreadCheckerTest, MethodAllowedOnDifferentThreadInRelease) { + ThreadCheckerClass::MethodOnDifferentThreadImpl(); +} +#endif // ENABLE_THREAD_CHECKER + +void ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl() { + scoped_ptr thread_checker_class( + new ThreadCheckerClass); + + // DoStuff doesn't assert when called on a different thread + // after a call to DetachFromThread. + thread_checker_class->DetachFromThread(); + CallDoStuffOnThread call_on_thread(thread_checker_class.get()); + + call_on_thread.Start(); + call_on_thread.Join(); + + // DoStuff should assert in debug builds only after moving to + // another thread. + thread_checker_class->DoStuff(); +} + +#if ENABLE_THREAD_CHECKER +TEST(ThreadCheckerDeathTest, DetachFromThreadInDebug) { + ASSERT_DEATH({ + ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl(); + }, ""); +} +#else +TEST(ThreadCheckerTest, DetachFromThreadInRelease) { + ThreadCheckerClass::DetachThenCallFromDifferentThreadImpl(); +} +#endif // ENABLE_THREAD_CHECKER + +#endif // GTEST_HAS_DEATH_TEST || !ENABLE_THREAD_CHECKER + +// Just in case we ever get lumped together with other compilation units. +#undef ENABLE_THREAD_CHECKER + +} // namespace rtc diff --git a/webrtc/base/thread_unittest.cc b/webrtc/base/thread_unittest.cc new file mode 100644 index 000000000..6a54ac7b3 --- /dev/null +++ b/webrtc/base/thread_unittest.cc @@ -0,0 +1,444 @@ +/* + * Copyright 2004 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 "webrtc/base/asyncinvoker.h" +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/event.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/thread.h" + +#if defined(WEBRTC_WIN) +#include // NOLINT +#endif + +using namespace rtc; + +// Generates a sequence of numbers (collaboratively). +class TestGenerator { + public: + TestGenerator() : last(0), count(0) {} + + int Next(int prev) { + int result = prev + last; + last = result; + count += 1; + return result; + } + + int last; + int count; +}; + +struct TestMessage : public MessageData { + explicit TestMessage(int v) : value(v) {} + virtual ~TestMessage() {} + + int value; +}; + +// Receives on a socket and sends by posting messages. +class SocketClient : public TestGenerator, public sigslot::has_slots<> { + public: + SocketClient(AsyncSocket* socket, const SocketAddress& addr, + Thread* post_thread, MessageHandler* phandler) + : socket_(AsyncUDPSocket::Create(socket, addr)), + post_thread_(post_thread), + post_handler_(phandler) { + socket_->SignalReadPacket.connect(this, &SocketClient::OnPacket); + } + + ~SocketClient() { + delete socket_; + } + + SocketAddress address() const { return socket_->GetLocalAddress(); } + + void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size, + const SocketAddress& remote_addr, + const PacketTime& packet_time) { + EXPECT_EQ(size, sizeof(uint32)); + uint32 prev = reinterpret_cast(buf)[0]; + uint32 result = Next(prev); + + post_thread_->PostDelayed(200, post_handler_, 0, new TestMessage(result)); + } + + private: + AsyncUDPSocket* socket_; + Thread* post_thread_; + MessageHandler* post_handler_; +}; + +// Receives messages and sends on a socket. +class MessageClient : public MessageHandler, public TestGenerator { + public: + MessageClient(Thread* pth, Socket* socket) + : socket_(socket) { + } + + virtual ~MessageClient() { + delete socket_; + } + + virtual void OnMessage(Message *pmsg) { + TestMessage* msg = static_cast(pmsg->pdata); + int result = Next(msg->value); + EXPECT_GE(socket_->Send(&result, sizeof(result)), 0); + delete msg; + } + + private: + Socket* socket_; +}; + +class CustomThread : public rtc::Thread { + public: + CustomThread() {} + virtual ~CustomThread() { Stop(); } + bool Start() { return false; } +}; + + +// A thread that does nothing when it runs and signals an event +// when it is destroyed. +class SignalWhenDestroyedThread : public Thread { + public: + SignalWhenDestroyedThread(Event* event) + : event_(event) { + } + + virtual ~SignalWhenDestroyedThread() { + Stop(); + event_->Set(); + } + + virtual void Run() { + // Do nothing. + } + + private: + Event* event_; +}; + +// Function objects to test Thread::Invoke. +struct FunctorA { + int operator()() { return 42; } +}; +class FunctorB { + public: + explicit FunctorB(bool* flag) : flag_(flag) {} + void operator()() { if (flag_) *flag_ = true; } + private: + bool* flag_; +}; +struct FunctorC { + int operator()() { + Thread::Current()->ProcessMessages(50); + return 24; + } +}; + +// See: https://code.google.com/p/webrtc/issues/detail?id=2409 +TEST(ThreadTest, DISABLED_Main) { + const SocketAddress addr("127.0.0.1", 0); + + // Create the messaging client on its own thread. + Thread th1; + Socket* socket = th1.socketserver()->CreateAsyncSocket(addr.family(), + SOCK_DGRAM); + MessageClient msg_client(&th1, socket); + + // Create the socket client on its own thread. + Thread th2; + AsyncSocket* asocket = + th2.socketserver()->CreateAsyncSocket(addr.family(), SOCK_DGRAM); + SocketClient sock_client(asocket, addr, &th1, &msg_client); + + socket->Connect(sock_client.address()); + + th1.Start(); + th2.Start(); + + // Get the messages started. + th1.PostDelayed(100, &msg_client, 0, new TestMessage(1)); + + // Give the clients a little while to run. + // Messages will be processed at 100, 300, 500, 700, 900. + Thread* th_main = Thread::Current(); + th_main->ProcessMessages(1000); + + // Stop the sending client. Give the receiver a bit longer to run, in case + // it is running on a machine that is under load (e.g. the build machine). + th1.Stop(); + th_main->ProcessMessages(200); + th2.Stop(); + + // Make sure the results were correct + EXPECT_EQ(5, msg_client.count); + EXPECT_EQ(34, msg_client.last); + EXPECT_EQ(5, sock_client.count); + EXPECT_EQ(55, sock_client.last); +} + +// Test that setting thread names doesn't cause a malfunction. +// There's no easy way to verify the name was set properly at this time. +TEST(ThreadTest, Names) { + // Default name + Thread *thread; + thread = new Thread(); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + thread = new Thread(); + // Name with no object parameter + EXPECT_TRUE(thread->SetName("No object", NULL)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + // Really long name + thread = new Thread(); + EXPECT_TRUE(thread->SetName("Abcdefghijklmnopqrstuvwxyz1234567890", this)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; +} + +// Test that setting thread priorities doesn't cause a malfunction. +// There's no easy way to verify the priority was set properly at this time. +TEST(ThreadTest, Priorities) { + Thread *thread; + thread = new Thread(); + EXPECT_TRUE(thread->SetPriority(PRIORITY_HIGH)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + thread = new Thread(); + EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL)); + EXPECT_TRUE(thread->Start()); + thread->Stop(); + delete thread; + + thread = new Thread(); + EXPECT_TRUE(thread->Start()); +#if defined(WEBRTC_WIN) + EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL)); +#else + EXPECT_FALSE(thread->SetPriority(PRIORITY_ABOVE_NORMAL)); +#endif + thread->Stop(); + delete thread; + +} + +TEST(ThreadTest, Wrap) { + Thread* current_thread = Thread::Current(); + current_thread->UnwrapCurrent(); + CustomThread* cthread = new CustomThread(); + EXPECT_TRUE(cthread->WrapCurrent()); + EXPECT_TRUE(cthread->RunningForTest()); + EXPECT_FALSE(cthread->IsOwned()); + cthread->UnwrapCurrent(); + EXPECT_FALSE(cthread->RunningForTest()); + delete cthread; + current_thread->WrapCurrent(); +} + +TEST(ThreadTest, Invoke) { + // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functors. + EXPECT_EQ(42, thread.Invoke(FunctorA())); + bool called = false; + FunctorB f2(&called); + thread.Invoke(f2); + EXPECT_TRUE(called); + // Try calling bare functions. + struct LocalFuncs { + static int Func1() { return 999; } + static void Func2() {} + }; + EXPECT_EQ(999, thread.Invoke(&LocalFuncs::Func1)); + thread.Invoke(&LocalFuncs::Func2); +} + +class AsyncInvokeTest : public testing::Test { + public: + void IntCallback(int value) { + EXPECT_EQ(expected_thread_, Thread::Current()); + int_value_ = value; + } + void AsyncInvokeIntCallback(AsyncInvoker* invoker, Thread* thread) { + expected_thread_ = thread; + invoker->AsyncInvoke(thread, FunctorC(), + &AsyncInvokeTest::IntCallback, + static_cast(this)); + invoke_started_.Set(); + } + void SetExpectedThreadForIntCallback(Thread* thread) { + expected_thread_ = thread; + } + + protected: + enum { kWaitTimeout = 1000 }; + AsyncInvokeTest() + : int_value_(0), + invoke_started_(true, false), + expected_thread_(NULL) {} + + int int_value_; + Event invoke_started_; + Thread* expected_thread_; +}; + +TEST_F(AsyncInvokeTest, FireAndForget) { + AsyncInvoker invoker; + // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functor. + bool called = false; + invoker.AsyncInvoke(&thread, FunctorB(&called)); + EXPECT_TRUE_WAIT(called, kWaitTimeout); +} + +TEST_F(AsyncInvokeTest, WithCallback) { + AsyncInvoker invoker; + // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functor. + SetExpectedThreadForIntCallback(Thread::Current()); + invoker.AsyncInvoke(&thread, FunctorA(), + &AsyncInvokeTest::IntCallback, + static_cast(this)); + EXPECT_EQ_WAIT(42, int_value_, kWaitTimeout); +} + +TEST_F(AsyncInvokeTest, CancelInvoker) { + // Create and start the thread. + Thread thread; + thread.Start(); + // Try destroying invoker during call. + { + AsyncInvoker invoker; + invoker.AsyncInvoke(&thread, FunctorC(), + &AsyncInvokeTest::IntCallback, + static_cast(this)); + } + // With invoker gone, callback should be cancelled. + Thread::Current()->ProcessMessages(kWaitTimeout); + EXPECT_EQ(0, int_value_); +} + +TEST_F(AsyncInvokeTest, CancelCallingThread) { + AsyncInvoker invoker; + { // Create and start the thread. + Thread thread; + thread.Start(); + // Try calling functor. + thread.Invoke(Bind(&AsyncInvokeTest::AsyncInvokeIntCallback, + static_cast(this), + &invoker, Thread::Current())); + // Wait for the call to begin. + ASSERT_TRUE(invoke_started_.Wait(kWaitTimeout)); + } + // Calling thread is gone. Return message shouldn't happen. + Thread::Current()->ProcessMessages(kWaitTimeout); + EXPECT_EQ(0, int_value_); +} + +TEST_F(AsyncInvokeTest, KillInvokerBeforeExecute) { + Thread thread; + thread.Start(); + { + AsyncInvoker invoker; + // Try calling functor. + thread.Invoke(Bind(&AsyncInvokeTest::AsyncInvokeIntCallback, + static_cast(this), + &invoker, Thread::Current())); + // Wait for the call to begin. + ASSERT_TRUE(invoke_started_.Wait(kWaitTimeout)); + } + // Invoker is destroyed. Function should not execute. + Thread::Current()->ProcessMessages(kWaitTimeout); + EXPECT_EQ(0, int_value_); +} + +TEST_F(AsyncInvokeTest, Flush) { + AsyncInvoker invoker; + bool flag1 = false; + bool flag2 = false; + // Queue two async calls to the current thread. + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag1)); + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag2)); + // Because we haven't pumped messages, these should not have run yet. + EXPECT_FALSE(flag1); + EXPECT_FALSE(flag2); + // Force them to run now. + invoker.Flush(Thread::Current()); + EXPECT_TRUE(flag1); + EXPECT_TRUE(flag2); +} + +TEST_F(AsyncInvokeTest, FlushWithIds) { + AsyncInvoker invoker; + bool flag1 = false; + bool flag2 = false; + // Queue two async calls to the current thread, one with a message id. + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag1), + 5); + invoker.AsyncInvoke(Thread::Current(), + FunctorB(&flag2)); + // Because we haven't pumped messages, these should not have run yet. + EXPECT_FALSE(flag1); + EXPECT_FALSE(flag2); + // Execute pending calls with id == 5. + invoker.Flush(Thread::Current(), 5); + EXPECT_TRUE(flag1); + EXPECT_FALSE(flag2); + flag1 = false; + // Execute all pending calls. The id == 5 call should not execute again. + invoker.Flush(Thread::Current()); + EXPECT_FALSE(flag1); + EXPECT_TRUE(flag2); +} + + +#if defined(WEBRTC_WIN) +class ComThreadTest : public testing::Test, public MessageHandler { + public: + ComThreadTest() : done_(false) {} + protected: + virtual void OnMessage(Message* message) { + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + // S_FALSE means the thread was already inited for a multithread apartment. + EXPECT_EQ(S_FALSE, hr); + if (SUCCEEDED(hr)) { + CoUninitialize(); + } + done_ = true; + } + bool done_; +}; + +TEST_F(ComThreadTest, ComInited) { + Thread* thread = new ComThread(); + EXPECT_TRUE(thread->Start()); + thread->Post(this, 0); + EXPECT_TRUE_WAIT(done_, 1000); + delete thread; +} +#endif diff --git a/webrtc/base/timeutils.cc b/webrtc/base/timeutils.cc new file mode 100644 index 000000000..dcf83e3dd --- /dev/null +++ b/webrtc/base/timeutils.cc @@ -0,0 +1,203 @@ +/* + * Copyright 2004 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 + +#if defined(WEBRTC_POSIX) +#include +#if defined(WEBRTC_MAC) +#include +#endif +#endif + +#if defined(WEBRTC_WIN) +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/timeutils.h" + +#define EFFICIENT_IMPLEMENTATION 1 + +namespace rtc { + +const uint32 HALF = 0x80000000; + +uint64 TimeNanos() { + int64 ticks = 0; +#if defined(WEBRTC_MAC) + static mach_timebase_info_data_t timebase; + if (timebase.denom == 0) { + // Get the timebase if this is the first time we run. + // Recommended by Apple's QA1398. + VERIFY(KERN_SUCCESS == mach_timebase_info(&timebase)); + } + // Use timebase to convert absolute time tick units into nanoseconds. + ticks = mach_absolute_time() * timebase.numer / timebase.denom; +#elif defined(WEBRTC_POSIX) + struct timespec ts; + // TODO: Do we need to handle the case when CLOCK_MONOTONIC + // is not supported? + clock_gettime(CLOCK_MONOTONIC, &ts); + ticks = kNumNanosecsPerSec * static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec); +#elif defined(WEBRTC_WIN) + static volatile LONG last_timegettime = 0; + static volatile int64 num_wrap_timegettime = 0; + volatile LONG* last_timegettime_ptr = &last_timegettime; + DWORD now = timeGetTime(); + // Atomically update the last gotten time + DWORD old = InterlockedExchange(last_timegettime_ptr, now); + if (now < old) { + // If now is earlier than old, there may have been a race between + // threads. + // 0x0fffffff ~3.1 days, the code will not take that long to execute + // so it must have been a wrap around. + if (old > 0xf0000000 && now < 0x0fffffff) { + num_wrap_timegettime++; + } + } + ticks = now + (num_wrap_timegettime << 32); + // TODO: Calculate with nanosecond precision. Otherwise, we're just + // wasting a multiply and divide when doing Time() on Windows. + ticks = ticks * kNumNanosecsPerMillisec; +#endif + return ticks; +} + +uint32 Time() { + return static_cast(TimeNanos() / kNumNanosecsPerMillisec); +} + +uint64 TimeMicros() { + return static_cast(TimeNanos() / kNumNanosecsPerMicrosec); +} + +#if defined(WEBRTC_WIN) +static const uint64 kFileTimeToUnixTimeEpochOffset = 116444736000000000ULL; + +struct timeval { + long tv_sec, tv_usec; // NOLINT +}; + +// Emulate POSIX gettimeofday(). +// Based on breakpad/src/third_party/glog/src/utilities.cc +static int gettimeofday(struct timeval *tv, void *tz) { + // FILETIME is measured in tens of microseconds since 1601-01-01 UTC. + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + LARGE_INTEGER li; + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + // Convert to seconds and microseconds since Unix time Epoch. + int64 micros = (li.QuadPart - kFileTimeToUnixTimeEpochOffset) / 10; + tv->tv_sec = static_cast(micros / kNumMicrosecsPerSec); // NOLINT + tv->tv_usec = static_cast(micros % kNumMicrosecsPerSec); // NOLINT + + return 0; +} + +// Emulate POSIX gmtime_r(). +static struct tm *gmtime_r(const time_t *timep, struct tm *result) { + // On Windows, gmtime is thread safe. + struct tm *tm = gmtime(timep); // NOLINT + if (tm == NULL) { + return NULL; + } + *result = *tm; + return result; +} +#endif // WEBRTC_WIN + +void CurrentTmTime(struct tm *tm, int *microseconds) { + struct timeval timeval; + if (gettimeofday(&timeval, NULL) < 0) { + // Incredibly unlikely code path. + timeval.tv_sec = timeval.tv_usec = 0; + } + time_t secs = timeval.tv_sec; + gmtime_r(&secs, tm); + *microseconds = timeval.tv_usec; +} + +uint32 TimeAfter(int32 elapsed) { + ASSERT(elapsed >= 0); + ASSERT(static_cast(elapsed) < HALF); + return Time() + elapsed; +} + +bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later) { + if (earlier <= later) { + return ((earlier <= middle) && (middle <= later)); + } else { + return !((later < middle) && (middle < earlier)); + } +} + +bool TimeIsLaterOrEqual(uint32 earlier, uint32 later) { +#if EFFICIENT_IMPLEMENTATION + int32 diff = later - earlier; + return (diff >= 0 && static_cast(diff) < HALF); +#else + const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF); + return later_or_equal; +#endif +} + +bool TimeIsLater(uint32 earlier, uint32 later) { +#if EFFICIENT_IMPLEMENTATION + int32 diff = later - earlier; + return (diff > 0 && static_cast(diff) < HALF); +#else + const bool earlier_or_equal = TimeIsBetween(later, earlier, later + HALF); + return !earlier_or_equal; +#endif +} + +int32 TimeDiff(uint32 later, uint32 earlier) { +#if EFFICIENT_IMPLEMENTATION + return later - earlier; +#else + const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF); + if (later_or_equal) { + if (earlier <= later) { + return static_cast(later - earlier); + } else { + return static_cast(later + (UINT32_MAX - earlier) + 1); + } + } else { + if (later <= earlier) { + return -static_cast(earlier - later); + } else { + return -static_cast(earlier + (UINT32_MAX - later) + 1); + } + } +#endif +} + +TimestampWrapAroundHandler::TimestampWrapAroundHandler() + : last_ts_(0), num_wrap_(0) {} + +int64 TimestampWrapAroundHandler::Unwrap(uint32 ts) { + if (ts < last_ts_) { + if (last_ts_ > 0xf0000000 && ts < 0x0fffffff) { + ++num_wrap_; + } + } + last_ts_ = ts; + int64_t unwrapped_ts = ts + (num_wrap_ << 32); + return unwrapped_ts; +} + +} // namespace rtc diff --git a/webrtc/base/timeutils.h b/webrtc/base/timeutils.h new file mode 100644 index 000000000..ca041a7d1 --- /dev/null +++ b/webrtc/base/timeutils.h @@ -0,0 +1,96 @@ +/* + * Copyright 2005 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_BASE_TIMEUTILS_H_ +#define WEBRTC_BASE_TIMEUTILS_H_ + +#include + +#include "webrtc/base/basictypes.h" + +namespace rtc { + +static const int64 kNumMillisecsPerSec = INT64_C(1000); +static const int64 kNumMicrosecsPerSec = INT64_C(1000000); +static const int64 kNumNanosecsPerSec = INT64_C(1000000000); + +static const int64 kNumMicrosecsPerMillisec = kNumMicrosecsPerSec / + kNumMillisecsPerSec; +static const int64 kNumNanosecsPerMillisec = kNumNanosecsPerSec / + kNumMillisecsPerSec; +static const int64 kNumNanosecsPerMicrosec = kNumNanosecsPerSec / + kNumMicrosecsPerSec; + +// January 1970, in NTP milliseconds. +static const int64 kJan1970AsNtpMillisecs = INT64_C(2208988800000); + +typedef uint32 TimeStamp; + +// Returns the current time in milliseconds. +uint32 Time(); +// Returns the current time in microseconds. +uint64 TimeMicros(); +// Returns the current time in nanoseconds. +uint64 TimeNanos(); + +// Stores current time in *tm and microseconds in *microseconds. +void CurrentTmTime(struct tm *tm, int *microseconds); + +// Returns a future timestamp, 'elapsed' milliseconds from now. +uint32 TimeAfter(int32 elapsed); + +// Comparisons between time values, which can wrap around. +bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later); // Inclusive +bool TimeIsLaterOrEqual(uint32 earlier, uint32 later); // Inclusive +bool TimeIsLater(uint32 earlier, uint32 later); // Exclusive + +// Returns the later of two timestamps. +inline uint32 TimeMax(uint32 ts1, uint32 ts2) { + return TimeIsLaterOrEqual(ts1, ts2) ? ts2 : ts1; +} + +// Returns the earlier of two timestamps. +inline uint32 TimeMin(uint32 ts1, uint32 ts2) { + return TimeIsLaterOrEqual(ts1, ts2) ? ts1 : ts2; +} + +// Number of milliseconds that would elapse between 'earlier' and 'later' +// timestamps. The value is negative if 'later' occurs before 'earlier'. +int32 TimeDiff(uint32 later, uint32 earlier); + +// The number of milliseconds that have elapsed since 'earlier'. +inline int32 TimeSince(uint32 earlier) { + return TimeDiff(Time(), earlier); +} + +// The number of milliseconds that will elapse between now and 'later'. +inline int32 TimeUntil(uint32 later) { + return TimeDiff(later, Time()); +} + +// Converts a unix timestamp in nanoseconds to an NTP timestamp in ms. +inline int64 UnixTimestampNanosecsToNtpMillisecs(int64 unix_ts_ns) { + return unix_ts_ns / kNumNanosecsPerMillisec + kJan1970AsNtpMillisecs; +} + +class TimestampWrapAroundHandler { + public: + TimestampWrapAroundHandler(); + + int64 Unwrap(uint32 ts); + + private: + uint32 last_ts_; + int64 num_wrap_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TIMEUTILS_H_ diff --git a/webrtc/base/timeutils_unittest.cc b/webrtc/base/timeutils_unittest.cc new file mode 100644 index 000000000..087fb0c28 --- /dev/null +++ b/webrtc/base/timeutils_unittest.cc @@ -0,0 +1,169 @@ +/* + * Copyright 2004 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 "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +TEST(TimeTest, TimeInMs) { + uint32 ts_earlier = Time(); + Thread::SleepMs(100); + uint32 ts_now = Time(); + // Allow for the thread to wakeup ~20ms early. + EXPECT_GE(ts_now, ts_earlier + 80); + // Make sure the Time is not returning in smaller unit like microseconds. + EXPECT_LT(ts_now, ts_earlier + 1000); +} + +TEST(TimeTest, Comparison) { + // Obtain two different times, in known order + TimeStamp ts_earlier = Time(); + Thread::SleepMs(100); + TimeStamp ts_now = Time(); + EXPECT_NE(ts_earlier, ts_now); + + // Common comparisons + EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_now)); + EXPECT_TRUE( TimeIsLater( ts_earlier, ts_now)); + EXPECT_FALSE(TimeIsLaterOrEqual(ts_now, ts_earlier)); + EXPECT_FALSE(TimeIsLater( ts_now, ts_earlier)); + + // Edge cases + EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_earlier)); + EXPECT_FALSE(TimeIsLater( ts_earlier, ts_earlier)); + + // Obtain a third time + TimeStamp ts_later = TimeAfter(100); + EXPECT_NE(ts_now, ts_later); + EXPECT_TRUE( TimeIsLater(ts_now, ts_later)); + EXPECT_TRUE( TimeIsLater(ts_earlier, ts_later)); + + // Common comparisons + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_now, ts_later)); + EXPECT_FALSE(TimeIsBetween(ts_earlier, ts_later, ts_now)); + EXPECT_FALSE(TimeIsBetween(ts_now, ts_earlier, ts_later)); + EXPECT_TRUE( TimeIsBetween(ts_now, ts_later, ts_earlier)); + EXPECT_TRUE( TimeIsBetween(ts_later, ts_earlier, ts_now)); + EXPECT_FALSE(TimeIsBetween(ts_later, ts_now, ts_earlier)); + + // Edge cases + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_earlier)); + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_later)); + EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_later, ts_later)); + + // Earlier of two times + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_now)); + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later)); + EXPECT_EQ(ts_earlier, TimeMin(ts_now, ts_earlier)); + EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier)); + + // Later of two times + EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_now, TimeMax(ts_earlier, ts_now)); + EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later)); + EXPECT_EQ(ts_now, TimeMax(ts_now, ts_earlier)); + EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier)); +} + +TEST(TimeTest, Intervals) { + TimeStamp ts_earlier = Time(); + TimeStamp ts_later = TimeAfter(500); + + // We can't depend on ts_later and ts_earlier to be exactly 500 apart + // since time elapses between the calls to Time() and TimeAfter(500) + EXPECT_LE(500, TimeDiff(ts_later, ts_earlier)); + EXPECT_GE(-500, TimeDiff(ts_earlier, ts_later)); + + // Time has elapsed since ts_earlier + EXPECT_GE(TimeSince(ts_earlier), 0); + + // ts_earlier is earlier than now, so TimeUntil ts_earlier is -ve + EXPECT_LE(TimeUntil(ts_earlier), 0); + + // ts_later likely hasn't happened yet, so TimeSince could be -ve + // but within 500 + EXPECT_GE(TimeSince(ts_later), -500); + + // TimeUntil ts_later is at most 500 + EXPECT_LE(TimeUntil(ts_later), 500); +} + +TEST(TimeTest, BoundaryComparison) { + // Obtain two different times, in known order + TimeStamp ts_earlier = static_cast(-50); + TimeStamp ts_later = ts_earlier + 100; + EXPECT_NE(ts_earlier, ts_later); + + // Common comparisons + EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_later)); + EXPECT_TRUE( TimeIsLater( ts_earlier, ts_later)); + EXPECT_FALSE(TimeIsLaterOrEqual(ts_later, ts_earlier)); + EXPECT_FALSE(TimeIsLater( ts_later, ts_earlier)); + + // Earlier of two times + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later)); + EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier)); + + // Later of two times + EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier)); + EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later)); + EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier)); + + // Interval + EXPECT_EQ(100, TimeDiff(ts_later, ts_earlier)); + EXPECT_EQ(-100, TimeDiff(ts_earlier, ts_later)); +} + +TEST(TimeTest, DISABLED_CurrentTmTime) { + struct tm tm; + int microseconds; + + time_t before = ::time(NULL); + CurrentTmTime(&tm, µseconds); + time_t after = ::time(NULL); + + // Assert that 'tm' represents a time between 'before' and 'after'. + // mktime() uses local time, so we have to compensate for that. + time_t local_delta = before - ::mktime(::gmtime(&before)); // NOLINT + time_t t = ::mktime(&tm) + local_delta; + + EXPECT_TRUE(before <= t && t <= after); + EXPECT_TRUE(0 <= microseconds && microseconds < 1000000); +} + +class TimestampWrapAroundHandlerTest : public testing::Test { + public: + TimestampWrapAroundHandlerTest() {} + + protected: + TimestampWrapAroundHandler wraparound_handler_; +}; + +TEST_F(TimestampWrapAroundHandlerTest, Unwrap) { + uint32 ts = 0xfffffff2; + int64 unwrapped_ts = ts; + EXPECT_EQ(ts, wraparound_handler_.Unwrap(ts)); + ts = 2; + unwrapped_ts += 0x10; + EXPECT_EQ(unwrapped_ts, wraparound_handler_.Unwrap(ts)); + ts = 0xfffffff2; + unwrapped_ts += 0xfffffff0; + EXPECT_EQ(unwrapped_ts, wraparound_handler_.Unwrap(ts)); + ts = 0; + unwrapped_ts += 0xe; + EXPECT_EQ(unwrapped_ts, wraparound_handler_.Unwrap(ts)); +} + +} // namespace rtc diff --git a/webrtc/base/timing.cc b/webrtc/base/timing.cc new file mode 100644 index 000000000..aa1fc4290 --- /dev/null +++ b/webrtc/base/timing.cc @@ -0,0 +1,112 @@ +/* + * Copyright 2008 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 "webrtc/base/timing.h" +#include "webrtc/base/timeutils.h" + +#if defined(WEBRTC_POSIX) +#include +#include +#include +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#endif +#elif defined(WEBRTC_WIN) +#include +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +Timing::Timing() { +#if defined(WEBRTC_WIN) + // This may fail, but we handle failure gracefully in the methods + // that use it (use alternative sleep method). + // + // TODO: Make it possible for user to tell if IdleWait will + // be done at lesser resolution because of this. + timer_handle_ = CreateWaitableTimer(NULL, // Security attributes. + FALSE, // Manual reset? + NULL); // Timer name. +#endif +} + +Timing::~Timing() { +#if defined(WEBRTC_WIN) + if (timer_handle_ != NULL) + CloseHandle(timer_handle_); +#endif +} + +double Timing::WallTimeNow() { +#if defined(WEBRTC_POSIX) + struct timeval time; + gettimeofday(&time, NULL); + // Convert from second (1.0) and microsecond (1e-6). + return (static_cast(time.tv_sec) + + static_cast(time.tv_usec) * 1.0e-6); + +#elif defined(WEBRTC_WIN) + struct _timeb time; + _ftime(&time); + // Convert from second (1.0) and milliseconds (1e-3). + return (static_cast(time.time) + + static_cast(time.millitm) * 1.0e-3); +#endif +} + +double Timing::TimerNow() { + return (static_cast(TimeNanos()) / kNumNanosecsPerSec); +} + +double Timing::BusyWait(double period) { + double start_time = TimerNow(); + while (TimerNow() - start_time < period) { + } + return TimerNow() - start_time; +} + +double Timing::IdleWait(double period) { + double start_time = TimerNow(); + +#if defined(WEBRTC_POSIX) + double sec_int, sec_frac = modf(period, &sec_int); + struct timespec ts; + ts.tv_sec = static_cast(sec_int); + ts.tv_nsec = static_cast(sec_frac * 1.0e9); // NOLINT + + // NOTE(liulk): for the NOLINT above, long is the appropriate POSIX + // type. + + // POSIX nanosleep may be interrupted by signals. + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) { + } + +#elif defined(WEBRTC_WIN) + if (timer_handle_ != NULL) { + LARGE_INTEGER due_time; + + // Negative indicates relative time. The unit is 100 nanoseconds. + due_time.QuadPart = -LONGLONG(period * 1.0e7); + + SetWaitableTimer(timer_handle_, &due_time, 0, NULL, NULL, TRUE); + WaitForSingleObject(timer_handle_, INFINITE); + } else { + // Still attempts to sleep with lesser resolution. + // The unit is in milliseconds. + Sleep(DWORD(period * 1.0e3)); + } +#endif + + return TimerNow() - start_time; +} + +} // namespace rtc diff --git a/webrtc/base/timing.h b/webrtc/base/timing.h new file mode 100644 index 000000000..58b17a9fb --- /dev/null +++ b/webrtc/base/timing.h @@ -0,0 +1,59 @@ +/* + * Copyright 2008 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_BASE_TIMING_H_ +#define WEBRTC_BASE_TIMING_H_ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#endif + +namespace rtc { + +class Timing { + public: + Timing(); + virtual ~Timing(); + + // WallTimeNow() returns the current wall-clock time in seconds, + // within 10 milliseconds resolution. + virtual double WallTimeNow(); + + // TimerNow() is like WallTimeNow(), but is monotonically + // increasing. It returns seconds in resolution of 10 microseconds + // or better. Although timer and wall-clock time have the same + // timing unit, they do not necessarily correlate because wall-clock + // time may be adjusted backwards, hence not monotonic. + // Made virtual so we can make a fake one. + virtual double TimerNow(); + + // BusyWait() exhausts CPU as long as the time elapsed is less than + // the specified interval in seconds. Returns the actual waiting + // time based on TimerNow() measurement. + double BusyWait(double period); + + // IdleWait() relinquishes control of CPU for specified period in + // seconds. It uses highest resolution sleep mechanism as possible, + // but does not otherwise guarantee the accuracy. Returns the + // actual waiting time based on TimerNow() measurement. + // + // This function is not re-entrant for an object. Create a fresh + // Timing object for each thread. + double IdleWait(double period); + + private: +#if defined(WEBRTC_WIN) + HANDLE timer_handle_; +#endif +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_TIMING_H_ diff --git a/webrtc/base/transformadapter.cc b/webrtc/base/transformadapter.cc new file mode 100644 index 000000000..76b750c29 --- /dev/null +++ b/webrtc/base/transformadapter.cc @@ -0,0 +1,185 @@ +/* + * Copyright 2004 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 "webrtc/base/transformadapter.h" + +#include + +#include "webrtc/base/common.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +TransformAdapter::TransformAdapter(StreamInterface * stream, + TransformInterface * transform, + bool direction_read) + : StreamAdapterInterface(stream), transform_(transform), + direction_read_(direction_read), state_(ST_PROCESSING), len_(0) { +} + +TransformAdapter::~TransformAdapter() { + TransformAdapter::Close(); + delete transform_; +} + +StreamResult +TransformAdapter::Read(void * buffer, size_t buffer_len, + size_t * read, int * error) { + if (!direction_read_) + return SR_EOS; + + while (state_ != ST_ERROR) { + if (state_ == ST_COMPLETE) + return SR_EOS; + + // Buffer more data + if ((state_ == ST_PROCESSING) && (len_ < sizeof(buffer_))) { + size_t subread; + StreamResult result = StreamAdapterInterface::Read( + buffer_ + len_, + sizeof(buffer_) - len_, + &subread, + &error_); + if (result == SR_BLOCK) { + return SR_BLOCK; + } else if (result == SR_ERROR) { + state_ = ST_ERROR; + break; + } else if (result == SR_EOS) { + state_ = ST_FLUSHING; + } else { + len_ += subread; + } + } + + // Process buffered data + size_t in_len = len_; + size_t out_len = buffer_len; + StreamResult result = transform_->Transform(buffer_, &in_len, + buffer, &out_len, + (state_ == ST_FLUSHING)); + ASSERT(result != SR_BLOCK); + if (result == SR_EOS) { + // Note: Don't signal SR_EOS this iteration, unless out_len is zero + state_ = ST_COMPLETE; + } else if (result == SR_ERROR) { + state_ = ST_ERROR; + error_ = -1; // TODO: propagate error + break; + } else if ((out_len == 0) && (state_ == ST_FLUSHING)) { + // If there is no output AND no more input, then something is wrong + state_ = ST_ERROR; + error_ = -1; // TODO: better error code? + break; + } + + len_ -= in_len; + if (len_ > 0) + memmove(buffer_, buffer_ + in_len, len_); + + if (out_len == 0) + continue; + + if (read) + *read = out_len; + return SR_SUCCESS; + } + + if (error) + *error = error_; + return SR_ERROR; +} + +StreamResult +TransformAdapter::Write(const void * data, size_t data_len, + size_t * written, int * error) { + if (direction_read_) + return SR_EOS; + + size_t bytes_written = 0; + while (state_ != ST_ERROR) { + if (state_ == ST_COMPLETE) + return SR_EOS; + + if (len_ < sizeof(buffer_)) { + // Process buffered data + size_t in_len = data_len; + size_t out_len = sizeof(buffer_) - len_; + StreamResult result = transform_->Transform(data, &in_len, + buffer_ + len_, &out_len, + (state_ == ST_FLUSHING)); + + ASSERT(result != SR_BLOCK); + if (result == SR_EOS) { + // Note: Don't signal SR_EOS this iteration, unless no data written + state_ = ST_COMPLETE; + } else if (result == SR_ERROR) { + ASSERT(false); // When this happens, think about what should be done + state_ = ST_ERROR; + error_ = -1; // TODO: propagate error + break; + } + + len_ = out_len; + bytes_written = in_len; + } + + size_t pos = 0; + while (pos < len_) { + size_t subwritten; + StreamResult result = StreamAdapterInterface::Write(buffer_ + pos, + len_ - pos, + &subwritten, + &error_); + if (result == SR_BLOCK) { + ASSERT(false); // TODO: we should handle this + return SR_BLOCK; + } else if (result == SR_ERROR) { + state_ = ST_ERROR; + break; + } else if (result == SR_EOS) { + state_ = ST_COMPLETE; + break; + } + + pos += subwritten; + } + + len_ -= pos; + if (len_ > 0) + memmove(buffer_, buffer_ + pos, len_); + + if (bytes_written == 0) + continue; + + if (written) + *written = bytes_written; + return SR_SUCCESS; + } + + if (error) + *error = error_; + return SR_ERROR; +} + +void +TransformAdapter::Close() { + if (!direction_read_ && (state_ == ST_PROCESSING)) { + state_ = ST_FLUSHING; + do { + Write(0, 0, NULL, NULL); + } while (state_ == ST_FLUSHING); + } + state_ = ST_COMPLETE; + StreamAdapterInterface::Close(); +} + +} // namespace rtc diff --git a/webrtc/base/transformadapter.h b/webrtc/base/transformadapter.h new file mode 100644 index 000000000..ad24438ea --- /dev/null +++ b/webrtc/base/transformadapter.h @@ -0,0 +1,80 @@ +/* + * Copyright 2004 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_BASE_TRANSFORMADAPTER_H__ +#define WEBRTC_BASE_TRANSFORMADAPTER_H__ + +#include "webrtc/base/stream.h" + +namespace rtc { +/////////////////////////////////////////////////////////////////////////////// + +class TransformInterface { +public: + virtual ~TransformInterface() { } + + // Transform should convert the in_len bytes of input into the out_len-sized + // output buffer. If flush is true, there will be no more data following + // input. + // After the transformation, in_len contains the number of bytes consumed, and + // out_len contains the number of bytes ready in output. + // Note: Transform should not return SR_BLOCK, as there is no asynchronous + // notification available. + virtual StreamResult Transform(const void * input, size_t * in_len, + void * output, size_t * out_len, + bool flush) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// TransformAdapter causes all data passed through to be transformed by the +// supplied TransformInterface object, which may apply compression, encryption, +// etc. + +class TransformAdapter : public StreamAdapterInterface { +public: + // Note that the transformation is unidirectional, in the direction specified + // by the constructor. Operations in the opposite direction result in SR_EOS. + TransformAdapter(StreamInterface * stream, + TransformInterface * transform, + bool direction_read); + virtual ~TransformAdapter(); + + virtual StreamResult Read(void * buffer, size_t buffer_len, + size_t * read, int * error); + virtual StreamResult Write(const void * data, size_t data_len, + size_t * written, int * error); + virtual void Close(); + + // Apriori, we can't tell what the transformation does to the stream length. + virtual bool GetAvailable(size_t* size) const { return false; } + virtual bool ReserveSize(size_t size) { return true; } + + // Transformations might not be restartable + virtual bool Rewind() { return false; } + +private: + enum State { ST_PROCESSING, ST_FLUSHING, ST_COMPLETE, ST_ERROR }; + enum { BUFFER_SIZE = 1024 }; + + TransformInterface * transform_; + bool direction_read_; + State state_; + int error_; + + char buffer_[BUFFER_SIZE]; + size_t len_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_TRANSFORMADAPTER_H__ diff --git a/webrtc/base/unittest_main.cc b/webrtc/base/unittest_main.cc new file mode 100644 index 000000000..5d412d5ed --- /dev/null +++ b/webrtc/base/unittest_main.cc @@ -0,0 +1,101 @@ +/* + * Copyright 2007 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. + */ +// +// A reuseable entry point for gunit tests. + +#if defined(WEBRTC_WIN) +#include +#endif + +#include "webrtc/base/flags.h" +#include "webrtc/base/fileutils.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" + +DEFINE_bool(help, false, "prints this message"); +DEFINE_string(log, "", "logging options to use"); +#if defined(WEBRTC_WIN) +DEFINE_int(crt_break_alloc, -1, "memory allocation to break on"); +DEFINE_bool(default_error_handlers, false, + "leave the default exception/dbg handler functions in place"); + +void TestInvalidParameterHandler(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t pReserved) { + LOG(LS_ERROR) << "InvalidParameter Handler called. Exiting."; + LOG(LS_ERROR) << expression << std::endl << function << std::endl << file + << std::endl << line; + exit(1); +} +void TestPureCallHandler() { + LOG(LS_ERROR) << "Purecall Handler called. Exiting."; + exit(1); +} +int TestCrtReportHandler(int report_type, char* msg, int* retval) { + LOG(LS_ERROR) << "CrtReport Handler called..."; + LOG(LS_ERROR) << msg; + if (report_type == _CRT_ASSERT) { + exit(1); + } else { + *retval = 0; + return TRUE; + } +} +#endif // WEBRTC_WIN + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, false); + if (FLAG_help) { + rtc::FlagList::Print(NULL, false); + return 0; + } + +#if defined(WEBRTC_WIN) + if (!FLAG_default_error_handlers) { + // Make sure any errors don't throw dialogs hanging the test run. + _set_invalid_parameter_handler(TestInvalidParameterHandler); + _set_purecall_handler(TestPureCallHandler); + _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, TestCrtReportHandler); + } + +#ifdef _DEBUG // Turn on memory leak checking on Windows. + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF |_CRTDBG_LEAK_CHECK_DF); + if (FLAG_crt_break_alloc >= 0) { + _crtBreakAlloc = FLAG_crt_break_alloc; + } +#endif // _DEBUG +#endif // WEBRTC_WIN + + rtc::Filesystem::SetOrganizationName("google"); + rtc::Filesystem::SetApplicationName("unittest"); + + // By default, log timestamps. Allow overrides by used of a --log flag. + rtc::LogMessage::LogTimestamps(); + if (*FLAG_log != '\0') { + rtc::LogMessage::ConfigureLogging(FLAG_log, "unittest.log"); + } + + int res = RUN_ALL_TESTS(); + + // clean up logging so we don't appear to leak memory. + rtc::LogMessage::ConfigureLogging("", ""); + +#if defined(WEBRTC_WIN) + // Unhook crt function so that we don't ever log after statics have been + // uninitialized. + if (!FLAG_default_error_handlers) + _CrtSetReportHook2(_CRT_RPTHOOK_REMOVE, TestCrtReportHandler); +#endif + + return res; +} diff --git a/webrtc/base/unixfilesystem.cc b/webrtc/base/unixfilesystem.cc new file mode 100644 index 000000000..081d561db --- /dev/null +++ b/webrtc/base/unixfilesystem.cc @@ -0,0 +1,572 @@ +/* + * Copyright 2004 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 "webrtc/base/unixfilesystem.h" + +#include +#include +#include +#include +#include + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#include +#include +#include "webrtc/base/macutils.h" +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +#if defined(WEBRTC_POSIX) && !defined(WEBRTC_MAC) || defined(WEBRTC_IOS) +#include +#if defined(WEBRTC_ANDROID) +#include +#elif !defined(__native_client__) +#include +#endif // !defined(__native_client__) +#include +#include +#include +#endif // WEBRTC_POSIX && !WEBRTC_MAC || WEBRTC_IOS + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include +#include +#endif + +#if defined(__native_client__) && !defined(__GLIBC__) +#include +#endif + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringutils.h" + +#if defined(WEBRTC_IOS) +// Defined in iosfilesystem.mm. No header file to discourage use +// elsewhere; other places should use GetApp{Data,Temp}Folder() in +// this file. Don't copy/paste. I mean it. +char* IOSDataDirectory(); +char* IOSTempDirectory(); +void IOSAppName(rtc::Pathname* path); +#endif + +namespace rtc { + +#if !defined(WEBRTC_ANDROID) && !defined(WEBRTC_IOS) +char* UnixFilesystem::app_temp_path_ = NULL; +#else +char* UnixFilesystem::provided_app_data_folder_ = NULL; +char* UnixFilesystem::provided_app_temp_folder_ = NULL; + +void UnixFilesystem::SetAppDataFolder(const std::string& folder) { + delete [] provided_app_data_folder_; + provided_app_data_folder_ = CopyString(folder); +} + +void UnixFilesystem::SetAppTempFolder(const std::string& folder) { + delete [] provided_app_temp_folder_; + provided_app_temp_folder_ = CopyString(folder); +} +#endif + +UnixFilesystem::UnixFilesystem() { +#if defined(WEBRTC_IOS) + if (!provided_app_data_folder_) + provided_app_data_folder_ = IOSDataDirectory(); + if (!provided_app_temp_folder_) + provided_app_temp_folder_ = IOSTempDirectory(); +#endif +} + +UnixFilesystem::~UnixFilesystem() {} + +bool UnixFilesystem::CreateFolder(const Pathname &path, mode_t mode) { + std::string pathname(path.pathname()); + int len = pathname.length(); + if ((len == 0) || (pathname[len - 1] != '/')) + return false; + + struct stat st; + int res = ::stat(pathname.c_str(), &st); + if (res == 0) { + // Something exists at this location, check if it is a directory + return S_ISDIR(st.st_mode) != 0; + } else if (errno != ENOENT) { + // Unexpected error + return false; + } + + // Directory doesn't exist, look up one directory level + do { + --len; + } while ((len > 0) && (pathname[len - 1] != '/')); + + if (!CreateFolder(Pathname(pathname.substr(0, len)), mode)) { + return false; + } + + LOG(LS_INFO) << "Creating folder: " << pathname; + return (0 == ::mkdir(pathname.c_str(), mode)); +} + +bool UnixFilesystem::CreateFolder(const Pathname &path) { + return CreateFolder(path, 0755); +} + +FileStream *UnixFilesystem::OpenFile(const Pathname &filename, + const std::string &mode) { + FileStream *fs = new FileStream(); + if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) { + delete fs; + fs = NULL; + } + return fs; +} + +bool UnixFilesystem::CreatePrivateFile(const Pathname &filename) { + int fd = open(filename.pathname().c_str(), + O_RDWR | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR); + if (fd < 0) { + LOG_ERR(LS_ERROR) << "open() failed."; + return false; + } + // Don't need to keep the file descriptor. + if (close(fd) < 0) { + LOG_ERR(LS_ERROR) << "close() failed."; + // Continue. + } + return true; +} + +bool UnixFilesystem::DeleteFile(const Pathname &filename) { + LOG(LS_INFO) << "Deleting file:" << filename.pathname(); + + if (!IsFile(filename)) { + ASSERT(IsFile(filename)); + return false; + } + return ::unlink(filename.pathname().c_str()) == 0; +} + +bool UnixFilesystem::DeleteEmptyFolder(const Pathname &folder) { + LOG(LS_INFO) << "Deleting folder" << folder.pathname(); + + if (!IsFolder(folder)) { + ASSERT(IsFolder(folder)); + return false; + } + std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1); + return ::rmdir(no_slash.c_str()) == 0; +} + +bool UnixFilesystem::GetTemporaryFolder(Pathname &pathname, bool create, + const std::string *append) { +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + FSRef fr; + if (0 != FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, + kCreateFolder, &fr)) + return false; + unsigned char buffer[NAME_MAX+1]; + if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer))) + return false; + pathname.SetPathname(reinterpret_cast(buffer), ""); +#elif defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + ASSERT(provided_app_temp_folder_ != NULL); + pathname.SetPathname(provided_app_temp_folder_, ""); +#else // !WEBRTC_MAC || WEBRTC_IOS && !WEBRTC_ANDROID + if (const char* tmpdir = getenv("TMPDIR")) { + pathname.SetPathname(tmpdir, ""); + } else if (const char* tmp = getenv("TMP")) { + pathname.SetPathname(tmp, ""); + } else { +#ifdef P_tmpdir + pathname.SetPathname(P_tmpdir, ""); +#else // !P_tmpdir + pathname.SetPathname("/tmp/", ""); +#endif // !P_tmpdir + } +#endif // !WEBRTC_MAC || WEBRTC_IOS && !WEBRTC_ANDROID + if (append) { + ASSERT(!append->empty()); + pathname.AppendFolder(*append); + } + return !create || CreateFolder(pathname); +} + +std::string UnixFilesystem::TempFilename(const Pathname &dir, + const std::string &prefix) { + int len = dir.pathname().size() + prefix.size() + 2 + 6; + char *tempname = new char[len]; + + snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(), + prefix.c_str()); + int fd = ::mkstemp(tempname); + if (fd != -1) + ::close(fd); + std::string ret(tempname); + delete[] tempname; + + return ret; +} + +bool UnixFilesystem::MoveFile(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFile(old_path)) { + ASSERT(IsFile(old_path)); + return false; + } + LOG(LS_VERBOSE) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) { + if (errno != EXDEV) + return false; + if (!CopyFile(old_path, new_path)) + return false; + if (!DeleteFile(old_path)) + return false; + } + return true; +} + +bool UnixFilesystem::MoveFolder(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFolder(old_path)) { + ASSERT(IsFolder(old_path)); + return false; + } + LOG(LS_VERBOSE) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) { + if (errno != EXDEV) + return false; + if (!CopyFolder(old_path, new_path)) + return false; + if (!DeleteFolderAndContents(old_path)) + return false; + } + return true; +} + +bool UnixFilesystem::IsFolder(const Pathname &path) { + struct stat st; + if (stat(path.pathname().c_str(), &st) < 0) + return false; + return S_ISDIR(st.st_mode); +} + +bool UnixFilesystem::CopyFile(const Pathname &old_path, + const Pathname &new_path) { + LOG(LS_VERBOSE) << "Copying " << old_path.pathname() + << " to " << new_path.pathname(); + char buf[256]; + size_t len; + + StreamInterface *source = OpenFile(old_path, "rb"); + if (!source) + return false; + + StreamInterface *dest = OpenFile(new_path, "wb"); + if (!dest) { + delete source; + return false; + } + + while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS) + dest->Write(buf, len, NULL, NULL); + + delete source; + delete dest; + return true; +} + +bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) { +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + ASSERT(provided_app_temp_folder_ != NULL); +#endif + + const char* const kTempPrefixes[] = { +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + provided_app_temp_folder_, +#else + "/tmp/", "/var/tmp/", +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + "/private/tmp/", "/private/var/tmp/", "/private/var/folders/", +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) +#endif // WEBRTC_ANDROID || WEBRTC_IOS + }; + for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) { + if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i], + strlen(kTempPrefixes[i]))) + return true; + } + return false; +} + +bool UnixFilesystem::IsFile(const Pathname& pathname) { + struct stat st; + int res = ::stat(pathname.pathname().c_str(), &st); + // Treat symlinks, named pipes, etc. all as files. + return res == 0 && !S_ISDIR(st.st_mode); +} + +bool UnixFilesystem::IsAbsent(const Pathname& pathname) { + struct stat st; + int res = ::stat(pathname.pathname().c_str(), &st); + // Note: we specifically maintain ENOTDIR as an error, because that implies + // that you could not call CreateFolder(pathname). + return res != 0 && ENOENT == errno; +} + +bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) { + struct stat st; + if (::stat(pathname.pathname().c_str(), &st) != 0) + return false; + *size = st.st_size; + return true; +} + +bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) { + struct stat st; + if (::stat(path.pathname().c_str(), &st) != 0) + return false; + switch (which) { + case FTT_CREATED: + *time = st.st_ctime; + break; + case FTT_MODIFIED: + *time = st.st_mtime; + break; + case FTT_ACCESSED: + *time = st.st_atime; + break; + default: + return false; + } + return true; +} + +bool UnixFilesystem::GetAppPathname(Pathname* path) { +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + ProcessSerialNumber psn = { 0, kCurrentProcess }; + CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn, + kProcessDictionaryIncludeAllInformationMask); + if (NULL == procinfo) + return false; + CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo, + kIOBundleExecutableKey); + std::string path8; + bool success = ToUtf8(cfpath, &path8); + CFRelease(procinfo); + if (success) + path->SetPathname(path8); + return success; +#elif defined(__native_client__) + return false; +#elif IOS + IOSAppName(path); + return true; +#else // WEBRTC_MAC && !defined(WEBRTC_IOS) + char buffer[PATH_MAX + 2]; + ssize_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1); + if ((len <= 0) || (len == PATH_MAX + 1)) + return false; + buffer[len] = '\0'; + path->SetPathname(buffer); + return true; +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) +} + +bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) { + ASSERT(!organization_name_.empty()); + ASSERT(!application_name_.empty()); + + // First get the base directory for app data. +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + if (per_user) { + // Use ~/Library/Application Support/// + FSRef fr; + if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType, + kCreateFolder, &fr)) + return false; + unsigned char buffer[NAME_MAX+1]; + if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer))) + return false; + path->SetPathname(reinterpret_cast(buffer), ""); + } else { + // TODO + return false; + } +#elif defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) // && !WEBRTC_MAC || WEBRTC_IOS + ASSERT(provided_app_data_folder_ != NULL); + path->SetPathname(provided_app_data_folder_, ""); +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) // && !WEBRTC_MAC && !WEBRTC_IOS && !WEBRTC_ANDROID + if (per_user) { + // We follow the recommendations in + // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + // It specifies separate directories for data and config files, but + // GetAppDataFolder() does not distinguish. We just return the config dir + // path. + const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); + if (xdg_config_home) { + path->SetPathname(xdg_config_home, ""); + } else { + // XDG says to default to $HOME/.config. We also support falling back to + // other synonyms for HOME if for some reason it is not defined. + const char* homedir; + if (const char* home = getenv("HOME")) { + homedir = home; + } else if (const char* dotdir = getenv("DOTDIR")) { + homedir = dotdir; + } else if (passwd* pw = getpwuid(geteuid())) { + homedir = pw->pw_dir; + } else { + return false; + } + path->SetPathname(homedir, ""); + path->AppendFolder(".config"); + } + } else { + // XDG does not define a standard directory for writable global data. Let's + // just use this. + path->SetPathname("/var/cache/", ""); + } +#endif // !WEBRTC_MAC && !WEBRTC_LINUX + + // Now add on a sub-path for our app. +#if defined(WEBRTC_MAC) || defined(WEBRTC_ANDROID) + path->AppendFolder(organization_name_); + path->AppendFolder(application_name_); +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + // XDG says to use a single directory level, so we concatenate the org and app + // name with a hyphen. We also do the Linuxy thing and convert to all + // lowercase with no spaces. + std::string subdir(organization_name_); + subdir.append("-"); + subdir.append(application_name_); + replace_substrs(" ", 1, "", 0, &subdir); + std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower); + path->AppendFolder(subdir); +#endif + if (!CreateFolder(*path, 0700)) { + return false; + } +#if !defined(__native_client__) + // If the folder already exists, it may have the wrong mode or be owned by + // someone else, both of which are security problems. Setting the mode + // avoids both issues since it will fail if the path is not owned by us. + if (0 != ::chmod(path->pathname().c_str(), 0700)) { + LOG_ERR(LS_ERROR) << "Can't set mode on " << path; + return false; + } +#endif + return true; +} + +bool UnixFilesystem::GetAppTempFolder(Pathname* path) { +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + ASSERT(provided_app_temp_folder_ != NULL); + path->SetPathname(provided_app_temp_folder_); + return true; +#else + ASSERT(!application_name_.empty()); + // TODO: Consider whether we are worried about thread safety. + if (app_temp_path_ != NULL && strlen(app_temp_path_) > 0) { + path->SetPathname(app_temp_path_); + return true; + } + + // Create a random directory as /tmp/-- + char buffer[128]; + sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d", + static_cast(getpid()), + static_cast(time(0))); + std::string folder(application_name_); + folder.append(buffer); + if (!GetTemporaryFolder(*path, true, &folder)) + return false; + + delete [] app_temp_path_; + app_temp_path_ = CopyString(path->pathname()); + // TODO: atexit(DeleteFolderAndContents(app_temp_path_)); + return true; +#endif +} + +bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { +#ifdef __native_client__ + return false; +#else // __native_client__ + ASSERT(NULL != freebytes); + // TODO: Consider making relative paths absolute using cwd. + // TODO: When popping off a symlink, push back on the components of the + // symlink, so we don't jump out of the target disk inadvertently. + Pathname existing_path(path.folder(), ""); + while (!existing_path.folder().empty() && IsAbsent(existing_path)) { + existing_path.SetFolder(existing_path.parent_folder()); + } +#if defined(WEBRTC_ANDROID) + struct statfs vfs; + memset(&vfs, 0, sizeof(vfs)); + if (0 != statfs(existing_path.pathname().c_str(), &vfs)) + return false; +#else + struct statvfs vfs; + memset(&vfs, 0, sizeof(vfs)); + if (0 != statvfs(existing_path.pathname().c_str(), &vfs)) + return false; +#endif // WEBRTC_ANDROID +#if defined(WEBRTC_LINUX) + *freebytes = static_cast(vfs.f_bsize) * vfs.f_bavail; +#elif defined(WEBRTC_MAC) + *freebytes = static_cast(vfs.f_frsize) * vfs.f_bavail; +#endif + + return true; +#endif // !__native_client__ +} + +Pathname UnixFilesystem::GetCurrentDirectory() { + Pathname cwd; + char buffer[PATH_MAX]; + char *path = getcwd(buffer, PATH_MAX); + + if (!path) { + LOG_ERR(LS_ERROR) << "getcwd() failed"; + return cwd; // returns empty pathname + } + cwd.SetFolder(std::string(path)); + + return cwd; +} + +char* UnixFilesystem::CopyString(const std::string& str) { + size_t size = str.length() + 1; + + char* buf = new char[size]; + if (!buf) { + return NULL; + } + + strcpyn(buf, size, str.c_str()); + return buf; +} + +} // namespace rtc + +#if defined(__native_client__) +extern "C" int __attribute__((weak)) +link(const char* oldpath, const char* newpath) { + errno = EACCES; + return -1; +} +#endif diff --git a/webrtc/base/unixfilesystem.h b/webrtc/base/unixfilesystem.h new file mode 100644 index 000000000..7b6c20edd --- /dev/null +++ b/webrtc/base/unixfilesystem.h @@ -0,0 +1,126 @@ +/* + * Copyright 2004 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_BASE_UNIXFILESYSTEM_H_ +#define WEBRTC_BASE_UNIXFILESYSTEM_H_ + +#include + +#include "webrtc/base/fileutils.h" + +namespace rtc { + +class UnixFilesystem : public FilesystemInterface { + public: + UnixFilesystem(); + virtual ~UnixFilesystem(); + +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + // Android does not have a native code API to fetch the app data or temp + // folders. That needs to be passed into this class from Java. Similarly, iOS + // only supports an Objective-C API for fetching the folder locations, so that + // needs to be passed in here from Objective-C. Or at least that used to be + // the case; now the ctor will do the work if necessary and possible. + // TODO(fischman): add an Android version that uses JNI and drop the + // SetApp*Folder() APIs once external users stop using them. + static void SetAppDataFolder(const std::string& folder); + static void SetAppTempFolder(const std::string& folder); +#endif + + // Opens a file. Returns an open StreamInterface if function succeeds. + // Otherwise, returns NULL. + virtual FileStream *OpenFile(const Pathname &filename, + const std::string &mode); + + // Atomically creates an empty file accessible only to the current user if one + // does not already exist at the given path, otherwise fails. + virtual bool CreatePrivateFile(const Pathname &filename); + + // This will attempt to delete the file located at filename. + // It will fail with VERIY if you pass it a non-existant file, or a directory. + virtual bool DeleteFile(const Pathname &filename); + + // This will attempt to delete the folder located at 'folder' + // It ASSERTs and returns false if you pass it a non-existant folder or a + // plain file. + virtual bool DeleteEmptyFolder(const Pathname &folder); + + // Creates a directory. This will call itself recursively to create /foo/bar + // even if /foo does not exist. All created directories are created with the + // given mode. + // Returns TRUE if function succeeds + virtual bool CreateFolder(const Pathname &pathname, mode_t mode); + + // As above, with mode = 0755. + virtual bool CreateFolder(const Pathname &pathname); + + // This moves a file from old_path to new_path, where "file" can be a plain + // file or directory, which will be moved recursively. + // Returns true if function succeeds. + virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path); + virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path); + + // This copies a file from old_path to _new_path where "file" can be a plain + // file or directory, which will be copied recursively. + // Returns true if function succeeds + virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path); + + // Returns true if a pathname is a directory + virtual bool IsFolder(const Pathname& pathname); + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPath(const Pathname& pathname); + + // Returns true of pathname represents an existing file + virtual bool IsFile(const Pathname& pathname); + + // Returns true if pathname refers to no filesystem object, every parent + // directory either exists, or is also absent. + virtual bool IsAbsent(const Pathname& pathname); + + virtual std::string TempFilename(const Pathname &dir, + const std::string &prefix); + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exists) + virtual bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append); + + virtual bool GetFileSize(const Pathname& path, size_t* size); + virtual bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time); + + // Returns the path to the running application. + virtual bool GetAppPathname(Pathname* path); + + virtual bool GetAppDataFolder(Pathname* path, bool per_user); + + // Get a temporary folder that is unique to the current user and application. + virtual bool GetAppTempFolder(Pathname* path); + + virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes); + + // Returns the absolute path of the current directory. + virtual Pathname GetCurrentDirectory(); + + private: +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) + static char* provided_app_data_folder_; + static char* provided_app_temp_folder_; +#else + static char* app_temp_path_; +#endif + + static char* CopyString(const std::string& str); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_UNIXFILESYSTEM_H_ diff --git a/webrtc/base/urlencode.cc b/webrtc/base/urlencode.cc new file mode 100644 index 000000000..b152829ae --- /dev/null +++ b/webrtc/base/urlencode.cc @@ -0,0 +1,183 @@ +/* + * Copyright 2008 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 "webrtc/base/urlencode.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/stringutils.h" + +static int HexPairValue(const char * code) { + int value = 0; + const char * pch = code; + for (;;) { + int digit = *pch++; + if (digit >= '0' && digit <= '9') { + value += digit - '0'; + } + else if (digit >= 'A' && digit <= 'F') { + value += digit - 'A' + 10; + } + else if (digit >= 'a' && digit <= 'f') { + value += digit - 'a' + 10; + } + else { + return -1; + } + if (pch == code + 2) + return value; + value <<= 4; + } +} + +static int InternalUrlDecode(const char *source, char *dest, + bool encode_space_as_plus) { + char * start = dest; + + while (*source) { + switch (*source) { + case '+': + if (encode_space_as_plus) { + *(dest++) = ' '; + } else { + *dest++ = *source; + } + break; + case '%': + if (source[1] && source[2]) { + int value = HexPairValue(source + 1); + if (value >= 0) { + *(dest++) = value; + source += 2; + } + else { + *dest++ = '?'; + } + } + else { + *dest++ = '?'; + } + break; + default: + *dest++ = *source; + } + source++; + } + + *dest = 0; + return static_cast(dest - start); +} + +static bool IsValidUrlChar(char ch, bool unsafe_only) { + if (unsafe_only) { + return !(ch <= ' ' || strchr("\\\"^&`<>[]{}", ch)); + } else { + return isalnum(ch) || strchr("-_.!~*'()", ch); + } +} + +namespace rtc { + +int UrlDecode(const char *source, char *dest) { + return InternalUrlDecode(source, dest, true); +} + +int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest) { + return InternalUrlDecode(source, dest, false); +} + +int InternalUrlEncode(const char *source, char *dest, unsigned int max, + bool encode_space_as_plus, bool unsafe_only) { + static const char *digits = "0123456789ABCDEF"; + if (max == 0) { + return 0; + } + + char *start = dest; + while (static_cast(dest - start) < max && *source) { + unsigned char ch = static_cast(*source); + if (*source == ' ' && encode_space_as_plus && !unsafe_only) { + *dest++ = '+'; + } else if (IsValidUrlChar(ch, unsafe_only)) { + *dest++ = *source; + } else { + if (static_cast(dest - start) + 4 > max) { + break; + } + *dest++ = '%'; + *dest++ = digits[(ch >> 4) & 0x0F]; + *dest++ = digits[ ch & 0x0F]; + } + source++; + } + ASSERT(static_cast(dest - start) < max); + *dest = 0; + + return static_cast(dest - start); +} + +int UrlEncode(const char *source, char *dest, unsigned max) { + return InternalUrlEncode(source, dest, max, true, false); +} + +int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest, + unsigned max) { + return InternalUrlEncode(source, dest, max, false, false); +} + +int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max) { + return InternalUrlEncode(source, dest, max, false, true); +} + +std::string +InternalUrlDecodeString(const std::string & encoded, + bool encode_space_as_plus) { + size_t needed_length = encoded.length() + 1; + char* buf = STACK_ARRAY(char, needed_length); + InternalUrlDecode(encoded.c_str(), buf, encode_space_as_plus); + return buf; +} + +std::string +UrlDecodeString(const std::string & encoded) { + return InternalUrlDecodeString(encoded, true); +} + +std::string +UrlDecodeStringWithoutEncodingSpaceAsPlus(const std::string & encoded) { + return InternalUrlDecodeString(encoded, false); +} + +std::string +InternalUrlEncodeString(const std::string & decoded, + bool encode_space_as_plus, + bool unsafe_only) { + int needed_length = static_cast(decoded.length()) * 3 + 1; + char* buf = STACK_ARRAY(char, needed_length); + InternalUrlEncode(decoded.c_str(), buf, needed_length, + encode_space_as_plus, unsafe_only); + return buf; +} + +std::string +UrlEncodeString(const std::string & decoded) { + return InternalUrlEncodeString(decoded, true, false); +} + +std::string +UrlEncodeStringWithoutEncodingSpaceAsPlus(const std::string & decoded) { + return InternalUrlEncodeString(decoded, false, false); +} + +std::string +UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded) { + return InternalUrlEncodeString(decoded, false, true); +} + +} // namespace rtc diff --git a/webrtc/base/urlencode.h b/webrtc/base/urlencode.h new file mode 100644 index 000000000..fc10f3880 --- /dev/null +++ b/webrtc/base/urlencode.h @@ -0,0 +1,46 @@ +/* + * Copyright 2008 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 _URLENCODE_H_ +#define _URLENCODE_H_ + +#include + +namespace rtc { + +// Decode all encoded characters. Also decode + as space. +int UrlDecode(const char *source, char *dest); + +// Decode all encoded characters. +int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest); + +// Encode all characters except alphas, numbers, and -_.!~*'() +// Also encode space as +. +int UrlEncode(const char *source, char *dest, unsigned max); + +// Encode all characters except alphas, numbers, and -_.!~*'() +int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest, + unsigned max); + +// Encode only unsafe chars, including \ "^&`<>[]{} +// Also encode space as %20, instead of + +int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max); + +std::string UrlDecodeString(const std::string & encoded); +std::string UrlDecodeStringWithoutEncodingSpaceAsPlus( + const std::string & encoded); +std::string UrlEncodeString(const std::string & decoded); +std::string UrlEncodeStringWithoutEncodingSpaceAsPlus( + const std::string & decoded); +std::string UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded); + +#endif + +} // namespace rtc diff --git a/webrtc/base/urlencode_unittest.cc b/webrtc/base/urlencode_unittest.cc new file mode 100644 index 000000000..52169132e --- /dev/null +++ b/webrtc/base/urlencode_unittest.cc @@ -0,0 +1,83 @@ +/* + * Copyright 2004 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 "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/urlencode.h" + +using rtc::UrlEncode; + +TEST(Urlencode, SourceTooLong) { + char source[] = "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"; + char dest[1]; + ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_EQ('\0', dest[0]); + + dest[0] = 'a'; + ASSERT_EQ(0, UrlEncode(source, dest, 0)); + ASSERT_EQ('a', dest[0]); +} + +TEST(Urlencode, OneCharacterConversion) { + char source[] = "^"; + char dest[4]; + ASSERT_EQ(3, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_STREQ("%5E", dest); +} + +TEST(Urlencode, ShortDestinationNoEncoding) { + // In this case we have a destination that would not be + // big enough to hold an encoding but is big enough to + // hold the text given. + char source[] = "aa"; + char dest[3]; + ASSERT_EQ(2, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_STREQ("aa", dest); +} + +TEST(Urlencode, ShortDestinationEncoding) { + // In this case we have a destination that is not + // big enough to hold the encoding. + char source[] = "&"; + char dest[3]; + ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_EQ('\0', dest[0]); +} + +TEST(Urlencode, Encoding1) { + char source[] = "A^ "; + char dest[8]; + ASSERT_EQ(5, UrlEncode(source, dest, ARRAY_SIZE(dest))); + ASSERT_STREQ("A%5E+", dest); +} + +TEST(Urlencode, Encoding2) { + char source[] = "A^ "; + char dest[8]; + ASSERT_EQ(7, rtc::UrlEncodeWithoutEncodingSpaceAsPlus(source, dest, + ARRAY_SIZE(dest))); + ASSERT_STREQ("A%5E%20", dest); +} + +TEST(Urldecode, Decoding1) { + char source[] = "A%5E+"; + char dest[8]; + ASSERT_EQ(3, rtc::UrlDecode(source, dest)); + ASSERT_STREQ("A^ ", dest); +} + +TEST(Urldecode, Decoding2) { + char source[] = "A%5E+"; + char dest[8]; + ASSERT_EQ(3, rtc::UrlDecodeWithoutEncodingSpaceAsPlus(source, dest)); + ASSERT_STREQ("A^+", dest); +} diff --git a/webrtc/base/versionparsing.cc b/webrtc/base/versionparsing.cc new file mode 100644 index 000000000..c3f982ff6 --- /dev/null +++ b/webrtc/base/versionparsing.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2004 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 "webrtc/base/versionparsing.h" + +#include + +namespace rtc { + +bool ParseVersionString(const std::string& version_str, + int num_expected_segments, + int version[]) { + size_t pos = 0; + for (int i = 0;;) { + size_t dot_pos = version_str.find('.', pos); + size_t n; + if (dot_pos == std::string::npos) { + // npos here is a special value meaning "to the end of the string" + n = std::string::npos; + } else { + n = dot_pos - pos; + } + + version[i] = atoi(version_str.substr(pos, n).c_str()); + + if (++i >= num_expected_segments) break; + + if (dot_pos == std::string::npos) { + // Previous segment was not terminated by a dot, but there's supposed to + // be more segments, so that's an error. + return false; + } + pos = dot_pos + 1; + } + return true; +} + +int CompareVersions(const int version1[], + const int version2[], + int num_segments) { + for (int i = 0; i < num_segments; ++i) { + int diff = version1[i] - version2[i]; + if (diff != 0) { + return diff; + } + } + return 0; +} + +} // namespace rtc diff --git a/webrtc/base/versionparsing.h b/webrtc/base/versionparsing.h new file mode 100644 index 000000000..be2d33238 --- /dev/null +++ b/webrtc/base/versionparsing.h @@ -0,0 +1,35 @@ +/* + * Copyright 2004 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_BASE_VERSIONPARSING_H_ +#define WEBRTC_BASE_VERSIONPARSING_H_ + +#include + +namespace rtc { + +// Parses a version string into an array. "num_expected_segments" must be the +// number of numerical segments that the version is expected to have (e.g., +// "1.1.2.0" has 4). "version" must be an array of that length to hold the +// parsed numbers. +// Returns "true" iff successful. +bool ParseVersionString(const std::string& version_str, + int num_expected_segments, + int version[]); + +// Computes the lexicographical order of two versions. The return value +// indicates the order in the standard way (e.g., see strcmp()). +int CompareVersions(const int version1[], + const int version2[], + int num_segments); + +} // namespace rtc + +#endif // WEBRTC_BASE_VERSIONPARSING_H_ diff --git a/webrtc/base/versionparsing_unittest.cc b/webrtc/base/versionparsing_unittest.cc new file mode 100644 index 000000000..51156991a --- /dev/null +++ b/webrtc/base/versionparsing_unittest.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2010 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 "webrtc/base/versionparsing.h" + +#include "webrtc/base/gunit.h" + +namespace rtc { + +static const int kExampleSegments = 4; + +typedef int ExampleVersion[kExampleSegments]; + +TEST(VersionParsing, TestGoodParse) { + ExampleVersion ver; + std::string str1("1.1.2.0"); + static const ExampleVersion expect1 = {1, 1, 2, 0}; + EXPECT_TRUE(ParseVersionString(str1, kExampleSegments, ver)); + EXPECT_EQ(0, CompareVersions(ver, expect1, kExampleSegments)); + std::string str2("2.0.0.1"); + static const ExampleVersion expect2 = {2, 0, 0, 1}; + EXPECT_TRUE(ParseVersionString(str2, kExampleSegments, ver)); + EXPECT_EQ(0, CompareVersions(ver, expect2, kExampleSegments)); +} + +TEST(VersionParsing, TestBadParse) { + ExampleVersion ver; + std::string str1("1.1.2"); + EXPECT_FALSE(ParseVersionString(str1, kExampleSegments, ver)); + std::string str2(""); + EXPECT_FALSE(ParseVersionString(str2, kExampleSegments, ver)); + std::string str3("garbarge"); + EXPECT_FALSE(ParseVersionString(str3, kExampleSegments, ver)); +} + +TEST(VersionParsing, TestCompare) { + static const ExampleVersion ver1 = {1, 0, 21, 0}; + static const ExampleVersion ver2 = {1, 1, 2, 0}; + static const ExampleVersion ver3 = {1, 1, 3, 0}; + static const ExampleVersion ver4 = {1, 1, 3, 9861}; + + // Test that every combination of comparisons has the expected outcome. + EXPECT_EQ(0, CompareVersions(ver1, ver1, kExampleSegments)); + EXPECT_EQ(0, CompareVersions(ver2, ver2, kExampleSegments)); + EXPECT_EQ(0, CompareVersions(ver3, ver3, kExampleSegments)); + EXPECT_EQ(0, CompareVersions(ver4, ver4, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver1, ver2, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver2, ver1, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver1, ver3, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver3, ver1, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver1, ver4, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver4, ver1, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver2, ver3, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver3, ver2, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver2, ver4, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver4, ver2, kExampleSegments)); + + EXPECT_GT(0, CompareVersions(ver3, ver4, kExampleSegments)); + EXPECT_LT(0, CompareVersions(ver4, ver3, kExampleSegments)); +} + +} // namespace rtc diff --git a/webrtc/base/virtualsocket_unittest.cc b/webrtc/base/virtualsocket_unittest.cc new file mode 100644 index 000000000..253d2c5be --- /dev/null +++ b/webrtc/base/virtualsocket_unittest.cc @@ -0,0 +1,1001 @@ +/* + * Copyright 2006 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 +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/base/logging.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/base/virtualsocketserver.h" + +using namespace rtc; + +// Sends at a constant rate but with random packet sizes. +struct Sender : public MessageHandler { + Sender(Thread* th, AsyncSocket* s, uint32 rt) + : thread(th), socket(new AsyncUDPSocket(s)), + done(false), rate(rt), count(0) { + last_send = rtc::Time(); + thread->PostDelayed(NextDelay(), this, 1); + } + + uint32 NextDelay() { + uint32 size = (rand() % 4096) + 1; + return 1000 * size / rate; + } + + void OnMessage(Message* pmsg) { + ASSERT_EQ(1u, pmsg->message_id); + + if (done) + return; + + uint32 cur_time = rtc::Time(); + uint32 delay = cur_time - last_send; + uint32 size = rate * delay / 1000; + size = std::min(size, 4096); + size = std::max(size, sizeof(uint32)); + + count += size; + memcpy(dummy, &cur_time, sizeof(cur_time)); + socket->Send(dummy, size, options); + + last_send = cur_time; + thread->PostDelayed(NextDelay(), this, 1); + } + + Thread* thread; + scoped_ptr socket; + rtc::PacketOptions options; + bool done; + uint32 rate; // bytes per second + uint32 count; + uint32 last_send; + char dummy[4096]; +}; + +struct Receiver : public MessageHandler, public sigslot::has_slots<> { + Receiver(Thread* th, AsyncSocket* s, uint32 bw) + : thread(th), socket(new AsyncUDPSocket(s)), bandwidth(bw), done(false), + count(0), sec_count(0), sum(0), sum_sq(0), samples(0) { + socket->SignalReadPacket.connect(this, &Receiver::OnReadPacket); + thread->PostDelayed(1000, this, 1); + } + + ~Receiver() { + thread->Clear(this); + } + + void OnReadPacket(AsyncPacketSocket* s, const char* data, size_t size, + const SocketAddress& remote_addr, + const PacketTime& packet_time) { + ASSERT_EQ(socket.get(), s); + ASSERT_GE(size, 4U); + + count += size; + sec_count += size; + + uint32 send_time = *reinterpret_cast(data); + uint32 recv_time = rtc::Time(); + uint32 delay = recv_time - send_time; + sum += delay; + sum_sq += delay * delay; + samples += 1; + } + + void OnMessage(Message* pmsg) { + ASSERT_EQ(1u, pmsg->message_id); + + if (done) + return; + + // It is always possible for us to receive more than expected because + // packets can be further delayed in delivery. + if (bandwidth > 0) + ASSERT_TRUE(sec_count <= 5 * bandwidth / 4); + sec_count = 0; + thread->PostDelayed(1000, this, 1); + } + + Thread* thread; + scoped_ptr socket; + uint32 bandwidth; + bool done; + size_t count; + size_t sec_count; + double sum; + double sum_sq; + uint32 samples; +}; + +class VirtualSocketServerTest : public testing::Test { + public: + VirtualSocketServerTest() : ss_(new VirtualSocketServer(NULL)), + kIPv4AnyAddress(IPAddress(INADDR_ANY), 0), + kIPv6AnyAddress(IPAddress(in6addr_any), 0) { + } + + void CheckAddressIncrementalization(const SocketAddress& post, + const SocketAddress& pre) { + EXPECT_EQ(post.port(), pre.port() + 1); + IPAddress post_ip = post.ipaddr(); + IPAddress pre_ip = pre.ipaddr(); + EXPECT_EQ(pre_ip.family(), post_ip.family()); + if (post_ip.family() == AF_INET) { + in_addr pre_ipv4 = pre_ip.ipv4_address(); + in_addr post_ipv4 = post_ip.ipv4_address(); + int difference = ntohl(post_ipv4.s_addr) - ntohl(pre_ipv4.s_addr); + EXPECT_EQ(1, difference); + } else if (post_ip.family() == AF_INET6) { + in6_addr post_ip6 = post_ip.ipv6_address(); + in6_addr pre_ip6 = pre_ip.ipv6_address(); + uint32* post_as_ints = reinterpret_cast(&post_ip6.s6_addr); + uint32* pre_as_ints = reinterpret_cast(&pre_ip6.s6_addr); + EXPECT_EQ(post_as_ints[3], pre_as_ints[3] + 1); + } + } + + void BasicTest(const SocketAddress& initial_addr) { + AsyncSocket* socket = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_DGRAM); + socket->Bind(initial_addr); + SocketAddress server_addr = socket->GetLocalAddress(); + // Make sure VSS didn't switch families on us. + EXPECT_EQ(server_addr.family(), initial_addr.family()); + + TestClient* client1 = new TestClient(new AsyncUDPSocket(socket)); + AsyncSocket* socket2 = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2)); + + SocketAddress client2_addr; + EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr)); + + SocketAddress client1_addr; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr)); + EXPECT_EQ(client1_addr, server_addr); + + SocketAddress empty = EmptySocketAddressWithFamily(initial_addr.family()); + for (int i = 0; i < 10; i++) { + client2 = new TestClient(AsyncUDPSocket::Create(ss_, empty)); + + SocketAddress next_client2_addr; + EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &next_client2_addr)); + CheckAddressIncrementalization(next_client2_addr, client2_addr); + // EXPECT_EQ(next_client2_addr.port(), client2_addr.port() + 1); + + SocketAddress server_addr2; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, next_client2_addr)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &server_addr2)); + EXPECT_EQ(server_addr2, server_addr); + + client2_addr = next_client2_addr; + } + } + + // initial_addr should be made from either INADDR_ANY or in6addr_any. + void ConnectTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + SocketAddress accept_addr; + const SocketAddress kEmptyAddr = + EmptySocketAddressWithFamily(initial_addr.family()); + + // Create client + AsyncSocket* client = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(client); + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(client->GetLocalAddress().IsNil()); + + // Create server + AsyncSocket* server = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(server); + EXPECT_NE(0, server->Listen(5)); // Bind required + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(server->GetState(), AsyncSocket::CS_CONNECTING); + + // No pending server connections + EXPECT_FALSE(sink.Check(server, testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_EQ(AF_UNSPEC, accept_addr.family()); + + // Attempt connect to listening socket + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + EXPECT_NE(client->GetLocalAddress(), kEmptyAddr); // Implicit Bind + EXPECT_NE(AF_UNSPEC, client->GetLocalAddress().family()); // Implicit Bind + EXPECT_NE(client->GetLocalAddress(), server->GetLocalAddress()); + + // Client is connecting + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE)); + + ss_->ProcessMessagesUntilIdle(); + + // Client still connecting + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE)); + + // Server has pending connection + EXPECT_TRUE(sink.Check(server, testing::SSE_READ)); + Socket* accepted = server->Accept(&accept_addr); + EXPECT_TRUE(NULL != accepted); + EXPECT_NE(accept_addr, kEmptyAddr); + EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr); + + EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_EQ(accepted->GetLocalAddress(), server->GetLocalAddress()); + EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress()); + + ss_->ProcessMessagesUntilIdle(); + + // Client has connected + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress()); + } + + void ConnectToNonListenerTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + SocketAddress accept_addr; + const SocketAddress nil_addr; + const SocketAddress empty_addr = + EmptySocketAddressWithFamily(initial_addr.family()); + + // Create client + AsyncSocket* client = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(client); + + // Create server + AsyncSocket* server = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + sink.Monitor(server); + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + // Attempt connect to non-listening socket + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + // No pending server connections + EXPECT_FALSE(sink.Check(server, testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_addr)); + EXPECT_EQ(accept_addr, nil_addr); + + // Connection failed + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR)); + EXPECT_EQ(client->GetRemoteAddress(), nil_addr); + } + + void CloseDuringConnectTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + SocketAddress accept_addr; + const SocketAddress empty_addr = + EmptySocketAddressWithFamily(initial_addr.family()); + + // Create client and server + scoped_ptr client(ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM)); + sink.Monitor(client.get()); + scoped_ptr server(ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM)); + sink.Monitor(server.get()); + + // Initiate connect + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + // Server close before socket enters accept queue + EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ)); + server->Close(); + + ss_->ProcessMessagesUntilIdle(); + + // Result: connection failed + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + + server.reset(ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + + // Initiate connect + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + // Server close while socket is in accept queue + EXPECT_TRUE(sink.Check(server.get(), testing::SSE_READ)); + server->Close(); + + ss_->ProcessMessagesUntilIdle(); + + // Result: connection failed + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR)); + + // New server + server.reset(ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM)); + sink.Monitor(server.get()); + + // Initiate connect + EXPECT_EQ(0, server->Bind(initial_addr)); + EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, server->Listen(5)); + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + // Server accepts connection + EXPECT_TRUE(sink.Check(server.get(), testing::SSE_READ)); + scoped_ptr accepted(server->Accept(&accept_addr)); + ASSERT_TRUE(NULL != accepted.get()); + sink.Monitor(accepted.get()); + + // Client closes before connection complets + EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED); + + // Connected message has not been processed yet. + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING); + client->Close(); + + ss_->ProcessMessagesUntilIdle(); + + // Result: accepted socket closes + EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_TRUE(sink.Check(accepted.get(), testing::SSE_CLOSE)); + EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE)); + } + + void CloseTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + const SocketAddress kEmptyAddr; + + // Create clients + AsyncSocket* a = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM); + sink.Monitor(a); + a->Bind(initial_addr); + EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family()); + + + scoped_ptr b(ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM)); + sink.Monitor(b.get()); + b->Bind(initial_addr); + EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, a->Connect(b->GetLocalAddress())); + EXPECT_EQ(0, b->Connect(a->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + EXPECT_TRUE(sink.Check(a, testing::SSE_OPEN)); + EXPECT_EQ(a->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_EQ(a->GetRemoteAddress(), b->GetLocalAddress()); + + EXPECT_TRUE(sink.Check(b.get(), testing::SSE_OPEN)); + EXPECT_EQ(b->GetState(), AsyncSocket::CS_CONNECTED); + EXPECT_EQ(b->GetRemoteAddress(), a->GetLocalAddress()); + + EXPECT_EQ(1, a->Send("a", 1)); + b->Close(); + EXPECT_EQ(1, a->Send("b", 1)); + + ss_->ProcessMessagesUntilIdle(); + + char buffer[10]; + EXPECT_FALSE(sink.Check(b.get(), testing::SSE_READ)); + EXPECT_EQ(-1, b->Recv(buffer, 10)); + + EXPECT_TRUE(sink.Check(a, testing::SSE_CLOSE)); + EXPECT_EQ(a->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_EQ(a->GetRemoteAddress(), kEmptyAddr); + + // No signal for Closer + EXPECT_FALSE(sink.Check(b.get(), testing::SSE_CLOSE)); + EXPECT_EQ(b->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_EQ(b->GetRemoteAddress(), kEmptyAddr); + } + + void TcpSendTest(const SocketAddress& initial_addr) { + testing::StreamSink sink; + const SocketAddress kEmptyAddr; + + // Connect two sockets + AsyncSocket* a = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM); + sink.Monitor(a); + a->Bind(initial_addr); + EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family()); + + AsyncSocket* b = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM); + sink.Monitor(b); + b->Bind(initial_addr); + EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, a->Connect(b->GetLocalAddress())); + EXPECT_EQ(0, b->Connect(a->GetLocalAddress())); + + ss_->ProcessMessagesUntilIdle(); + + const size_t kBufferSize = 2000; + ss_->set_send_buffer_capacity(kBufferSize); + ss_->set_recv_buffer_capacity(kBufferSize); + + const size_t kDataSize = 5000; + char send_buffer[kDataSize], recv_buffer[kDataSize]; + for (size_t i = 0; i < kDataSize; ++i) + send_buffer[i] = static_cast(i % 256); + memset(recv_buffer, 0, sizeof(recv_buffer)); + size_t send_pos = 0, recv_pos = 0; + + // Can't send more than send buffer in one write + int result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(static_cast(kBufferSize), result); + send_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE)); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Receive buffer is already filled, fill send buffer again + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(static_cast(kBufferSize), result); + send_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE)); + EXPECT_FALSE(sink.Check(b, testing::SSE_READ)); + + // No more room in send or receive buffer + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(-1, result); + EXPECT_TRUE(a->IsBlocking()); + + // Read a subset of the data + result = b->Recv(recv_buffer + recv_pos, 500); + EXPECT_EQ(500, result); + recv_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(a, testing::SSE_WRITE)); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Room for more on the sending side + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(500, result); + send_pos += result; + + // Empty the recv buffer + while (true) { + result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos); + if (result < 0) { + EXPECT_EQ(-1, result); + EXPECT_TRUE(b->IsBlocking()); + break; + } + recv_pos += result; + } + + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Continue to empty the recv buffer + while (true) { + result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos); + if (result < 0) { + EXPECT_EQ(-1, result); + EXPECT_TRUE(b->IsBlocking()); + break; + } + recv_pos += result; + } + + // Send last of the data + result = a->Send(send_buffer + send_pos, kDataSize - send_pos); + EXPECT_EQ(500, result); + send_pos += result; + + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(b, testing::SSE_READ)); + + // Receive the last of the data + while (true) { + result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos); + if (result < 0) { + EXPECT_EQ(-1, result); + EXPECT_TRUE(b->IsBlocking()); + break; + } + recv_pos += result; + } + + ss_->ProcessMessagesUntilIdle(); + EXPECT_FALSE(sink.Check(b, testing::SSE_READ)); + + // The received data matches the sent data + EXPECT_EQ(kDataSize, send_pos); + EXPECT_EQ(kDataSize, recv_pos); + EXPECT_EQ(0, memcmp(recv_buffer, send_buffer, kDataSize)); + } + + void TcpSendsPacketsInOrderTest(const SocketAddress& initial_addr) { + const SocketAddress kEmptyAddr; + + // Connect two sockets + AsyncSocket* a = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + AsyncSocket* b = ss_->CreateAsyncSocket(initial_addr.family(), + SOCK_STREAM); + a->Bind(initial_addr); + EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family()); + + b->Bind(initial_addr); + EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.family()); + + EXPECT_EQ(0, a->Connect(b->GetLocalAddress())); + EXPECT_EQ(0, b->Connect(a->GetLocalAddress())); + ss_->ProcessMessagesUntilIdle(); + + // First, deliver all packets in 0 ms. + char buffer[2] = { 0, 0 }; + const char cNumPackets = 10; + for (char i = 0; i < cNumPackets; ++i) { + buffer[0] = '0' + i; + EXPECT_EQ(1, a->Send(buffer, 1)); + } + + ss_->ProcessMessagesUntilIdle(); + + for (char i = 0; i < cNumPackets; ++i) { + EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer))); + EXPECT_EQ(static_cast('0' + i), buffer[0]); + } + + // Next, deliver packets at random intervals + const uint32 mean = 50; + const uint32 stddev = 50; + + ss_->set_delay_mean(mean); + ss_->set_delay_stddev(stddev); + ss_->UpdateDelayDistribution(); + + for (char i = 0; i < cNumPackets; ++i) { + buffer[0] = 'A' + i; + EXPECT_EQ(1, a->Send(buffer, 1)); + } + + ss_->ProcessMessagesUntilIdle(); + + for (char i = 0; i < cNumPackets; ++i) { + EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer))); + EXPECT_EQ(static_cast('A' + i), buffer[0]); + } + } + + void BandwidthTest(const SocketAddress& initial_addr) { + AsyncSocket* send_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + AsyncSocket* recv_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + ASSERT_EQ(0, send_socket->Bind(initial_addr)); + ASSERT_EQ(0, recv_socket->Bind(initial_addr)); + EXPECT_EQ(send_socket->GetLocalAddress().family(), initial_addr.family()); + EXPECT_EQ(recv_socket->GetLocalAddress().family(), initial_addr.family()); + ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress())); + + uint32 bandwidth = 64 * 1024; + ss_->set_bandwidth(bandwidth); + + Thread* pthMain = Thread::Current(); + Sender sender(pthMain, send_socket, 80 * 1024); + Receiver receiver(pthMain, recv_socket, bandwidth); + + pthMain->ProcessMessages(5000); + sender.done = true; + pthMain->ProcessMessages(5000); + + ASSERT_TRUE(receiver.count >= 5 * 3 * bandwidth / 4); + ASSERT_TRUE(receiver.count <= 6 * bandwidth); // queue could drain for 1s + + ss_->set_bandwidth(0); + } + + void DelayTest(const SocketAddress& initial_addr) { + time_t seed = ::time(NULL); + LOG(LS_VERBOSE) << "seed = " << seed; + srand(static_cast(seed)); + + const uint32 mean = 2000; + const uint32 stddev = 500; + + ss_->set_delay_mean(mean); + ss_->set_delay_stddev(stddev); + ss_->UpdateDelayDistribution(); + + AsyncSocket* send_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + AsyncSocket* recv_socket = + ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM); + ASSERT_EQ(0, send_socket->Bind(initial_addr)); + ASSERT_EQ(0, recv_socket->Bind(initial_addr)); + EXPECT_EQ(send_socket->GetLocalAddress().family(), initial_addr.family()); + EXPECT_EQ(recv_socket->GetLocalAddress().family(), initial_addr.family()); + ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress())); + + Thread* pthMain = Thread::Current(); + // Avg packet size is 2K, so at 200KB/s for 10s, we should see about + // 1000 packets, which is necessary to get a good distribution. + Sender sender(pthMain, send_socket, 100 * 2 * 1024); + Receiver receiver(pthMain, recv_socket, 0); + + pthMain->ProcessMessages(10000); + sender.done = receiver.done = true; + ss_->ProcessMessagesUntilIdle(); + + const double sample_mean = receiver.sum / receiver.samples; + double num = + receiver.samples * receiver.sum_sq - receiver.sum * receiver.sum; + double den = receiver.samples * (receiver.samples - 1); + const double sample_stddev = sqrt(num / den); + LOG(LS_VERBOSE) << "mean=" << sample_mean << " stddev=" << sample_stddev; + + EXPECT_LE(500u, receiver.samples); + // We initially used a 0.1 fudge factor, but on the build machine, we + // have seen the value differ by as much as 0.13. + EXPECT_NEAR(mean, sample_mean, 0.15 * mean); + EXPECT_NEAR(stddev, sample_stddev, 0.15 * stddev); + + ss_->set_delay_mean(0); + ss_->set_delay_stddev(0); + ss_->UpdateDelayDistribution(); + } + + // Test cross-family communication between a client bound to client_addr and a + // server bound to server_addr. shouldSucceed indicates if communication is + // expected to work or not. + void CrossFamilyConnectionTest(const SocketAddress& client_addr, + const SocketAddress& server_addr, + bool shouldSucceed) { + testing::StreamSink sink; + SocketAddress accept_address; + const SocketAddress kEmptyAddr; + + // Client gets a IPv4 address + AsyncSocket* client = ss_->CreateAsyncSocket(client_addr.family(), + SOCK_STREAM); + sink.Monitor(client); + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr); + client->Bind(client_addr); + + // Server gets a non-mapped non-any IPv6 address. + // IPv4 sockets should not be able to connect to this. + AsyncSocket* server = ss_->CreateAsyncSocket(server_addr.family(), + SOCK_STREAM); + sink.Monitor(server); + server->Bind(server_addr); + server->Listen(5); + + if (shouldSucceed) { + EXPECT_EQ(0, client->Connect(server->GetLocalAddress())); + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(server, testing::SSE_READ)); + Socket* accepted = server->Accept(&accept_address); + EXPECT_TRUE(NULL != accepted); + EXPECT_NE(kEmptyAddr, accept_address); + ss_->ProcessMessagesUntilIdle(); + EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress()); + } else { + // Check that the connection failed. + EXPECT_EQ(-1, client->Connect(server->GetLocalAddress())); + ss_->ProcessMessagesUntilIdle(); + + EXPECT_FALSE(sink.Check(server, testing::SSE_READ)); + EXPECT_TRUE(NULL == server->Accept(&accept_address)); + EXPECT_EQ(accept_address, kEmptyAddr); + EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED); + EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN)); + EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr); + } + } + + // Test cross-family datagram sending between a client bound to client_addr + // and a server bound to server_addr. shouldSucceed indicates if sending is + // expected to succed or not. + void CrossFamilyDatagramTest(const SocketAddress& client_addr, + const SocketAddress& server_addr, + bool shouldSucceed) { + AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM); + socket->Bind(server_addr); + SocketAddress bound_server_addr = socket->GetLocalAddress(); + TestClient* client1 = new TestClient(new AsyncUDPSocket(socket)); + + AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM); + socket2->Bind(client_addr); + TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2)); + SocketAddress client2_addr; + + if (shouldSucceed) { + EXPECT_EQ(3, client2->SendTo("foo", 3, bound_server_addr)); + EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr)); + SocketAddress client1_addr; + EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr)); + EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr)); + EXPECT_EQ(client1_addr, bound_server_addr); + } else { + EXPECT_EQ(-1, client2->SendTo("foo", 3, bound_server_addr)); + EXPECT_FALSE(client1->CheckNextPacket("foo", 3, 0)); + } + } + + protected: + virtual void SetUp() { + Thread::Current()->set_socketserver(ss_); + } + virtual void TearDown() { + Thread::Current()->set_socketserver(NULL); + } + + VirtualSocketServer* ss_; + const SocketAddress kIPv4AnyAddress; + const SocketAddress kIPv6AnyAddress; +}; + +TEST_F(VirtualSocketServerTest, basic_v4) { + SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 5000); + BasicTest(ipv4_test_addr); +} + +TEST_F(VirtualSocketServerTest, basic_v6) { + SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 5000); + BasicTest(ipv6_test_addr); +} + +TEST_F(VirtualSocketServerTest, connect_v4) { + ConnectTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, connect_v6) { + ConnectTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, connect_to_non_listener_v4) { + ConnectToNonListenerTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, connect_to_non_listener_v6) { + ConnectToNonListenerTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_during_connect_v4) { + CloseDuringConnectTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_during_connect_v6) { + CloseDuringConnectTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_v4) { + CloseTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, close_v6) { + CloseTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, tcp_send_v4) { + TcpSendTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, tcp_send_v6) { + TcpSendTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v4) { + TcpSendsPacketsInOrderTest(kIPv4AnyAddress); +} + +TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v6) { + TcpSendsPacketsInOrderTest(kIPv6AnyAddress); +} + +TEST_F(VirtualSocketServerTest, bandwidth_v4) { + SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000); + BandwidthTest(ipv4_test_addr); +} + +TEST_F(VirtualSocketServerTest, bandwidth_v6) { + SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000); + BandwidthTest(ipv6_test_addr); +} + +TEST_F(VirtualSocketServerTest, delay_v4) { + SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000); + DelayTest(ipv4_test_addr); +} + +// See: https://code.google.com/p/webrtc/issues/detail?id=2409 +TEST_F(VirtualSocketServerTest, DISABLED_delay_v6) { + SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000); + DelayTest(ipv6_test_addr); +} + +// Works, receiving socket sees 127.0.0.2. +TEST_F(VirtualSocketServerTest, CanConnectFromMappedIPv6ToIPv4Any) { + CrossFamilyConnectionTest(SocketAddress("::ffff:127.0.0.2", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +// Fails. +TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToIPv4Any) { + CrossFamilyConnectionTest(SocketAddress("::2", 0), + SocketAddress("0.0.0.0", 5000), + false); +} + +// Fails. +TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToMappedIPv6) { + CrossFamilyConnectionTest(SocketAddress("::2", 0), + SocketAddress("::ffff:127.0.0.1", 5000), + false); +} + +// Works. receiving socket sees ::ffff:127.0.0.2. +TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToIPv6Any) { + CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::", 5000), + true); +} + +// Fails. +TEST_F(VirtualSocketServerTest, CantConnectFromIPv4ToUnMappedIPv6) { + CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::1", 5000), + false); +} + +// Works. Receiving socket sees ::ffff:127.0.0.1. +TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToMappedIPv6) { + CrossFamilyConnectionTest(SocketAddress("127.0.0.1", 0), + SocketAddress("::ffff:127.0.0.2", 5000), + true); +} + +// Works, receiving socket sees a result from GetNextIP. +TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv6ToIPv4Any) { + CrossFamilyConnectionTest(SocketAddress("::", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +// Works, receiving socket sees whatever GetNextIP gave the client. +TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv4ToIPv6Any) { + CrossFamilyConnectionTest(SocketAddress("0.0.0.0", 0), + SocketAddress("::", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv4ToIPv6Any) { + CrossFamilyDatagramTest(SocketAddress("0.0.0.0", 0), + SocketAddress("::", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromMappedIPv6ToIPv4Any) { + CrossFamilyDatagramTest(SocketAddress("::ffff:127.0.0.1", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToIPv4Any) { + CrossFamilyDatagramTest(SocketAddress("::2", 0), + SocketAddress("0.0.0.0", 5000), + false); +} + +TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToMappedIPv6) { + CrossFamilyDatagramTest(SocketAddress("::2", 0), + SocketAddress("::ffff:127.0.0.1", 5000), + false); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToIPv6Any) { + CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CantSendDatagramFromIPv4ToUnMappedIPv6) { + CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0), + SocketAddress("::1", 5000), + false); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToMappedIPv6) { + CrossFamilyDatagramTest(SocketAddress("127.0.0.1", 0), + SocketAddress("::ffff:127.0.0.2", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv6ToIPv4Any) { + CrossFamilyDatagramTest(SocketAddress("::", 0), + SocketAddress("0.0.0.0", 5000), + true); +} + +TEST_F(VirtualSocketServerTest, CreatesStandardDistribution) { + const uint32 kTestMean[] = { 10, 100, 333, 1000 }; + const double kTestDev[] = { 0.25, 0.1, 0.01 }; + // TODO: The current code only works for 1000 data points or more. + const uint32 kTestSamples[] = { /*10, 100,*/ 1000 }; + for (size_t midx = 0; midx < ARRAY_SIZE(kTestMean); ++midx) { + for (size_t didx = 0; didx < ARRAY_SIZE(kTestDev); ++didx) { + for (size_t sidx = 0; sidx < ARRAY_SIZE(kTestSamples); ++sidx) { + ASSERT_LT(0u, kTestSamples[sidx]); + const uint32 kStdDev = + static_cast(kTestDev[didx] * kTestMean[midx]); + VirtualSocketServer::Function* f = + VirtualSocketServer::CreateDistribution(kTestMean[midx], + kStdDev, + kTestSamples[sidx]); + ASSERT_TRUE(NULL != f); + ASSERT_EQ(kTestSamples[sidx], f->size()); + double sum = 0; + for (uint32 i = 0; i < f->size(); ++i) { + sum += (*f)[i].second; + } + const double mean = sum / f->size(); + double sum_sq_dev = 0; + for (uint32 i = 0; i < f->size(); ++i) { + double dev = (*f)[i].second - mean; + sum_sq_dev += dev * dev; + } + const double stddev = sqrt(sum_sq_dev / f->size()); + EXPECT_NEAR(kTestMean[midx], mean, 0.1 * kTestMean[midx]) + << "M=" << kTestMean[midx] + << " SD=" << kStdDev + << " N=" << kTestSamples[sidx]; + EXPECT_NEAR(kStdDev, stddev, 0.1 * kStdDev) + << "M=" << kTestMean[midx] + << " SD=" << kStdDev + << " N=" << kTestSamples[sidx]; + delete f; + } + } + } +} diff --git a/webrtc/base/virtualsocketserver.cc b/webrtc/base/virtualsocketserver.cc new file mode 100644 index 000000000..f8e8ddeb8 --- /dev/null +++ b/webrtc/base/virtualsocketserver.cc @@ -0,0 +1,1101 @@ +/* + * Copyright 2004 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 "webrtc/base/virtualsocketserver.h" + +#include +#include + +#include +#include +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddresspair.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { +#if defined(WEBRTC_WIN) +const in_addr kInitialNextIPv4 = { {0x01, 0, 0, 0} }; +#else +// This value is entirely arbitrary, hence the lack of concern about endianness. +const in_addr kInitialNextIPv4 = { 0x01000000 }; +#endif +// Starts at ::2 so as to not cause confusion with ::1. +const in6_addr kInitialNextIPv6 = { { { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 + } } }; + +const uint16 kFirstEphemeralPort = 49152; +const uint16 kLastEphemeralPort = 65535; +const uint16 kEphemeralPortCount = kLastEphemeralPort - kFirstEphemeralPort + 1; +const uint32 kDefaultNetworkCapacity = 64 * 1024; +const uint32 kDefaultTcpBufferSize = 32 * 1024; + +const uint32 UDP_HEADER_SIZE = 28; // IP + UDP headers +const uint32 TCP_HEADER_SIZE = 40; // IP + TCP headers +const uint32 TCP_MSS = 1400; // Maximum segment size + +// Note: The current algorithm doesn't work for sample sizes smaller than this. +const int NUM_SAMPLES = 1000; + +enum { + MSG_ID_PACKET, + MSG_ID_CONNECT, + MSG_ID_DISCONNECT, +}; + +// Packets are passed between sockets as messages. We copy the data just like +// the kernel does. +class Packet : public MessageData { + public: + Packet(const char* data, size_t size, const SocketAddress& from) + : size_(size), consumed_(0), from_(from) { + ASSERT(NULL != data); + data_ = new char[size_]; + memcpy(data_, data, size_); + } + + virtual ~Packet() { + delete[] data_; + } + + const char* data() const { return data_ + consumed_; } + size_t size() const { return size_ - consumed_; } + const SocketAddress& from() const { return from_; } + + // Remove the first size bytes from the data. + void Consume(size_t size) { + ASSERT(size + consumed_ < size_); + consumed_ += size; + } + + private: + char* data_; + size_t size_, consumed_; + SocketAddress from_; +}; + +struct MessageAddress : public MessageData { + explicit MessageAddress(const SocketAddress& a) : addr(a) { } + SocketAddress addr; +}; + +// Implements the socket interface using the virtual network. Packets are +// passed as messages using the message queue of the socket server. +class VirtualSocket : public AsyncSocket, public MessageHandler { + public: + VirtualSocket(VirtualSocketServer* server, int family, int type, bool async) + : server_(server), family_(family), type_(type), async_(async), + state_(CS_CLOSED), error_(0), listen_queue_(NULL), + write_enabled_(false), + network_size_(0), recv_buffer_size_(0), bound_(false), was_any_(false) { + ASSERT((type_ == SOCK_DGRAM) || (type_ == SOCK_STREAM)); + ASSERT(async_ || (type_ != SOCK_STREAM)); // We only support async streams + } + + virtual ~VirtualSocket() { + Close(); + + for (RecvBuffer::iterator it = recv_buffer_.begin(); + it != recv_buffer_.end(); ++it) { + delete *it; + } + } + + virtual SocketAddress GetLocalAddress() const { + return local_addr_; + } + + virtual SocketAddress GetRemoteAddress() const { + return remote_addr_; + } + + // Used by server sockets to set the local address without binding. + void SetLocalAddress(const SocketAddress& addr) { + local_addr_ = addr; + } + + virtual int Bind(const SocketAddress& addr) { + if (!local_addr_.IsNil()) { + error_ = EINVAL; + return -1; + } + local_addr_ = addr; + int result = server_->Bind(this, &local_addr_); + if (result != 0) { + local_addr_.Clear(); + error_ = EADDRINUSE; + } else { + bound_ = true; + was_any_ = addr.IsAnyIP(); + } + return result; + } + + virtual int Connect(const SocketAddress& addr) { + return InitiateConnect(addr, true); + } + + virtual int Close() { + if (!local_addr_.IsNil() && bound_) { + // Remove from the binding table. + server_->Unbind(local_addr_, this); + bound_ = false; + } + + if (SOCK_STREAM == type_) { + // Cancel pending sockets + if (listen_queue_) { + while (!listen_queue_->empty()) { + SocketAddress addr = listen_queue_->front(); + + // Disconnect listening socket. + server_->Disconnect(server_->LookupBinding(addr)); + listen_queue_->pop_front(); + } + delete listen_queue_; + listen_queue_ = NULL; + } + // Disconnect stream sockets + if (CS_CONNECTED == state_) { + // Disconnect remote socket, check if it is a child of a server socket. + VirtualSocket* socket = + server_->LookupConnection(local_addr_, remote_addr_); + if (!socket) { + // Not a server socket child, then see if it is bound. + // TODO: If this is indeed a server socket that has no + // children this will cause the server socket to be + // closed. This might lead to unexpected results, how to fix this? + socket = server_->LookupBinding(remote_addr_); + } + server_->Disconnect(socket); + + // Remove mapping for both directions. + server_->RemoveConnection(remote_addr_, local_addr_); + server_->RemoveConnection(local_addr_, remote_addr_); + } + // Cancel potential connects + MessageList msgs; + if (server_->msg_queue_) { + server_->msg_queue_->Clear(this, MSG_ID_CONNECT, &msgs); + } + for (MessageList::iterator it = msgs.begin(); it != msgs.end(); ++it) { + ASSERT(NULL != it->pdata); + MessageAddress* data = static_cast(it->pdata); + + // Lookup remote side. + VirtualSocket* socket = server_->LookupConnection(local_addr_, + data->addr); + if (socket) { + // Server socket, remote side is a socket retreived by + // accept. Accepted sockets are not bound so we will not + // find it by looking in the bindings table. + server_->Disconnect(socket); + server_->RemoveConnection(local_addr_, data->addr); + } else { + server_->Disconnect(server_->LookupBinding(data->addr)); + } + delete data; + } + // Clear incoming packets and disconnect messages + if (server_->msg_queue_) { + server_->msg_queue_->Clear(this); + } + } + + state_ = CS_CLOSED; + local_addr_.Clear(); + remote_addr_.Clear(); + return 0; + } + + virtual int Send(const void *pv, size_t cb) { + if (CS_CONNECTED != state_) { + error_ = ENOTCONN; + return -1; + } + if (SOCK_DGRAM == type_) { + return SendUdp(pv, cb, remote_addr_); + } else { + return SendTcp(pv, cb); + } + } + + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + if (SOCK_DGRAM == type_) { + return SendUdp(pv, cb, addr); + } else { + if (CS_CONNECTED != state_) { + error_ = ENOTCONN; + return -1; + } + return SendTcp(pv, cb); + } + } + + virtual int Recv(void *pv, size_t cb) { + SocketAddress addr; + return RecvFrom(pv, cb, &addr); + } + + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + // If we don't have a packet, then either error or wait for one to arrive. + if (recv_buffer_.empty()) { + if (async_) { + error_ = EAGAIN; + return -1; + } + while (recv_buffer_.empty()) { + Message msg; + server_->msg_queue_->Get(&msg); + server_->msg_queue_->Dispatch(&msg); + } + } + + // Return the packet at the front of the queue. + Packet* packet = recv_buffer_.front(); + size_t data_read = _min(cb, packet->size()); + memcpy(pv, packet->data(), data_read); + *paddr = packet->from(); + + if (data_read < packet->size()) { + packet->Consume(data_read); + } else { + recv_buffer_.pop_front(); + delete packet; + } + + if (SOCK_STREAM == type_) { + bool was_full = (recv_buffer_size_ == server_->recv_buffer_capacity_); + recv_buffer_size_ -= data_read; + if (was_full) { + VirtualSocket* sender = server_->LookupBinding(remote_addr_); + ASSERT(NULL != sender); + server_->SendTcp(sender); + } + } + + return static_cast(data_read); + } + + virtual int Listen(int backlog) { + ASSERT(SOCK_STREAM == type_); + ASSERT(CS_CLOSED == state_); + if (local_addr_.IsNil()) { + error_ = EINVAL; + return -1; + } + ASSERT(NULL == listen_queue_); + listen_queue_ = new ListenQueue; + state_ = CS_CONNECTING; + return 0; + } + + virtual VirtualSocket* Accept(SocketAddress *paddr) { + if (NULL == listen_queue_) { + error_ = EINVAL; + return NULL; + } + while (!listen_queue_->empty()) { + VirtualSocket* socket = new VirtualSocket(server_, AF_INET, type_, + async_); + + // Set the new local address to the same as this server socket. + socket->SetLocalAddress(local_addr_); + // Sockets made from a socket that 'was Any' need to inherit that. + socket->set_was_any(was_any_); + SocketAddress remote_addr(listen_queue_->front()); + int result = socket->InitiateConnect(remote_addr, false); + listen_queue_->pop_front(); + if (result != 0) { + delete socket; + continue; + } + socket->CompleteConnect(remote_addr, false); + if (paddr) { + *paddr = remote_addr; + } + return socket; + } + error_ = EWOULDBLOCK; + return NULL; + } + + virtual int GetError() const { + return error_; + } + + virtual void SetError(int error) { + error_ = error; + } + + virtual ConnState GetState() const { + return state_; + } + + virtual int GetOption(Option opt, int* value) { + OptionsMap::const_iterator it = options_map_.find(opt); + if (it == options_map_.end()) { + return -1; + } + *value = it->second; + return 0; // 0 is success to emulate getsockopt() + } + + virtual int SetOption(Option opt, int value) { + options_map_[opt] = value; + return 0; // 0 is success to emulate setsockopt() + } + + virtual int EstimateMTU(uint16* mtu) { + if (CS_CONNECTED != state_) + return ENOTCONN; + else + return 65536; + } + + void OnMessage(Message *pmsg) { + if (pmsg->message_id == MSG_ID_PACKET) { + //ASSERT(!local_addr_.IsAny()); + ASSERT(NULL != pmsg->pdata); + Packet* packet = static_cast(pmsg->pdata); + + recv_buffer_.push_back(packet); + + if (async_) { + SignalReadEvent(this); + } + } else if (pmsg->message_id == MSG_ID_CONNECT) { + ASSERT(NULL != pmsg->pdata); + MessageAddress* data = static_cast(pmsg->pdata); + if (listen_queue_ != NULL) { + listen_queue_->push_back(data->addr); + if (async_) { + SignalReadEvent(this); + } + } else if ((SOCK_STREAM == type_) && (CS_CONNECTING == state_)) { + CompleteConnect(data->addr, true); + } else { + LOG(LS_VERBOSE) << "Socket at " << local_addr_ << " is not listening"; + server_->Disconnect(server_->LookupBinding(data->addr)); + } + delete data; + } else if (pmsg->message_id == MSG_ID_DISCONNECT) { + ASSERT(SOCK_STREAM == type_); + if (CS_CLOSED != state_) { + int error = (CS_CONNECTING == state_) ? ECONNREFUSED : 0; + state_ = CS_CLOSED; + remote_addr_.Clear(); + if (async_) { + SignalCloseEvent(this, error); + } + } + } else { + ASSERT(false); + } + } + + bool was_any() { return was_any_; } + void set_was_any(bool was_any) { was_any_ = was_any; } + + private: + struct NetworkEntry { + size_t size; + uint32 done_time; + }; + + typedef std::deque ListenQueue; + typedef std::deque NetworkQueue; + typedef std::vector SendBuffer; + typedef std::list RecvBuffer; + typedef std::map OptionsMap; + + int InitiateConnect(const SocketAddress& addr, bool use_delay) { + if (!remote_addr_.IsNil()) { + error_ = (CS_CONNECTED == state_) ? EISCONN : EINPROGRESS; + return -1; + } + if (local_addr_.IsNil()) { + // If there's no local address set, grab a random one in the correct AF. + int result = 0; + if (addr.ipaddr().family() == AF_INET) { + result = Bind(SocketAddress("0.0.0.0", 0)); + } else if (addr.ipaddr().family() == AF_INET6) { + result = Bind(SocketAddress("::", 0)); + } + if (result != 0) { + return result; + } + } + if (type_ == SOCK_DGRAM) { + remote_addr_ = addr; + state_ = CS_CONNECTED; + } else { + int result = server_->Connect(this, addr, use_delay); + if (result != 0) { + error_ = EHOSTUNREACH; + return -1; + } + state_ = CS_CONNECTING; + } + return 0; + } + + void CompleteConnect(const SocketAddress& addr, bool notify) { + ASSERT(CS_CONNECTING == state_); + remote_addr_ = addr; + state_ = CS_CONNECTED; + server_->AddConnection(remote_addr_, local_addr_, this); + if (async_ && notify) { + SignalConnectEvent(this); + } + } + + int SendUdp(const void* pv, size_t cb, const SocketAddress& addr) { + // If we have not been assigned a local port, then get one. + if (local_addr_.IsNil()) { + local_addr_ = EmptySocketAddressWithFamily(addr.ipaddr().family()); + int result = server_->Bind(this, &local_addr_); + if (result != 0) { + local_addr_.Clear(); + error_ = EADDRINUSE; + return result; + } + } + + // Send the data in a message to the appropriate socket. + return server_->SendUdp(this, static_cast(pv), cb, addr); + } + + int SendTcp(const void* pv, size_t cb) { + size_t capacity = server_->send_buffer_capacity_ - send_buffer_.size(); + if (0 == capacity) { + write_enabled_ = true; + error_ = EWOULDBLOCK; + return -1; + } + size_t consumed = _min(cb, capacity); + const char* cpv = static_cast(pv); + send_buffer_.insert(send_buffer_.end(), cpv, cpv + consumed); + server_->SendTcp(this); + return static_cast(consumed); + } + + VirtualSocketServer* server_; + int family_; + int type_; + bool async_; + ConnState state_; + int error_; + SocketAddress local_addr_; + SocketAddress remote_addr_; + + // Pending sockets which can be Accepted + ListenQueue* listen_queue_; + + // Data which tcp has buffered for sending + SendBuffer send_buffer_; + bool write_enabled_; + + // Critical section to protect the recv_buffer and queue_ + CriticalSection crit_; + + // Network model that enforces bandwidth and capacity constraints + NetworkQueue network_; + size_t network_size_; + + // Data which has been received from the network + RecvBuffer recv_buffer_; + // The amount of data which is in flight or in recv_buffer_ + size_t recv_buffer_size_; + + // Is this socket bound? + bool bound_; + + // When we bind a socket to Any, VSS's Bind gives it another address. For + // dual-stack sockets, we want to distinguish between sockets that were + // explicitly given a particular address and sockets that had one picked + // for them by VSS. + bool was_any_; + + // Store the options that are set + OptionsMap options_map_; + + friend class VirtualSocketServer; +}; + +VirtualSocketServer::VirtualSocketServer(SocketServer* ss) + : server_(ss), server_owned_(false), msg_queue_(NULL), stop_on_idle_(false), + network_delay_(Time()), next_ipv4_(kInitialNextIPv4), + next_ipv6_(kInitialNextIPv6), next_port_(kFirstEphemeralPort), + bindings_(new AddressMap()), connections_(new ConnectionMap()), + bandwidth_(0), network_capacity_(kDefaultNetworkCapacity), + send_buffer_capacity_(kDefaultTcpBufferSize), + recv_buffer_capacity_(kDefaultTcpBufferSize), + delay_mean_(0), delay_stddev_(0), delay_samples_(NUM_SAMPLES), + delay_dist_(NULL), drop_prob_(0.0) { + if (!server_) { + server_ = new PhysicalSocketServer(); + server_owned_ = true; + } + UpdateDelayDistribution(); +} + +VirtualSocketServer::~VirtualSocketServer() { + delete bindings_; + delete connections_; + delete delay_dist_; + if (server_owned_) { + delete server_; + } +} + +IPAddress VirtualSocketServer::GetNextIP(int family) { + if (family == AF_INET) { + IPAddress next_ip(next_ipv4_); + next_ipv4_.s_addr = + HostToNetwork32(NetworkToHost32(next_ipv4_.s_addr) + 1); + return next_ip; + } else if (family == AF_INET6) { + IPAddress next_ip(next_ipv6_); + uint32* as_ints = reinterpret_cast(&next_ipv6_.s6_addr); + as_ints[3] += 1; + return next_ip; + } + return IPAddress(); +} + +uint16 VirtualSocketServer::GetNextPort() { + uint16 port = next_port_; + if (next_port_ < kLastEphemeralPort) { + ++next_port_; + } else { + next_port_ = kFirstEphemeralPort; + } + return port; +} + +Socket* VirtualSocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* VirtualSocketServer::CreateSocket(int family, int type) { + return CreateSocketInternal(family, type); +} + +AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int family, int type) { + return CreateSocketInternal(family, type); +} + +VirtualSocket* VirtualSocketServer::CreateSocketInternal(int family, int type) { + return new VirtualSocket(this, family, type, true); +} + +void VirtualSocketServer::SetMessageQueue(MessageQueue* msg_queue) { + msg_queue_ = msg_queue; + if (msg_queue_) { + msg_queue_->SignalQueueDestroyed.connect(this, + &VirtualSocketServer::OnMessageQueueDestroyed); + } +} + +bool VirtualSocketServer::Wait(int cmsWait, bool process_io) { + ASSERT(msg_queue_ == Thread::Current()); + if (stop_on_idle_ && Thread::Current()->empty()) { + return false; + } + return socketserver()->Wait(cmsWait, process_io); +} + +void VirtualSocketServer::WakeUp() { + socketserver()->WakeUp(); +} + +bool VirtualSocketServer::ProcessMessagesUntilIdle() { + ASSERT(msg_queue_ == Thread::Current()); + stop_on_idle_ = true; + while (!msg_queue_->empty()) { + Message msg; + if (msg_queue_->Get(&msg, kForever)) { + msg_queue_->Dispatch(&msg); + } + } + stop_on_idle_ = false; + return !msg_queue_->IsQuitting(); +} + +int VirtualSocketServer::Bind(VirtualSocket* socket, + const SocketAddress& addr) { + ASSERT(NULL != socket); + // Address must be completely specified at this point + ASSERT(!IPIsUnspec(addr.ipaddr())); + ASSERT(addr.port() != 0); + + // Normalize the address (turns v6-mapped addresses into v4-addresses). + SocketAddress normalized(addr.ipaddr().Normalized(), addr.port()); + + AddressMap::value_type entry(normalized, socket); + return bindings_->insert(entry).second ? 0 : -1; +} + +int VirtualSocketServer::Bind(VirtualSocket* socket, SocketAddress* addr) { + ASSERT(NULL != socket); + + if (IPIsAny(addr->ipaddr())) { + addr->SetIP(GetNextIP(addr->ipaddr().family())); + } else if (!IPIsUnspec(addr->ipaddr())) { + addr->SetIP(addr->ipaddr().Normalized()); + } else { + ASSERT(false); + } + + if (addr->port() == 0) { + for (int i = 0; i < kEphemeralPortCount; ++i) { + addr->SetPort(GetNextPort()); + if (bindings_->find(*addr) == bindings_->end()) { + break; + } + } + } + + return Bind(socket, *addr); +} + +VirtualSocket* VirtualSocketServer::LookupBinding(const SocketAddress& addr) { + SocketAddress normalized(addr.ipaddr().Normalized(), + addr.port()); + AddressMap::iterator it = bindings_->find(normalized); + return (bindings_->end() != it) ? it->second : NULL; +} + +int VirtualSocketServer::Unbind(const SocketAddress& addr, + VirtualSocket* socket) { + SocketAddress normalized(addr.ipaddr().Normalized(), + addr.port()); + ASSERT((*bindings_)[normalized] == socket); + bindings_->erase(bindings_->find(normalized)); + return 0; +} + +void VirtualSocketServer::AddConnection(const SocketAddress& local, + const SocketAddress& remote, + VirtualSocket* remote_socket) { + // Add this socket pair to our routing table. This will allow + // multiple clients to connect to the same server address. + SocketAddress local_normalized(local.ipaddr().Normalized(), + local.port()); + SocketAddress remote_normalized(remote.ipaddr().Normalized(), + remote.port()); + SocketAddressPair address_pair(local_normalized, remote_normalized); + connections_->insert(std::pair(address_pair, remote_socket)); +} + +VirtualSocket* VirtualSocketServer::LookupConnection( + const SocketAddress& local, + const SocketAddress& remote) { + SocketAddress local_normalized(local.ipaddr().Normalized(), + local.port()); + SocketAddress remote_normalized(remote.ipaddr().Normalized(), + remote.port()); + SocketAddressPair address_pair(local_normalized, remote_normalized); + ConnectionMap::iterator it = connections_->find(address_pair); + return (connections_->end() != it) ? it->second : NULL; +} + +void VirtualSocketServer::RemoveConnection(const SocketAddress& local, + const SocketAddress& remote) { + SocketAddress local_normalized(local.ipaddr().Normalized(), + local.port()); + SocketAddress remote_normalized(remote.ipaddr().Normalized(), + remote.port()); + SocketAddressPair address_pair(local_normalized, remote_normalized); + connections_->erase(address_pair); +} + +static double Random() { + return static_cast(rand()) / RAND_MAX; +} + +int VirtualSocketServer::Connect(VirtualSocket* socket, + const SocketAddress& remote_addr, + bool use_delay) { + uint32 delay = use_delay ? GetRandomTransitDelay() : 0; + VirtualSocket* remote = LookupBinding(remote_addr); + if (!CanInteractWith(socket, remote)) { + LOG(LS_INFO) << "Address family mismatch between " + << socket->GetLocalAddress() << " and " << remote_addr; + return -1; + } + if (remote != NULL) { + SocketAddress addr = socket->GetLocalAddress(); + msg_queue_->PostDelayed(delay, remote, MSG_ID_CONNECT, + new MessageAddress(addr)); + } else { + LOG(LS_INFO) << "No one listening at " << remote_addr; + msg_queue_->PostDelayed(delay, socket, MSG_ID_DISCONNECT); + } + return 0; +} + +bool VirtualSocketServer::Disconnect(VirtualSocket* socket) { + if (socket) { + // Remove the mapping. + msg_queue_->Post(socket, MSG_ID_DISCONNECT); + return true; + } + return false; +} + +int VirtualSocketServer::SendUdp(VirtualSocket* socket, + const char* data, size_t data_size, + const SocketAddress& remote_addr) { + // See if we want to drop this packet. + if (Random() < drop_prob_) { + LOG(LS_VERBOSE) << "Dropping packet: bad luck"; + return static_cast(data_size); + } + + VirtualSocket* recipient = LookupBinding(remote_addr); + if (!recipient) { + // Make a fake recipient for address family checking. + scoped_ptr dummy_socket( + CreateSocketInternal(AF_INET, SOCK_DGRAM)); + dummy_socket->SetLocalAddress(remote_addr); + if (!CanInteractWith(socket, dummy_socket.get())) { + LOG(LS_VERBOSE) << "Incompatible address families: " + << socket->GetLocalAddress() << " and " << remote_addr; + return -1; + } + LOG(LS_VERBOSE) << "No one listening at " << remote_addr; + return static_cast(data_size); + } + + if (!CanInteractWith(socket, recipient)) { + LOG(LS_VERBOSE) << "Incompatible address families: " + << socket->GetLocalAddress() << " and " << remote_addr; + return -1; + } + + CritScope cs(&socket->crit_); + + uint32 cur_time = Time(); + PurgeNetworkPackets(socket, cur_time); + + // Determine whether we have enough bandwidth to accept this packet. To do + // this, we need to update the send queue. Once we know it's current size, + // we know whether we can fit this packet. + // + // NOTE: There are better algorithms for maintaining such a queue (such as + // "Derivative Random Drop"); however, this algorithm is a more accurate + // simulation of what a normal network would do. + + size_t packet_size = data_size + UDP_HEADER_SIZE; + if (socket->network_size_ + packet_size > network_capacity_) { + LOG(LS_VERBOSE) << "Dropping packet: network capacity exceeded"; + return static_cast(data_size); + } + + AddPacketToNetwork(socket, recipient, cur_time, data, data_size, + UDP_HEADER_SIZE, false); + + return static_cast(data_size); +} + +void VirtualSocketServer::SendTcp(VirtualSocket* socket) { + // TCP can't send more data than will fill up the receiver's buffer. + // We track the data that is in the buffer plus data in flight using the + // recipient's recv_buffer_size_. Anything beyond that must be stored in the + // sender's buffer. We will trigger the buffered data to be sent when data + // is read from the recv_buffer. + + // Lookup the local/remote pair in the connections table. + VirtualSocket* recipient = LookupConnection(socket->local_addr_, + socket->remote_addr_); + if (!recipient) { + LOG(LS_VERBOSE) << "Sending data to no one."; + return; + } + + CritScope cs(&socket->crit_); + + uint32 cur_time = Time(); + PurgeNetworkPackets(socket, cur_time); + + while (true) { + size_t available = recv_buffer_capacity_ - recipient->recv_buffer_size_; + size_t max_data_size = _min(available, TCP_MSS - TCP_HEADER_SIZE); + size_t data_size = _min(socket->send_buffer_.size(), max_data_size); + if (0 == data_size) + break; + + AddPacketToNetwork(socket, recipient, cur_time, &socket->send_buffer_[0], + data_size, TCP_HEADER_SIZE, true); + recipient->recv_buffer_size_ += data_size; + + size_t new_buffer_size = socket->send_buffer_.size() - data_size; + // Avoid undefined access beyond the last element of the vector. + // This only happens when new_buffer_size is 0. + if (data_size < socket->send_buffer_.size()) { + // memmove is required for potentially overlapping source/destination. + memmove(&socket->send_buffer_[0], &socket->send_buffer_[data_size], + new_buffer_size); + } + socket->send_buffer_.resize(new_buffer_size); + } + + if (socket->write_enabled_ + && (socket->send_buffer_.size() < send_buffer_capacity_)) { + socket->write_enabled_ = false; + socket->SignalWriteEvent(socket); + } +} + +void VirtualSocketServer::AddPacketToNetwork(VirtualSocket* sender, + VirtualSocket* recipient, + uint32 cur_time, + const char* data, + size_t data_size, + size_t header_size, + bool ordered) { + VirtualSocket::NetworkEntry entry; + entry.size = data_size + header_size; + + sender->network_size_ += entry.size; + uint32 send_delay = SendDelay(static_cast(sender->network_size_)); + entry.done_time = cur_time + send_delay; + sender->network_.push_back(entry); + + // Find the delay for crossing the many virtual hops of the network. + uint32 transit_delay = GetRandomTransitDelay(); + + // Post the packet as a message to be delivered (on our own thread) + Packet* p = new Packet(data, data_size, sender->local_addr_); + uint32 ts = TimeAfter(send_delay + transit_delay); + if (ordered) { + // Ensure that new packets arrive after previous ones + // TODO: consider ordering on a per-socket basis, since this + // introduces artifical delay. + ts = TimeMax(ts, network_delay_); + } + msg_queue_->PostAt(ts, recipient, MSG_ID_PACKET, p); + network_delay_ = TimeMax(ts, network_delay_); +} + +void VirtualSocketServer::PurgeNetworkPackets(VirtualSocket* socket, + uint32 cur_time) { + while (!socket->network_.empty() && + (socket->network_.front().done_time <= cur_time)) { + ASSERT(socket->network_size_ >= socket->network_.front().size); + socket->network_size_ -= socket->network_.front().size; + socket->network_.pop_front(); + } +} + +uint32 VirtualSocketServer::SendDelay(uint32 size) { + if (bandwidth_ == 0) + return 0; + else + return 1000 * size / bandwidth_; +} + +#if 0 +void PrintFunction(std::vector >* f) { + return; + double sum = 0; + for (uint32 i = 0; i < f->size(); ++i) { + std::cout << (*f)[i].first << '\t' << (*f)[i].second << std::endl; + sum += (*f)[i].second; + } + if (!f->empty()) { + const double mean = sum / f->size(); + double sum_sq_dev = 0; + for (uint32 i = 0; i < f->size(); ++i) { + double dev = (*f)[i].second - mean; + sum_sq_dev += dev * dev; + } + std::cout << "Mean = " << mean << " StdDev = " + << sqrt(sum_sq_dev / f->size()) << std::endl; + } +} +#endif // + +void VirtualSocketServer::UpdateDelayDistribution() { + Function* dist = CreateDistribution(delay_mean_, delay_stddev_, + delay_samples_); + // We take a lock just to make sure we don't leak memory. + { + CritScope cs(&delay_crit_); + delete delay_dist_; + delay_dist_ = dist; + } +} + +static double PI = 4 * atan(1.0); + +static double Normal(double x, double mean, double stddev) { + double a = (x - mean) * (x - mean) / (2 * stddev * stddev); + return exp(-a) / (stddev * sqrt(2 * PI)); +} + +#if 0 // static unused gives a warning +static double Pareto(double x, double min, double k) { + if (x < min) + return 0; + else + return k * std::pow(min, k) / std::pow(x, k+1); +} +#endif + +VirtualSocketServer::Function* VirtualSocketServer::CreateDistribution( + uint32 mean, uint32 stddev, uint32 samples) { + Function* f = new Function(); + + if (0 == stddev) { + f->push_back(Point(mean, 1.0)); + } else { + double start = 0; + if (mean >= 4 * static_cast(stddev)) + start = mean - 4 * static_cast(stddev); + double end = mean + 4 * static_cast(stddev); + + for (uint32 i = 0; i < samples; i++) { + double x = start + (end - start) * i / (samples - 1); + double y = Normal(x, mean, stddev); + f->push_back(Point(x, y)); + } + } + return Resample(Invert(Accumulate(f)), 0, 1, samples); +} + +uint32 VirtualSocketServer::GetRandomTransitDelay() { + size_t index = rand() % delay_dist_->size(); + double delay = (*delay_dist_)[index].second; + //LOG_F(LS_INFO) << "random[" << index << "] = " << delay; + return static_cast(delay); +} + +struct FunctionDomainCmp { + bool operator()(const VirtualSocketServer::Point& p1, + const VirtualSocketServer::Point& p2) { + return p1.first < p2.first; + } + bool operator()(double v1, const VirtualSocketServer::Point& p2) { + return v1 < p2.first; + } + bool operator()(const VirtualSocketServer::Point& p1, double v2) { + return p1.first < v2; + } +}; + +VirtualSocketServer::Function* VirtualSocketServer::Accumulate(Function* f) { + ASSERT(f->size() >= 1); + double v = 0; + for (Function::size_type i = 0; i < f->size() - 1; ++i) { + double dx = (*f)[i + 1].first - (*f)[i].first; + double avgy = ((*f)[i + 1].second + (*f)[i].second) / 2; + (*f)[i].second = v; + v = v + dx * avgy; + } + (*f)[f->size()-1].second = v; + return f; +} + +VirtualSocketServer::Function* VirtualSocketServer::Invert(Function* f) { + for (Function::size_type i = 0; i < f->size(); ++i) + std::swap((*f)[i].first, (*f)[i].second); + + std::sort(f->begin(), f->end(), FunctionDomainCmp()); + return f; +} + +VirtualSocketServer::Function* VirtualSocketServer::Resample( + Function* f, double x1, double x2, uint32 samples) { + Function* g = new Function(); + + for (size_t i = 0; i < samples; i++) { + double x = x1 + (x2 - x1) * i / (samples - 1); + double y = Evaluate(f, x); + g->push_back(Point(x, y)); + } + + delete f; + return g; +} + +double VirtualSocketServer::Evaluate(Function* f, double x) { + Function::iterator iter = + std::lower_bound(f->begin(), f->end(), x, FunctionDomainCmp()); + if (iter == f->begin()) { + return (*f)[0].second; + } else if (iter == f->end()) { + ASSERT(f->size() >= 1); + return (*f)[f->size() - 1].second; + } else if (iter->first == x) { + return iter->second; + } else { + double x1 = (iter - 1)->first; + double y1 = (iter - 1)->second; + double x2 = iter->first; + double y2 = iter->second; + return y1 + (y2 - y1) * (x - x1) / (x2 - x1); + } +} + +bool VirtualSocketServer::CanInteractWith(VirtualSocket* local, + VirtualSocket* remote) { + if (!local || !remote) { + return false; + } + IPAddress local_ip = local->GetLocalAddress().ipaddr(); + IPAddress remote_ip = remote->GetLocalAddress().ipaddr(); + IPAddress local_normalized = local_ip.Normalized(); + IPAddress remote_normalized = remote_ip.Normalized(); + // Check if the addresses are the same family after Normalization (turns + // mapped IPv6 address into IPv4 addresses). + // This will stop unmapped V6 addresses from talking to mapped V6 addresses. + if (local_normalized.family() == remote_normalized.family()) { + return true; + } + + // If ip1 is IPv4 and ip2 is :: and ip2 is not IPV6_V6ONLY. + int remote_v6_only = 0; + remote->GetOption(Socket::OPT_IPV6_V6ONLY, &remote_v6_only); + if (local_ip.family() == AF_INET && !remote_v6_only && IPIsAny(remote_ip)) { + return true; + } + // Same check, backwards. + int local_v6_only = 0; + local->GetOption(Socket::OPT_IPV6_V6ONLY, &local_v6_only); + if (remote_ip.family() == AF_INET && !local_v6_only && IPIsAny(local_ip)) { + return true; + } + + // Check to see if either socket was explicitly bound to IPv6-any. + // These sockets can talk with anyone. + if (local_ip.family() == AF_INET6 && local->was_any()) { + return true; + } + if (remote_ip.family() == AF_INET6 && remote->was_any()) { + return true; + } + + return false; +} + +} // namespace rtc diff --git a/webrtc/base/virtualsocketserver.h b/webrtc/base/virtualsocketserver.h new file mode 100644 index 000000000..87e35364c --- /dev/null +++ b/webrtc/base/virtualsocketserver.h @@ -0,0 +1,234 @@ +/* + * Copyright 2004 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_BASE_VIRTUALSOCKETSERVER_H_ +#define WEBRTC_BASE_VIRTUALSOCKETSERVER_H_ + +#include + +#include +#include + +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/socketserver.h" + +namespace rtc { + +class VirtualSocket; +class SocketAddressPair; + +// Simulates a network in the same manner as a loopback interface. The +// interface can create as many addresses as you want. All of the sockets +// created by this network will be able to communicate with one another, unless +// they are bound to addresses from incompatible families. +class VirtualSocketServer : public SocketServer, public sigslot::has_slots<> { + public: + // TODO: Add "owned" parameter. + // If "owned" is set, the supplied socketserver will be deleted later. + explicit VirtualSocketServer(SocketServer* ss); + virtual ~VirtualSocketServer(); + + SocketServer* socketserver() { return server_; } + + // Limits the network bandwidth (maximum bytes per second). Zero means that + // all sends occur instantly. Defaults to 0. + uint32 bandwidth() const { return bandwidth_; } + void set_bandwidth(uint32 bandwidth) { bandwidth_ = bandwidth; } + + // Limits the amount of data which can be in flight on the network without + // packet loss (on a per sender basis). Defaults to 64 KB. + uint32 network_capacity() const { return network_capacity_; } + void set_network_capacity(uint32 capacity) { + network_capacity_ = capacity; + } + + // The amount of data which can be buffered by tcp on the sender's side + uint32 send_buffer_capacity() const { return send_buffer_capacity_; } + void set_send_buffer_capacity(uint32 capacity) { + send_buffer_capacity_ = capacity; + } + + // The amount of data which can be buffered by tcp on the receiver's side + uint32 recv_buffer_capacity() const { return recv_buffer_capacity_; } + void set_recv_buffer_capacity(uint32 capacity) { + recv_buffer_capacity_ = capacity; + } + + // Controls the (transit) delay for packets sent in the network. This does + // not inclue the time required to sit in the send queue. Both of these + // values are measured in milliseconds. Defaults to no delay. + uint32 delay_mean() const { return delay_mean_; } + uint32 delay_stddev() const { return delay_stddev_; } + uint32 delay_samples() const { return delay_samples_; } + void set_delay_mean(uint32 delay_mean) { delay_mean_ = delay_mean; } + void set_delay_stddev(uint32 delay_stddev) { + delay_stddev_ = delay_stddev; + } + void set_delay_samples(uint32 delay_samples) { + delay_samples_ = delay_samples; + } + + // If the (transit) delay parameters are modified, this method should be + // called to recompute the new distribution. + void UpdateDelayDistribution(); + + // Controls the (uniform) probability that any sent packet is dropped. This + // is separate from calculations to drop based on queue size. + double drop_probability() { return drop_prob_; } + void set_drop_probability(double drop_prob) { + assert((0 <= drop_prob) && (drop_prob <= 1)); + drop_prob_ = drop_prob; + } + + // SocketFactory: + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + // SocketServer: + virtual void SetMessageQueue(MessageQueue* queue); + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + typedef std::pair Point; + typedef std::vector Function; + + static Function* CreateDistribution(uint32 mean, uint32 stddev, + uint32 samples); + + // Similar to Thread::ProcessMessages, but it only processes messages until + // there are no immediate messages or pending network traffic. Returns false + // if Thread::Stop() was called. + bool ProcessMessagesUntilIdle(); + + protected: + // Returns a new IP not used before in this network. + IPAddress GetNextIP(int family); + uint16 GetNextPort(); + + VirtualSocket* CreateSocketInternal(int family, int type); + + // Binds the given socket to addr, assigning and IP and Port if necessary + int Bind(VirtualSocket* socket, SocketAddress* addr); + + // Binds the given socket to the given (fully-defined) address. + int Bind(VirtualSocket* socket, const SocketAddress& addr); + + // Find the socket bound to the given address + VirtualSocket* LookupBinding(const SocketAddress& addr); + + int Unbind(const SocketAddress& addr, VirtualSocket* socket); + + // Adds a mapping between this socket pair and the socket. + void AddConnection(const SocketAddress& client, + const SocketAddress& server, + VirtualSocket* socket); + + // Find the socket pair corresponding to this server address. + VirtualSocket* LookupConnection(const SocketAddress& client, + const SocketAddress& server); + + void RemoveConnection(const SocketAddress& client, + const SocketAddress& server); + + // Connects the given socket to the socket at the given address + int Connect(VirtualSocket* socket, const SocketAddress& remote_addr, + bool use_delay); + + // Sends a disconnect message to the socket at the given address + bool Disconnect(VirtualSocket* socket); + + // Sends the given packet to the socket at the given address (if one exists). + int SendUdp(VirtualSocket* socket, const char* data, size_t data_size, + const SocketAddress& remote_addr); + + // Moves as much data as possible from the sender's buffer to the network + void SendTcp(VirtualSocket* socket); + + // Places a packet on the network. + void AddPacketToNetwork(VirtualSocket* socket, VirtualSocket* recipient, + uint32 cur_time, const char* data, size_t data_size, + size_t header_size, bool ordered); + + // Removes stale packets from the network + void PurgeNetworkPackets(VirtualSocket* socket, uint32 cur_time); + + // Computes the number of milliseconds required to send a packet of this size. + uint32 SendDelay(uint32 size); + + // Returns a random transit delay chosen from the appropriate distribution. + uint32 GetRandomTransitDelay(); + + // Basic operations on functions. Those that return a function also take + // ownership of the function given (and hence, may modify or delete it). + static Function* Accumulate(Function* f); + static Function* Invert(Function* f); + static Function* Resample(Function* f, double x1, double x2, uint32 samples); + static double Evaluate(Function* f, double x); + + // NULL out our message queue if it goes away. Necessary in the case where + // our lifetime is greater than that of the thread we are using, since we + // try to send Close messages for all connected sockets when we shutdown. + void OnMessageQueueDestroyed() { msg_queue_ = NULL; } + + // Determine if two sockets should be able to communicate. + // We don't (currently) specify an address family for sockets; instead, + // the currently bound address is used to infer the address family. + // Any socket that is not explicitly bound to an IPv4 address is assumed to be + // dual-stack capable. + // This function tests if two addresses can communicate, as well as the + // sockets to which they may be bound (the addresses may or may not yet be + // bound to the sockets). + // First the addresses are tested (after normalization): + // If both have the same family, then communication is OK. + // If only one is IPv4 then false, unless the other is bound to ::. + // This applies even if the IPv4 address is 0.0.0.0. + // The socket arguments are optional; the sockets are checked to see if they + // were explicitly bound to IPv6-any ('::'), and if so communication is + // permitted. + // NB: This scheme doesn't permit non-dualstack IPv6 sockets. + static bool CanInteractWith(VirtualSocket* local, VirtualSocket* remote); + + private: + friend class VirtualSocket; + + typedef std::map AddressMap; + typedef std::map ConnectionMap; + + SocketServer* server_; + bool server_owned_; + MessageQueue* msg_queue_; + bool stop_on_idle_; + uint32 network_delay_; + in_addr next_ipv4_; + in6_addr next_ipv6_; + uint16 next_port_; + AddressMap* bindings_; + ConnectionMap* connections_; + + uint32 bandwidth_; + uint32 network_capacity_; + uint32 send_buffer_capacity_; + uint32 recv_buffer_capacity_; + uint32 delay_mean_; + uint32 delay_stddev_; + uint32 delay_samples_; + Function* delay_dist_; + CriticalSection delay_crit_; + + double drop_prob_; + DISALLOW_EVIL_CONSTRUCTORS(VirtualSocketServer); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_VIRTUALSOCKETSERVER_H_ diff --git a/webrtc/base/win32.cc b/webrtc/base/win32.cc new file mode 100644 index 000000000..8f5661225 --- /dev/null +++ b/webrtc/base/win32.cc @@ -0,0 +1,456 @@ +/* + * Copyright 2004 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 "webrtc/base/win32.h" + +#include +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +// Helper function declarations for inet_ntop/inet_pton. +static const char* inet_ntop_v4(const void* src, char* dst, socklen_t size); +static const char* inet_ntop_v6(const void* src, char* dst, socklen_t size); +static int inet_pton_v4(const char* src, void* dst); +static int inet_pton_v6(const char* src, void* dst); + +// Implementation of inet_ntop (create a printable representation of an +// ip address). XP doesn't have its own inet_ntop, and +// WSAAddressToString requires both IPv6 to be installed and for Winsock +// to be initialized. +const char* win32_inet_ntop(int af, const void *src, + char* dst, socklen_t size) { + if (!src || !dst) { + return NULL; + } + switch (af) { + case AF_INET: { + return inet_ntop_v4(src, dst, size); + } + case AF_INET6: { + return inet_ntop_v6(src, dst, size); + } + } + return NULL; +} + +// As above, but for inet_pton. Implements inet_pton for v4 and v6. +// Note that our inet_ntop will output normal 'dotted' v4 addresses only. +int win32_inet_pton(int af, const char* src, void* dst) { + if (!src || !dst) { + return 0; + } + if (af == AF_INET) { + return inet_pton_v4(src, dst); + } else if (af == AF_INET6) { + return inet_pton_v6(src, dst); + } + return -1; +} + +// Helper function for inet_ntop for IPv4 addresses. +// Outputs "dotted-quad" decimal notation. +const char* inet_ntop_v4(const void* src, char* dst, socklen_t size) { + if (size < INET_ADDRSTRLEN) { + return NULL; + } + const struct in_addr* as_in_addr = + reinterpret_cast(src); + rtc::sprintfn(dst, size, "%d.%d.%d.%d", + as_in_addr->S_un.S_un_b.s_b1, + as_in_addr->S_un.S_un_b.s_b2, + as_in_addr->S_un.S_un_b.s_b3, + as_in_addr->S_un.S_un_b.s_b4); + return dst; +} + +// Helper function for inet_ntop for IPv6 addresses. +const char* inet_ntop_v6(const void* src, char* dst, socklen_t size) { + if (size < INET6_ADDRSTRLEN) { + return NULL; + } + const uint16* as_shorts = + reinterpret_cast(src); + int runpos[8]; + int current = 1; + int max = 1; + int maxpos = -1; + int run_array_size = ARRAY_SIZE(runpos); + // Run over the address marking runs of 0s. + for (int i = 0; i < run_array_size; ++i) { + if (as_shorts[i] == 0) { + runpos[i] = current; + if (current > max) { + maxpos = i; + max = current; + } + ++current; + } else { + runpos[i] = -1; + current =1; + } + } + + if (max > 1) { + int tmpmax = maxpos; + // Run back through, setting -1 for all but the longest run. + for (int i = run_array_size - 1; i >= 0; i--) { + if (i > tmpmax) { + runpos[i] = -1; + } else if (runpos[i] == -1) { + // We're less than maxpos, we hit a -1, so the 'good' run is done. + // Setting tmpmax -1 means all remaining positions get set to -1. + tmpmax = -1; + } + } + } + + char* cursor = dst; + // Print IPv4 compatible and IPv4 mapped addresses using the IPv4 helper. + // These addresses have an initial run of either eight zero-bytes followed + // by 0xFFFF, or an initial run of ten zero-bytes. + if (runpos[0] == 1 && (maxpos == 5 || + (maxpos == 4 && as_shorts[5] == 0xFFFF))) { + *cursor++ = ':'; + *cursor++ = ':'; + if (maxpos == 4) { + cursor += rtc::sprintfn(cursor, INET6_ADDRSTRLEN - 2, "ffff:"); + } + const struct in_addr* as_v4 = + reinterpret_cast(&(as_shorts[6])); + inet_ntop_v4(as_v4, cursor, + static_cast(INET6_ADDRSTRLEN - (cursor - dst))); + } else { + for (int i = 0; i < run_array_size; ++i) { + if (runpos[i] == -1) { + cursor += rtc::sprintfn(cursor, + INET6_ADDRSTRLEN - (cursor - dst), + "%x", NetworkToHost16(as_shorts[i])); + if (i != 7 && runpos[i + 1] != 1) { + *cursor++ = ':'; + } + } else if (runpos[i] == 1) { + // Entered the run; print the colons and skip the run. + *cursor++ = ':'; + *cursor++ = ':'; + i += (max - 1); + } + } + } + return dst; +} + +// Helper function for inet_pton for IPv4 addresses. +// |src| points to a character string containing an IPv4 network address in +// dotted-decimal format, "ddd.ddd.ddd.ddd", where ddd is a decimal number +// of up to three digits in the range 0 to 255. +// The address is converted and copied to dst, +// which must be sizeof(struct in_addr) (4) bytes (32 bits) long. +int inet_pton_v4(const char* src, void* dst) { + const int kIpv4AddressSize = 4; + int found = 0; + const char* src_pos = src; + unsigned char result[kIpv4AddressSize] = {0}; + + while (*src_pos != '\0') { + // strtol won't treat whitespace characters in the begining as an error, + // so check to ensure this is started with digit before passing to strtol. + if (!isdigit(*src_pos)) { + return 0; + } + char* end_pos; + long value = strtol(src_pos, &end_pos, 10); + if (value < 0 || value > 255 || src_pos == end_pos) { + return 0; + } + ++found; + if (found > kIpv4AddressSize) { + return 0; + } + result[found - 1] = static_cast(value); + src_pos = end_pos; + if (*src_pos == '.') { + // There's more. + ++src_pos; + } else if (*src_pos != '\0') { + // If it's neither '.' nor '\0' then return fail. + return 0; + } + } + if (found != kIpv4AddressSize) { + return 0; + } + memcpy(dst, result, sizeof(result)); + return 1; +} + +// Helper function for inet_pton for IPv6 addresses. +int inet_pton_v6(const char* src, void* dst) { + // sscanf will pick any other invalid chars up, but it parses 0xnnnn as hex. + // Check for literal x in the input string. + const char* readcursor = src; + char c = *readcursor++; + while (c) { + if (c == 'x') { + return 0; + } + c = *readcursor++; + } + readcursor = src; + + struct in6_addr an_addr; + memset(&an_addr, 0, sizeof(an_addr)); + + uint16* addr_cursor = reinterpret_cast(&an_addr.s6_addr[0]); + uint16* addr_end = reinterpret_cast(&an_addr.s6_addr[16]); + bool seencompressed = false; + + // Addresses that start with "::" (i.e., a run of initial zeros) or + // "::ffff:" can potentially be IPv4 mapped or compatibility addresses. + // These have dotted-style IPv4 addresses on the end (e.g. "::192.168.7.1"). + if (*readcursor == ':' && *(readcursor+1) == ':' && + *(readcursor + 2) != 0) { + // Check for periods, which we'll take as a sign of v4 addresses. + const char* addrstart = readcursor + 2; + if (rtc::strchr(addrstart, ".")) { + const char* colon = rtc::strchr(addrstart, "::"); + if (colon) { + uint16 a_short; + int bytesread = 0; + if (sscanf(addrstart, "%hx%n", &a_short, &bytesread) != 1 || + a_short != 0xFFFF || bytesread != 4) { + // Colons + periods means has to be ::ffff:a.b.c.d. But it wasn't. + return 0; + } else { + an_addr.s6_addr[10] = 0xFF; + an_addr.s6_addr[11] = 0xFF; + addrstart = colon + 1; + } + } + struct in_addr v4; + if (inet_pton_v4(addrstart, &v4.s_addr)) { + memcpy(&an_addr.s6_addr[12], &v4, sizeof(v4)); + memcpy(dst, &an_addr, sizeof(an_addr)); + return 1; + } else { + // Invalid v4 address. + return 0; + } + } + } + + // For addresses without a trailing IPv4 component ('normal' IPv6 addresses). + while (*readcursor != 0 && addr_cursor < addr_end) { + if (*readcursor == ':') { + if (*(readcursor + 1) == ':') { + if (seencompressed) { + // Can only have one compressed run of zeroes ("::") per address. + return 0; + } + // Hit a compressed run. Count colons to figure out how much of the + // address is skipped. + readcursor += 2; + const char* coloncounter = readcursor; + int coloncount = 0; + if (*coloncounter == 0) { + // Special case - trailing ::. + addr_cursor = addr_end; + } else { + while (*coloncounter) { + if (*coloncounter == ':') { + ++coloncount; + } + ++coloncounter; + } + // (coloncount + 1) is the number of shorts left in the address. + addr_cursor = addr_end - (coloncount + 1); + seencompressed = true; + } + } else { + ++readcursor; + } + } else { + uint16 word; + int bytesread = 0; + if (sscanf(readcursor, "%hx%n", &word, &bytesread) != 1) { + return 0; + } else { + *addr_cursor = HostToNetwork16(word); + ++addr_cursor; + readcursor += bytesread; + if (*readcursor != ':' && *readcursor != '\0') { + return 0; + } + } + } + } + + if (*readcursor != '\0' || addr_cursor < addr_end) { + // Catches addresses too short or too long. + return 0; + } + memcpy(dst, &an_addr, sizeof(an_addr)); + return 1; +} + +// +// Unix time is in seconds relative to 1/1/1970. So we compute the windows +// FILETIME of that time/date, then we add/subtract in appropriate units to +// convert to/from unix time. +// The units of FILETIME are 100ns intervals, so by multiplying by or dividing +// by 10000000, we can convert to/from seconds. +// +// FileTime = UnixTime*10000000 + FileTime(1970) +// UnixTime = (FileTime-FileTime(1970))/10000000 +// + +void FileTimeToUnixTime(const FILETIME& ft, time_t* ut) { + ASSERT(NULL != ut); + + // FILETIME has an earlier date base than time_t (1/1/1970), so subtract off + // the difference. + SYSTEMTIME base_st; + memset(&base_st, 0, sizeof(base_st)); + base_st.wDay = 1; + base_st.wMonth = 1; + base_st.wYear = 1970; + + FILETIME base_ft; + SystemTimeToFileTime(&base_st, &base_ft); + + ULARGE_INTEGER base_ul, current_ul; + memcpy(&base_ul, &base_ft, sizeof(FILETIME)); + memcpy(¤t_ul, &ft, sizeof(FILETIME)); + + // Divide by big number to convert to seconds, then subtract out the 1970 + // base date value. + const ULONGLONG RATIO = 10000000; + *ut = static_cast((current_ul.QuadPart - base_ul.QuadPart) / RATIO); +} + +void UnixTimeToFileTime(const time_t& ut, FILETIME* ft) { + ASSERT(NULL != ft); + + // FILETIME has an earlier date base than time_t (1/1/1970), so add in + // the difference. + SYSTEMTIME base_st; + memset(&base_st, 0, sizeof(base_st)); + base_st.wDay = 1; + base_st.wMonth = 1; + base_st.wYear = 1970; + + FILETIME base_ft; + SystemTimeToFileTime(&base_st, &base_ft); + + ULARGE_INTEGER base_ul; + memcpy(&base_ul, &base_ft, sizeof(FILETIME)); + + // Multiply by big number to convert to 100ns units, then add in the 1970 + // base date value. + const ULONGLONG RATIO = 10000000; + ULARGE_INTEGER current_ul; + current_ul.QuadPart = base_ul.QuadPart + static_cast(ut) * RATIO; + memcpy(ft, ¤t_ul, sizeof(FILETIME)); +} + +bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename) { + // TODO: Integrate into fileutils.h + // TODO: Handle wide and non-wide cases via TCHAR? + // TODO: Skip \\?\ processing if the length is not > MAX_PATH? + // TODO: Write unittests + + // Convert to Utf16 + int wlen = ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), + static_cast(utf8.length() + 1), NULL, + 0); + if (0 == wlen) { + return false; + } + wchar_t* wfilename = STACK_ARRAY(wchar_t, wlen); + if (0 == ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), + static_cast(utf8.length() + 1), + wfilename, wlen)) { + return false; + } + // Replace forward slashes with backslashes + std::replace(wfilename, wfilename + wlen, L'/', L'\\'); + // Convert to complete filename + DWORD full_len = ::GetFullPathName(wfilename, 0, NULL, NULL); + if (0 == full_len) { + return false; + } + wchar_t* filepart = NULL; + wchar_t* full_filename = STACK_ARRAY(wchar_t, full_len + 6); + wchar_t* start = full_filename + 6; + if (0 == ::GetFullPathName(wfilename, full_len, start, &filepart)) { + return false; + } + // Add long-path prefix + const wchar_t kLongPathPrefix[] = L"\\\\?\\UNC"; + if ((start[0] != L'\\') || (start[1] != L'\\')) { + // Non-unc path: + // Becomes: \\?\ + start -= 4; + ASSERT(start >= full_filename); + memcpy(start, kLongPathPrefix, 4 * sizeof(wchar_t)); + } else if (start[2] != L'?') { + // Unc path: \\\ + // Becomes: \\?\UNC\\ + start -= 6; + ASSERT(start >= full_filename); + memcpy(start, kLongPathPrefix, 7 * sizeof(wchar_t)); + } else { + // Already in long-path form. + } + filename->assign(start); + return true; +} + +bool GetOsVersion(int* major, int* minor, int* build) { + OSVERSIONINFO info = {0}; + info.dwOSVersionInfoSize = sizeof(info); + if (GetVersionEx(&info)) { + if (major) *major = info.dwMajorVersion; + if (minor) *minor = info.dwMinorVersion; + if (build) *build = info.dwBuildNumber; + return true; + } + return false; +} + +bool GetCurrentProcessIntegrityLevel(int* level) { + bool ret = false; + HANDLE process = ::GetCurrentProcess(), token; + if (OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, &token)) { + DWORD size; + if (!GetTokenInformation(token, TokenIntegrityLevel, NULL, 0, &size) && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + + char* buf = STACK_ARRAY(char, size); + TOKEN_MANDATORY_LABEL* til = + reinterpret_cast(buf); + if (GetTokenInformation(token, TokenIntegrityLevel, til, size, &size)) { + + DWORD count = *GetSidSubAuthorityCount(til->Label.Sid); + *level = *GetSidSubAuthority(til->Label.Sid, count - 1); + ret = true; + } + } + CloseHandle(token); + } + return ret; +} +} // namespace rtc diff --git a/webrtc/base/win32.h b/webrtc/base/win32.h new file mode 100644 index 000000000..bf5da254c --- /dev/null +++ b/webrtc/base/win32.h @@ -0,0 +1,129 @@ +/* + * Copyright 2004 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_BASE_WIN32_H_ +#define WEBRTC_BASE_WIN32_H_ + +#if defined(WEBRTC_WIN) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +// Make sure we don't get min/max macros +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include + +#ifndef SECURITY_MANDATORY_LABEL_AUTHORITY +// Add defines that we use if we are compiling against older sdks +#define SECURITY_MANDATORY_MEDIUM_RID (0x00002000L) +#define TokenIntegrityLevel static_cast(0x19) +typedef struct _TOKEN_MANDATORY_LABEL { + SID_AND_ATTRIBUTES Label; +} TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL; +#endif // SECURITY_MANDATORY_LABEL_AUTHORITY + +#undef SetPort + +#include + +#include "webrtc/base/stringutils.h" +#include "webrtc/base/basictypes.h" + +namespace rtc { + +const char* win32_inet_ntop(int af, const void *src, char* dst, socklen_t size); +int win32_inet_pton(int af, const char* src, void *dst); + +/////////////////////////////////////////////////////////////////////////////// + +inline std::wstring ToUtf16(const char* utf8, size_t len) { + int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast(len), + NULL, 0); + wchar_t* ws = STACK_ARRAY(wchar_t, len16); + ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast(len), ws, len16); + return std::wstring(ws, len16); +} + +inline std::wstring ToUtf16(const std::string& str) { + return ToUtf16(str.data(), str.length()); +} + +inline std::string ToUtf8(const wchar_t* wide, size_t len) { + int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast(len), + NULL, 0, NULL, NULL); + char* ns = STACK_ARRAY(char, len8); + ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast(len), ns, len8, + NULL, NULL); + return std::string(ns, len8); +} + +inline std::string ToUtf8(const wchar_t* wide) { + return ToUtf8(wide, wcslen(wide)); +} + +inline std::string ToUtf8(const std::wstring& wstr) { + return ToUtf8(wstr.data(), wstr.length()); +} + +// Convert FILETIME to time_t +void FileTimeToUnixTime(const FILETIME& ft, time_t* ut); + +// Convert time_t to FILETIME +void UnixTimeToFileTime(const time_t& ut, FILETIME * ft); + +// Convert a Utf8 path representation to a non-length-limited Unicode pathname. +bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename); + +// Convert a FILETIME to a UInt64 +inline uint64 ToUInt64(const FILETIME& ft) { + ULARGE_INTEGER r = {ft.dwLowDateTime, ft.dwHighDateTime}; + return r.QuadPart; +} + +enum WindowsMajorVersions { + kWindows2000 = 5, + kWindowsVista = 6, +}; +bool GetOsVersion(int* major, int* minor, int* build); + +inline bool IsWindowsVistaOrLater() { + int major; + return (GetOsVersion(&major, NULL, NULL) && major >= kWindowsVista); +} + +inline bool IsWindowsXpOrLater() { + int major, minor; + return (GetOsVersion(&major, &minor, NULL) && + (major >= kWindowsVista || + (major == kWindows2000 && minor >= 1))); +} + +// Determine the current integrity level of the process. +bool GetCurrentProcessIntegrityLevel(int* level); + +inline bool IsCurrentProcessLowIntegrity() { + int level; + return (GetCurrentProcessIntegrityLevel(&level) && + level < SECURITY_MANDATORY_MEDIUM_RID); +} + +bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_WIN +#endif // WEBRTC_BASE_WIN32_H_ diff --git a/webrtc/base/win32_unittest.cc b/webrtc/base/win32_unittest.cc new file mode 100644 index 000000000..0050c7726 --- /dev/null +++ b/webrtc/base/win32_unittest.cc @@ -0,0 +1,62 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/win32.h" +#include "webrtc/base/winping.h" + +#if !defined(WEBRTC_WIN) +#error Only for Windows +#endif + +namespace rtc { + +class Win32Test : public testing::Test { + public: + Win32Test() { + } +}; + +TEST_F(Win32Test, FileTimeToUInt64Test) { + FILETIME ft; + ft.dwHighDateTime = 0xBAADF00D; + ft.dwLowDateTime = 0xFEED3456; + + uint64 expected = 0xBAADF00DFEED3456; + EXPECT_EQ(expected, ToUInt64(ft)); +} + +TEST_F(Win32Test, WinPingTest) { + WinPing ping; + ASSERT_TRUE(ping.IsValid()); + + // Test valid ping cases. + WinPing::PingResult result = ping.Ping(IPAddress(INADDR_LOOPBACK), 20, 50, 1, + false); + ASSERT_EQ(WinPing::PING_SUCCESS, result); + if (HasIPv6Enabled()) { + WinPing::PingResult v6result = ping.Ping(IPAddress(in6addr_loopback), 20, + 50, 1, false); + ASSERT_EQ(WinPing::PING_SUCCESS, v6result); + } + + // Test invalid parameter cases. + ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping( + IPAddress(INADDR_LOOPBACK), 0, 50, 1, false)); + ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping( + IPAddress(INADDR_LOOPBACK), 20, 0, 1, false)); + ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping( + IPAddress(INADDR_LOOPBACK), 20, 50, 0, false)); +} + +} // namespace rtc diff --git a/webrtc/base/win32filesystem.cc b/webrtc/base/win32filesystem.cc new file mode 100644 index 000000000..73f8ef0cf --- /dev/null +++ b/webrtc/base/win32filesystem.cc @@ -0,0 +1,460 @@ +/* + * Copyright 2004 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 "webrtc/base/win32filesystem.h" + +#include "webrtc/base/win32.h" +#include +#include +#include + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/stringutils.h" + +// In several places in this file, we test the integrity level of the process +// before calling GetLongPathName. We do this because calling GetLongPathName +// when running under protected mode IE (a low integrity process) can result in +// a virtualized path being returned, which is wrong if you only plan to read. +// TODO: Waiting to hear back from IE team on whether this is the +// best approach; IEIsProtectedModeProcess is another possible solution. + +namespace rtc { + +bool Win32Filesystem::CreateFolder(const Pathname &pathname) { + if (pathname.pathname().empty() || !pathname.filename().empty()) + return false; + + std::wstring path16; + if (!Utf8ToWindowsFilename(pathname.pathname(), &path16)) + return false; + + DWORD res = ::GetFileAttributes(path16.c_str()); + if (res != INVALID_FILE_ATTRIBUTES) { + // Something exists at this location, check if it is a directory + return ((res & FILE_ATTRIBUTE_DIRECTORY) != 0); + } else if ((GetLastError() != ERROR_FILE_NOT_FOUND) + && (GetLastError() != ERROR_PATH_NOT_FOUND)) { + // Unexpected error + return false; + } + + // Directory doesn't exist, look up one directory level + if (!pathname.parent_folder().empty()) { + Pathname parent(pathname); + parent.SetFolder(pathname.parent_folder()); + if (!CreateFolder(parent)) { + return false; + } + } + + return (::CreateDirectory(path16.c_str(), NULL) != 0); +} + +FileStream *Win32Filesystem::OpenFile(const Pathname &filename, + const std::string &mode) { + FileStream *fs = new FileStream(); + if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) { + delete fs; + fs = NULL; + } + return fs; +} + +bool Win32Filesystem::CreatePrivateFile(const Pathname &filename) { + // To make the file private to the current user, we first must construct a + // SECURITY_DESCRIPTOR specifying an ACL. This code is mostly based upon + // http://msdn.microsoft.com/en-us/library/ms707085%28VS.85%29.aspx + + // Get the current process token. + HANDLE process_token = INVALID_HANDLE_VALUE; + if (!::OpenProcessToken(::GetCurrentProcess(), + TOKEN_QUERY, + &process_token)) { + LOG_ERR(LS_ERROR) << "OpenProcessToken() failed"; + return false; + } + + // Get the size of its TOKEN_USER structure. Return value is not checked + // because we expect it to fail. + DWORD token_user_size = 0; + (void)::GetTokenInformation(process_token, + TokenUser, + NULL, + 0, + &token_user_size); + + // Get the TOKEN_USER structure. + scoped_ptr token_user_bytes(new char[token_user_size]); + PTOKEN_USER token_user = reinterpret_cast( + token_user_bytes.get()); + memset(token_user, 0, token_user_size); + BOOL success = ::GetTokenInformation(process_token, + TokenUser, + token_user, + token_user_size, + &token_user_size); + // We're now done with this. + ::CloseHandle(process_token); + if (!success) { + LOG_ERR(LS_ERROR) << "GetTokenInformation() failed"; + return false; + } + + if (!IsValidSid(token_user->User.Sid)) { + LOG_ERR(LS_ERROR) << "Current process has invalid user SID"; + return false; + } + + // Compute size needed for an ACL that allows access to just this user. + int acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + + GetLengthSid(token_user->User.Sid); + + // Allocate it. + scoped_ptr acl_bytes(new char[acl_size]); + PACL acl = reinterpret_cast(acl_bytes.get()); + memset(acl, 0, acl_size); + if (!::InitializeAcl(acl, acl_size, ACL_REVISION)) { + LOG_ERR(LS_ERROR) << "InitializeAcl() failed"; + return false; + } + + // Allow access to only the current user. + if (!::AddAccessAllowedAce(acl, + ACL_REVISION, + GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL, + token_user->User.Sid)) { + LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed"; + return false; + } + + // Now make the security descriptor. + SECURITY_DESCRIPTOR security_descriptor; + if (!::InitializeSecurityDescriptor(&security_descriptor, + SECURITY_DESCRIPTOR_REVISION)) { + LOG_ERR(LS_ERROR) << "InitializeSecurityDescriptor() failed"; + return false; + } + + // Put the ACL in it. + if (!::SetSecurityDescriptorDacl(&security_descriptor, + TRUE, + acl, + FALSE)) { + LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed"; + return false; + } + + // Finally create the file. + SECURITY_ATTRIBUTES security_attributes; + security_attributes.nLength = sizeof(security_attributes); + security_attributes.lpSecurityDescriptor = &security_descriptor; + security_attributes.bInheritHandle = FALSE; + HANDLE handle = ::CreateFile( + ToUtf16(filename.pathname()).c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + &security_attributes, + CREATE_NEW, + 0, + NULL); + if (INVALID_HANDLE_VALUE == handle) { + LOG_ERR(LS_ERROR) << "CreateFile() failed"; + return false; + } + if (!::CloseHandle(handle)) { + LOG_ERR(LS_ERROR) << "CloseFile() failed"; + // Continue. + } + return true; +} + +bool Win32Filesystem::DeleteFile(const Pathname &filename) { + LOG(LS_INFO) << "Deleting file " << filename.pathname(); + if (!IsFile(filename)) { + ASSERT(IsFile(filename)); + return false; + } + return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0; +} + +bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) { + LOG(LS_INFO) << "Deleting folder " << folder.pathname(); + + std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1); + return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0; +} + +bool Win32Filesystem::GetTemporaryFolder(Pathname &pathname, bool create, + const std::string *append) { + wchar_t buffer[MAX_PATH + 1]; + if (!::GetTempPath(ARRAY_SIZE(buffer), buffer)) + return false; + if (!IsCurrentProcessLowIntegrity() && + !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + size_t len = strlen(buffer); + if ((len > 0) && (buffer[len-1] != '\\')) { + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, L"\\"); + } + if (len >= ARRAY_SIZE(buffer) - 1) + return false; + pathname.clear(); + pathname.SetFolder(ToUtf8(buffer)); + if (append != NULL) { + ASSERT(!append->empty()); + pathname.AppendFolder(*append); + } + return !create || CreateFolder(pathname); +} + +std::string Win32Filesystem::TempFilename(const Pathname &dir, + const std::string &prefix) { + wchar_t filename[MAX_PATH]; + if (::GetTempFileName(ToUtf16(dir.pathname()).c_str(), + ToUtf16(prefix).c_str(), 0, filename) != 0) + return ToUtf8(filename); + ASSERT(false); + return ""; +} + +bool Win32Filesystem::MoveFile(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFile(old_path)) { + ASSERT(IsFile(old_path)); + return false; + } + LOG(LS_INFO) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + return ::MoveFile(ToUtf16(old_path.pathname()).c_str(), + ToUtf16(new_path.pathname()).c_str()) != 0; +} + +bool Win32Filesystem::MoveFolder(const Pathname &old_path, + const Pathname &new_path) { + if (!IsFolder(old_path)) { + ASSERT(IsFolder(old_path)); + return false; + } + LOG(LS_INFO) << "Moving " << old_path.pathname() + << " to " << new_path.pathname(); + if (::MoveFile(ToUtf16(old_path.pathname()).c_str(), + ToUtf16(new_path.pathname()).c_str()) == 0) { + if (::GetLastError() != ERROR_NOT_SAME_DEVICE) { + LOG_GLE(LS_ERROR) << "Failed to move file"; + return false; + } + if (!CopyFolder(old_path, new_path)) + return false; + if (!DeleteFolderAndContents(old_path)) + return false; + } + return true; +} + +bool Win32Filesystem::IsFolder(const Pathname &path) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data)) + return false; + return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == + FILE_ATTRIBUTE_DIRECTORY; +} + +bool Win32Filesystem::IsFile(const Pathname &path) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data)) + return false; + return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +bool Win32Filesystem::IsAbsent(const Pathname& path) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data)) + return false; + DWORD err = ::GetLastError(); + return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err); +} + +bool Win32Filesystem::CopyFile(const Pathname &old_path, + const Pathname &new_path) { + return ::CopyFile(ToUtf16(old_path.pathname()).c_str(), + ToUtf16(new_path.pathname()).c_str(), TRUE) != 0; +} + +bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) { + TCHAR buffer[MAX_PATH + 1]; + if (!::GetTempPath(ARRAY_SIZE(buffer), buffer)) + return false; + if (!IsCurrentProcessLowIntegrity() && + !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + return (::strnicmp(ToUtf16(pathname.pathname()).c_str(), + buffer, strlen(buffer)) == 0); +} + +bool Win32Filesystem::GetFileSize(const Pathname &pathname, size_t *size) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(), + GetFileExInfoStandard, &data) == 0) + return false; + *size = data.nFileSizeLow; + return true; +} + +bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which, + time_t* time) { + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(), + GetFileExInfoStandard, &data) == 0) + return false; + switch (which) { + case FTT_CREATED: + FileTimeToUnixTime(data.ftCreationTime, time); + break; + case FTT_MODIFIED: + FileTimeToUnixTime(data.ftLastWriteTime, time); + break; + case FTT_ACCESSED: + FileTimeToUnixTime(data.ftLastAccessTime, time); + break; + default: + return false; + } + return true; +} + +bool Win32Filesystem::GetAppPathname(Pathname* path) { + TCHAR buffer[MAX_PATH + 1]; + if (0 == ::GetModuleFileName(NULL, buffer, ARRAY_SIZE(buffer))) + return false; + path->SetPathname(ToUtf8(buffer)); + return true; +} + +bool Win32Filesystem::GetAppDataFolder(Pathname* path, bool per_user) { + ASSERT(!organization_name_.empty()); + ASSERT(!application_name_.empty()); + TCHAR buffer[MAX_PATH + 1]; + int csidl = per_user ? CSIDL_LOCAL_APPDATA : CSIDL_COMMON_APPDATA; + if (!::SHGetSpecialFolderPath(NULL, buffer, csidl, TRUE)) + return false; + if (!IsCurrentProcessLowIntegrity() && + !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + size_t len = strcatn(buffer, ARRAY_SIZE(buffer), __T("\\")); + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + ToUtf16(organization_name_).c_str()); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\")); + } + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + ToUtf16(application_name_).c_str()); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\")); + } + if (len >= ARRAY_SIZE(buffer) - 1) + return false; + path->clear(); + path->SetFolder(ToUtf8(buffer)); + return CreateFolder(*path); +} + +bool Win32Filesystem::GetAppTempFolder(Pathname* path) { + if (!GetAppPathname(path)) + return false; + std::string filename(path->filename()); + return GetTemporaryFolder(*path, true, &filename); +} + +bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) { + if (!freebytes) { + return false; + } + char drive[4]; + std::wstring drive16; + const wchar_t* target_drive = NULL; + if (path.GetDrive(drive, sizeof(drive))) { + drive16 = ToUtf16(drive); + target_drive = drive16.c_str(); + } else if (path.folder().substr(0, 2) == "\\\\") { + // UNC path, fail. + // TODO: Handle UNC paths. + return false; + } else { + // The path is probably relative. GetDriveType and GetDiskFreeSpaceEx + // use the current drive if NULL is passed as the drive name. + // TODO: Add method to Pathname to determine if the path is relative. + // TODO: Add method to Pathname to convert a path to absolute. + } + UINT driveType = ::GetDriveType(target_drive); + if ( (driveType & DRIVE_REMOTE) || (driveType & DRIVE_UNKNOWN) ) { + LOG(LS_VERBOSE) << " remove or unknown drive " << drive; + return false; + } + + int64 totalNumberOfBytes; // receives the number of bytes on disk + int64 totalNumberOfFreeBytes; // receives the free bytes on disk + // make sure things won't change in 64 bit machine + // TODO replace with compile time assert + ASSERT(sizeof(ULARGE_INTEGER) == sizeof(uint64)); //NOLINT + if (::GetDiskFreeSpaceEx(target_drive, + (PULARGE_INTEGER)freebytes, + (PULARGE_INTEGER)&totalNumberOfBytes, + (PULARGE_INTEGER)&totalNumberOfFreeBytes)) { + return true; + } else { + LOG(LS_VERBOSE) << " GetDiskFreeSpaceEx returns error "; + return false; + } +} + +Pathname Win32Filesystem::GetCurrentDirectory() { + Pathname cwd; + int path_len = 0; + scoped_ptr path; + do { + int needed = ::GetCurrentDirectory(path_len, path.get()); + if (needed == 0) { + // Error. + LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed"; + return cwd; // returns empty pathname + } + if (needed <= path_len) { + // It wrote successfully. + break; + } + // Else need to re-alloc for "needed". + path.reset(new wchar_t[needed]); + path_len = needed; + } while (true); + cwd.SetFolder(ToUtf8(path.get())); + return cwd; +} + +// TODO: Consider overriding DeleteFolderAndContents for speed and potentially +// better OS integration (recycle bin?) +/* + std::wstring temp_path16 = ToUtf16(temp_path.pathname()); + temp_path16.append(1, '*'); + temp_path16.append(1, '\0'); + + SHFILEOPSTRUCT file_op = { 0 }; + file_op.wFunc = FO_DELETE; + file_op.pFrom = temp_path16.c_str(); + file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT; + return (0 == SHFileOperation(&file_op)); +*/ + +} // namespace rtc diff --git a/webrtc/base/win32filesystem.h b/webrtc/base/win32filesystem.h new file mode 100644 index 000000000..3cd5373e3 --- /dev/null +++ b/webrtc/base/win32filesystem.h @@ -0,0 +1,101 @@ +/* + * Copyright 2004 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_BASE_WIN32FILESYSTEM_H__ +#define _WEBRTC_BASE_WIN32FILESYSTEM_H__ + +#include "fileutils.h" + +namespace rtc { + +class Win32Filesystem : public FilesystemInterface { + public: + // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise, + // returns NULL. + virtual FileStream *OpenFile(const Pathname &filename, + const std::string &mode); + + // Atomically creates an empty file accessible only to the current user if one + // does not already exist at the given path, otherwise fails. + virtual bool CreatePrivateFile(const Pathname &filename); + + // This will attempt to delete the path located at filename. + // If the path points to a folder, it will fail with VERIFY + virtual bool DeleteFile(const Pathname &filename); + + // This will attempt to delete an empty folder. If the path does not point to + // a folder, it fails with VERIFY. If the folder is not empty, it fails normally + virtual bool DeleteEmptyFolder(const Pathname &folder); + + // Creates a directory. This will call itself recursively to create /foo/bar even if + // /foo does not exist. + // Returns TRUE if function succeeds + virtual bool CreateFolder(const Pathname &pathname); + + // This moves a file from old_path to new_path. If the new path is on a + // different volume than the old, it will attempt to copy and then delete + // the folder + // Returns true if the file is successfully moved + virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path); + + // Moves a folder from old_path to new_path. If the new path is on a different + // volume from the old, it will attempt to Copy and then Delete the folder + // Returns true if the folder is successfully moved + virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path); + + // This copies a file from old_path to _new_path + // Returns true if function succeeds + virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path); + + // Returns true if a pathname is a directory + virtual bool IsFolder(const Pathname& pathname); + + // Returns true if a file exists at path + virtual bool IsFile(const Pathname &path); + + // Returns true if pathname refers to no filesystem object, every parent + // directory either exists, or is also absent. + virtual bool IsAbsent(const Pathname& pathname); + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPath(const Pathname& pathname); + + // All of the following functions set pathname and return true if successful. + // Returned paths always include a trailing backslash. + // If create is true, the path will be recursively created. + // If append is non-NULL, it will be appended (and possibly created). + + virtual std::string TempFilename(const Pathname &dir, const std::string &prefix); + + virtual bool GetFileSize(const Pathname& path, size_t* size); + virtual bool GetFileTime(const Pathname& path, FileTimeType which, + time_t* time); + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exists) + virtual bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append); + + // Returns the path to the running application. + virtual bool GetAppPathname(Pathname* path); + + virtual bool GetAppDataFolder(Pathname* path, bool per_user); + + // Get a temporary folder that is unique to the current user and application. + virtual bool GetAppTempFolder(Pathname* path); + + virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes); + + virtual Pathname GetCurrentDirectory(); +}; + +} // namespace rtc + +#endif // WEBRTC_WINFILESYSTEM_H__ diff --git a/webrtc/base/win32regkey.cc b/webrtc/base/win32regkey.cc new file mode 100644 index 000000000..1ed0d4ea2 --- /dev/null +++ b/webrtc/base/win32regkey.cc @@ -0,0 +1,1102 @@ +/* + * Copyright 2003 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. + */ + +// Registry configuration wrapers class implementation +// +// Change made by S. Ganesh - ganesh@google.com: +// Use SHQueryValueEx instead of RegQueryValueEx throughout. +// A call to the SHLWAPI function is essentially a call to the standard +// function but with post-processing: +// * to fix REG_SZ or REG_EXPAND_SZ data that is not properly null-terminated; +// * to expand REG_EXPAND_SZ data. + +#include "webrtc/base/win32regkey.h" + +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { + +RegKey::RegKey() { + h_key_ = NULL; +} + +RegKey::~RegKey() { + Close(); +} + +HRESULT RegKey::Create(HKEY parent_key, const wchar_t* key_name) { + return Create(parent_key, + key_name, + REG_NONE, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, + NULL, + NULL); +} + +HRESULT RegKey::Open(HKEY parent_key, const wchar_t* key_name) { + return Open(parent_key, key_name, KEY_ALL_ACCESS); +} + +bool RegKey::HasValue(const TCHAR* value_name) const { + return (ERROR_SUCCESS == ::RegQueryValueEx(h_key_, value_name, NULL, + NULL, NULL, NULL)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_DWORD, &value); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64 value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_QWORD, &value); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, + REG_BINARY, &value, sizeof(value)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double value) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, + REG_BINARY, &value, sizeof(value)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const TCHAR* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return SetValueStaticHelper(full_key_name, value_name, + REG_SZ, const_cast(value)); +} + +HRESULT RegKey::SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const uint8* value, + DWORD byte_count) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_BINARY, + const_cast(value), byte_count); +} + +HRESULT RegKey::SetValueMultiSZ(const wchar_t* full_key_name, + const wchar_t* value_name, + const uint8* value, + DWORD byte_count) { + ASSERT(full_key_name != NULL); + + return SetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ, + const_cast(value), byte_count); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_DWORD, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_QWORD, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float* value) { + ASSERT(value != NULL); + ASSERT(full_key_name != NULL); + + DWORD byte_count = 0; + scoped_ptr buffer; + HRESULT hr = GetValueStaticHelper(full_key_name, value_name, + REG_BINARY, buffer.accept(), &byte_count); + if (SUCCEEDED(hr)) { + ASSERT(byte_count == sizeof(*value)); + if (byte_count == sizeof(*value)) { + *value = *reinterpret_cast(buffer.get()); + } + } + return hr; +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double* value) { + ASSERT(value != NULL); + ASSERT(full_key_name != NULL); + + DWORD byte_count = 0; + scoped_ptr buffer; + HRESULT hr = GetValueStaticHelper(full_key_name, value_name, + REG_BINARY, buffer.accept(), &byte_count); + if (SUCCEEDED(hr)) { + ASSERT(byte_count == sizeof(*value)); + if (byte_count == sizeof(*value)) { + *value = *reinterpret_cast(buffer.get()); + } + } + return hr; +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + wchar_t** value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_SZ, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::wstring* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + scoped_ptr buffer; + HRESULT hr = RegKey::GetValue(full_key_name, value_name, buffer.accept()); + if (SUCCEEDED(hr)) { + value->assign(buffer.get()); + } + return hr; +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::vector* value) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + + return GetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ, value); +} + +HRESULT RegKey::GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + uint8** value, + DWORD* byte_count) { + ASSERT(full_key_name != NULL); + ASSERT(value != NULL); + ASSERT(byte_count != NULL); + + return GetValueStaticHelper(full_key_name, value_name, + REG_BINARY, value, byte_count); +} + +HRESULT RegKey::DeleteSubKey(const wchar_t* key_name) { + ASSERT(key_name != NULL); + ASSERT(h_key_ != NULL); + + LONG res = ::RegDeleteKey(h_key_, key_name); + HRESULT hr = HRESULT_FROM_WIN32(res); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { + hr = S_FALSE; + } + return hr; +} + +HRESULT RegKey::DeleteValue(const wchar_t* value_name) { + ASSERT(h_key_ != NULL); + + LONG res = ::RegDeleteValue(h_key_, value_name); + HRESULT hr = HRESULT_FROM_WIN32(res); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { + hr = S_FALSE; + } + return hr; +} + +HRESULT RegKey::Close() { + HRESULT hr = S_OK; + if (h_key_ != NULL) { + LONG res = ::RegCloseKey(h_key_); + hr = HRESULT_FROM_WIN32(res); + h_key_ = NULL; + } + return hr; +} + +HRESULT RegKey::Create(HKEY parent_key, + const wchar_t* key_name, + wchar_t* lpszClass, + DWORD options, + REGSAM sam_desired, + LPSECURITY_ATTRIBUTES lpSecAttr, + LPDWORD lpdwDisposition) { + ASSERT(key_name != NULL); + ASSERT(parent_key != NULL); + + DWORD dw = 0; + HKEY h_key = NULL; + LONG res = ::RegCreateKeyEx(parent_key, key_name, 0, lpszClass, options, + sam_desired, lpSecAttr, &h_key, &dw); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (lpdwDisposition) { + *lpdwDisposition = dw; + } + + // we have to close the currently opened key + // before replacing it with the new one + if (hr == S_OK) { + hr = Close(); + ASSERT(hr == S_OK); + h_key_ = h_key; + } + return hr; +} + +HRESULT RegKey::Open(HKEY parent_key, + const wchar_t* key_name, + REGSAM sam_desired) { + ASSERT(key_name != NULL); + ASSERT(parent_key != NULL); + + HKEY h_key = NULL; + LONG res = ::RegOpenKeyEx(parent_key, key_name, 0, sam_desired, &h_key); + HRESULT hr = HRESULT_FROM_WIN32(res); + + // we have to close the currently opened key + // before replacing it with the new one + if (hr == S_OK) { + // close the currently opened key if any + hr = Close(); + ASSERT(hr == S_OK); + h_key_ = h_key; + } + return hr; +} + +// save the key and all of its subkeys and values to a file +HRESULT RegKey::Save(const wchar_t* full_key_name, const wchar_t* file_name) { + ASSERT(full_key_name != NULL); + ASSERT(file_name != NULL); + + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + if (!h_key) { + return E_FAIL; + } + + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (FAILED(hr)) { + return hr; + } + + AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, true); + LONG res = ::RegSaveKey(key.h_key_, file_name, NULL); + AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, false); + + return HRESULT_FROM_WIN32(res); +} + +// restore the key and all of its subkeys and values which are saved into a file +HRESULT RegKey::Restore(const wchar_t* full_key_name, + const wchar_t* file_name) { + ASSERT(full_key_name != NULL); + ASSERT(file_name != NULL); + + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + if (!h_key) { + return E_FAIL; + } + + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_WRITE); + if (FAILED(hr)) { + return hr; + } + + AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, true); + LONG res = ::RegRestoreKey(key.h_key_, file_name, REG_FORCE_RESTORE); + AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, false); + + return HRESULT_FROM_WIN32(res); +} + +// check if the current key has the specified subkey +bool RegKey::HasSubkey(const wchar_t* key_name) const { + ASSERT(key_name != NULL); + + RegKey key; + HRESULT hr = key.Open(h_key_, key_name, KEY_READ); + key.Close(); + return hr == S_OK; +} + +// static flush key +HRESULT RegKey::FlushKey(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + LONG res = ::RegFlushKey(h_key); + hr = HRESULT_FROM_WIN32(res); + } + return hr; +} + +// static SET helper +HRESULT RegKey::SetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD byte_count) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + hr = key.Create(h_key, key_name.c_str()); + if (hr == S_OK) { + switch (type) { + case REG_DWORD: + hr = key.SetValue(value_name, *(static_cast(value))); + break; + case REG_QWORD: + hr = key.SetValue(value_name, *(static_cast(value))); + break; + case REG_SZ: + hr = key.SetValue(value_name, static_cast(value)); + break; + case REG_BINARY: + hr = key.SetValue(value_name, static_cast(value), + byte_count); + break; + case REG_MULTI_SZ: + hr = key.SetValue(value_name, static_cast(value), + byte_count, type); + break; + default: + ASSERT(false); + hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + break; + } + // close the key after writing + HRESULT temp_hr = key.Close(); + if (hr == S_OK) { + hr = temp_hr; + } + } + } + return hr; +} + +// static GET helper +HRESULT RegKey::GetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD* byte_count) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (hr == S_OK) { + switch (type) { + case REG_DWORD: + hr = key.GetValue(value_name, reinterpret_cast(value)); + break; + case REG_QWORD: + hr = key.GetValue(value_name, reinterpret_cast(value)); + break; + case REG_SZ: + hr = key.GetValue(value_name, reinterpret_cast(value)); + break; + case REG_MULTI_SZ: + hr = key.GetValue(value_name, reinterpret_cast< + std::vector*>(value)); + break; + case REG_BINARY: + hr = key.GetValue(value_name, reinterpret_cast(value), + byte_count); + break; + default: + ASSERT(false); + hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + break; + } + // close the key after writing + HRESULT temp_hr = key.Close(); + if (hr == S_OK) { + hr = temp_hr; + } + } + } + return hr; +} + +// GET helper +HRESULT RegKey::GetValueHelper(const wchar_t* value_name, + DWORD* type, + uint8** value, + DWORD* byte_count) const { + ASSERT(byte_count != NULL); + ASSERT(value != NULL); + ASSERT(type != NULL); + + // init return buffer + *value = NULL; + + // get the size of the return data buffer + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, type, NULL, byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (hr == S_OK) { + // if the value length is 0, nothing to do + if (*byte_count != 0) { + // allocate the buffer + *value = new byte[*byte_count]; + ASSERT(*value != NULL); + + // make the call again to get the data + res = ::SHQueryValueEx(h_key_, value_name, NULL, + type, *value, byte_count); + hr = HRESULT_FROM_WIN32(res); + ASSERT(hr == S_OK); + } + } + return hr; +} + +// Int32 Get +HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD* value) const { + ASSERT(value != NULL); + + DWORD type = 0; + DWORD byte_count = sizeof(DWORD); + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + value, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + ASSERT((hr != S_OK) || (type == REG_DWORD)); + ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD))); + return hr; +} + +// Int64 Get +HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD64* value) const { + ASSERT(value != NULL); + + DWORD type = 0; + DWORD byte_count = sizeof(DWORD64); + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + value, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + ASSERT((hr != S_OK) || (type == REG_QWORD)); + ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD64))); + return hr; +} + +// String Get +HRESULT RegKey::GetValue(const wchar_t* value_name, wchar_t** value) const { + ASSERT(value != NULL); + + DWORD byte_count = 0; + DWORD type = 0; + + // first get the size of the string buffer + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, + &type, NULL, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (hr == S_OK) { + // allocate room for the string and a terminating \0 + *value = new wchar_t[(byte_count / sizeof(wchar_t)) + 1]; + + if ((*value) != NULL) { + if (byte_count != 0) { + // make the call again + res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + *value, &byte_count); + hr = HRESULT_FROM_WIN32(res); + } else { + (*value)[0] = L'\0'; + } + + ASSERT((hr != S_OK) || (type == REG_SZ) || + (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ)); + } else { + hr = E_OUTOFMEMORY; + } + } + + return hr; +} + +// get a string value +HRESULT RegKey::GetValue(const wchar_t* value_name, std::wstring* value) const { + ASSERT(value != NULL); + + DWORD byte_count = 0; + DWORD type = 0; + + // first get the size of the string buffer + LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, + &type, NULL, &byte_count); + HRESULT hr = HRESULT_FROM_WIN32(res); + + if (hr == S_OK) { + if (byte_count != 0) { + // Allocate some memory and make the call again + value->resize(byte_count / sizeof(wchar_t) + 1); + res = ::SHQueryValueEx(h_key_, value_name, NULL, &type, + &value->at(0), &byte_count); + hr = HRESULT_FROM_WIN32(res); + value->resize(wcslen(value->data())); + } else { + value->clear(); + } + + ASSERT((hr != S_OK) || (type == REG_SZ) || + (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ)); + } + + return hr; +} + +// convert REG_MULTI_SZ bytes to string array +HRESULT RegKey::MultiSZBytesToStringArray(const uint8* buffer, + DWORD byte_count, + std::vector* value) { + ASSERT(buffer != NULL); + ASSERT(value != NULL); + + const wchar_t* data = reinterpret_cast(buffer); + DWORD data_len = byte_count / sizeof(wchar_t); + value->clear(); + if (data_len > 1) { + // must be terminated by two null characters + if (data[data_len - 1] != 0 || data[data_len - 2] != 0) { + return E_INVALIDARG; + } + + // put null-terminated strings into arrays + while (*data) { + std::wstring str(data); + value->push_back(str); + data += str.length() + 1; + } + } + return S_OK; +} + +// get a std::vector value from REG_MULTI_SZ type +HRESULT RegKey::GetValue(const wchar_t* value_name, + std::vector* value) const { + ASSERT(value != NULL); + + DWORD byte_count = 0; + DWORD type = 0; + uint8* buffer = 0; + + // first get the size of the buffer + HRESULT hr = GetValueHelper(value_name, &type, &buffer, &byte_count); + ASSERT((hr != S_OK) || (type == REG_MULTI_SZ)); + + if (SUCCEEDED(hr)) { + hr = MultiSZBytesToStringArray(buffer, byte_count, value); + } + + return hr; +} + +// Binary data Get +HRESULT RegKey::GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count) const { + ASSERT(byte_count != NULL); + ASSERT(value != NULL); + + DWORD type = 0; + HRESULT hr = GetValueHelper(value_name, &type, value, byte_count); + ASSERT((hr != S_OK) || (type == REG_MULTI_SZ) || (type == REG_BINARY)); + return hr; +} + +// Raw data get +HRESULT RegKey::GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count, + DWORD*type) const { + ASSERT(type != NULL); + ASSERT(byte_count != NULL); + ASSERT(value != NULL); + + return GetValueHelper(value_name, type, value, byte_count); +} + +// Int32 set +HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD value) const { + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_DWORD, + reinterpret_cast(&value), + sizeof(DWORD)); + return HRESULT_FROM_WIN32(res); +} + +// Int64 set +HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD64 value) const { + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_QWORD, + reinterpret_cast(&value), + sizeof(DWORD64)); + return HRESULT_FROM_WIN32(res); +} + +// String set +HRESULT RegKey::SetValue(const wchar_t* value_name, + const wchar_t* value) const { + ASSERT(value != NULL); + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_SZ, + reinterpret_cast(value), + (lstrlen(value) + 1) * sizeof(wchar_t)); + return HRESULT_FROM_WIN32(res); +} + +// Binary data set +HRESULT RegKey::SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count) const { + ASSERT(h_key_ != NULL); + + // special case - if 'value' is NULL make sure byte_count is zero + if (value == NULL) { + byte_count = 0; + } + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, + REG_BINARY, value, byte_count); + return HRESULT_FROM_WIN32(res); +} + +// Raw data set +HRESULT RegKey::SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count, + DWORD type) const { + ASSERT(value != NULL); + ASSERT(h_key_ != NULL); + + LONG res = ::RegSetValueEx(h_key_, value_name, NULL, type, value, byte_count); + return HRESULT_FROM_WIN32(res); +} + +bool RegKey::HasKey(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + key.Close(); + return S_OK == hr; + } + return false; +} + +// static version of HasValue +bool RegKey::HasValue(const wchar_t* full_key_name, const wchar_t* value_name) { + ASSERT(full_key_name != NULL); + + bool has_value = false; + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + if (key.Open(h_key, key_name.c_str(), KEY_READ) == S_OK) { + has_value = key.HasValue(value_name); + key.Close(); + } + } + return has_value; +} + +HRESULT RegKey::GetValueType(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value_type) { + ASSERT(full_key_name != NULL); + ASSERT(value_type != NULL); + + *value_type = REG_NONE; + + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (SUCCEEDED(hr)) { + LONG res = ::SHQueryValueEx(key.h_key_, value_name, NULL, value_type, + NULL, NULL); + if (res != ERROR_SUCCESS) { + hr = HRESULT_FROM_WIN32(res); + } + } + + return hr; +} + +HRESULT RegKey::DeleteKey(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + return DeleteKey(full_key_name, true); +} + +HRESULT RegKey::DeleteKey(const wchar_t* full_key_name, bool recursively) { + ASSERT(full_key_name != NULL); + + // need to open the parent key first + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + // get the parent key + std::wstring parent_key(GetParentKeyInfo(&key_name)); + + RegKey key; + HRESULT hr = key.Open(h_key, parent_key.c_str()); + + if (hr == S_OK) { + hr = recursively ? key.RecurseDeleteSubKey(key_name.c_str()) + : key.DeleteSubKey(key_name.c_str()); + } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { + hr = S_FALSE; + } + + key.Close(); + return hr; +} + +HRESULT RegKey::DeleteValue(const wchar_t* full_key_name, + const wchar_t* value_name) { + ASSERT(full_key_name != NULL); + + HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); + // get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + if (h_key != NULL) { + RegKey key; + hr = key.Open(h_key, key_name.c_str()); + if (hr == S_OK) { + hr = key.DeleteValue(value_name); + key.Close(); + } + } + return hr; +} + +HRESULT RegKey::RecurseDeleteSubKey(const wchar_t* key_name) { + ASSERT(key_name != NULL); + + RegKey key; + HRESULT hr = key.Open(h_key_, key_name); + + if (hr == S_OK) { + // enumerate all subkeys of this key and recursivelly delete them + FILETIME time = {0}; + wchar_t key_name_buf[kMaxKeyNameChars] = {0}; + DWORD key_name_buf_size = kMaxKeyNameChars; + while (hr == S_OK && + ::RegEnumKeyEx(key.h_key_, 0, key_name_buf, &key_name_buf_size, + NULL, NULL, NULL, &time) == ERROR_SUCCESS) { + hr = key.RecurseDeleteSubKey(key_name_buf); + + // restore the buffer size + key_name_buf_size = kMaxKeyNameChars; + } + // close the top key + key.Close(); + } + + if (hr == S_OK) { + // the key has no more children keys + // delete the key and all of its values + hr = DeleteSubKey(key_name); + } + + return hr; +} + +HKEY RegKey::GetRootKeyInfo(std::wstring* full_key_name) { + ASSERT(full_key_name != NULL); + + HKEY h_key = NULL; + // get the root HKEY + size_t index = full_key_name->find(L'\\'); + std::wstring root_key; + + if (index == -1) { + root_key = *full_key_name; + *full_key_name = L""; + } else { + root_key = full_key_name->substr(0, index); + *full_key_name = full_key_name->substr(index + 1, + full_key_name->length() - index - 1); + } + + for (std::wstring::iterator iter = root_key.begin(); + iter != root_key.end(); ++iter) { + *iter = toupper(*iter); + } + + if (!root_key.compare(L"HKLM") || + !root_key.compare(L"HKEY_LOCAL_MACHINE")) { + h_key = HKEY_LOCAL_MACHINE; + } else if (!root_key.compare(L"HKCU") || + !root_key.compare(L"HKEY_CURRENT_USER")) { + h_key = HKEY_CURRENT_USER; + } else if (!root_key.compare(L"HKU") || + !root_key.compare(L"HKEY_USERS")) { + h_key = HKEY_USERS; + } else if (!root_key.compare(L"HKCR") || + !root_key.compare(L"HKEY_CLASSES_ROOT")) { + h_key = HKEY_CLASSES_ROOT; + } + + return h_key; +} + + +// Returns true if this key name is 'safe' for deletion +// (doesn't specify a key root) +bool RegKey::SafeKeyNameForDeletion(const wchar_t* key_name) { + ASSERT(key_name != NULL); + std::wstring key(key_name); + + HKEY root_key = GetRootKeyInfo(&key); + + if (!root_key) { + key = key_name; + } + if (key.empty()) { + return false; + } + bool found_subkey = false, backslash_found = false; + for (size_t i = 0 ; i < key.length() ; ++i) { + if (key[i] == L'\\') { + backslash_found = true; + } else if (backslash_found) { + found_subkey = true; + break; + } + } + return (root_key == HKEY_USERS) ? found_subkey : true; +} + +std::wstring RegKey::GetParentKeyInfo(std::wstring* key_name) { + ASSERT(key_name != NULL); + + // get the parent key + size_t index = key_name->rfind(L'\\'); + std::wstring parent_key; + if (index == -1) { + parent_key = L""; + } else { + parent_key = key_name->substr(0, index); + *key_name = key_name->substr(index + 1, key_name->length() - index - 1); + } + + return parent_key; +} + +// get the number of values for this key +uint32 RegKey::GetValueCount() { + DWORD num_values = 0; + + if (ERROR_SUCCESS != ::RegQueryInfoKey( + h_key_, // key handle + NULL, // buffer for class name + NULL, // size of class string + NULL, // reserved + NULL, // number of subkeys + NULL, // longest subkey size + NULL, // longest class string + &num_values, // number of values for this key + NULL, // longest value name + NULL, // longest value data + NULL, // security descriptor + NULL)) { // last write time + ASSERT(false); + } + return num_values; +} + +// Enumerators for the value_names for this key + +// Called to get the value name for the given value name index +// Use GetValueCount() to get the total value_name count for this key +// Returns failure if no key at the specified index +HRESULT RegKey::GetValueNameAt(int index, std::wstring* value_name, + DWORD* type) { + ASSERT(value_name != NULL); + + LONG res = ERROR_SUCCESS; + wchar_t value_name_buf[kMaxValueNameChars] = {0}; + DWORD value_name_buf_size = kMaxValueNameChars; + res = ::RegEnumValue(h_key_, index, value_name_buf, &value_name_buf_size, + NULL, type, NULL, NULL); + + if (res == ERROR_SUCCESS) { + value_name->assign(value_name_buf); + } + + return HRESULT_FROM_WIN32(res); +} + +uint32 RegKey::GetSubkeyCount() { + // number of values for key + DWORD num_subkeys = 0; + + if (ERROR_SUCCESS != ::RegQueryInfoKey( + h_key_, // key handle + NULL, // buffer for class name + NULL, // size of class string + NULL, // reserved + &num_subkeys, // number of subkeys + NULL, // longest subkey size + NULL, // longest class string + NULL, // number of values for this key + NULL, // longest value name + NULL, // longest value data + NULL, // security descriptor + NULL)) { // last write time + ASSERT(false); + } + return num_subkeys; +} + +HRESULT RegKey::GetSubkeyNameAt(int index, std::wstring* key_name) { + ASSERT(key_name != NULL); + + LONG res = ERROR_SUCCESS; + wchar_t key_name_buf[kMaxKeyNameChars] = {0}; + DWORD key_name_buf_size = kMaxKeyNameChars; + + res = ::RegEnumKeyEx(h_key_, index, key_name_buf, &key_name_buf_size, + NULL, NULL, NULL, NULL); + + if (res == ERROR_SUCCESS) { + key_name->assign(key_name_buf); + } + + return HRESULT_FROM_WIN32(res); +} + +// Is the key empty: having no sub-keys and values +bool RegKey::IsKeyEmpty(const wchar_t* full_key_name) { + ASSERT(full_key_name != NULL); + + bool is_empty = true; + + // Get the root HKEY + std::wstring key_name(full_key_name); + HKEY h_key = GetRootKeyInfo(&key_name); + + // Open the key to check + if (h_key != NULL) { + RegKey key; + HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ); + if (SUCCEEDED(hr)) { + is_empty = key.GetSubkeyCount() == 0 && key.GetValueCount() == 0; + key.Close(); + } + } + + return is_empty; +} + +bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable) { + ASSERT(privilege != NULL); + + bool ret = false; + HANDLE token; + if (::OpenProcessToken(::GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) { + LUID luid; + memset(&luid, 0, sizeof(luid)); + if (::LookupPrivilegeValue(NULL, privilege, &luid)) { + TOKEN_PRIVILEGES privs; + privs.PrivilegeCount = 1; + privs.Privileges[0].Luid = luid; + privs.Privileges[0].Attributes = to_enable ? SE_PRIVILEGE_ENABLED : 0; + if (::AdjustTokenPrivileges(token, FALSE, &privs, 0, NULL, 0)) { + ret = true; + } else { + LOG_GLE(LS_ERROR) << "AdjustTokenPrivileges failed"; + } + } else { + LOG_GLE(LS_ERROR) << "LookupPrivilegeValue failed"; + } + CloseHandle(token); + } else { + LOG_GLE(LS_ERROR) << "OpenProcessToken(GetCurrentProcess) failed"; + } + + return ret; +} + +} // namespace rtc diff --git a/webrtc/base/win32regkey.h b/webrtc/base/win32regkey.h new file mode 100644 index 000000000..b33d4dc2b --- /dev/null +++ b/webrtc/base/win32regkey.h @@ -0,0 +1,337 @@ +/* + * Copyright 2003 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. + */ + +// Registry configuration wrappers class +// +// Offers static functions for convenient +// fast access for individual values +// +// Also provides a wrapper class for efficient +// batch operations on values of a given registry key. +// + +#ifndef WEBRTC_BASE_WIN32REGKEY_H_ +#define WEBRTC_BASE_WIN32REGKEY_H_ + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/win32.h" + +namespace rtc { + +// maximum sizes registry key and value names +const int kMaxKeyNameChars = 255 + 1; +const int kMaxValueNameChars = 16383 + 1; + +class RegKey { + public: + // constructor + RegKey(); + + // destructor + ~RegKey(); + + // create a reg key + HRESULT Create(HKEY parent_key, const wchar_t* key_name); + + HRESULT Create(HKEY parent_key, + const wchar_t* key_name, + wchar_t* reg_class, + DWORD options, + REGSAM sam_desired, + LPSECURITY_ATTRIBUTES lp_sec_attr, + LPDWORD lp_disposition); + + // open an existing reg key + HRESULT Open(HKEY parent_key, const wchar_t* key_name); + + HRESULT Open(HKEY parent_key, const wchar_t* key_name, REGSAM sam_desired); + + // close this reg key + HRESULT Close(); + + // check if the key has a specified value + bool HasValue(const wchar_t* value_name) const; + + // get the number of values for this key + uint32 GetValueCount(); + + // Called to get the value name for the given value name index + // Use GetValueCount() to get the total value_name count for this key + // Returns failure if no key at the specified index + // If you modify the key while enumerating, the indexes will be out of order. + // Since the index order is not guaranteed, you need to reset your counting + // loop. + // 'type' refers to REG_DWORD, REG_QWORD, etc.. + // 'type' can be NULL if not interested in the value type + HRESULT GetValueNameAt(int index, std::wstring* value_name, DWORD* type); + + // check if the current key has the specified subkey + bool HasSubkey(const wchar_t* key_name) const; + + // get the number of subkeys for this key + uint32 GetSubkeyCount(); + + // Called to get the key name for the given key index + // Use GetSubkeyCount() to get the total count for this key + // Returns failure if no key at the specified index + // If you modify the key while enumerating, the indexes will be out of order. + // Since the index order is not guaranteed, you need to reset your counting + // loop. + HRESULT GetSubkeyNameAt(int index, std::wstring* key_name); + + // SETTERS + + // set an int32 value - use when reading multiple values from a key + HRESULT SetValue(const wchar_t* value_name, DWORD value) const; + + // set an int64 value + HRESULT SetValue(const wchar_t* value_name, DWORD64 value) const; + + // set a string value + HRESULT SetValue(const wchar_t* value_name, const wchar_t* value) const; + + // set binary data + HRESULT SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count) const; + + // set raw data, including type + HRESULT SetValue(const wchar_t* value_name, + const uint8* value, + DWORD byte_count, + DWORD type) const; + + // GETTERS + + // get an int32 value + HRESULT GetValue(const wchar_t* value_name, DWORD* value) const; + + // get an int64 value + HRESULT GetValue(const wchar_t* value_name, DWORD64* value) const; + + // get a string value - the caller must free the return buffer + HRESULT GetValue(const wchar_t* value_name, wchar_t** value) const; + + // get a string value + HRESULT GetValue(const wchar_t* value_name, std::wstring* value) const; + + // get a std::vector value from REG_MULTI_SZ type + HRESULT GetValue(const wchar_t* value_name, + std::vector* value) const; + + // get binary data - the caller must free the return buffer + HRESULT GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count) const; + + // get raw data, including type - the caller must free the return buffer + HRESULT GetValue(const wchar_t* value_name, + uint8** value, + DWORD* byte_count, + DWORD* type) const; + + // STATIC VERSIONS + + // flush + static HRESULT FlushKey(const wchar_t* full_key_name); + + // check if a key exists + static bool HasKey(const wchar_t* full_key_name); + + // check if the key has a specified value + static bool HasValue(const wchar_t* full_key_name, const wchar_t* value_name); + + // SETTERS + + // STATIC int32 set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD value); + + // STATIC int64 set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64 value); + + // STATIC float set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float value); + + // STATIC double set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double value); + + // STATIC string set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const wchar_t* value); + + // STATIC binary data set + static HRESULT SetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + const uint8* value, + DWORD byte_count); + + // STATIC multi-string set + static HRESULT SetValueMultiSZ(const wchar_t* full_key_name, + const TCHAR* value_name, + const uint8* value, + DWORD byte_count); + + // GETTERS + + // STATIC int32 get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value); + + // STATIC int64 get + // + // Note: if you are using time64 you should + // likely use GetLimitedTimeValue (util.h) instead of this method. + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD64* value); + + // STATIC float get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + float* value); + + // STATIC double get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + double* value); + + // STATIC string get + // Note: the caller must free the return buffer for wchar_t* version + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + wchar_t** value); + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::wstring* value); + + // STATIC REG_MULTI_SZ get + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + std::vector* value); + + // STATIC get binary data - the caller must free the return buffer + static HRESULT GetValue(const wchar_t* full_key_name, + const wchar_t* value_name, + uint8** value, + DWORD* byte_count); + + // Get type of a registry value + static HRESULT GetValueType(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD* value_type); + + // delete a subkey of the current key (with no subkeys) + HRESULT DeleteSubKey(const wchar_t* key_name); + + // recursively delete a sub key of the current key (and all its subkeys) + HRESULT RecurseDeleteSubKey(const wchar_t* key_name); + + // STATIC version of delete key - handles nested keys also + // delete a key and all its sub-keys recursively + // Returns S_FALSE if key didn't exist, S_OK if deletion was successful, + // and failure otherwise. + static HRESULT DeleteKey(const wchar_t* full_key_name); + + // STATIC version of delete key + // delete a key recursively or non-recursively + // Returns S_FALSE if key didn't exist, S_OK if deletion was successful, + // and failure otherwise. + static HRESULT DeleteKey(const wchar_t* full_key_name, bool recursive); + + // delete the specified value + HRESULT DeleteValue(const wchar_t* value_name); + + // STATIC version of delete value + // Returns S_FALSE if key didn't exist, S_OK if deletion was successful, + // and failure otherwise. + static HRESULT DeleteValue(const wchar_t* full_key_name, + const wchar_t* value_name); + + // Peek inside (use a RegKey as a smart wrapper around a registry handle) + HKEY key() { return h_key_; } + + // helper function to get the HKEY and the root key from a string + // modifies the argument in place and returns the key name + // e.g. HKLM\\Software\\Google\... returns HKLM, "Software\\Google\..." + // Necessary for the static versions that use the full name of the reg key + static HKEY GetRootKeyInfo(std::wstring* full_key_name); + + // Returns true if this key name is 'safe' for deletion (doesn't specify a key + // root) + static bool SafeKeyNameForDeletion(const wchar_t* key_name); + + // save the key and all of its subkeys and values to a file + static HRESULT Save(const wchar_t* full_key_name, const wchar_t* file_name); + + // restore the key and all of its subkeys and values which are saved into a + // file + static HRESULT Restore(const wchar_t* full_key_name, + const wchar_t* file_name); + + // Is the key empty: having no sub-keys and values + static bool IsKeyEmpty(const wchar_t* full_key_name); + + private: + + // helper function to get any value from the registry + // used when the size of the data is unknown + HRESULT GetValueHelper(const wchar_t* value_name, + DWORD* type, uint8** value, + DWORD* byte_count) const; + + // helper function to get the parent key name and the subkey from a string + // modifies the argument in place and returns the key name + // Necessary for the static versions that use the full name of the reg key + static std::wstring GetParentKeyInfo(std::wstring* key_name); + + // common SET Helper for the static case + static HRESULT SetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD byte_count = 0); + + // common GET Helper for the static case + static HRESULT GetValueStaticHelper(const wchar_t* full_key_name, + const wchar_t* value_name, + DWORD type, + LPVOID value, + DWORD* byte_count = NULL); + + // convert REG_MULTI_SZ bytes to string array + static HRESULT MultiSZBytesToStringArray(const uint8* buffer, + DWORD byte_count, + std::vector* value); + + // the HKEY for the current key + HKEY h_key_; + + // for unittest + friend void RegKeyHelperFunctionsTest(); + + DISALLOW_EVIL_CONSTRUCTORS(RegKey); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32REGKEY_H_ diff --git a/webrtc/base/win32regkey_unittest.cc b/webrtc/base/win32regkey_unittest.cc new file mode 100644 index 000000000..1e7738182 --- /dev/null +++ b/webrtc/base/win32regkey_unittest.cc @@ -0,0 +1,590 @@ +/* + * Copyright 2003 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. + */ + +// Unittest for registry access API + +#include "webrtc/base/gunit.h" +#include "webrtc/base/common.h" +#include "webrtc/base/win32regkey.h" + +namespace rtc { + +#ifndef EXPECT_SUCCEEDED +#define EXPECT_SUCCEEDED(x) EXPECT_TRUE(SUCCEEDED(x)) +#endif + +#ifndef EXPECT_FAILED +#define EXPECT_FAILED(x) EXPECT_TRUE(FAILED(x)) +#endif + +#define kBaseKey L"Software\\Google\\__TEST" +#define kSubkeyName L"subkey_test" + +const wchar_t kRkey1[] = kBaseKey; +const wchar_t kRkey1SubkeyName[] = kSubkeyName; +const wchar_t kRkey1Subkey[] = kBaseKey L"\\" kSubkeyName; +const wchar_t kFullRkey1[] = L"HKCU\\" kBaseKey; +const wchar_t kFullRkey1Subkey[] = L"HKCU\\" kBaseKey L"\\" kSubkeyName; + +const wchar_t kValNameInt[] = L"Int32 Value"; +const DWORD kIntVal = 20; +const DWORD kIntVal2 = 30; + +const wchar_t kValNameInt64[] = L"Int64 Value"; +const DWORD64 kIntVal64 = 119600064000000000uI64; + +const wchar_t kValNameFloat[] = L"Float Value"; +const float kFloatVal = 12.3456789f; + +const wchar_t kValNameDouble[] = L"Double Value"; +const double kDoubleVal = 98.7654321; + +const wchar_t kValNameStr[] = L"Str Value"; +const wchar_t kStrVal[] = L"Some string data 1"; +const wchar_t kStrVal2[] = L"Some string data 2"; + +const wchar_t kValNameBinary[] = L"Binary Value"; +const char kBinaryVal[] = "Some binary data abcdefghi 1"; +const char kBinaryVal2[] = "Some binary data abcdefghi 2"; + +const wchar_t kValNameMultiStr[] = L"MultiStr Value"; +const wchar_t kMultiSZ[] = L"abc\0def\0P12345\0"; +const wchar_t kEmptyMultiSZ[] = L""; +const wchar_t kInvalidMultiSZ[] = {L'6', L'7', L'8'}; + +// friend function of RegKey +void RegKeyHelperFunctionsTest() { + // Try out some dud values + std::wstring temp_key = L""; + EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL); + EXPECT_STREQ(temp_key.c_str(), L""); + + temp_key = L"a"; + EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL); + EXPECT_STREQ(temp_key.c_str(), L""); + + // The basics + temp_key = L"HKLM\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_LOCAL_MACHINE\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKCU\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_CURRENT_USER\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKU\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_USERS\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKCR\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"HKEY_CLASSES_ROOT\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + // Make sure it is case insensitive + temp_key = L"hkcr\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"hkey_CLASSES_ROOT\\a"; + EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + // + // Test RegKey::GetParentKeyInfo + // + + // dud cases + temp_key = L""; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L""); + EXPECT_STREQ(temp_key.c_str(), L""); + + temp_key = L"a"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L""); + EXPECT_STREQ(temp_key.c_str(), L"a"); + + temp_key = L"a\\b"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"a"); + EXPECT_STREQ(temp_key.c_str(), L"b"); + + temp_key = L"\\b"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L""); + EXPECT_STREQ(temp_key.c_str(), L"b"); + + // Some regular cases + temp_key = L"HKEY_CLASSES_ROOT\\moon"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), + L"HKEY_CLASSES_ROOT"); + EXPECT_STREQ(temp_key.c_str(), L"moon"); + + temp_key = L"HKEY_CLASSES_ROOT\\moon\\doggy"; + EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), + L"HKEY_CLASSES_ROOT\\moon"); + EXPECT_STREQ(temp_key.c_str(), L"doggy"); + + // + // Test MultiSZBytesToStringArray + // + + std::vector result; + EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray( + reinterpret_cast(kMultiSZ), sizeof(kMultiSZ), &result)); + EXPECT_EQ(result.size(), 3); + EXPECT_STREQ(result[0].c_str(), L"abc"); + EXPECT_STREQ(result[1].c_str(), L"def"); + EXPECT_STREQ(result[2].c_str(), L"P12345"); + + EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray( + reinterpret_cast(kEmptyMultiSZ), + sizeof(kEmptyMultiSZ), &result)); + EXPECT_EQ(result.size(), 0); + EXPECT_FALSE(SUCCEEDED(RegKey::MultiSZBytesToStringArray( + reinterpret_cast(kInvalidMultiSZ), + sizeof(kInvalidMultiSZ), &result))); +} + +TEST(RegKeyTest, RegKeyHelperFunctionsTest) { + RegKeyHelperFunctionsTest(); +} + +TEST(RegKeyTest, RegKeyNonStaticFunctionsTest) { + DWORD int_val = 0; + DWORD64 int64_val = 0; + wchar_t* str_val = NULL; + uint8* binary_val = NULL; + DWORD uint8_count = 0; + + // Just in case... + // make sure the no test key residue is left from previous aborted runs + RegKey::DeleteKey(kFullRkey1); + + // initial state + RegKey r_key; + EXPECT_TRUE(r_key.key() == NULL); + + // create a reg key + EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1)); + + // do the create twice - it should return the already created one + EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1)); + + // now do an open - should work just fine + EXPECT_SUCCEEDED(r_key.Open(HKEY_CURRENT_USER, kRkey1)); + + // get an in-existent value + EXPECT_EQ(r_key.GetValue(kValNameInt, &int_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // set and get some values + + // set an INT 32 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameInt)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal); + + // set it again! + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal2)); + + // read it again + EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal2); + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameInt)); + + // set an INT 64 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameInt64)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt64, &int64_val)); + EXPECT_EQ(int64_val, kIntVal64); + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt64)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameInt64)); + + // set a string + EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameStr)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val)); + EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0); + delete[] str_val; + + // set it again + EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal2)); + + // read it again + EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val)); + EXPECT_TRUE(lstrcmp(str_val, kStrVal2) == 0); + delete[] str_val; + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameStr)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameInt)); + + // set a binary value + EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary, + reinterpret_cast(kBinaryVal), sizeof(kBinaryVal) - 1)); + + // check that the value exists + EXPECT_TRUE(r_key.HasValue(kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count)); + EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal) - 1) == 0); + delete[] binary_val; + + // set it again + EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary, + reinterpret_cast(kBinaryVal2), sizeof(kBinaryVal) - 1)); + + // read it again + EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count)); + EXPECT_TRUE(memcmp(binary_val, kBinaryVal2, sizeof(kBinaryVal2) - 1) == 0); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(r_key.HasValue(kValNameBinary)); + + // set some values and check the total count + + // set an INT 32 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal)); + + // set an INT 64 + EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64)); + + // set a string + EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal)); + + // set a binary value + EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary, + reinterpret_cast(kBinaryVal), sizeof(kBinaryVal) - 1)); + + // get the value count + uint32 value_count = r_key.GetValueCount(); + EXPECT_EQ(value_count, 4); + + // check the value names + std::wstring value_name; + DWORD type = 0; + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(0, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameInt); + EXPECT_EQ(type, REG_DWORD); + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(1, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameInt64); + EXPECT_EQ(type, REG_QWORD); + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(2, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameStr); + EXPECT_EQ(type, REG_SZ); + + EXPECT_SUCCEEDED(r_key.GetValueNameAt(3, &value_name, &type)); + EXPECT_STREQ(value_name.c_str(), kValNameBinary); + EXPECT_EQ(type, REG_BINARY); + + // check that there are no more values + EXPECT_FAILED(r_key.GetValueNameAt(4, &value_name, &type)); + + uint32 subkey_count = r_key.GetSubkeyCount(); + EXPECT_EQ(subkey_count, 0); + + // now create a subkey and make sure we can get the name + RegKey temp_key; + EXPECT_SUCCEEDED(temp_key.Create(HKEY_CURRENT_USER, kRkey1Subkey)); + + // check the subkey exists + EXPECT_TRUE(r_key.HasSubkey(kRkey1SubkeyName)); + + // check the name + EXPECT_EQ(r_key.GetSubkeyCount(), 1); + + std::wstring subkey_name; + EXPECT_SUCCEEDED(r_key.GetSubkeyNameAt(0, &subkey_name)); + EXPECT_STREQ(subkey_name.c_str(), kRkey1SubkeyName); + + // delete the key + EXPECT_SUCCEEDED(r_key.DeleteSubKey(kRkey1)); + + // close this key + EXPECT_SUCCEEDED(r_key.Close()); + + // whack the whole key + EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1)); +} + +TEST(RegKeyTest, RegKeyStaticFunctionsTest) { + DWORD int_val = 0; + DWORD64 int64_val = 0; + float float_val = 0; + double double_val = 0; + wchar_t* str_val = NULL; + std::wstring wstr_val; + uint8* binary_val = NULL; + DWORD uint8_count = 0; + + // Just in case... + // make sure the no test key residue is left from previous aborted runs + RegKey::DeleteKey(kFullRkey1); + + // get an in-existent value from an un-existent key + EXPECT_EQ(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // set int32 + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt, kIntVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt)); + + // get an in-existent value from an existent key + EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &int_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt)); + + // set int64 + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt64, kIntVal64)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt64)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt64, &int64_val)); + EXPECT_EQ(int64_val, kIntVal64); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt64)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt64)); + + // set float + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameFloat, kFloatVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameFloat)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val)); + EXPECT_EQ(float_val, kFloatVal); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameFloat)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameFloat)); + EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val)); + + // set double + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameDouble, kDoubleVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameDouble)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val)); + EXPECT_EQ(double_val, kDoubleVal); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameDouble)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameDouble)); + EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val)); + + // set string + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameStr, kStrVal)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameStr)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &str_val)); + EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0); + delete[] str_val; + + // read it back in std::wstring + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &wstr_val)); + EXPECT_STREQ(wstr_val.c_str(), kStrVal); + + // get an in-existent value from an existent key + EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &str_val), + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameStr)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameStr)); + + // set binary + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, + reinterpret_cast(kBinaryVal), sizeof(kBinaryVal)-1)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary, + &binary_val, &uint8_count)); + EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal)-1) == 0); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // special case - set a binary value with length 0 + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, + reinterpret_cast(kBinaryVal), 0)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary, + &binary_val, &uint8_count)); + EXPECT_EQ(uint8_count, 0); + EXPECT_TRUE(binary_val == NULL); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // special case - set a NULL binary value + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, NULL, 100)); + + // check that the value exists + EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // read it back + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary, + &binary_val, &uint8_count)); + EXPECT_EQ(uint8_count, 0); + EXPECT_TRUE(binary_val == NULL); + delete[] binary_val; + + // delete the value + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary)); + + // check that the value is gone + EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary)); + + // test read/write REG_MULTI_SZ value + std::vector result; + EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr, + reinterpret_cast(kMultiSZ), sizeof(kMultiSZ))); + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result)); + EXPECT_EQ(result.size(), 3); + EXPECT_STREQ(result[0].c_str(), L"abc"); + EXPECT_STREQ(result[1].c_str(), L"def"); + EXPECT_STREQ(result[2].c_str(), L"P12345"); + EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr, + reinterpret_cast(kEmptyMultiSZ), sizeof(kEmptyMultiSZ))); + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result)); + EXPECT_EQ(result.size(), 0); + // writing REG_MULTI_SZ value will automatically add ending null characters + EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr, + reinterpret_cast(kInvalidMultiSZ), sizeof(kInvalidMultiSZ))); + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result)); + EXPECT_EQ(result.size(), 1); + EXPECT_STREQ(result[0].c_str(), L"678"); + + // Run the following test only in dev machine + // This is because the build machine might not have admin privilege +#ifdef IS_PRIVATE_BUILD + // get a temp file name + wchar_t temp_path[MAX_PATH] = {0}; + EXPECT_LT(::GetTempPath(ARRAY_SIZE(temp_path), temp_path), + static_cast(ARRAY_SIZE(temp_path))); + wchar_t temp_file[MAX_PATH] = {0}; + EXPECT_NE(::GetTempFileName(temp_path, L"rkut_", + ::GetTickCount(), temp_file), 0); + + // test save + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt, kIntVal)); + EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt64, kIntVal64)); + EXPECT_SUCCEEDED(RegKey::Save(kFullRkey1Subkey, temp_file)); + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt)); + EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt64)); + + // test restore + EXPECT_SUCCEEDED(RegKey::Restore(kFullRkey1Subkey, temp_file)); + int_val = 0; + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey, kValNameInt, &int_val)); + EXPECT_EQ(int_val, kIntVal); + int64_val = 0; + EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey, + kValNameInt64, + &int64_val)); + EXPECT_EQ(int64_val, kIntVal64); + + // delete the temp file + EXPECT_EQ(TRUE, ::DeleteFile(temp_file)); +#endif + + // whack the whole key + EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1)); +} + +} // namespace rtc diff --git a/webrtc/base/win32securityerrors.cc b/webrtc/base/win32securityerrors.cc new file mode 100644 index 000000000..71fe466a9 --- /dev/null +++ b/webrtc/base/win32securityerrors.cc @@ -0,0 +1,49 @@ +/* + * Copyright 2004 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 "webrtc/base/win32.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// + +extern const ConstantLabel SECURITY_ERRORS[]; + +const ConstantLabel SECURITY_ERRORS[] = { + KLABEL(SEC_I_COMPLETE_AND_CONTINUE), + KLABEL(SEC_I_COMPLETE_NEEDED), + KLABEL(SEC_I_CONTEXT_EXPIRED), + KLABEL(SEC_I_CONTINUE_NEEDED), + KLABEL(SEC_I_INCOMPLETE_CREDENTIALS), + KLABEL(SEC_I_RENEGOTIATE), + KLABEL(SEC_E_CERT_EXPIRED), + KLABEL(SEC_E_INCOMPLETE_MESSAGE), + KLABEL(SEC_E_INSUFFICIENT_MEMORY), + KLABEL(SEC_E_INTERNAL_ERROR), + KLABEL(SEC_E_INVALID_HANDLE), + KLABEL(SEC_E_INVALID_TOKEN), + KLABEL(SEC_E_LOGON_DENIED), + KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY), + KLABEL(SEC_E_NO_CREDENTIALS), + KLABEL(SEC_E_NOT_OWNER), + KLABEL(SEC_E_OK), + KLABEL(SEC_E_SECPKG_NOT_FOUND), + KLABEL(SEC_E_TARGET_UNKNOWN), + KLABEL(SEC_E_UNKNOWN_CREDENTIALS), + KLABEL(SEC_E_UNSUPPORTED_FUNCTION), + KLABEL(SEC_E_UNTRUSTED_ROOT), + KLABEL(SEC_E_WRONG_PRINCIPAL), + LASTLABEL +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/win32socketinit.cc b/webrtc/base/win32socketinit.cc new file mode 100644 index 000000000..02a6c26f4 --- /dev/null +++ b/webrtc/base/win32socketinit.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2009 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 "webrtc/base/win32socketinit.h" + +#include "webrtc/base/win32.h" + +namespace rtc { + +// Please don't remove this function. +void EnsureWinsockInit() { + // The default implementation uses a global initializer, so WSAStartup + // happens at module load time. Thus we don't need to do anything here. + // The hook is provided so that a client that statically links with + // libjingle can override it, to provide its own initialization. +} + +#if defined(WEBRTC_WIN) +class WinsockInitializer { + public: + WinsockInitializer() { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(1, 0); + err_ = WSAStartup(wVersionRequested, &wsaData); + } + ~WinsockInitializer() { + if (!err_) + WSACleanup(); + } + int error() { + return err_; + } + private: + int err_; +}; +WinsockInitializer g_winsockinit; +#endif + +} // namespace rtc diff --git a/webrtc/base/win32socketinit.h b/webrtc/base/win32socketinit.h new file mode 100644 index 000000000..46d27cba0 --- /dev/null +++ b/webrtc/base/win32socketinit.h @@ -0,0 +1,20 @@ +/* + * Copyright 2009 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_BASE_WIN32SOCKETINIT_H_ +#define WEBRTC_BASE_WIN32SOCKETINIT_H_ + +namespace rtc { + +void EnsureWinsockInit(); + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32SOCKETINIT_H_ diff --git a/webrtc/base/win32socketserver.cc b/webrtc/base/win32socketserver.cc new file mode 100644 index 000000000..d0b736c58 --- /dev/null +++ b/webrtc/base/win32socketserver.cc @@ -0,0 +1,850 @@ +/* + * Copyright 2004 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 "webrtc/base/win32socketserver.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/winping.h" +#include "webrtc/base/win32window.h" +#include // NOLINT + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Socket +/////////////////////////////////////////////////////////////////////////////// + +// TODO: Move this to a common place where PhysicalSocketServer can +// share it. +// Standard MTUs +static const uint16 PACKET_MAXIMUMS[] = { + 65535, // Theoretical maximum, Hyperchannel + 32000, // Nothing + 17914, // 16Mb IBM Token Ring + 8166, // IEEE 802.4 + // 4464 // IEEE 802.5 (4Mb max) + 4352, // FDDI + // 2048, // Wideband Network + 2002, // IEEE 802.5 (4Mb recommended) + // 1536, // Expermental Ethernet Networks + // 1500, // Ethernet, Point-to-Point (default) + 1492, // IEEE 802.3 + 1006, // SLIP, ARPANET + // 576, // X.25 Networks + // 544, // DEC IP Portal + // 512, // NETBIOS + 508, // IEEE 802/Source-Rt Bridge, ARCNET + 296, // Point-to-Point (low delay) + 68, // Official minimum + 0, // End of list marker +}; + +static const int IP_HEADER_SIZE = 20u; +static const int ICMP_HEADER_SIZE = 8u; +static const int ICMP_PING_TIMEOUT_MILLIS = 10000u; + +// TODO: Enable for production builds also? Use FormatMessage? +#ifdef _DEBUG +LPCSTR WSAErrorToString(int error, LPCSTR *description_result) { + LPCSTR string = "Unspecified"; + LPCSTR description = "Unspecified description"; + switch (error) { + case ERROR_SUCCESS: + string = "SUCCESS"; + description = "Operation succeeded"; + break; + case WSAEWOULDBLOCK: + string = "WSAEWOULDBLOCK"; + description = "Using a non-blocking socket, will notify later"; + break; + case WSAEACCES: + string = "WSAEACCES"; + description = "Access denied, or sharing violation"; + break; + case WSAEADDRNOTAVAIL: + string = "WSAEADDRNOTAVAIL"; + description = "Address is not valid in this context"; + break; + case WSAENETDOWN: + string = "WSAENETDOWN"; + description = "Network is down"; + break; + case WSAENETUNREACH: + string = "WSAENETUNREACH"; + description = "Network is up, but unreachable"; + break; + case WSAENETRESET: + string = "WSANETRESET"; + description = "Connection has been reset due to keep-alive activity"; + break; + case WSAECONNABORTED: + string = "WSAECONNABORTED"; + description = "Aborted by host"; + break; + case WSAECONNRESET: + string = "WSAECONNRESET"; + description = "Connection reset by host"; + break; + case WSAETIMEDOUT: + string = "WSAETIMEDOUT"; + description = "Timed out, host failed to respond"; + break; + case WSAECONNREFUSED: + string = "WSAECONNREFUSED"; + description = "Host actively refused connection"; + break; + case WSAEHOSTDOWN: + string = "WSAEHOSTDOWN"; + description = "Host is down"; + break; + case WSAEHOSTUNREACH: + string = "WSAEHOSTUNREACH"; + description = "Host is unreachable"; + break; + case WSAHOST_NOT_FOUND: + string = "WSAHOST_NOT_FOUND"; + description = "No such host is known"; + break; + } + if (description_result) { + *description_result = description; + } + return string; +} + +void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) { + LPCSTR description_string; + LPCSTR error_string = WSAErrorToString(error, &description_string); + LOG(LS_INFO) << context << " = " << error + << " (" << error_string << ":" << description_string << ") [" + << address.ToString() << "]"; +} +#else +void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {} +#endif + +///////////////////////////////////////////////////////////////////////////// +// Win32Socket::EventSink +///////////////////////////////////////////////////////////////////////////// + +#define WM_SOCKETNOTIFY (WM_USER + 50) +#define WM_DNSNOTIFY (WM_USER + 51) + +struct Win32Socket::DnsLookup { + HANDLE handle; + uint16 port; + char buffer[MAXGETHOSTSTRUCT]; +}; + +class Win32Socket::EventSink : public Win32Window { + public: + explicit EventSink(Win32Socket * parent) : parent_(parent) { } + + void Dispose(); + + virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result); + virtual void OnNcDestroy(); + + private: + bool OnSocketNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result); + bool OnDnsNotify(WPARAM wParam, LPARAM lParam, LRESULT& result); + + Win32Socket * parent_; +}; + +void Win32Socket::EventSink::Dispose() { + parent_ = NULL; + if (::IsWindow(handle())) { + ::DestroyWindow(handle()); + } else { + delete this; + } +} + +bool Win32Socket::EventSink::OnMessage(UINT uMsg, WPARAM wParam, + LPARAM lParam, LRESULT& result) { + switch (uMsg) { + case WM_SOCKETNOTIFY: + case WM_TIMER: + return OnSocketNotify(uMsg, wParam, lParam, result); + case WM_DNSNOTIFY: + return OnDnsNotify(wParam, lParam, result); + } + return false; +} + +bool Win32Socket::EventSink::OnSocketNotify(UINT uMsg, WPARAM wParam, + LPARAM lParam, LRESULT& result) { + result = 0; + + int wsa_event = WSAGETSELECTEVENT(lParam); + int wsa_error = WSAGETSELECTERROR(lParam); + + // Treat connect timeouts as close notifications + if (uMsg == WM_TIMER) { + wsa_event = FD_CLOSE; + wsa_error = WSAETIMEDOUT; + } + + if (parent_) + parent_->OnSocketNotify(static_cast(wParam), wsa_event, wsa_error); + return true; +} + +bool Win32Socket::EventSink::OnDnsNotify(WPARAM wParam, LPARAM lParam, + LRESULT& result) { + result = 0; + + int error = WSAGETASYNCERROR(lParam); + if (parent_) + parent_->OnDnsNotify(reinterpret_cast(wParam), error); + return true; +} + +void Win32Socket::EventSink::OnNcDestroy() { + if (parent_) { + LOG(LS_ERROR) << "EventSink hwnd is being destroyed, but the event sink" + " hasn't yet been disposed."; + } else { + delete this; + } +} + +///////////////////////////////////////////////////////////////////////////// +// Win32Socket +///////////////////////////////////////////////////////////////////////////// + +Win32Socket::Win32Socket() + : socket_(INVALID_SOCKET), error_(0), state_(CS_CLOSED), connect_time_(0), + closing_(false), close_error_(0), sink_(NULL), dns_(NULL) { +} + +Win32Socket::~Win32Socket() { + Close(); +} + +bool Win32Socket::CreateT(int family, int type) { + Close(); + int proto = (SOCK_DGRAM == type) ? IPPROTO_UDP : IPPROTO_TCP; + socket_ = ::WSASocket(family, type, proto, NULL, NULL, 0); + if (socket_ == INVALID_SOCKET) { + UpdateLastError(); + return false; + } + if ((SOCK_DGRAM == type) && !SetAsync(FD_READ | FD_WRITE)) { + return false; + } + return true; +} + +int Win32Socket::Attach(SOCKET s) { + ASSERT(socket_ == INVALID_SOCKET); + if (socket_ != INVALID_SOCKET) + return SOCKET_ERROR; + + ASSERT(s != INVALID_SOCKET); + if (s == INVALID_SOCKET) + return SOCKET_ERROR; + + socket_ = s; + state_ = CS_CONNECTED; + + if (!SetAsync(FD_READ | FD_WRITE | FD_CLOSE)) + return SOCKET_ERROR; + + return 0; +} + +void Win32Socket::SetTimeout(int ms) { + if (sink_) + ::SetTimer(sink_->handle(), 1, ms, 0); +} + +SocketAddress Win32Socket::GetLocalAddress() const { + sockaddr_storage addr = {0}; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(socket_, reinterpret_cast(&addr), + &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } else { + LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket=" + << socket_; + } + return address; +} + +SocketAddress Win32Socket::GetRemoteAddress() const { + sockaddr_storage addr = {0}; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(socket_, reinterpret_cast(&addr), + &addrlen); + SocketAddress address; + if (result >= 0) { + SocketAddressFromSockAddrStorage(addr, &address); + } else { + LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket=" + << socket_; + } + return address; +} + +int Win32Socket::Bind(const SocketAddress& addr) { + ASSERT(socket_ != INVALID_SOCKET); + if (socket_ == INVALID_SOCKET) + return SOCKET_ERROR; + + sockaddr_storage saddr; + size_t len = addr.ToSockAddrStorage(&saddr); + int err = ::bind(socket_, + reinterpret_cast(&saddr), + static_cast(len)); + UpdateLastError(); + return err; +} + +int Win32Socket::Connect(const SocketAddress& addr) { + if (state_ != CS_CLOSED) { + SetError(EALREADY); + return SOCKET_ERROR; + } + + if (!addr.IsUnresolvedIP()) { + return DoConnect(addr); + } + + LOG_F(LS_INFO) << "async dns lookup (" << addr.hostname() << ")"; + DnsLookup * dns = new DnsLookup; + if (!sink_) { + // Explicitly create the sink ourselves here; we can't rely on SetAsync + // because we don't have a socket_ yet. + CreateSink(); + } + // TODO: Replace with IPv6 compatible lookup. + dns->handle = WSAAsyncGetHostByName(sink_->handle(), WM_DNSNOTIFY, + addr.hostname().c_str(), dns->buffer, + sizeof(dns->buffer)); + + if (!dns->handle) { + LOG_F(LS_ERROR) << "WSAAsyncGetHostByName error: " << WSAGetLastError(); + delete dns; + UpdateLastError(); + Close(); + return SOCKET_ERROR; + } + + dns->port = addr.port(); + dns_ = dns; + state_ = CS_CONNECTING; + return 0; +} + +int Win32Socket::DoConnect(const SocketAddress& addr) { + if ((socket_ == INVALID_SOCKET) && !CreateT(addr.family(), SOCK_STREAM)) { + return SOCKET_ERROR; + } + if (!SetAsync(FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE)) { + return SOCKET_ERROR; + } + + sockaddr_storage saddr = {0}; + size_t len = addr.ToSockAddrStorage(&saddr); + connect_time_ = Time(); + int result = connect(socket_, + reinterpret_cast(&saddr), + static_cast(len)); + if (result != SOCKET_ERROR) { + state_ = CS_CONNECTED; + } else { + int code = WSAGetLastError(); + if (code == WSAEWOULDBLOCK) { + state_ = CS_CONNECTING; + } else { + ReportWSAError("WSAAsync:connect", code, addr); + error_ = code; + Close(); + return SOCKET_ERROR; + } + } + addr_ = addr; + + return 0; +} + +int Win32Socket::GetError() const { + return error_; +} + +void Win32Socket::SetError(int error) { + error_ = error; +} + +Socket::ConnState Win32Socket::GetState() const { + return state_; +} + +int Win32Socket::GetOption(Option opt, int* value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + + char* p = reinterpret_cast(value); + int optlen = sizeof(value); + return ::getsockopt(socket_, slevel, sopt, p, &optlen); +} + +int Win32Socket::SetOption(Option opt, int value) { + int slevel; + int sopt; + if (TranslateOption(opt, &slevel, &sopt) == -1) + return -1; + + const char* p = reinterpret_cast(&value); + return ::setsockopt(socket_, slevel, sopt, p, sizeof(value)); +} + +int Win32Socket::Send(const void* buffer, size_t length) { + int sent = ::send(socket_, + reinterpret_cast(buffer), + static_cast(length), + 0); + UpdateLastError(); + return sent; +} + +int Win32Socket::SendTo(const void* buffer, size_t length, + const SocketAddress& addr) { + sockaddr_storage saddr; + size_t addr_len = addr.ToSockAddrStorage(&saddr); + int sent = ::sendto(socket_, reinterpret_cast(buffer), + static_cast(length), 0, + reinterpret_cast(&saddr), + static_cast(addr_len)); + UpdateLastError(); + return sent; +} + +int Win32Socket::Recv(void* buffer, size_t length) { + int received = ::recv(socket_, static_cast(buffer), + static_cast(length), 0); + UpdateLastError(); + if (closing_ && received <= static_cast(length)) + PostClosed(); + return received; +} + +int Win32Socket::RecvFrom(void* buffer, size_t length, + SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + int received = ::recvfrom(socket_, static_cast(buffer), + static_cast(length), 0, + reinterpret_cast(&saddr), &addr_len); + UpdateLastError(); + if (received != SOCKET_ERROR) + SocketAddressFromSockAddrStorage(saddr, out_addr); + if (closing_ && received <= static_cast(length)) + PostClosed(); + return received; +} + +int Win32Socket::Listen(int backlog) { + int err = ::listen(socket_, backlog); + if (!SetAsync(FD_ACCEPT)) + return SOCKET_ERROR; + + UpdateLastError(); + if (err == 0) + state_ = CS_CONNECTING; + return err; +} + +Win32Socket* Win32Socket::Accept(SocketAddress* out_addr) { + sockaddr_storage saddr; + socklen_t addr_len = sizeof(saddr); + SOCKET s = ::accept(socket_, reinterpret_cast(&saddr), &addr_len); + UpdateLastError(); + if (s == INVALID_SOCKET) + return NULL; + if (out_addr) + SocketAddressFromSockAddrStorage(saddr, out_addr); + Win32Socket* socket = new Win32Socket; + if (0 == socket->Attach(s)) + return socket; + delete socket; + return NULL; +} + +int Win32Socket::Close() { + int err = 0; + if (socket_ != INVALID_SOCKET) { + err = ::closesocket(socket_); + socket_ = INVALID_SOCKET; + closing_ = false; + close_error_ = 0; + UpdateLastError(); + } + if (dns_) { + WSACancelAsyncRequest(dns_->handle); + delete dns_; + dns_ = NULL; + } + if (sink_) { + sink_->Dispose(); + sink_ = NULL; + } + addr_.Clear(); + state_ = CS_CLOSED; + return err; +} + +int Win32Socket::EstimateMTU(uint16* mtu) { + SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + error_ = ENOTCONN; + return -1; + } + + WinPing ping; + if (!ping.IsValid()) { + error_ = EINVAL; // can't think of a better error ID + return -1; + } + + for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) { + int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE; + WinPing::PingResult result = ping.Ping(addr.ipaddr(), size, + ICMP_PING_TIMEOUT_MILLIS, 1, false); + if (result == WinPing::PING_FAIL) { + error_ = EINVAL; // can't think of a better error ID + return -1; + } + if (result != WinPing::PING_TOO_LARGE) { + *mtu = PACKET_MAXIMUMS[level]; + return 0; + } + } + + ASSERT(false); + return 0; +} + +void Win32Socket::CreateSink() { + ASSERT(NULL == sink_); + + // Create window + sink_ = new EventSink(this); + sink_->Create(NULL, L"EventSink", 0, 0, 0, 0, 10, 10); +} + +bool Win32Socket::SetAsync(int events) { + if (NULL == sink_) { + CreateSink(); + ASSERT(NULL != sink_); + } + + // start the async select + if (WSAAsyncSelect(socket_, sink_->handle(), WM_SOCKETNOTIFY, events) + == SOCKET_ERROR) { + UpdateLastError(); + Close(); + return false; + } + + return true; +} + +bool Win32Socket::HandleClosed(int close_error) { + // WM_CLOSE will be received before all data has been read, so we need to + // hold on to it until the read buffer has been drained. + char ch; + closing_ = true; + close_error_ = close_error; + return (::recv(socket_, &ch, 1, MSG_PEEK) <= 0); +} + +void Win32Socket::PostClosed() { + // If we see that the buffer is indeed drained, then send the close. + closing_ = false; + ::PostMessage(sink_->handle(), WM_SOCKETNOTIFY, + socket_, WSAMAKESELECTREPLY(FD_CLOSE, close_error_)); +} + +void Win32Socket::UpdateLastError() { + error_ = WSAGetLastError(); +} + +int Win32Socket::TranslateOption(Option opt, int* slevel, int* sopt) { + switch (opt) { + case OPT_DONTFRAGMENT: + *slevel = IPPROTO_IP; + *sopt = IP_DONTFRAGMENT; + break; + case OPT_RCVBUF: + *slevel = SOL_SOCKET; + *sopt = SO_RCVBUF; + break; + case OPT_SNDBUF: + *slevel = SOL_SOCKET; + *sopt = SO_SNDBUF; + break; + case OPT_NODELAY: + *slevel = IPPROTO_TCP; + *sopt = TCP_NODELAY; + break; + case OPT_DSCP: + LOG(LS_WARNING) << "Socket::OPT_DSCP not supported."; + return -1; + default: + ASSERT(false); + return -1; + } + return 0; +} + +void Win32Socket::OnSocketNotify(SOCKET socket, int event, int error) { + // Ignore events if we're already closed. + if (socket != socket_) + return; + + error_ = error; + switch (event) { + case FD_CONNECT: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:connect notify", error, addr_); +#ifdef _DEBUG + int32 duration = TimeSince(connect_time_); + LOG(LS_INFO) << "WSAAsync:connect error (" << duration + << " ms), faking close"; +#endif + state_ = CS_CLOSED; + // If you get an error connecting, close doesn't really do anything + // and it certainly doesn't send back any close notification, but + // we really only maintain a few states, so it is easiest to get + // back into a known state by pretending that a close happened, even + // though the connect event never did occur. + SignalCloseEvent(this, error); + } else { +#ifdef _DEBUG + int32 duration = TimeSince(connect_time_); + LOG(LS_INFO) << "WSAAsync:connect (" << duration << " ms)"; +#endif + state_ = CS_CONNECTED; + SignalConnectEvent(this); + } + break; + + case FD_ACCEPT: + case FD_READ: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:read notify", error, addr_); + } else { + SignalReadEvent(this); + } + break; + + case FD_WRITE: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:write notify", error, addr_); + } else { + SignalWriteEvent(this); + } + break; + + case FD_CLOSE: + if (HandleClosed(error)) { + ReportWSAError("WSAAsync:close notify", error, addr_); + state_ = CS_CLOSED; + SignalCloseEvent(this, error); + } + break; + } +} + +void Win32Socket::OnDnsNotify(HANDLE task, int error) { + if (!dns_ || dns_->handle != task) + return; + + uint32 ip = 0; + if (error == 0) { + hostent* pHost = reinterpret_cast(dns_->buffer); + uint32 net_ip = *reinterpret_cast(pHost->h_addr_list[0]); + ip = NetworkToHost32(net_ip); + } + + LOG_F(LS_INFO) << "(" << IPAddress(ip).ToSensitiveString() + << ", " << error << ")"; + + if (error == 0) { + SocketAddress address(ip, dns_->port); + error = DoConnect(address); + } else { + Close(); + } + + if (error) { + error_ = error; + SignalCloseEvent(this, error_); + } else { + delete dns_; + dns_ = NULL; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Win32SocketServer +// Provides cricket base services on top of a win32 gui thread +/////////////////////////////////////////////////////////////////////////////// + +static UINT s_wm_wakeup_id = 0; +const TCHAR Win32SocketServer::kWindowName[] = L"libjingle Message Window"; + +Win32SocketServer::Win32SocketServer(MessageQueue* message_queue) + : message_queue_(message_queue), + wnd_(this), + posted_(false), + hdlg_(NULL) { + if (s_wm_wakeup_id == 0) + s_wm_wakeup_id = RegisterWindowMessage(L"WM_WAKEUP"); + if (!wnd_.Create(NULL, kWindowName, 0, 0, 0, 0, 0, 0)) { + LOG_GLE(LS_ERROR) << "Failed to create message window."; + } +} + +Win32SocketServer::~Win32SocketServer() { + if (wnd_.handle() != NULL) { + KillTimer(wnd_.handle(), 1); + wnd_.Destroy(); + } +} + +Socket* Win32SocketServer::CreateSocket(int type) { + return CreateSocket(AF_INET, type); +} + +Socket* Win32SocketServer::CreateSocket(int family, int type) { + return CreateAsyncSocket(family, type); +} + +AsyncSocket* Win32SocketServer::CreateAsyncSocket(int type) { + return CreateAsyncSocket(AF_INET, type); +} + +AsyncSocket* Win32SocketServer::CreateAsyncSocket(int family, int type) { + Win32Socket* socket = new Win32Socket; + if (socket->CreateT(family, type)) { + return socket; + } + delete socket; + return NULL; +} + +void Win32SocketServer::SetMessageQueue(MessageQueue* queue) { + message_queue_ = queue; +} + +bool Win32SocketServer::Wait(int cms, bool process_io) { + BOOL b; + if (process_io) { + // Spin the Win32 message pump at least once, and as long as requested. + // This is the Thread::ProcessMessages case. + uint32 start = Time(); + do { + MSG msg; + SetTimer(wnd_.handle(), 0, cms, NULL); + // Get the next available message. If we have a modeless dialog, give + // give the message to IsDialogMessage, which will return true if it + // was a message for the dialog that it handled internally. + // Otherwise, dispatch as usual via Translate/DispatchMessage. + b = GetMessage(&msg, NULL, 0, 0); + if (b == -1) { + LOG_GLE(LS_ERROR) << "GetMessage failed."; + return false; + } else if(b) { + if (!hdlg_ || !IsDialogMessage(hdlg_, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + KillTimer(wnd_.handle(), 0); + } while (b && TimeSince(start) < cms); + } else if (cms != 0) { + // Sit and wait forever for a WakeUp. This is the Thread::Send case. + ASSERT(cms == -1); + MSG msg; + b = GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id); + { + CritScope scope(&cs_); + posted_ = false; + } + } else { + // No-op (cms == 0 && !process_io). This is the Pump case. + b = TRUE; + } + return (b != FALSE); +} + +void Win32SocketServer::WakeUp() { + if (wnd_.handle()) { + // Set the "message pending" flag, if not already set. + { + CritScope scope(&cs_); + if (posted_) + return; + posted_ = true; + } + + PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0); + } +} + +void Win32SocketServer::Pump() { + // Clear the "message pending" flag. + { + CritScope scope(&cs_); + posted_ = false; + } + + // Dispatch all the messages that are currently in our queue. If new messages + // are posted during the dispatch, they will be handled in the next Pump. + // We use max(1, ...) to make sure we try to dispatch at least once, since + // this allow us to process "sent" messages, not included in the size() count. + Message msg; + for (size_t max_messages_to_process = _max(1, message_queue_->size()); + max_messages_to_process > 0 && message_queue_->Get(&msg, 0, false); + --max_messages_to_process) { + message_queue_->Dispatch(&msg); + } + + // Anything remaining? + int delay = message_queue_->GetDelay(); + if (delay == -1) { + KillTimer(wnd_.handle(), 1); + } else { + SetTimer(wnd_.handle(), 1, delay, NULL); + } +} + +bool Win32SocketServer::MessageWindow::OnMessage(UINT wm, WPARAM wp, + LPARAM lp, LRESULT& lr) { + bool handled = false; + if (wm == s_wm_wakeup_id || (wm == WM_TIMER && wp == 1)) { + ss_->Pump(); + lr = 0; + handled = true; + } + return handled; +} + +} // namespace rtc diff --git a/webrtc/base/win32socketserver.h b/webrtc/base/win32socketserver.h new file mode 100644 index 000000000..a03f6c028 --- /dev/null +++ b/webrtc/base/win32socketserver.h @@ -0,0 +1,164 @@ +/* + * Copyright 2004 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_BASE_WIN32SOCKETSERVER_H_ +#define WEBRTC_BASE_WIN32SOCKETSERVER_H_ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/socketfactory.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/win32window.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Socket +/////////////////////////////////////////////////////////////////////////////// + +class Win32Socket : public AsyncSocket { + public: + Win32Socket(); + virtual ~Win32Socket(); + + bool CreateT(int family, int type); + + int Attach(SOCKET s); + void SetTimeout(int ms); + + // AsyncSocket Interface + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Bind(const SocketAddress& addr); + virtual int Connect(const SocketAddress& addr); + virtual int Send(const void *buffer, size_t length); + virtual int SendTo(const void *buffer, size_t length, const SocketAddress& addr); + virtual int Recv(void *buffer, size_t length); + virtual int RecvFrom(void *buffer, size_t length, SocketAddress *out_addr); + virtual int Listen(int backlog); + virtual Win32Socket *Accept(SocketAddress *out_addr); + virtual int Close(); + virtual int GetError() const; + virtual void SetError(int error); + virtual ConnState GetState() const; + virtual int EstimateMTU(uint16* mtu); + virtual int GetOption(Option opt, int* value); + virtual int SetOption(Option opt, int value); + + private: + void CreateSink(); + bool SetAsync(int events); + int DoConnect(const SocketAddress& addr); + bool HandleClosed(int close_error); + void PostClosed(); + void UpdateLastError(); + static int TranslateOption(Option opt, int* slevel, int* sopt); + + void OnSocketNotify(SOCKET socket, int event, int error); + void OnDnsNotify(HANDLE task, int error); + + SOCKET socket_; + int error_; + ConnState state_; + SocketAddress addr_; // address that we connected to (see DoConnect) + uint32 connect_time_; + bool closing_; + int close_error_; + + class EventSink; + friend class EventSink; + EventSink * sink_; + + struct DnsLookup; + DnsLookup * dns_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Win32SocketServer +/////////////////////////////////////////////////////////////////////////////// + +class Win32SocketServer : public SocketServer { + public: + explicit Win32SocketServer(MessageQueue* message_queue); + virtual ~Win32SocketServer(); + + void set_modeless_dialog(HWND hdlg) { + hdlg_ = hdlg; + } + + // SocketServer Interface + virtual Socket* CreateSocket(int type); + virtual Socket* CreateSocket(int family, int type); + + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int family, int type); + + virtual void SetMessageQueue(MessageQueue* queue); + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + void Pump(); + + HWND handle() { return wnd_.handle(); } + + private: + class MessageWindow : public Win32Window { + public: + explicit MessageWindow(Win32SocketServer* ss) : ss_(ss) {} + private: + virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result); + Win32SocketServer* ss_; + }; + + static const TCHAR kWindowName[]; + MessageQueue *message_queue_; + MessageWindow wnd_; + CriticalSection cs_; + bool posted_; + HWND hdlg_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Win32Thread. Automatically pumps Windows messages. +/////////////////////////////////////////////////////////////////////////////// + +class Win32Thread : public Thread { + public: + Win32Thread() : ss_(this), id_(0) { + set_socketserver(&ss_); + } + virtual ~Win32Thread() { + Stop(); + set_socketserver(NULL); + } + virtual void Run() { + id_ = GetCurrentThreadId(); + Thread::Run(); + id_ = 0; + } + virtual void Quit() { + PostThreadMessage(id_, WM_QUIT, 0, 0); + } + private: + Win32SocketServer ss_; + DWORD id_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_WIN + +#endif // WEBRTC_BASE_WIN32SOCKETSERVER_H_ diff --git a/webrtc/base/win32socketserver_unittest.cc b/webrtc/base/win32socketserver_unittest.cc new file mode 100644 index 000000000..1d3ef2ea3 --- /dev/null +++ b/webrtc/base/win32socketserver_unittest.cc @@ -0,0 +1,157 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/socket_unittest.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/win32socketserver.h" + +namespace rtc { + +// Test that Win32SocketServer::Wait works as expected. +TEST(Win32SocketServerTest, TestWait) { + Win32SocketServer server(NULL); + uint32 start = Time(); + server.Wait(1000, true); + EXPECT_GE(TimeSince(start), 1000); +} + +// Test that Win32Socket::Pump does not touch general Windows messages. +TEST(Win32SocketServerTest, TestPump) { + Win32SocketServer server(NULL); + SocketServerScope scope(&server); + EXPECT_EQ(TRUE, PostMessage(NULL, WM_USER, 999, 0)); + server.Pump(); + MSG msg; + EXPECT_EQ(TRUE, PeekMessage(&msg, NULL, WM_USER, 0, PM_REMOVE)); + EXPECT_EQ(WM_USER, msg.message); + EXPECT_EQ(999, msg.wParam); +} + +// Test that Win32Socket passes all the generic Socket tests. +class Win32SocketTest : public SocketTest { + protected: + Win32SocketTest() : server_(NULL), scope_(&server_) {} + Win32SocketServer server_; + SocketServerScope scope_; +}; + +TEST_F(Win32SocketTest, TestConnectIPv4) { + SocketTest::TestConnectIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectIPv6) { + SocketTest::TestConnectIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupIPv4) { + SocketTest::TestConnectWithDnsLookupIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupIPv6) { + SocketTest::TestConnectWithDnsLookupIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectFailIPv4) { + SocketTest::TestConnectFailIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectFailIPv6) { + SocketTest::TestConnectFailIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupFailIPv4) { + SocketTest::TestConnectWithDnsLookupFailIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWithDnsLookupFailIPv6) { + SocketTest::TestConnectWithDnsLookupFailIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWithClosedSocketIPv4) { + SocketTest::TestConnectWithClosedSocketIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWithClosedSocketIPv6) { + SocketTest::TestConnectWithClosedSocketIPv6(); +} + +TEST_F(Win32SocketTest, TestConnectWhileNotClosedIPv4) { + SocketTest::TestConnectWhileNotClosedIPv4(); +} + +TEST_F(Win32SocketTest, TestConnectWhileNotClosedIPv6) { + SocketTest::TestConnectWhileNotClosedIPv6(); +} + +TEST_F(Win32SocketTest, TestServerCloseDuringConnectIPv4) { + SocketTest::TestServerCloseDuringConnectIPv4(); +} + +TEST_F(Win32SocketTest, TestServerCloseDuringConnectIPv6) { + SocketTest::TestServerCloseDuringConnectIPv6(); +} + +TEST_F(Win32SocketTest, TestClientCloseDuringConnectIPv4) { + SocketTest::TestClientCloseDuringConnectIPv4(); +} + +TEST_F(Win32SocketTest, TestClientCloseDuringConnectIPv6) { + SocketTest::TestClientCloseDuringConnectIPv6(); +} + +TEST_F(Win32SocketTest, TestServerCloseIPv4) { + SocketTest::TestServerCloseIPv4(); +} + +TEST_F(Win32SocketTest, TestServerCloseIPv6) { + SocketTest::TestServerCloseIPv6(); +} + +TEST_F(Win32SocketTest, TestCloseInClosedCallbackIPv4) { + SocketTest::TestCloseInClosedCallbackIPv4(); +} + +TEST_F(Win32SocketTest, TestCloseInClosedCallbackIPv6) { + SocketTest::TestCloseInClosedCallbackIPv6(); +} + +TEST_F(Win32SocketTest, TestSocketServerWaitIPv4) { + SocketTest::TestSocketServerWaitIPv4(); +} + +TEST_F(Win32SocketTest, TestSocketServerWaitIPv6) { + SocketTest::TestSocketServerWaitIPv6(); +} + +TEST_F(Win32SocketTest, TestTcpIPv4) { + SocketTest::TestTcpIPv4(); +} + +TEST_F(Win32SocketTest, TestTcpIPv6) { + SocketTest::TestTcpIPv6(); +} + +TEST_F(Win32SocketTest, TestUdpIPv4) { + SocketTest::TestUdpIPv4(); +} + +TEST_F(Win32SocketTest, TestUdpIPv6) { + SocketTest::TestUdpIPv6(); +} + +TEST_F(Win32SocketTest, TestGetSetOptionsIPv4) { + SocketTest::TestGetSetOptionsIPv4(); +} + +TEST_F(Win32SocketTest, TestGetSetOptionsIPv6) { + SocketTest::TestGetSetOptionsIPv6(); +} + +} // namespace rtc diff --git a/webrtc/base/win32toolhelp.h b/webrtc/base/win32toolhelp.h new file mode 100644 index 000000000..dfafdb317 --- /dev/null +++ b/webrtc/base/win32toolhelp.h @@ -0,0 +1,172 @@ +/* + * Copyright 2010 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_BASE_WIN32TOOLHELP_H_ +#define WEBRTC_BASE_WIN32TOOLHELP_H_ + +#if !defined(WEBRTC_WIN) +#error WEBRTC_WIN Only +#endif + +#include "webrtc/base/win32.h" + +// Should be included first, but that causes redefinitions. +#include + +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +// The toolhelp api used to enumerate processes and their modules +// on Windows is very repetetive and clunky to use. This little +// template wraps it to make it a little more programmer friendly. +// +// Traits: Traits type that adapts the enumerator to the corresponding +// win32 toolhelp api. Each traits class need to: +// - define the type of the enumerated data as a public symbol Type +// +// - implement bool First(HANDLE, T*) normally calls a +// Xxxx32First method in the toolhelp API. Ex Process32First(...) +// +// - implement bool Next(HANDLE, T*) normally calls a +// Xxxx32Next method in the toolhelp API. Ex Process32Next(...) +// +// - implement bool CloseHandle(HANDLE) +// +template +class ToolhelpEnumeratorBase { + public: + ToolhelpEnumeratorBase(HANDLE snapshot) + : snapshot_(snapshot), broken_(false), first_(true) { + + // Clear out the Traits::Type structure instance. + Zero(¤t_); + } + + virtual ~ToolhelpEnumeratorBase() { + Close(); + } + + // Moves forward to the next object using the First and Next + // pointers. If either First or Next ever indicates an failure + // all subsequent calls to this method will fail; the enumerator + // object is considered broken. + bool Next() { + if (!Valid()) { + return false; + } + + // Move the iteration forward. + current_.dwSize = sizeof(typename Traits::Type); + bool incr_ok = false; + if (first_) { + incr_ok = Traits::First(snapshot_, ¤t_); + first_ = false; + } else { + incr_ok = Traits::Next(snapshot_, ¤t_); + } + + if (!incr_ok) { + Zero(¤t_); + broken_ = true; + } + + return incr_ok; + } + + const typename Traits::Type& current() const { + return current_; + } + + void Close() { + if (snapshot_ != INVALID_HANDLE_VALUE) { + Traits::CloseHandle(snapshot_); + snapshot_ = INVALID_HANDLE_VALUE; + } + } + + private: + // Checks the state of the snapshot handle. + bool Valid() { + return snapshot_ != INVALID_HANDLE_VALUE && !broken_; + } + + static void Zero(typename Traits::Type* buff) { + ZeroMemory(buff, sizeof(typename Traits::Type)); + } + + HANDLE snapshot_; + typename Traits::Type current_; + bool broken_; + bool first_; +}; + +class ToolhelpTraits { + public: + static HANDLE CreateSnapshot(uint32 flags, uint32 process_id) { + return CreateToolhelp32Snapshot(flags, process_id); + } + + static bool CloseHandle(HANDLE handle) { + return ::CloseHandle(handle) == TRUE; + } +}; + +class ToolhelpProcessTraits : public ToolhelpTraits { + public: + typedef PROCESSENTRY32 Type; + + static bool First(HANDLE handle, Type* t) { + return ::Process32First(handle, t) == TRUE; + } + + static bool Next(HANDLE handle, Type* t) { + return ::Process32Next(handle, t) == TRUE; + } +}; + +class ProcessEnumerator : public ToolhelpEnumeratorBase { + public: + ProcessEnumerator() + : ToolhelpEnumeratorBase( + ToolhelpProcessTraits::CreateSnapshot(TH32CS_SNAPPROCESS, 0)) { + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ProcessEnumerator); +}; + +class ToolhelpModuleTraits : public ToolhelpTraits { + public: + typedef MODULEENTRY32 Type; + + static bool First(HANDLE handle, Type* t) { + return ::Module32First(handle, t) == TRUE; + } + + static bool Next(HANDLE handle, Type* t) { + return ::Module32Next(handle, t) == TRUE; + } +}; + +class ModuleEnumerator : public ToolhelpEnumeratorBase { + public: + explicit ModuleEnumerator(uint32 process_id) + : ToolhelpEnumeratorBase( + ToolhelpModuleTraits::CreateSnapshot(TH32CS_SNAPMODULE, + process_id)) { + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ModuleEnumerator); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32TOOLHELP_H_ diff --git a/webrtc/base/win32toolhelp_unittest.cc b/webrtc/base/win32toolhelp_unittest.cc new file mode 100644 index 000000000..280f2ec98 --- /dev/null +++ b/webrtc/base/win32toolhelp_unittest.cc @@ -0,0 +1,278 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/win32toolhelp.h" + +namespace rtc { + +typedef struct { + // Required to match the toolhelp api struct 'design'. + DWORD dwSize; + int a; + uint32 b; +} TestData; + +class Win32ToolhelpTest : public testing::Test { + public: + Win32ToolhelpTest() { + } + + HANDLE AsHandle() { + return reinterpret_cast(this); + } + + static Win32ToolhelpTest* AsFixture(HANDLE handle) { + return reinterpret_cast(handle); + } + + static bool First(HANDLE handle, TestData* d) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + // This method should be called only once for every test. + // If it is called more than once it return false which + // should break the test. + EXPECT_EQ(0, tst->first_called_); // Just to be safe. + if (tst->first_called_ > 0) { + return false; + } + + *d = kTestData[0]; + tst->index_ = 1; + ++(tst->first_called_); + return true; + } + + static bool Next(HANDLE handle, TestData* d) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + ++(tst->next_called_); + + if (tst->index_ >= kTestDataSize) { + return FALSE; + } + + *d = kTestData[tst->index_]; + ++(tst->index_); + return true; + } + + static bool Fail(HANDLE handle, TestData* d) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + ++(tst->fail_called_); + return false; + } + + static bool CloseHandle(HANDLE handle) { + Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle); + ++(tst->close_handle_called_); + return true; + } + + protected: + virtual void SetUp() { + fail_called_ = 0; + first_called_ = 0; + next_called_ = 0; + close_handle_called_ = 0; + index_ = 0; + } + + static bool AllZero(const TestData& data) { + return data.dwSize == 0 && data.a == 0 && data.b == 0; + } + + static bool Equals(const TestData& expected, const TestData& actual) { + return expected.dwSize == actual.dwSize + && expected.a == actual.a + && expected.b == actual.b; + } + + bool CheckCallCounters(int first, int next, int fail, int close) { + bool match = first_called_ == first && next_called_ == next + && fail_called_ == fail && close_handle_called_ == close; + + if (!match) { + LOG(LS_ERROR) << "Expected: (" + << first << ", " + << next << ", " + << fail << ", " + << close << ")"; + + LOG(LS_ERROR) << "Actual: (" + << first_called_ << ", " + << next_called_ << ", " + << fail_called_ << ", " + << close_handle_called_ << ")"; + } + return match; + } + + static const int kTestDataSize = 3; + static const TestData kTestData[]; + int index_; + int first_called_; + int fail_called_; + int next_called_; + int close_handle_called_; +}; + +const TestData Win32ToolhelpTest::kTestData[] = { + {1, 1, 1}, {2, 2, 2}, {3, 3, 3} +}; + + +class TestTraits { + public: + typedef TestData Type; + + static bool First(HANDLE handle, Type* t) { + return Win32ToolhelpTest::First(handle, t); + } + + static bool Next(HANDLE handle, Type* t) { + return Win32ToolhelpTest::Next(handle, t); + } + + static bool CloseHandle(HANDLE handle) { + return Win32ToolhelpTest::CloseHandle(handle); + } +}; + +class BadFirstTraits { + public: + typedef TestData Type; + + static bool First(HANDLE handle, Type* t) { + return Win32ToolhelpTest::Fail(handle, t); + } + + static bool Next(HANDLE handle, Type* t) { + // This should never be called. + ADD_FAILURE(); + return false; + } + + static bool CloseHandle(HANDLE handle) { + return Win32ToolhelpTest::CloseHandle(handle); + } +}; + +class BadNextTraits { + public: + typedef TestData Type; + + static bool First(HANDLE handle, Type* t) { + return Win32ToolhelpTest::First(handle, t); + } + + static bool Next(HANDLE handle, Type* t) { + return Win32ToolhelpTest::Fail(handle, t); + } + + static bool CloseHandle(HANDLE handle) { + return Win32ToolhelpTest::CloseHandle(handle); + } +}; + +// The toolhelp in normally inherited but most of +// these tests only excercise the methods from the +// traits therefore I use a typedef to make the +// test code easier to read. +typedef rtc::ToolhelpEnumeratorBase EnumeratorForTest; + +TEST_F(Win32ToolhelpTest, TestNextWithInvalidCtorHandle) { + EnumeratorForTest t(INVALID_HANDLE_VALUE); + + EXPECT_FALSE(t.Next()); + EXPECT_TRUE(CheckCallCounters(0, 0, 0, 0)); +} + +// Tests that Next() returns false if the first-pointer +// function fails. +TEST_F(Win32ToolhelpTest, TestNextFirstFails) { + typedef rtc::ToolhelpEnumeratorBase BadEnumerator; + rtc::scoped_ptr t(new BadEnumerator(AsHandle())); + + // If next ever fails it shall always fail. + EXPECT_FALSE(t->Next()); + EXPECT_FALSE(t->Next()); + EXPECT_FALSE(t->Next()); + t.reset(); + EXPECT_TRUE(CheckCallCounters(0, 0, 1, 1)); +} + +// Tests that Next() returns false if the next-pointer +// function fails. +TEST_F(Win32ToolhelpTest, TestNextNextFails) { + typedef rtc::ToolhelpEnumeratorBase BadEnumerator; + rtc::scoped_ptr t(new BadEnumerator(AsHandle())); + + // If next ever fails it shall always fail. No more calls + // shall be dispatched to Next(...). + EXPECT_TRUE(t->Next()); + EXPECT_FALSE(t->Next()); + EXPECT_FALSE(t->Next()); + t.reset(); + EXPECT_TRUE(CheckCallCounters(1, 0, 1, 1)); +} + + +// Tests that current returns an object is all zero's +// if Next() hasn't been called. +TEST_F(Win32ToolhelpTest, TestCurrentNextNotCalled) { + rtc::scoped_ptr t(new EnumeratorForTest(AsHandle())); + EXPECT_TRUE(AllZero(t->current())); + t.reset(); + EXPECT_TRUE(CheckCallCounters(0, 0, 0, 1)); +} + +// Tests the simple everything works path through the code. +TEST_F(Win32ToolhelpTest, TestCurrentNextCalled) { + rtc::scoped_ptr t(new EnumeratorForTest(AsHandle())); + + EXPECT_TRUE(t->Next()); + EXPECT_TRUE(Equals(t->current(), kTestData[0])); + EXPECT_TRUE(t->Next()); + EXPECT_TRUE(Equals(t->current(), kTestData[1])); + EXPECT_TRUE(t->Next()); + EXPECT_TRUE(Equals(t->current(), kTestData[2])); + EXPECT_FALSE(t->Next()); + t.reset(); + EXPECT_TRUE(CheckCallCounters(1, 3, 0, 1)); +} + +TEST_F(Win32ToolhelpTest, TestCurrentProcess) { + WCHAR buf[MAX_PATH]; + GetModuleFileName(NULL, buf, ARRAY_SIZE(buf)); + std::wstring name = ToUtf16(Pathname(ToUtf8(buf)).filename()); + + rtc::ProcessEnumerator processes; + bool found = false; + while (processes.Next()) { + if (!name.compare(processes.current().szExeFile)) { + found = true; + break; + } + } + EXPECT_TRUE(found); + + rtc::ModuleEnumerator modules(processes.current().th32ProcessID); + found = false; + while (modules.Next()) { + if (!name.compare(modules.current().szModule)) { + found = true; + break; + } + } + EXPECT_TRUE(found); +} + +} // namespace rtc diff --git a/webrtc/base/win32window.cc b/webrtc/base/win32window.cc new file mode 100644 index 000000000..4d4101405 --- /dev/null +++ b/webrtc/base/win32window.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2004 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 "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/win32window.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Window +/////////////////////////////////////////////////////////////////////////////// + +static const wchar_t kWindowBaseClassName[] = L"WindowBaseClass"; +HINSTANCE Win32Window::instance_ = NULL; +ATOM Win32Window::window_class_ = 0; + +Win32Window::Win32Window() : wnd_(NULL) { +} + +Win32Window::~Win32Window() { + ASSERT(NULL == wnd_); +} + +bool Win32Window::Create(HWND parent, const wchar_t* title, DWORD style, + DWORD exstyle, int x, int y, int cx, int cy) { + if (wnd_) { + // Window already exists. + return false; + } + + if (!window_class_) { + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&Win32Window::WndProc), + &instance_)) { + LOG_GLE(LS_ERROR) << "GetModuleHandleEx failed"; + return false; + } + + // Class not registered, register it. + WNDCLASSEX wcex; + memset(&wcex, 0, sizeof(wcex)); + wcex.cbSize = sizeof(wcex); + wcex.hInstance = instance_; + wcex.lpfnWndProc = &Win32Window::WndProc; + wcex.lpszClassName = kWindowBaseClassName; + window_class_ = ::RegisterClassEx(&wcex); + if (!window_class_) { + LOG_GLE(LS_ERROR) << "RegisterClassEx failed"; + return false; + } + } + wnd_ = ::CreateWindowEx(exstyle, kWindowBaseClassName, title, style, + x, y, cx, cy, parent, NULL, instance_, this); + return (NULL != wnd_); +} + +void Win32Window::Destroy() { + VERIFY(::DestroyWindow(wnd_) != FALSE); +} + +void Win32Window::Shutdown() { + if (window_class_) { + ::UnregisterClass(MAKEINTATOM(window_class_), instance_); + window_class_ = 0; + } +} + +bool Win32Window::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result) { + switch (uMsg) { + case WM_CLOSE: + if (!OnClose()) { + result = 0; + return true; + } + break; + } + return false; +} + +LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + Win32Window* that = reinterpret_cast( + ::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (!that && (WM_CREATE == uMsg)) { + CREATESTRUCT* cs = reinterpret_cast(lParam); + that = static_cast(cs->lpCreateParams); + that->wnd_ = hwnd; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(that)); + } + if (that) { + LRESULT result; + bool handled = that->OnMessage(uMsg, wParam, lParam, result); + if (WM_DESTROY == uMsg) { + for (HWND child = ::GetWindow(hwnd, GW_CHILD); child; + child = ::GetWindow(child, GW_HWNDNEXT)) { + LOG(LS_INFO) << "Child window: " << static_cast(child); + } + } + if (WM_NCDESTROY == uMsg) { + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL); + that->wnd_ = NULL; + that->OnNcDestroy(); + } + if (handled) { + return result; + } + } + return ::DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +} // namespace rtc diff --git a/webrtc/base/win32window.h b/webrtc/base/win32window.h new file mode 100644 index 000000000..c0ba6b23d --- /dev/null +++ b/webrtc/base/win32window.h @@ -0,0 +1,60 @@ +/* + * Copyright 2004 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_BASE_WIN32WINDOW_H_ +#define WEBRTC_BASE_WIN32WINDOW_H_ + +#if defined(WEBRTC_WIN) + +#include "webrtc/base/win32.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Window +/////////////////////////////////////////////////////////////////////////////// + +class Win32Window { + public: + Win32Window(); + virtual ~Win32Window(); + + HWND handle() const { return wnd_; } + + bool Create(HWND parent, const wchar_t* title, DWORD style, DWORD exstyle, + int x, int y, int cx, int cy); + void Destroy(); + + // Call this when your DLL unloads. + static void Shutdown(); + + protected: + virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, + LRESULT& result); + + virtual bool OnClose() { return true; } + virtual void OnNcDestroy() { } + + private: + static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + + HWND wnd_; + static HINSTANCE instance_; + static ATOM window_class_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_WIN + +#endif // WEBRTC_BASE_WIN32WINDOW_H_ diff --git a/webrtc/base/win32window_unittest.cc b/webrtc/base/win32window_unittest.cc new file mode 100644 index 000000000..5dba67eb5 --- /dev/null +++ b/webrtc/base/win32window_unittest.cc @@ -0,0 +1,66 @@ +/* + * Copyright 2009 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 "webrtc/base/gunit.h" +#include "webrtc/base/common.h" +#include "webrtc/base/win32window.h" +#include "webrtc/base/logging.h" + +static LRESULT kDummyResult = 0x1234ABCD; + +class TestWindow : public rtc::Win32Window { + public: + TestWindow() : destroyed_(false) { memset(&msg_, 0, sizeof(msg_)); } + const MSG& msg() const { return msg_; } + bool destroyed() const { return destroyed_; } + + virtual bool OnMessage(UINT uMsg, WPARAM wParam, + LPARAM lParam, LRESULT& result) { + msg_.message = uMsg; + msg_.wParam = wParam; + msg_.lParam = lParam; + result = kDummyResult; + return true; + } + virtual void OnNcDestroy() { + destroyed_ = true; + } + + private: + MSG msg_; + bool destroyed_; +}; + +TEST(Win32WindowTest, Basics) { + TestWindow wnd; + EXPECT_TRUE(wnd.handle() == NULL); + EXPECT_FALSE(wnd.destroyed()); + EXPECT_TRUE(wnd.Create(0, L"Test", 0, 0, 0, 0, 100, 100)); + EXPECT_TRUE(wnd.handle() != NULL); + EXPECT_EQ(kDummyResult, ::SendMessage(wnd.handle(), WM_USER, 1, 2)); + EXPECT_EQ(WM_USER, wnd.msg().message); + EXPECT_EQ(1, wnd.msg().wParam); + EXPECT_EQ(2, wnd.msg().lParam); + wnd.Destroy(); + EXPECT_TRUE(wnd.handle() == NULL); + EXPECT_TRUE(wnd.destroyed()); +} + +TEST(Win32WindowTest, MultipleWindows) { + TestWindow wnd1, wnd2; + EXPECT_TRUE(wnd1.Create(0, L"Test", 0, 0, 0, 0, 100, 100)); + EXPECT_TRUE(wnd2.Create(0, L"Test", 0, 0, 0, 0, 100, 100)); + EXPECT_TRUE(wnd1.handle() != NULL); + EXPECT_TRUE(wnd2.handle() != NULL); + wnd1.Destroy(); + wnd2.Destroy(); + EXPECT_TRUE(wnd2.handle() == NULL); + EXPECT_TRUE(wnd1.handle() == NULL); +} diff --git a/webrtc/base/win32windowpicker.cc b/webrtc/base/win32windowpicker.cc new file mode 100644 index 000000000..b4550ae4a --- /dev/null +++ b/webrtc/base/win32windowpicker.cc @@ -0,0 +1,143 @@ +/* + * Copyright 2010 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 "webrtc/base/win32windowpicker.h" + +#include +#include + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace rtc { + +namespace { + +// Window class names that we want to filter out. +const char kProgramManagerClass[] = "Progman"; +const char kButtonClass[] = "Button"; + +} // namespace + +BOOL CALLBACK Win32WindowPicker::EnumProc(HWND hwnd, LPARAM l_param) { + WindowDescriptionList* descriptions = + reinterpret_cast(l_param); + + // Skip windows that are invisible, minimized, have no title, or are owned, + // unless they have the app window style set. Except for minimized windows, + // this is what Alt-Tab does. + // TODO: Figure out how to grab a thumbnail of a minimized window and + // include them in the list. + int len = GetWindowTextLength(hwnd); + HWND owner = GetWindow(hwnd, GW_OWNER); + LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) || + (owner && !(exstyle & WS_EX_APPWINDOW))) { + // TODO: Investigate if windows without title still could be + // interesting to share. We could use the name of the process as title: + // + // GetWindowThreadProcessId() + // OpenProcess() + // QueryFullProcessImageName() + return TRUE; + } + + // Skip the Program Manager window and the Start button. + TCHAR class_name_w[500]; + ::GetClassName(hwnd, class_name_w, 500); + std::string class_name = ToUtf8(class_name_w); + if (class_name == kProgramManagerClass || class_name == kButtonClass) { + // We don't want the Program Manager window nor the Start button. + return TRUE; + } + + TCHAR window_title[500]; + GetWindowText(hwnd, window_title, ARRAY_SIZE(window_title)); + std::string title = ToUtf8(window_title); + + WindowId id(hwnd); + WindowDescription desc(id, title); + descriptions->push_back(desc); + return TRUE; +} + +BOOL CALLBACK Win32WindowPicker::MonitorEnumProc(HMONITOR h_monitor, + HDC hdc_monitor, + LPRECT lprc_monitor, + LPARAM l_param) { + DesktopDescriptionList* desktop_desc = + reinterpret_cast(l_param); + + DesktopId id(h_monitor, static_cast(desktop_desc->size())); + // TODO: Figure out an appropriate desktop title. + DesktopDescription desc(id, ""); + + // Determine whether it's the primary monitor. + MONITORINFO monitor_info = {0}; + monitor_info.cbSize = sizeof(monitor_info); + bool primary = (GetMonitorInfo(h_monitor, &monitor_info) && + (monitor_info.dwFlags & MONITORINFOF_PRIMARY) != 0); + desc.set_primary(primary); + + desktop_desc->push_back(desc); + return TRUE; +} + +Win32WindowPicker::Win32WindowPicker() { +} + +bool Win32WindowPicker::Init() { + return true; +} +// TODO: Consider changing enumeration to clear() descriptions +// before append(). +bool Win32WindowPicker::GetWindowList(WindowDescriptionList* descriptions) { + LPARAM desc = reinterpret_cast(descriptions); + return EnumWindows(Win32WindowPicker::EnumProc, desc) != FALSE; +} + +bool Win32WindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { + // Create a fresh WindowDescriptionList so that we can use desktop_desc.size() + // in MonitorEnumProc to compute the desktop index. + DesktopDescriptionList desktop_desc; + HDC hdc = GetDC(NULL); + bool success = false; + if (EnumDisplayMonitors(hdc, NULL, Win32WindowPicker::MonitorEnumProc, + reinterpret_cast(&desktop_desc)) != FALSE) { + // Append the desktop descriptions to the end of the returned descriptions. + descriptions->insert(descriptions->end(), desktop_desc.begin(), + desktop_desc.end()); + success = true; + } + ReleaseDC(NULL, hdc); + return success; +} + +bool Win32WindowPicker::GetDesktopDimensions(const DesktopId& id, + int* width, + int* height) { + MONITORINFOEX monitor_info; + monitor_info.cbSize = sizeof(MONITORINFOEX); + if (!GetMonitorInfo(id.id(), &monitor_info)) { + return false; + } + *width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left; + *height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top; + return true; +} + +bool Win32WindowPicker::IsVisible(const WindowId& id) { + return (::IsWindow(id.id()) != FALSE && ::IsWindowVisible(id.id()) != FALSE); +} + +bool Win32WindowPicker::MoveToFront(const WindowId& id) { + return SetForegroundWindow(id.id()) != FALSE; +} + +} // namespace rtc diff --git a/webrtc/base/win32windowpicker.h b/webrtc/base/win32windowpicker.h new file mode 100644 index 000000000..9c84bfd98 --- /dev/null +++ b/webrtc/base/win32windowpicker.h @@ -0,0 +1,39 @@ +/* + * Copyright 2010 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_BASE_WIN32WINDOWPICKER_H_ +#define WEBRTC_BASE_WIN32WINDOWPICKER_H_ + +#include "webrtc/base/win32.h" +#include "webrtc/base/windowpicker.h" + +namespace rtc { + +class Win32WindowPicker : public WindowPicker { + public: + Win32WindowPicker(); + virtual bool Init(); + virtual bool IsVisible(const WindowId& id); + virtual bool MoveToFront(const WindowId& id); + virtual bool GetWindowList(WindowDescriptionList* descriptions); + virtual bool GetDesktopList(DesktopDescriptionList* descriptions); + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height); + + protected: + static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM l_param); + static BOOL CALLBACK MonitorEnumProc(HMONITOR h_monitor, + HDC hdc_monitor, + LPRECT lprc_monitor, + LPARAM l_param); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WIN32WINDOWPICKER_H_ diff --git a/webrtc/base/win32windowpicker_unittest.cc b/webrtc/base/win32windowpicker_unittest.cc new file mode 100644 index 000000000..71e8af6bf --- /dev/null +++ b/webrtc/base/win32windowpicker_unittest.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/win32window.h" +#include "webrtc/base/win32windowpicker.h" +#include "webrtc/base/windowpicker.h" + +#if !defined(WEBRTC_WIN) +#error Only for Windows +#endif + +namespace rtc { + +static const TCHAR* kVisibleWindowTitle = L"Visible Window"; +static const TCHAR* kInvisibleWindowTitle = L"Invisible Window"; + +class Win32WindowPickerForTest : public Win32WindowPicker { + public: + Win32WindowPickerForTest() { + EXPECT_TRUE(visible_window_.Create(NULL, kVisibleWindowTitle, WS_VISIBLE, + 0, 0, 0, 0, 0)); + EXPECT_TRUE(invisible_window_.Create(NULL, kInvisibleWindowTitle, 0, + 0, 0, 0, 0, 0)); + } + + ~Win32WindowPickerForTest() { + visible_window_.Destroy(); + invisible_window_.Destroy(); + } + + virtual bool GetWindowList(WindowDescriptionList* descriptions) { + if (!Win32WindowPicker::EnumProc(visible_window_.handle(), + reinterpret_cast(descriptions))) { + return false; + } + if (!Win32WindowPicker::EnumProc(invisible_window_.handle(), + reinterpret_cast(descriptions))) { + return false; + } + return true; + } + + Win32Window* visible_window() { + return &visible_window_; + } + + Win32Window* invisible_window() { + return &invisible_window_; + } + + private: + Win32Window visible_window_; + Win32Window invisible_window_; +}; + +TEST(Win32WindowPickerTest, TestGetWindowList) { + Win32WindowPickerForTest window_picker; + WindowDescriptionList descriptions; + EXPECT_TRUE(window_picker.GetWindowList(&descriptions)); + EXPECT_EQ(1, descriptions.size()); + WindowDescription desc = descriptions.front(); + EXPECT_EQ(window_picker.visible_window()->handle(), desc.id().id()); + TCHAR window_title[500]; + GetWindowText(window_picker.visible_window()->handle(), window_title, + ARRAY_SIZE(window_title)); + EXPECT_EQ(0, wcscmp(window_title, kVisibleWindowTitle)); +} + +TEST(Win32WindowPickerTest, TestIsVisible) { + Win32WindowPickerForTest window_picker; + HWND visible_id = window_picker.visible_window()->handle(); + HWND invisible_id = window_picker.invisible_window()->handle(); + EXPECT_TRUE(window_picker.IsVisible(WindowId(visible_id))); + EXPECT_FALSE(window_picker.IsVisible(WindowId(invisible_id))); +} + +TEST(Win32WindowPickerTest, TestMoveToFront) { + Win32WindowPickerForTest window_picker; + HWND visible_id = window_picker.visible_window()->handle(); + HWND invisible_id = window_picker.invisible_window()->handle(); + + // There are a number of condition where SetForegroundWindow might + // fail depending on the state of the calling process. To be on the + // safe side we doesn't expect MoveToFront to return true, just test + // that we don't crash. + window_picker.MoveToFront(WindowId(visible_id)); + window_picker.MoveToFront(WindowId(invisible_id)); +} + +} // namespace rtc diff --git a/webrtc/base/window.h b/webrtc/base/window.h new file mode 100644 index 000000000..d96102652 --- /dev/null +++ b/webrtc/base/window.h @@ -0,0 +1,124 @@ +/* + * Copyright 2004 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_BASE_WINDOW_H_ +#define WEBRTC_BASE_WINDOW_H_ + +#include "webrtc/base/stringencode.h" + +// Define platform specific window types. +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +typedef unsigned long Window; // Avoid include . +#elif defined(WEBRTC_WIN) +// We commonly include win32.h in webrtc/base so just include it here. +#include "webrtc/base/win32.h" // Include HWND, HMONITOR. +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +typedef unsigned int CGWindowID; +typedef unsigned int CGDirectDisplayID; +#endif + +namespace rtc { + +class WindowId { + public: + // Define WindowT for each platform. +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + typedef Window WindowT; +#elif defined(WEBRTC_WIN) + typedef HWND WindowT; +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + typedef CGWindowID WindowT; +#else + typedef unsigned int WindowT; +#endif + + static WindowId Cast(uint64 id) { +#if defined(WEBRTC_WIN) + return WindowId(reinterpret_cast(id)); +#else + return WindowId(static_cast(id)); +#endif + } + + static uint64 Format(const WindowT& id) { +#if defined(WEBRTC_WIN) + return static_cast(reinterpret_cast(id)); +#else + return static_cast(id); +#endif + } + + WindowId() : id_(0) {} + WindowId(const WindowT& id) : id_(id) {} // NOLINT + const WindowT& id() const { return id_; } + bool IsValid() const { return id_ != 0; } + bool Equals(const WindowId& other) const { + return id_ == other.id(); + } + + private: + WindowT id_; +}; + +class DesktopId { + public: + // Define DesktopT for each platform. +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) + typedef Window DesktopT; +#elif defined(WEBRTC_WIN) + typedef HMONITOR DesktopT; +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + typedef CGDirectDisplayID DesktopT; +#else + typedef unsigned int DesktopT; +#endif + + static DesktopId Cast(int id, int index) { +#if defined(WEBRTC_WIN) + return DesktopId(reinterpret_cast(id), index); +#else + return DesktopId(static_cast(id), index); +#endif + } + + DesktopId() : id_(0), index_(-1) {} + DesktopId(const DesktopT& id, int index) // NOLINT + : id_(id), index_(index) { + } + const DesktopT& id() const { return id_; } + int index() const { return index_; } + bool IsValid() const { return index_ != -1; } + bool Equals(const DesktopId& other) const { + return id_ == other.id() && index_ == other.index(); + } + + private: + // Id is the platform specific desktop identifier. + DesktopT id_; + // Index is the desktop index as enumerated by each platform. + // Desktop capturer typically takes the index instead of id. + int index_; +}; + +// Window event types. +enum WindowEvent { + WE_RESIZE = 0, + WE_CLOSE = 1, + WE_MINIMIZE = 2, + WE_RESTORE = 3, +}; + +inline std::string ToString(const WindowId& window) { + return ToString(window.id()); +} + +} // namespace rtc + +#endif // WEBRTC_BASE_WINDOW_H_ diff --git a/webrtc/base/windowpicker.h b/webrtc/base/windowpicker.h new file mode 100644 index 000000000..3ae7b0e49 --- /dev/null +++ b/webrtc/base/windowpicker.h @@ -0,0 +1,84 @@ +/* + * Copyright 2010 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_BASE_WINDOWPICKER_H_ +#define WEBRTC_BASE_WINDOWPICKER_H_ + +#include +#include + +#include "webrtc/base/window.h" + +namespace rtc { + +class WindowDescription { + public: + WindowDescription() : id_() {} + WindowDescription(const WindowId& id, const std::string& title) + : id_(id), title_(title) { + } + const WindowId& id() const { return id_; } + void set_id(const WindowId& id) { id_ = id; } + const std::string& title() const { return title_; } + void set_title(const std::string& title) { title_ = title; } + + private: + WindowId id_; + std::string title_; +}; + +class DesktopDescription { + public: + DesktopDescription() : id_() {} + DesktopDescription(const DesktopId& id, const std::string& title) + : id_(id), title_(title), primary_(false) { + } + const DesktopId& id() const { return id_; } + void set_id(const DesktopId& id) { id_ = id; } + const std::string& title() const { return title_; } + void set_title(const std::string& title) { title_ = title; } + // Indicates whether it is the primary desktop in the system. + bool primary() const { return primary_; } + void set_primary(bool primary) { primary_ = primary; } + + private: + DesktopId id_; + std::string title_; + bool primary_; +}; + +typedef std::vector WindowDescriptionList; +typedef std::vector DesktopDescriptionList; + +class WindowPicker { + public: + virtual ~WindowPicker() {} + virtual bool Init() = 0; + + // TODO: Move this two methods to window.h when we no longer need to load + // CoreGraphics dynamically. + virtual bool IsVisible(const WindowId& id) = 0; + virtual bool MoveToFront(const WindowId& id) = 0; + + // Gets a list of window description and appends to descriptions. + // Returns true if successful. + virtual bool GetWindowList(WindowDescriptionList* descriptions) = 0; + // Gets a list of desktop descriptions and appends to descriptions. + // Returns true if successful. + virtual bool GetDesktopList(DesktopDescriptionList* descriptions) = 0; + // Gets the width and height of a desktop. + // Returns true if successful. + virtual bool GetDesktopDimensions(const DesktopId& id, int* width, + int* height) = 0; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WINDOWPICKER_H_ diff --git a/webrtc/base/windowpicker_unittest.cc b/webrtc/base/windowpicker_unittest.cc new file mode 100644 index 000000000..edd01bc0b --- /dev/null +++ b/webrtc/base/windowpicker_unittest.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2012 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 "webrtc/base/gunit.h" +#include "webrtc/base/testutils.h" +#include "webrtc/base/window.h" +#include "webrtc/base/windowpicker.h" +#include "webrtc/base/windowpickerfactory.h" + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +# define DISABLE_ON_MAC(name) DISABLED_ ## name +#else +# define DISABLE_ON_MAC(name) name +#endif + +TEST(WindowPickerTest, GetWindowList) { + MAYBE_SKIP_SCREENCAST_TEST(); + if (!rtc::WindowPickerFactory::IsSupported()) { + LOG(LS_INFO) << "skipping test: window capturing is not supported with " + << "current configuration."; + } + rtc::scoped_ptr picker( + rtc::WindowPickerFactory::CreateWindowPicker()); + EXPECT_TRUE(picker->Init()); + rtc::WindowDescriptionList descriptions; + EXPECT_TRUE(picker->GetWindowList(&descriptions)); +} + +// TODO(hughv) Investigate why this fails on pulse but not locally after +// upgrading to XCode 4.5. The failure is GetDesktopList returning FALSE. +TEST(WindowPickerTest, DISABLE_ON_MAC(GetDesktopList)) { + MAYBE_SKIP_SCREENCAST_TEST(); + if (!rtc::WindowPickerFactory::IsSupported()) { + LOG(LS_INFO) << "skipping test: window capturing is not supported with " + << "current configuration."; + } + rtc::scoped_ptr picker( + rtc::WindowPickerFactory::CreateWindowPicker()); + EXPECT_TRUE(picker->Init()); + rtc::DesktopDescriptionList descriptions; + EXPECT_TRUE(picker->GetDesktopList(&descriptions)); + if (descriptions.size() > 0) { + int width = 0; + int height = 0; + EXPECT_TRUE(picker->GetDesktopDimensions(descriptions[0].id(), &width, + &height)); + EXPECT_GT(width, 0); + EXPECT_GT(height, 0); + + // Test |IsPrimaryDesktop|. Only one desktop should be a primary. + bool found_primary = false; + for (rtc::DesktopDescriptionList::iterator it = descriptions.begin(); + it != descriptions.end(); ++it) { + if (it->primary()) { + EXPECT_FALSE(found_primary); + found_primary = true; + } + } + EXPECT_TRUE(found_primary); + } +} diff --git a/webrtc/base/windowpickerfactory.h b/webrtc/base/windowpickerfactory.h new file mode 100644 index 000000000..00a4cc469 --- /dev/null +++ b/webrtc/base/windowpickerfactory.h @@ -0,0 +1,59 @@ +/* + * Copyright 2010 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_BASE_WINDOWPICKERFACTORY_H_ +#define WEBRTC_BASE_WINDOWPICKERFACTORY_H_ + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32windowpicker.h" +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "webrtc/base/macutils.h" +#include "webrtc/base/macwindowpicker.h" +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +#include "webrtc/base/linuxwindowpicker.h" +#endif + +#include "webrtc/base/windowpicker.h" + +namespace rtc { + +class WindowPickerFactory { + public: + virtual ~WindowPickerFactory() {} + + // Instance method for dependency injection. + virtual WindowPicker* Create() { + return CreateWindowPicker(); + } + + static WindowPicker* CreateWindowPicker() { +#if defined(WEBRTC_WIN) + return new Win32WindowPicker(); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + return new MacWindowPicker(); +#elif defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) && defined(HAVE_X11) + return new LinuxWindowPicker(); +#else + return NULL; +#endif + } + + static bool IsSupported() { +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + return GetOSVersionName() >= kMacOSLeopard; +#else + return true; +#endif + } +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WINDOWPICKERFACTORY_H_ diff --git a/webrtc/base/winfirewall.cc b/webrtc/base/winfirewall.cc new file mode 100644 index 000000000..97e6d1518 --- /dev/null +++ b/webrtc/base/winfirewall.cc @@ -0,0 +1,155 @@ +/* + * Copyright 2004 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 "webrtc/base/winfirewall.h" + +#include "webrtc/base/win32.h" + +#include +#include + +#define RELEASE(lpUnk) do { \ + if ((lpUnk) != NULL) { \ + (lpUnk)->Release(); \ + (lpUnk) = NULL; \ + } \ +} while (0) + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// WinFirewall +////////////////////////////////////////////////////////////////////// + +WinFirewall::WinFirewall() : mgr_(NULL), policy_(NULL), profile_(NULL) { +} + +WinFirewall::~WinFirewall() { + Shutdown(); +} + +bool WinFirewall::Initialize(HRESULT* result) { + if (mgr_) { + if (result) { + *result = S_OK; + } + return true; + } + + HRESULT hr = CoCreateInstance(__uuidof(NetFwMgr), + 0, CLSCTX_INPROC_SERVER, + __uuidof(INetFwMgr), + reinterpret_cast(&mgr_)); + if (SUCCEEDED(hr) && (mgr_ != NULL)) + hr = mgr_->get_LocalPolicy(&policy_); + if (SUCCEEDED(hr) && (policy_ != NULL)) + hr = policy_->get_CurrentProfile(&profile_); + + if (result) + *result = hr; + return SUCCEEDED(hr) && (profile_ != NULL); +} + +void WinFirewall::Shutdown() { + RELEASE(profile_); + RELEASE(policy_); + RELEASE(mgr_); +} + +bool WinFirewall::Enabled() const { + if (!profile_) + return false; + + VARIANT_BOOL fwEnabled = VARIANT_FALSE; + profile_->get_FirewallEnabled(&fwEnabled); + return (fwEnabled != VARIANT_FALSE); +} + +bool WinFirewall::QueryAuthorized(const char* filename, bool* authorized) + const { + return QueryAuthorizedW(ToUtf16(filename).c_str(), authorized); +} + +bool WinFirewall::QueryAuthorizedW(const wchar_t* filename, bool* authorized) + const { + *authorized = false; + bool success = false; + + if (!profile_) + return false; + + _bstr_t bfilename = filename; + + INetFwAuthorizedApplications* apps = NULL; + HRESULT hr = profile_->get_AuthorizedApplications(&apps); + if (SUCCEEDED(hr) && (apps != NULL)) { + INetFwAuthorizedApplication* app = NULL; + hr = apps->Item(bfilename, &app); + if (SUCCEEDED(hr) && (app != NULL)) { + VARIANT_BOOL fwEnabled = VARIANT_FALSE; + hr = app->get_Enabled(&fwEnabled); + app->Release(); + + if (SUCCEEDED(hr)) { + success = true; + *authorized = (fwEnabled != VARIANT_FALSE); + } + } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + // No entry in list of authorized apps + success = true; + } else { + // Unexpected error + } + apps->Release(); + } + + return success; +} + +bool WinFirewall::AddApplication(const char* filename, + const char* friendly_name, + bool authorized, + HRESULT* result) { + return AddApplicationW(ToUtf16(filename).c_str(), + ToUtf16(friendly_name).c_str(), authorized, result); +} + +bool WinFirewall::AddApplicationW(const wchar_t* filename, + const wchar_t* friendly_name, + bool authorized, + HRESULT* result) { + INetFwAuthorizedApplications* apps = NULL; + HRESULT hr = profile_->get_AuthorizedApplications(&apps); + if (SUCCEEDED(hr) && (apps != NULL)) { + INetFwAuthorizedApplication* app = NULL; + hr = CoCreateInstance(__uuidof(NetFwAuthorizedApplication), + 0, CLSCTX_INPROC_SERVER, + __uuidof(INetFwAuthorizedApplication), + reinterpret_cast(&app)); + if (SUCCEEDED(hr) && (app != NULL)) { + _bstr_t bstr = filename; + hr = app->put_ProcessImageFileName(bstr); + bstr = friendly_name; + if (SUCCEEDED(hr)) + hr = app->put_Name(bstr); + if (SUCCEEDED(hr)) + hr = app->put_Enabled(authorized ? VARIANT_TRUE : VARIANT_FALSE); + if (SUCCEEDED(hr)) + hr = apps->Add(app); + app->Release(); + } + apps->Release(); + } + if (result) + *result = hr; + return SUCCEEDED(hr); +} + +} // namespace rtc diff --git a/webrtc/base/winfirewall.h b/webrtc/base/winfirewall.h new file mode 100644 index 000000000..a74631baf --- /dev/null +++ b/webrtc/base/winfirewall.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 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_BASE_WINFIREWALL_H_ +#define WEBRTC_BASE_WINFIREWALL_H_ + +#ifndef _HRESULT_DEFINED +#define _HRESULT_DEFINED +typedef long HRESULT; // Can't forward declare typedef, but don't need all win +#endif // !_HRESULT_DEFINED + +struct INetFwMgr; +struct INetFwPolicy; +struct INetFwProfile; + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// WinFirewall +////////////////////////////////////////////////////////////////////// + +class WinFirewall { + public: + WinFirewall(); + ~WinFirewall(); + + bool Initialize(HRESULT* result); + void Shutdown(); + + bool Enabled() const; + bool QueryAuthorized(const char* filename, bool* authorized) const; + bool QueryAuthorizedW(const wchar_t* filename, bool* authorized) const; + + bool AddApplication(const char* filename, const char* friendly_name, + bool authorized, HRESULT* result); + bool AddApplicationW(const wchar_t* filename, const wchar_t* friendly_name, + bool authorized, HRESULT* result); + + private: + INetFwMgr* mgr_; + INetFwPolicy* policy_; + INetFwProfile* profile_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace rtc + +#endif // WEBRTC_BASE_WINFIREWALL_H_ diff --git a/webrtc/base/winfirewall_unittest.cc b/webrtc/base/winfirewall_unittest.cc new file mode 100644 index 000000000..e5c3d6ac1 --- /dev/null +++ b/webrtc/base/winfirewall_unittest.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2010 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 "webrtc/base/gunit.h" +#include "webrtc/base/winfirewall.h" + +#include + +namespace rtc { + +TEST(WinFirewallTest, ReadStatus) { + ::CoInitialize(NULL); + WinFirewall fw; + HRESULT hr; + bool authorized; + + EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized)); + EXPECT_TRUE(fw.Initialize(&hr)); + EXPECT_EQ(S_OK, hr); + + EXPECT_TRUE(fw.QueryAuthorized("bogus.exe", &authorized)); + + // Unless we mock out INetFwMgr we can't really have an expectation either way + // about whether we're authorized. It will depend on the settings of the + // machine running the test. Same goes for AddApplication. + + fw.Shutdown(); + EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized)); + + ::CoUninitialize(); +} + +} // namespace rtc diff --git a/webrtc/base/winping.cc b/webrtc/base/winping.cc new file mode 100644 index 000000000..cbb0847bb --- /dev/null +++ b/webrtc/base/winping.cc @@ -0,0 +1,359 @@ +/* + * Copyright 2004 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 "webrtc/base/winping.h" + +#include +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/ipaddress.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Found in IPExport.h +////////////////////////////////////////////////////////////////////// + +typedef struct icmp_echo_reply { + ULONG Address; // Replying address + ULONG Status; // Reply IP_STATUS + ULONG RoundTripTime; // RTT in milliseconds + USHORT DataSize; // Reply data size in bytes + USHORT Reserved; // Reserved for system use + PVOID Data; // Pointer to the reply data + struct ip_option_information Options; // Reply options +} ICMP_ECHO_REPLY, * PICMP_ECHO_REPLY; + +typedef struct icmpv6_echo_reply_lh { + sockaddr_in6 Address; + ULONG Status; + unsigned int RoundTripTime; +} ICMPV6_ECHO_REPLY, *PICMPV6_ECHO_REPLY; + +// +// IP_STATUS codes returned from IP APIs +// + +#define IP_STATUS_BASE 11000 + +#define IP_SUCCESS 0 +#define IP_BUF_TOO_SMALL (IP_STATUS_BASE + 1) +#define IP_DEST_NET_UNREACHABLE (IP_STATUS_BASE + 2) +#define IP_DEST_HOST_UNREACHABLE (IP_STATUS_BASE + 3) +#define IP_DEST_PROT_UNREACHABLE (IP_STATUS_BASE + 4) +#define IP_DEST_PORT_UNREACHABLE (IP_STATUS_BASE + 5) +#define IP_NO_RESOURCES (IP_STATUS_BASE + 6) +#define IP_BAD_OPTION (IP_STATUS_BASE + 7) +#define IP_HW_ERROR (IP_STATUS_BASE + 8) +#define IP_PACKET_TOO_BIG (IP_STATUS_BASE + 9) +#define IP_REQ_TIMED_OUT (IP_STATUS_BASE + 10) +#define IP_BAD_REQ (IP_STATUS_BASE + 11) +#define IP_BAD_ROUTE (IP_STATUS_BASE + 12) +#define IP_TTL_EXPIRED_TRANSIT (IP_STATUS_BASE + 13) +#define IP_TTL_EXPIRED_REASSEM (IP_STATUS_BASE + 14) +#define IP_PARAM_PROBLEM (IP_STATUS_BASE + 15) +#define IP_SOURCE_QUENCH (IP_STATUS_BASE + 16) +#define IP_OPTION_TOO_BIG (IP_STATUS_BASE + 17) +#define IP_BAD_DESTINATION (IP_STATUS_BASE + 18) + +#define IP_ADDR_DELETED (IP_STATUS_BASE + 19) +#define IP_SPEC_MTU_CHANGE (IP_STATUS_BASE + 20) +#define IP_MTU_CHANGE (IP_STATUS_BASE + 21) +#define IP_UNLOAD (IP_STATUS_BASE + 22) +#define IP_ADDR_ADDED (IP_STATUS_BASE + 23) +#define IP_MEDIA_CONNECT (IP_STATUS_BASE + 24) +#define IP_MEDIA_DISCONNECT (IP_STATUS_BASE + 25) +#define IP_BIND_ADAPTER (IP_STATUS_BASE + 26) +#define IP_UNBIND_ADAPTER (IP_STATUS_BASE + 27) +#define IP_DEVICE_DOES_NOT_EXIST (IP_STATUS_BASE + 28) +#define IP_DUPLICATE_ADDRESS (IP_STATUS_BASE + 29) +#define IP_INTERFACE_METRIC_CHANGE (IP_STATUS_BASE + 30) +#define IP_RECONFIG_SECFLTR (IP_STATUS_BASE + 31) +#define IP_NEGOTIATING_IPSEC (IP_STATUS_BASE + 32) +#define IP_INTERFACE_WOL_CAPABILITY_CHANGE (IP_STATUS_BASE + 33) +#define IP_DUPLICATE_IPADD (IP_STATUS_BASE + 34) + +#define IP_GENERAL_FAILURE (IP_STATUS_BASE + 50) +#define MAX_IP_STATUS IP_GENERAL_FAILURE +#define IP_PENDING (IP_STATUS_BASE + 255) + +// +// Values used in the IP header Flags field. +// +#define IP_FLAG_DF 0x2 // Don't fragment this packet. + +// +// Supported IP Option Types. +// +// These types define the options which may be used in the OptionsData field +// of the ip_option_information structure. See RFC 791 for a complete +// description of each. +// +#define IP_OPT_EOL 0 // End of list option +#define IP_OPT_NOP 1 // No operation +#define IP_OPT_SECURITY 0x82 // Security option +#define IP_OPT_LSRR 0x83 // Loose source route +#define IP_OPT_SSRR 0x89 // Strict source route +#define IP_OPT_RR 0x7 // Record route +#define IP_OPT_TS 0x44 // Timestamp +#define IP_OPT_SID 0x88 // Stream ID (obsolete) +#define IP_OPT_ROUTER_ALERT 0x94 // Router Alert Option + +#define MAX_OPT_SIZE 40 // Maximum length of IP options in bytes + +////////////////////////////////////////////////////////////////////// +// Global Constants and Types +////////////////////////////////////////////////////////////////////// + +const char * const ICMP_DLL_NAME = "Iphlpapi.dll"; +const char * const ICMP_CREATE_FUNC = "IcmpCreateFile"; +const char * const ICMP_CLOSE_FUNC = "IcmpCloseHandle"; +const char * const ICMP_SEND_FUNC = "IcmpSendEcho"; +const char * const ICMP6_CREATE_FUNC = "Icmp6CreateFile"; +const char * const ICMP6_CLOSE_FUNC = "Icmp6CloseHandle"; +const char * const ICMP6_SEND_FUNC = "Icmp6SendEcho2"; + +inline uint32 ReplySize(uint32 data_size, int family) { + if (family == AF_INET) { + // A ping error message is 8 bytes long, so make sure we allow for at least + // 8 bytes of reply data. + return sizeof(ICMP_ECHO_REPLY) + rtc::_max(8, data_size); + } else if (family == AF_INET6) { + // Per MSDN, Send6IcmpEcho2 needs at least one ICMPV6_ECHO_REPLY, + // 8 bytes for ICMP header, _and_ an IO_BLOCK_STATUS (2 pointers), + // in addition to the data size. + return sizeof(ICMPV6_ECHO_REPLY) + data_size + 8 + (2 * sizeof(DWORD*)); + } else { + return 0; + } +} + +////////////////////////////////////////////////////////////////////// +// WinPing +////////////////////////////////////////////////////////////////////// + +WinPing::WinPing() + : dll_(0), hping_(INVALID_HANDLE_VALUE), create_(0), close_(0), send_(0), + create6_(0), send6_(0), data_(0), dlen_(0), reply_(0), + rlen_(0), valid_(false) { + + dll_ = LoadLibraryA(ICMP_DLL_NAME); + if (!dll_) { + LOG(LERROR) << "LoadLibrary: " << GetLastError(); + return; + } + + create_ = (PIcmpCreateFile) GetProcAddress(dll_, ICMP_CREATE_FUNC); + close_ = (PIcmpCloseHandle) GetProcAddress(dll_, ICMP_CLOSE_FUNC); + send_ = (PIcmpSendEcho) GetProcAddress(dll_, ICMP_SEND_FUNC); + if (!create_ || !close_ || !send_) { + LOG(LERROR) << "GetProcAddress(ICMP_*): " << GetLastError(); + return; + } + hping_ = create_(); + if (hping_ == INVALID_HANDLE_VALUE) { + LOG(LERROR) << "IcmpCreateFile: " << GetLastError(); + return; + } + + if (HasIPv6Enabled()) { + create6_ = (PIcmp6CreateFile) GetProcAddress(dll_, ICMP6_CREATE_FUNC); + send6_ = (PIcmp6SendEcho2) GetProcAddress(dll_, ICMP6_SEND_FUNC); + if (!create6_ || !send6_) { + LOG(LERROR) << "GetProcAddress(ICMP6_*): " << GetLastError(); + return; + } + hping6_ = create6_(); + if (hping6_ == INVALID_HANDLE_VALUE) { + LOG(LERROR) << "Icmp6CreateFile: " << GetLastError(); + } + } + + dlen_ = 0; + rlen_ = ReplySize(dlen_, AF_INET); + data_ = new char[dlen_]; + reply_ = new char[rlen_]; + + valid_ = true; +} + +WinPing::~WinPing() { + if ((hping_ != INVALID_HANDLE_VALUE) && close_) { + if (!close_(hping_)) + LOG(WARNING) << "IcmpCloseHandle: " << GetLastError(); + } + if ((hping6_ != INVALID_HANDLE_VALUE) && close_) { + if (!close_(hping6_)) { + LOG(WARNING) << "Icmp6CloseHandle: " << GetLastError(); + } + } + + if (dll_) + FreeLibrary(dll_); + + delete[] data_; + delete[] reply_; +} + +WinPing::PingResult WinPing::Ping( + IPAddress ip, uint32 data_size, uint32 timeout, uint8 ttl, + bool allow_fragments) { + + if (data_size == 0 || timeout == 0 || ttl == 0) { + LOG(LERROR) << "IcmpSendEcho: data_size/timeout/ttl is 0."; + return PING_INVALID_PARAMS; + } + + assert(IsValid()); + + IP_OPTION_INFORMATION ipopt; + memset(&ipopt, 0, sizeof(ipopt)); + if (!allow_fragments) + ipopt.Flags |= IP_FLAG_DF; + ipopt.Ttl = ttl; + + uint32 reply_size = ReplySize(data_size, ip.family()); + + if (data_size > dlen_) { + delete [] data_; + dlen_ = data_size; + data_ = new char[dlen_]; + memset(data_, 'z', dlen_); + } + + if (reply_size > rlen_) { + delete [] reply_; + rlen_ = reply_size; + reply_ = new char[rlen_]; + } + DWORD result = 0; + if (ip.family() == AF_INET) { + result = send_(hping_, ip.ipv4_address().S_un.S_addr, + data_, uint16(data_size), &ipopt, + reply_, reply_size, timeout); + } else if (ip.family() == AF_INET6) { + sockaddr_in6 src = {0}; + sockaddr_in6 dst = {0}; + src.sin6_family = AF_INET6; + dst.sin6_family = AF_INET6; + dst.sin6_addr = ip.ipv6_address(); + result = send6_(hping6_, NULL, NULL, NULL, + &src, &dst, + data_, int16(data_size), &ipopt, + reply_, reply_size, timeout); + } + if (result == 0) { + DWORD error = GetLastError(); + if (error == IP_PACKET_TOO_BIG) + return PING_TOO_LARGE; + if (error == IP_REQ_TIMED_OUT) + return PING_TIMEOUT; + LOG(LERROR) << "IcmpSendEcho(" << ip.ToSensitiveString() + << ", " << data_size << "): " << error; + return PING_FAIL; + } + + return PING_SUCCESS; +} + +////////////////////////////////////////////////////////////////////// +// Microsoft Documenation +////////////////////////////////////////////////////////////////////// +// +// Routine Name: +// +// IcmpCreateFile +// +// Routine Description: +// +// Opens a handle on which ICMP Echo Requests can be issued. +// +// Arguments: +// +// None. +// +// Return Value: +// +// An open file handle or INVALID_HANDLE_VALUE. Extended error information +// is available by calling GetLastError(). +// +////////////////////////////////////////////////////////////////////// +// +// Routine Name: +// +// IcmpCloseHandle +// +// Routine Description: +// +// Closes a handle opened by ICMPOpenFile. +// +// Arguments: +// +// IcmpHandle - The handle to close. +// +// Return Value: +// +// TRUE if the handle was closed successfully, otherwise FALSE. Extended +// error information is available by calling GetLastError(). +// +////////////////////////////////////////////////////////////////////// +// +// Routine Name: +// +// IcmpSendEcho +// +// Routine Description: +// +// Sends an ICMP Echo request and returns any replies. The +// call returns when the timeout has expired or the reply buffer +// is filled. +// +// Arguments: +// +// IcmpHandle - An open handle returned by ICMPCreateFile. +// +// DestinationAddress - The destination of the echo request. +// +// RequestData - A buffer containing the data to send in the +// request. +// +// RequestSize - The number of bytes in the request data buffer. +// +// RequestOptions - Pointer to the IP header options for the request. +// May be NULL. +// +// ReplyBuffer - A buffer to hold any replies to the request. +// On return, the buffer will contain an array of +// ICMP_ECHO_REPLY structures followed by the +// options and data for the replies. The buffer +// should be large enough to hold at least one +// ICMP_ECHO_REPLY structure plus +// MAX(RequestSize, 8) bytes of data since an ICMP +// error message contains 8 bytes of data. +// +// ReplySize - The size in bytes of the reply buffer. +// +// Timeout - The time in milliseconds to wait for replies. +// +// Return Value: +// +// Returns the number of ICMP_ECHO_REPLY structures stored in ReplyBuffer. +// The status of each reply is contained in the structure. If the return +// value is zero, extended error information is available via +// GetLastError(). +// +////////////////////////////////////////////////////////////////////// + +} // namespace rtc diff --git a/webrtc/base/winping.h b/webrtc/base/winping.h new file mode 100644 index 000000000..75f82b7b4 --- /dev/null +++ b/webrtc/base/winping.h @@ -0,0 +1,103 @@ +/* + * Copyright 2004 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_BASE_WINPING_H__ +#define WEBRTC_BASE_WINPING_H__ + +#if defined(WEBRTC_WIN) + +#include "webrtc/base/win32.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/IPAddress.h" + +namespace rtc { + +// This class wraps a Win32 API for doing ICMP pinging. This API, unlike the +// the normal socket APIs (as implemented on Win9x), will return an error if +// an ICMP packet with the dont-fragment bit set is too large. This means this +// class can be used to detect the MTU to a given address. + +typedef struct ip_option_information { + UCHAR Ttl; // Time To Live + UCHAR Tos; // Type Of Service + UCHAR Flags; // IP header flags + UCHAR OptionsSize; // Size in bytes of options data + PUCHAR OptionsData; // Pointer to options data +} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION; + +typedef HANDLE (WINAPI *PIcmpCreateFile)(); + +typedef BOOL (WINAPI *PIcmpCloseHandle)(HANDLE icmp_handle); + +typedef HANDLE (WINAPI *PIcmp6CreateFile)(); + +typedef BOOL (WINAPI *PIcmp6CloseHandle)(HANDLE icmp_handle); + +typedef DWORD (WINAPI *PIcmpSendEcho)( + HANDLE IcmpHandle, + ULONG DestinationAddress, + LPVOID RequestData, + WORD RequestSize, + PIP_OPTION_INFORMATION RequestOptions, + LPVOID ReplyBuffer, + DWORD ReplySize, + DWORD Timeout); + +typedef DWORD (WINAPI *PIcmp6SendEcho2)( + HANDLE IcmpHandle, + HANDLE Event, + FARPROC ApcRoutine, + PVOID ApcContext, + struct sockaddr_in6 *SourceAddress, + struct sockaddr_in6 *DestinationAddress, + LPVOID RequestData, + WORD RequestSize, + PIP_OPTION_INFORMATION RequestOptions, + LPVOID ReplyBuffer, + DWORD ReplySize, + DWORD Timeout +); + +class WinPing { +public: + WinPing(); + ~WinPing(); + + // Determines whether the class was initialized correctly. + bool IsValid() { return valid_; } + + // Attempts to send a ping with the given parameters. + enum PingResult { PING_FAIL, PING_INVALID_PARAMS, + PING_TOO_LARGE, PING_TIMEOUT, PING_SUCCESS }; + PingResult Ping( + IPAddress ip, uint32 data_size, uint32 timeout_millis, uint8 ttl, + bool allow_fragments); + +private: + HMODULE dll_; + HANDLE hping_; + HANDLE hping6_; + PIcmpCreateFile create_; + PIcmpCloseHandle close_; + PIcmpSendEcho send_; + PIcmp6CreateFile create6_; + PIcmp6SendEcho2 send6_; + char* data_; + uint32 dlen_; + char* reply_; + uint32 rlen_; + bool valid_; +}; + +} // namespace rtc + +#endif // WEBRTC_WIN + +#endif // WEBRTC_BASE_WINPING_H__ diff --git a/webrtc/base/worker.cc b/webrtc/base/worker.cc new file mode 100644 index 000000000..1b48b9b1d --- /dev/null +++ b/webrtc/base/worker.cc @@ -0,0 +1,75 @@ +/* + * Copyright 2004 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 "webrtc/base/worker.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +enum { + MSG_HAVEWORK = 0, +}; + +Worker::Worker() : worker_thread_(NULL) {} + +Worker::~Worker() { + // We need to already be stopped before being destroyed. We cannot call + // StopWork() from here because the subclass's data has already been + // destructed, so OnStop() cannot be called. + ASSERT(!worker_thread_); +} + +bool Worker::StartWork() { + rtc::Thread *me = rtc::Thread::Current(); + if (worker_thread_) { + if (worker_thread_ == me) { + // Already working on this thread, so nothing to do. + return true; + } else { + LOG(LS_ERROR) << "Automatically switching threads is not supported"; + ASSERT(false); + return false; + } + } + worker_thread_ = me; + OnStart(); + return true; +} + +bool Worker::StopWork() { + if (!worker_thread_) { + // Already not working, so nothing to do. + return true; + } else if (worker_thread_ != rtc::Thread::Current()) { + LOG(LS_ERROR) << "Stopping from a different thread is not supported"; + ASSERT(false); + return false; + } + OnStop(); + worker_thread_->Clear(this, MSG_HAVEWORK); + worker_thread_ = NULL; + return true; +} + +void Worker::HaveWork() { + ASSERT(worker_thread_ != NULL); + worker_thread_->Post(this, MSG_HAVEWORK); +} + +void Worker::OnMessage(rtc::Message *msg) { + ASSERT(msg->message_id == MSG_HAVEWORK); + ASSERT(worker_thread_ == rtc::Thread::Current()); + OnHaveWork(); +} + +} // namespace rtc diff --git a/webrtc/base/worker.h b/webrtc/base/worker.h new file mode 100644 index 000000000..694a78057 --- /dev/null +++ b/webrtc/base/worker.h @@ -0,0 +1,72 @@ +/* + * Copyright 2004 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_BASE_WORKER_H_ +#define WEBRTC_BASE_WORKER_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/messagehandler.h" + +namespace rtc { + +class Thread; + +// A worker is an object that performs some specific long-lived task in an +// event-driven manner. +// The only method that should be considered thread-safe is HaveWork(), which +// allows you to signal the availability of work from any thread. All other +// methods are thread-hostile. Specifically: +// StartWork()/StopWork() should not be called concurrently with themselves or +// each other, and it is an error to call them while the worker is running on +// a different thread. +// The destructor may not be called if the worker is currently running +// (regardless of the thread), but you can call StopWork() in a subclass's +// destructor. +class Worker : private MessageHandler { + public: + Worker(); + + // Destroys this Worker, but it must have already been stopped via StopWork(). + virtual ~Worker(); + + // Attaches the worker to the current thread and begins processing work if not + // already doing so. + bool StartWork(); + // Stops processing work if currently doing so and detaches from the current + // thread. + bool StopWork(); + + protected: + // Signal that work is available to be done. May only be called within the + // lifetime of a OnStart()/OnStop() pair. + void HaveWork(); + + // These must be implemented by a subclass. + // Called on the worker thread to start working. + virtual void OnStart() = 0; + // Called on the worker thread when work has been signalled via HaveWork(). + virtual void OnHaveWork() = 0; + // Called on the worker thread to stop working. Upon return, any pending + // OnHaveWork() calls are cancelled. + virtual void OnStop() = 0; + + private: + // Inherited from MessageHandler. + virtual void OnMessage(Message *msg); + + // The thread that is currently doing the work. + Thread *worker_thread_; + + DISALLOW_COPY_AND_ASSIGN(Worker); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_WORKER_H_ diff --git a/webrtc/build/OWNERS b/webrtc/build/OWNERS index 7e75a0fe0..2978427ca 100644 --- a/webrtc/build/OWNERS +++ b/webrtc/build/OWNERS @@ -2,3 +2,8 @@ fischman@webrtc.org kjellander@webrtc.org wu@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/build/apk_tests.gyp b/webrtc/build/apk_tests.gyp index 1df046925..01859753e 100644 --- a/webrtc/build/apk_tests.gyp +++ b/webrtc/build/apk_tests.gyp @@ -88,20 +88,6 @@ '../../../build/apk_test.gypi', ], }, - { - 'target_name': 'neteq_unittests_apk', - 'type': 'none', - 'variables': { - 'test_suite_name': 'neteq_unittests', - 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)neteq_unittests<(SHARED_LIB_SUFFIX)', - }, - 'dependencies': [ - '<(webrtc_root)/modules/modules.gyp:neteq_unittests', - ], - 'includes': [ - '../../../build/apk_test.gypi', - ], - }, { 'target_name': 'system_wrappers_unittests_apk', 'type': 'none', @@ -213,7 +199,36 @@ 'includes': [ '../../../build/apk_test.gypi', ], - } + }, + { + 'target_name': 'video_capture_tests_apk', + 'type': 'none', + 'variables': { + 'test_suite_name': 'video_capture_tests', + 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)video_capture_tests<(SHARED_LIB_SUFFIX)', + }, + 'dependencies': [ + '<(webrtc_root)/modules/modules.gyp:video_capture_tests', + 'video_capture_java', + ], + 'includes': [ + '../../../build/apk_test.gypi', + ], + }, + { + # Used only by video_capture_tests_apk above, and impossible to use in the + # standalone build, which is why it's declared here instead of under + # modules/video_capture/ (to avoid the need for a forked _noop.gyp file + # like this file has; see comment at the top of this file). + 'target_name': 'video_capture_java', + 'type': 'none', + 'variables': { + 'java_in_dir': '<(webrtc_root)/modules/video_capture/android/java', + }, + 'includes': [ + '../../../build/java.gypi', + ], + }, ], } diff --git a/webrtc/build/apk_tests_noop.gyp b/webrtc/build/apk_tests_noop.gyp index ed64863d0..3523e79bb 100644 --- a/webrtc/build/apk_tests_noop.gyp +++ b/webrtc/build/apk_tests_noop.gyp @@ -29,10 +29,6 @@ 'target_name': 'modules_unittests_apk', 'type': 'none', }, - { - 'target_name': 'neteq_unittests_apk', - 'type': 'none', - }, { 'target_name': 'system_wrappers_unittests_apk', 'type': 'none', @@ -65,5 +61,9 @@ 'target_name': 'audio_codec_speed_tests_apk', 'type': 'none', }, + { + 'target_name': 'video_capture_tests_apk', + 'type': 'none', + }, ], } diff --git a/webrtc/build/common.gypi b/webrtc/build/common.gypi index a323f1138..b7b4a23e3 100644 --- a/webrtc/build/common.gypi +++ b/webrtc/build/common.gypi @@ -55,6 +55,12 @@ 'webrtc_vp8_dir%': '<(webrtc_vp8_dir)', 'include_opus%': '<(include_opus)', 'rbe_components_path%': '<(rbe_components_path)', + 'external_libraries%': '0', + 'json_root%': '<(DEPTH)/third_party/jsoncpp/source/include/', + # openssl needs to be defined or gyp will complain. Is is only used when + # when providing external libraries so just use current directory as a + # placeholder. + 'ssl_root%': '.', # The Chromium common.gypi we use treats all gyp files without # chromium_code==1 as third party code. This disables many of the @@ -86,9 +92,14 @@ 'enable_protobuf%': 1, # Disable these to not build components which can be externally provided. + 'build_json%': 1, 'build_libjpeg%': 1, 'build_libyuv%': 1, 'build_libvpx%': 1, + 'build_ssl%': 1, + + # Disable by default + 'have_dbus_glib%': 0, # Enable to use the Mozilla internal settings. 'build_with_mozilla%': 0, @@ -137,7 +148,6 @@ ['OS=="ios"', { 'build_libjpeg%': 0, 'enable_protobuf%': 0, - 'include_tests%': 0, }], ['target_arch=="arm" or target_arch=="armv7"', { 'prefer_fixed_point%': 1, @@ -146,9 +156,6 @@ }, 'target_defaults': { 'include_dirs': [ - # Allow includes to be prefixed with webrtc/ in case it is not an - # immediate subdirectory of <(DEPTH). - '../..', # To include the top-level directory when building in Chrome, so we can # use full paths (e.g. headers inside testing/ or third_party/). '<(DEPTH)', @@ -163,6 +170,14 @@ 'WEBRTC_MOZILLA_BUILD', ], }], + ['have_dbus_glib==1', { + 'defines': [ + 'HAVE_DBUS_GLIB', + ], + 'cflags': [ + '& video_streams, + const void* encoder_settings) = 0; virtual void DestroyVideoSendStream(VideoSendStream* send_stream) = 0; diff --git a/webrtc/common.gyp b/webrtc/common.gyp new file mode 100644 index 000000000..b6b6354ab --- /dev/null +++ b/webrtc/common.gyp @@ -0,0 +1,20 @@ +# Copyright (c) 2014 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. +{ + 'includes': ['build/common.gypi'], + 'targets': [ + { + 'target_name': 'webrtc_common', + 'type': 'static_library', + 'sources': [ + 'config.h', + 'config.cc', + ], + }, + ], +} diff --git a/webrtc/common_audio/BUILD.gn b/webrtc/common_audio/BUILD.gn new file mode 100644 index 000000000..4dcc32265 --- /dev/null +++ b/webrtc/common_audio/BUILD.gn @@ -0,0 +1,26 @@ +# Copyright (c) 2014 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. + +import("//build/config/arm.gni") +import("../build/webrtc.gni") + +source_set("common_audio") { + # TODO(andrew): Implement. +} + +if (cpu_arch == "x86" || cpu_arch == "x64") { + source_set("common_audio_sse2") { + # TODO(andrew): Implement. + } +} + +if (cpu_arch == "arm" && arm_version == 7) { + source_set("common_audio_neon") { + # TODO(andrew): Implement. + } +} diff --git a/webrtc/common_audio/OWNERS b/webrtc/common_audio/OWNERS index 84582f2c9..98ec4728b 100644 --- a/webrtc/common_audio/OWNERS +++ b/webrtc/common_audio/OWNERS @@ -2,3 +2,12 @@ bjornv@webrtc.org tina.legrand@webrtc.org jan.skoglund@webrtc.org andrew@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/common_audio/common_audio.gyp b/webrtc/common_audio/common_audio.gyp index c3f141b44..3bed4e4d8 100644 --- a/webrtc/common_audio/common_audio.gyp +++ b/webrtc/common_audio/common_audio.gyp @@ -215,7 +215,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], @@ -226,7 +226,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'targets': [ { 'target_name': 'common_audio_unittests_apk_target', diff --git a/webrtc/common_audio/common_audio_unittests.isolate b/webrtc/common_audio/common_audio_unittests.isolate index 49f1e984b..cc5e6ab45 100644 --- a/webrtc/common_audio/common_audio_unittests.isolate +++ b/webrtc/common_audio/common_audio_unittests.isolate @@ -8,27 +8,25 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../data/', - '../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/common_audio_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/common_audio_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/common_audio/resampler/include/push_resampler.h b/webrtc/common_audio/resampler/include/push_resampler.h index 770a99261..f04dc0f3e 100644 --- a/webrtc/common_audio/resampler/include/push_resampler.h +++ b/webrtc/common_audio/resampler/include/push_resampler.h @@ -20,6 +20,7 @@ class PushSincResampler; // Wraps PushSincResampler to provide stereo support. // TODO(ajm): add support for an arbitrary number of channels. +template class PushResampler { public: PushResampler(); @@ -32,22 +33,18 @@ class PushResampler { // Returns the total number of samples provided in destination (e.g. 32 kHz, // 2 channel audio gives 640 samples). - int Resample(const int16_t* src, int src_length, int16_t* dst, - int dst_capacity); + int Resample(const T* src, int src_length, T* dst, int dst_capacity); private: - int ResampleSinc(const int16_t* src, int src_length, int16_t* dst, - int dst_capacity); - scoped_ptr sinc_resampler_; scoped_ptr sinc_resampler_right_; int src_sample_rate_hz_; int dst_sample_rate_hz_; int num_channels_; - scoped_array src_left_; - scoped_array src_right_; - scoped_array dst_left_; - scoped_array dst_right_; + scoped_ptr src_left_; + scoped_ptr src_right_; + scoped_ptr dst_left_; + scoped_ptr dst_right_; }; } // namespace webrtc diff --git a/webrtc/common_audio/resampler/push_resampler.cc b/webrtc/common_audio/resampler/push_resampler.cc index 29944187d..973c8f74f 100644 --- a/webrtc/common_audio/resampler/push_resampler.cc +++ b/webrtc/common_audio/resampler/push_resampler.cc @@ -18,22 +18,21 @@ namespace webrtc { -PushResampler::PushResampler() +template +PushResampler::PushResampler() : src_sample_rate_hz_(0), dst_sample_rate_hz_(0), - num_channels_(0), - src_left_(NULL), - src_right_(NULL), - dst_left_(NULL), - dst_right_(NULL) { + num_channels_(0) { } -PushResampler::~PushResampler() { +template +PushResampler::~PushResampler() { } -int PushResampler::InitializeIfNeeded(int src_sample_rate_hz, - int dst_sample_rate_hz, - int num_channels) { +template +int PushResampler::InitializeIfNeeded(int src_sample_rate_hz, + int dst_sample_rate_hz, + int num_channels) { if (src_sample_rate_hz == src_sample_rate_hz_ && dst_sample_rate_hz == dst_sample_rate_hz_ && num_channels == num_channels_) @@ -53,10 +52,10 @@ int PushResampler::InitializeIfNeeded(int src_sample_rate_hz, sinc_resampler_.reset(new PushSincResampler(src_size_10ms_mono, dst_size_10ms_mono)); if (num_channels_ == 2) { - src_left_.reset(new int16_t[src_size_10ms_mono]); - src_right_.reset(new int16_t[src_size_10ms_mono]); - dst_left_.reset(new int16_t[dst_size_10ms_mono]); - dst_right_.reset(new int16_t[dst_size_10ms_mono]); + src_left_.reset(new T[src_size_10ms_mono]); + src_right_.reset(new T[src_size_10ms_mono]); + dst_left_.reset(new T[dst_size_10ms_mono]); + dst_right_.reset(new T[dst_size_10ms_mono]); sinc_resampler_right_.reset(new PushSincResampler(src_size_10ms_mono, dst_size_10ms_mono)); } @@ -64,8 +63,9 @@ int PushResampler::InitializeIfNeeded(int src_sample_rate_hz, return 0; } -int PushResampler::Resample(const int16_t* src, int src_length, - int16_t* dst, int dst_capacity) { +template +int PushResampler::Resample(const T* src, int src_length, T* dst, + int dst_capacity) { const int src_size_10ms = src_sample_rate_hz_ * num_channels_ / 100; const int dst_size_10ms = dst_sample_rate_hz_ * num_channels_ / 100; if (src_length != src_size_10ms || dst_capacity < dst_size_10ms) @@ -74,13 +74,13 @@ int PushResampler::Resample(const int16_t* src, int src_length, if (src_sample_rate_hz_ == dst_sample_rate_hz_) { // The old resampler provides this memcpy facility in the case of matching // sample rates, so reproduce it here for the sinc resampler. - memcpy(dst, src, src_length * sizeof(int16_t)); + memcpy(dst, src, src_length * sizeof(T)); return src_length; } if (num_channels_ == 2) { const int src_length_mono = src_length / num_channels_; const int dst_capacity_mono = dst_capacity / num_channels_; - int16_t* deinterleaved[] = {src_left_.get(), src_right_.get()}; + T* deinterleaved[] = {src_left_.get(), src_right_.get()}; Deinterleave(src, src_length_mono, num_channels_, deinterleaved); int dst_length_mono = @@ -98,4 +98,8 @@ int PushResampler::Resample(const int16_t* src, int src_length, } } +// Explictly generate required instantiations. +template class PushResampler; +template class PushResampler; + } // namespace webrtc diff --git a/webrtc/common_audio/resampler/push_resampler_unittest.cc b/webrtc/common_audio/resampler/push_resampler_unittest.cc index c40923bf9..4449f4c63 100644 --- a/webrtc/common_audio/resampler/push_resampler_unittest.cc +++ b/webrtc/common_audio/resampler/push_resampler_unittest.cc @@ -16,7 +16,7 @@ namespace webrtc { TEST(PushResamplerTest, VerifiesInputParameters) { - PushResampler resampler; + PushResampler resampler; EXPECT_EQ(-1, resampler.InitializeIfNeeded(-1, 16000, 1)); EXPECT_EQ(-1, resampler.InitializeIfNeeded(16000, -1, 1)); EXPECT_EQ(-1, resampler.InitializeIfNeeded(16000, 16000, 0)); diff --git a/webrtc/common_audio/resampler/push_sinc_resampler.cc b/webrtc/common_audio/resampler/push_sinc_resampler.cc index 3469ff3f8..027555902 100644 --- a/webrtc/common_audio/resampler/push_sinc_resampler.cc +++ b/webrtc/common_audio/resampler/push_sinc_resampler.cc @@ -10,6 +10,7 @@ #include "webrtc/common_audio/include/audio_util.h" +#include #include #include "webrtc/common_audio/resampler/push_sinc_resampler.h" diff --git a/webrtc/common_audio/resampler/push_sinc_resampler.h b/webrtc/common_audio/resampler/push_sinc_resampler.h index fa1bb3e71..df724e2e5 100644 --- a/webrtc/common_audio/resampler/push_sinc_resampler.h +++ b/webrtc/common_audio/resampler/push_sinc_resampler.h @@ -11,8 +11,8 @@ #ifndef WEBRTC_COMMON_AUDIO_RESAMPLER_PUSH_SINC_RESAMPLER_H_ #define WEBRTC_COMMON_AUDIO_RESAMPLER_PUSH_SINC_RESAMPLER_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/common_audio/resampler/sinc_resampler.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" @@ -44,6 +44,9 @@ class PushSincResampler : public SincResamplerCallback { virtual void Run(int frames, float* destination) OVERRIDE; SincResampler* get_resampler_for_testing() { return resampler_.get(); } + static float AlgorithmicDelaySeconds(int source_rate_hz) { + return 1.f / source_rate_hz * SincResampler::kKernelSize / 2; + } private: scoped_ptr resampler_; diff --git a/webrtc/common_audio/resampler/sinc_resampler.cc b/webrtc/common_audio/resampler/sinc_resampler.cc index 502993468..84f8125b5 100644 --- a/webrtc/common_audio/resampler/sinc_resampler.cc +++ b/webrtc/common_audio/resampler/sinc_resampler.cc @@ -90,6 +90,7 @@ #include "webrtc/system_wrappers/interface/cpu_features_wrapper.h" #include "webrtc/typedefs.h" +#include #include #include diff --git a/webrtc/common_audio/resampler/sinc_resampler.h b/webrtc/common_audio/resampler/sinc_resampler.h index 8f4d079c0..71ade798c 100644 --- a/webrtc/common_audio/resampler/sinc_resampler.h +++ b/webrtc/common_audio/resampler/sinc_resampler.h @@ -14,8 +14,8 @@ #ifndef WEBRTC_COMMON_AUDIO_RESAMPLER_SINC_RESAMPLER_H_ #define WEBRTC_COMMON_AUDIO_RESAMPLER_SINC_RESAMPLER_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/aligned_malloc.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/test/testsupport/gtest_prod_util.h" #include "webrtc/typedefs.h" diff --git a/webrtc/common_audio/resampler/sinc_resampler_unittest.cc b/webrtc/common_audio/resampler/sinc_resampler_unittest.cc index c085cfc20..97908625d 100644 --- a/webrtc/common_audio/resampler/sinc_resampler_unittest.cc +++ b/webrtc/common_audio/resampler/sinc_resampler_unittest.cc @@ -62,7 +62,7 @@ TEST(SincResamplerTest, ChunkedResample) { static const int kChunks = 2; int max_chunk_size = resampler.ChunkSize() * kChunks; - scoped_array resampled_destination(new float[max_chunk_size]); + scoped_ptr resampled_destination(new float[max_chunk_size]); // Verify requesting ChunkSize() frames causes a single callback. EXPECT_CALL(mock_source, Run(_, _)) @@ -81,7 +81,7 @@ TEST(SincResamplerTest, Flush) { MockSource mock_source; SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, &mock_source); - scoped_array resampled_destination(new float[resampler.ChunkSize()]); + scoped_ptr resampled_destination(new float[resampler.ChunkSize()]); // Fill the resampler with junk data. EXPECT_CALL(mock_source, Run(_, _)) @@ -266,7 +266,7 @@ TEST_P(SincResamplerTest, Resample) { // Force an update to the sample rate ratio to ensure dyanmic sample rate // changes are working correctly. - scoped_array kernel(new float[SincResampler::kKernelStorageSize]); + scoped_ptr kernel(new float[SincResampler::kKernelStorageSize]); memcpy(kernel.get(), resampler.get_kernel_for_testing(), SincResampler::kKernelStorageSize); resampler.SetRatio(M_PI); @@ -278,8 +278,8 @@ TEST_P(SincResamplerTest, Resample) { // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. - scoped_array resampled_destination(new float[output_samples]); - scoped_array pure_destination(new float[output_samples]); + scoped_ptr resampled_destination(new float[output_samples]); + scoped_ptr pure_destination(new float[output_samples]); // Generate resampled signal. resampler.Resample(output_samples, resampled_destination.get()); diff --git a/webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h b/webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h index 6a5cbf5c2..7e9fe75e3 100644 --- a/webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h +++ b/webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h @@ -14,8 +14,8 @@ #ifndef WEBRTC_COMMON_AUDIO_RESAMPLER_SINUSOIDAL_LINEAR_CHIRP_SOURCE_H_ #define WEBRTC_COMMON_AUDIO_RESAMPLER_SINUSOIDAL_LINEAR_CHIRP_SOURCE_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/common_audio/resampler/sinc_resampler.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" namespace webrtc { diff --git a/webrtc/common_audio/signal_processing/complex_fft.c b/webrtc/common_audio/signal_processing/complex_fft.c index 2e8eb323c..c82306473 100644 --- a/webrtc/common_audio/signal_processing/complex_fft.c +++ b/webrtc/common_audio/signal_processing/complex_fft.c @@ -105,7 +105,6 @@ int WebRtcSpl_ComplexFFT(int16_t frfi[], int stages, int mode) #ifdef WEBRTC_ARCH_ARM_V7 int32_t wri = 0; - int32_t frfi_r = 0; __asm __volatile("pkhbt %0, %1, %2, lsl #16" : "=r"(wri) : "r"((int32_t)wr), "r"((int32_t)wi)); #endif @@ -115,19 +114,19 @@ int WebRtcSpl_ComplexFFT(int16_t frfi[], int stages, int mode) j = i + l; #ifdef WEBRTC_ARCH_ARM_V7 + register int32_t frfi_r; __asm __volatile( - "pkhbt %[frfi_r], %[frfi_even], %[frfi_odd], lsl #16\n\t" - "smlsd %[tr32], %[wri], %[frfi_r], %[cfftrnd]\n\t" - "smladx %[ti32], %[wri], %[frfi_r], %[cfftrnd]\n\t" - :[frfi_r]"+r"(frfi_r), - [tr32]"=r"(tr32), - [ti32]"=r"(ti32) - :[frfi_even]"r"((int32_t)frfi[2*j]), - [frfi_odd]"r"((int32_t)frfi[2*j +1]), - [wri]"r"(wri), - [cfftrnd]"r"(CFFTRND) - ); - + "pkhbt %[frfi_r], %[frfi_even], %[frfi_odd]," + " lsl #16\n\t" + "smlsd %[tr32], %[wri], %[frfi_r], %[cfftrnd]\n\t" + "smladx %[ti32], %[wri], %[frfi_r], %[cfftrnd]\n\t" + :[frfi_r]"=&r"(frfi_r), + [tr32]"=&r"(tr32), + [ti32]"=r"(ti32) + :[frfi_even]"r"((int32_t)frfi[2*j]), + [frfi_odd]"r"((int32_t)frfi[2*j +1]), + [wri]"r"(wri), + [cfftrnd]"r"(CFFTRND)); #else tr32 = WEBRTC_SPL_MUL_16_16(wr, frfi[2 * j]) - WEBRTC_SPL_MUL_16_16(wi, frfi[2 * j + 1]) + CFFTRND; @@ -252,7 +251,6 @@ int WebRtcSpl_ComplexIFFT(int16_t frfi[], int stages, int mode) #ifdef WEBRTC_ARCH_ARM_V7 int32_t wri = 0; - int32_t frfi_r = 0; __asm __volatile("pkhbt %0, %1, %2, lsl #16" : "=r"(wri) : "r"((int32_t)wr), "r"((int32_t)wi)); #endif @@ -262,12 +260,13 @@ int WebRtcSpl_ComplexIFFT(int16_t frfi[], int stages, int mode) j = i + l; #ifdef WEBRTC_ARCH_ARM_V7 + register int32_t frfi_r; __asm __volatile( "pkhbt %[frfi_r], %[frfi_even], %[frfi_odd], lsl #16\n\t" "smlsd %[tr32], %[wri], %[frfi_r], %[cifftrnd]\n\t" "smladx %[ti32], %[wri], %[frfi_r], %[cifftrnd]\n\t" - :[frfi_r]"+r"(frfi_r), - [tr32]"=r"(tr32), + :[frfi_r]"=&r"(frfi_r), + [tr32]"=&r"(tr32), [ti32]"=r"(ti32) :[frfi_even]"r"((int32_t)frfi[2*j]), [frfi_odd]"r"((int32_t)frfi[2*j +1]), diff --git a/webrtc/common_audio/signal_processing/include/signal_processing_library.h b/webrtc/common_audio/signal_processing/include/signal_processing_library.h index 60c60bc96..3a5d51cc1 100644 --- a/webrtc/common_audio/signal_processing/include/signal_processing_library.h +++ b/webrtc/common_audio/signal_processing/include/signal_processing_library.h @@ -27,7 +27,6 @@ #define WEBRTC_SPL_WORD32_MAX (int32_t)0x7fffffff #define WEBRTC_SPL_WORD32_MIN (int32_t)0x80000000 #define WEBRTC_SPL_MAX_LPC_ORDER 14 -#define WEBRTC_SPL_MAX_SEED_USED 0x80000000L #define WEBRTC_SPL_MIN(A, B) (A < B ? A : B) // Get min value #define WEBRTC_SPL_MAX(A, B) (A > B ? A : B) // Get max value // TODO(kma/bjorn): For the next two macros, investigate how to correct the code @@ -54,12 +53,8 @@ ((int32_t) ((int32_t)(a) * (int32_t)(b))) #define WEBRTC_SPL_UMUL(a, b) \ ((uint32_t) ((uint32_t)(a) * (uint32_t)(b))) -#define WEBRTC_SPL_UMUL_RSFT16(a, b) \ - ((uint32_t) ((uint32_t)(a) * (uint32_t)(b)) >> 16) #define WEBRTC_SPL_UMUL_16_16(a, b) \ ((uint32_t) (uint16_t)(a) * (uint16_t)(b)) -#define WEBRTC_SPL_UMUL_16_16_RSFT16(a, b) \ - (((uint32_t) (uint16_t)(a) * (uint16_t)(b)) >> 16) #define WEBRTC_SPL_UMUL_32_16(a, b) \ ((uint32_t) ((uint32_t)(a) * (uint16_t)(b))) #define WEBRTC_SPL_UMUL_32_16_RSFT16(a, b) \ @@ -83,11 +78,6 @@ #define WEBRTC_SPL_MUL_32_32_RSFT32(a32a, a32b, b32) \ ((int32_t)(WEBRTC_SPL_MUL_16_32_RSFT16(a32a, b32) \ + (WEBRTC_SPL_MUL_16_32_RSFT16(a32b, b32) >> 16))) -#define WEBRTC_SPL_MUL_32_32_RSFT32BI(a32, b32) \ - ((int32_t)(WEBRTC_SPL_MUL_16_32_RSFT16(( \ - (int16_t)(a32 >> 16)), b32) + \ - (WEBRTC_SPL_MUL_16_32_RSFT16(( \ - (int16_t)((a32 & 0x0000FFFF) >> 1)), b32) >> 15))) #endif #endif @@ -107,8 +97,6 @@ #define WEBRTC_SPL_MUL_16_16_RSFT_WITH_ROUND(a, b, c) \ ((WEBRTC_SPL_MUL_16_16(a, b) + ((int32_t) \ (((int32_t)1) << ((c) - 1)))) >> (c)) -#define WEBRTC_SPL_MUL_16_16_RSFT_WITH_FIXROUND(a, b) \ - ((WEBRTC_SPL_MUL_16_16(a, b) + ((int32_t) (1 << 14))) >> 15) // C + the 32 most significant bits of A * B #define WEBRTC_SPL_SCALEDIFF32(A, B, C) \ @@ -120,10 +108,7 @@ #define WEBRTC_SPL_SUB_SAT_W32(a, b) WebRtcSpl_SubSatW32(a, b) #define WEBRTC_SPL_ADD_SAT_W16(a, b) WebRtcSpl_AddSatW16(a, b) -#define WEBRTC_SPL_SUB_SAT_W16(a, b) WebRtcSpl_SubSatW16(a, b) -// We cannot do casting here due to signed/unsigned problem -#define WEBRTC_SPL_IS_NEG(a) ((a) & 0x80000000) // Shifting with negative numbers allowed // Positive means left shift #define WEBRTC_SPL_SHIFT_W16(x, c) \ @@ -138,8 +123,6 @@ #define WEBRTC_SPL_RSHIFT_W32(x, c) ((x) >> (c)) #define WEBRTC_SPL_LSHIFT_W32(x, c) ((x) << (c)) -#define WEBRTC_SPL_RSHIFT_U16(x, c) ((uint16_t)(x) >> (c)) -#define WEBRTC_SPL_LSHIFT_U16(x, c) ((uint16_t)(x) << (c)) #define WEBRTC_SPL_RSHIFT_U32(x, c) ((uint32_t)(x) >> (c)) #define WEBRTC_SPL_LSHIFT_U32(x, c) ((uint32_t)(x) << (c)) @@ -672,7 +655,6 @@ void WebRtcSpl_SqrtOfOneMinusXSquared(int16_t* in_vector, // Randomization functions. Implementations collected in // randomization_functions.c and descriptions at bottom of this file. -uint32_t WebRtcSpl_IncreaseSeed(uint32_t* seed); int16_t WebRtcSpl_RandU(uint32_t* seed); int16_t WebRtcSpl_RandN(uint32_t* seed); int16_t WebRtcSpl_RandUArray(int16_t* vector, diff --git a/webrtc/common_audio/signal_processing/include/spl_inl_armv7.h b/webrtc/common_audio/signal_processing/include/spl_inl_armv7.h index fdbcb4343..a437a5556 100644 --- a/webrtc/common_audio/signal_processing/include/spl_inl_armv7.h +++ b/webrtc/common_audio/signal_processing/include/spl_inl_armv7.h @@ -41,7 +41,7 @@ static __inline int32_t WEBRTC_SPL_MUL_32_32_RSFT32(int16_t a, __asm __volatile ( "pkhbt %[tmp], %[b], %[a], lsl #16\n\t" "smmulr %[tmp], %[tmp], %[c]\n\t" - :[tmp]"+r"(tmp) + :[tmp]"+&r"(tmp) :[a]"r"(a), [b]"r"(b), [c]"r"(c) diff --git a/webrtc/common_audio/signal_processing/randomization_functions.c b/webrtc/common_audio/signal_processing/randomization_functions.c index e2711fd9f..73f24093c 100644 --- a/webrtc/common_audio/signal_processing/randomization_functions.c +++ b/webrtc/common_audio/signal_processing/randomization_functions.c @@ -11,7 +11,6 @@ /* * This file contains implementations of the randomization functions - * WebRtcSpl_IncreaseSeed() * WebRtcSpl_RandU() * WebRtcSpl_RandN() * WebRtcSpl_RandUArray() @@ -22,6 +21,8 @@ #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" +static const uint32_t kMaxSeedUsed = 0x80000000; + static const int16_t kRandNTable[] = { 9178, -7260, 40, 10189, 4894, -3531, -13779, 14764, -4008, -8884, -8990, 1008, 7368, 5184, 3251, -5817, @@ -89,31 +90,26 @@ static const int16_t kRandNTable[] = { 2374, -5797, 11839, 8940, -11874, 18213, 2855, 10492 }; -uint32_t WebRtcSpl_IncreaseSeed(uint32_t *seed) -{ - seed[0] = (seed[0] * ((int32_t)69069) + 1) & (WEBRTC_SPL_MAX_SEED_USED - 1); - return seed[0]; +static uint32_t IncreaseSeed(uint32_t* seed) { + seed[0] = (seed[0] * ((int32_t)69069) + 1) & (kMaxSeedUsed - 1); + return seed[0]; } -int16_t WebRtcSpl_RandU(uint32_t *seed) -{ - return (int16_t)(WebRtcSpl_IncreaseSeed(seed) >> 16); +int16_t WebRtcSpl_RandU(uint32_t* seed) { + return (int16_t)(IncreaseSeed(seed) >> 16); } -int16_t WebRtcSpl_RandN(uint32_t *seed) -{ - return kRandNTable[WebRtcSpl_IncreaseSeed(seed) >> 23]; +int16_t WebRtcSpl_RandN(uint32_t* seed) { + return kRandNTable[IncreaseSeed(seed) >> 23]; } -// Creates an array of uniformly distributed variables +// Creates an array of uniformly distributed variables. int16_t WebRtcSpl_RandUArray(int16_t* vector, int16_t vector_length, - uint32_t* seed) -{ - int i; - for (i = 0; i < vector_length; i++) - { - vector[i] = WebRtcSpl_RandU(seed); - } - return vector_length; + uint32_t* seed) { + int i; + for (i = 0; i < vector_length; i++) { + vector[i] = WebRtcSpl_RandU(seed); + } + return vector_length; } diff --git a/webrtc/common_audio/signal_processing/real_fft_unittest.cc b/webrtc/common_audio/signal_processing/real_fft_unittest.cc index fa98836b9..9bd35cd68 100644 --- a/webrtc/common_audio/signal_processing/real_fft_unittest.cc +++ b/webrtc/common_audio/signal_processing/real_fft_unittest.cc @@ -10,6 +10,7 @@ #include "webrtc/common_audio/signal_processing/include/real_fft.h" #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" +#include "webrtc/test/testsupport/gtest_disable.h" #include "webrtc/typedefs.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/webrtc/common_audio/signal_processing/signal_processing_unittest.cc b/webrtc/common_audio/signal_processing/signal_processing_unittest.cc index a1bf0d5e8..81ca36945 100644 --- a/webrtc/common_audio/signal_processing/signal_processing_unittest.cc +++ b/webrtc/common_audio/signal_processing/signal_processing_unittest.cc @@ -46,9 +46,7 @@ TEST_F(SplTest, MacroTest) { EXPECT_EQ(-2147483645, WEBRTC_SPL_MUL(a, b)); EXPECT_EQ(2147483651u, WEBRTC_SPL_UMUL(a, b)); b = WEBRTC_SPL_WORD16_MAX >> 1; - EXPECT_EQ(65535u, WEBRTC_SPL_UMUL_RSFT16(a, b)); EXPECT_EQ(1073627139u, WEBRTC_SPL_UMUL_16_16(a, b)); - EXPECT_EQ(16382u, WEBRTC_SPL_UMUL_16_16_RSFT16(a, b)); EXPECT_EQ(4294918147u, WEBRTC_SPL_UMUL_32_16(a, b)); EXPECT_EQ(65535u, WEBRTC_SPL_UMUL_32_16_RSFT16(a, b)); EXPECT_EQ(-49149, WEBRTC_SPL_MUL_16_U16(a, b)); @@ -63,15 +61,12 @@ TEST_F(SplTest, MacroTest) { EXPECT_EQ(-3, WEBRTC_SPL_MUL_16_32_RSFT14(a, b)); EXPECT_EQ(-24, WEBRTC_SPL_MUL_16_32_RSFT11(a, b)); - int a32 = WEBRTC_SPL_WORD32_MAX; int a32a = (WEBRTC_SPL_WORD32_MAX >> 16); int a32b = (WEBRTC_SPL_WORD32_MAX & 0x0000ffff); EXPECT_EQ(5, WEBRTC_SPL_MUL_32_32_RSFT32(a32a, a32b, A)); - EXPECT_EQ(5, WEBRTC_SPL_MUL_32_32_RSFT32BI(a32, A)); EXPECT_EQ(-12288, WEBRTC_SPL_MUL_16_16_RSFT(a, b, 2)); EXPECT_EQ(-12287, WEBRTC_SPL_MUL_16_16_RSFT_WITH_ROUND(a, b, 2)); - EXPECT_EQ(-1, WEBRTC_SPL_MUL_16_16_RSFT_WITH_FIXROUND(a, b)); EXPECT_EQ(16380, WEBRTC_SPL_ADD_SAT_W32(a, b)); EXPECT_EQ(21, WEBRTC_SPL_SAT(a, A, B)); @@ -80,9 +75,6 @@ TEST_F(SplTest, MacroTest) { EXPECT_EQ(16386, WEBRTC_SPL_SUB_SAT_W32(a, b)); EXPECT_EQ(16380, WEBRTC_SPL_ADD_SAT_W16(a, b)); - EXPECT_EQ(16386, WEBRTC_SPL_SUB_SAT_W16(a, b)); - - EXPECT_TRUE(WEBRTC_SPL_IS_NEG(b)); // Shifting with negative numbers allowed int shift_amount = 1; // Workaround compiler warning using variable here. @@ -97,8 +89,6 @@ TEST_F(SplTest, MacroTest) { EXPECT_EQ(8191, WEBRTC_SPL_RSHIFT_W32(a, 1)); EXPECT_EQ(32766, WEBRTC_SPL_LSHIFT_W32(a, 1)); - EXPECT_EQ(8191, WEBRTC_SPL_RSHIFT_U16(a, 1)); - EXPECT_EQ(32766, WEBRTC_SPL_LSHIFT_U16(a, 1)); EXPECT_EQ(8191u, WEBRTC_SPL_RSHIFT_U32(a, 1)); EXPECT_EQ(32766u, WEBRTC_SPL_LSHIFT_U32(a, 1)); @@ -117,16 +107,12 @@ TEST_F(SplTest, MacroTest) { WEBRTC_SPL_WORD32_MAX)); EXPECT_EQ(0x3fffffff, WEBRTC_SPL_MUL_32_32_RSFT32(WEBRTC_SPL_WORD16_MAX, 0xffff, WEBRTC_SPL_WORD32_MAX)); - EXPECT_EQ(0x3fffffff, WEBRTC_SPL_MUL_32_32_RSFT32BI(WEBRTC_SPL_WORD32_MAX, - WEBRTC_SPL_WORD32_MAX)); #else EXPECT_EQ(-1073741823, WEBRTC_SPL_MUL_16_32_RSFT16(WEBRTC_SPL_WORD16_MIN, WEBRTC_SPL_WORD32_MAX)); EXPECT_EQ(0x3fff7ffe, WEBRTC_SPL_MUL_32_32_RSFT32(WEBRTC_SPL_WORD16_MAX, 0xffff, WEBRTC_SPL_WORD32_MAX)); - EXPECT_EQ(0x3ffffffd, WEBRTC_SPL_MUL_32_32_RSFT32BI(WEBRTC_SPL_WORD32_MAX, - WEBRTC_SPL_WORD32_MAX)); #endif } @@ -497,7 +483,7 @@ TEST_F(SplTest, RandTest) { int16_t b16[kVectorSize]; uint32_t bSeed = 100000; - EXPECT_EQ(464449057u, WebRtcSpl_IncreaseSeed(&bSeed)); + EXPECT_EQ(7086, WebRtcSpl_RandU(&bSeed)); EXPECT_EQ(31565, WebRtcSpl_RandU(&bSeed)); EXPECT_EQ(-9786, WebRtcSpl_RandN(&bSeed)); EXPECT_EQ(kVectorSize, WebRtcSpl_RandUArray(b16, kVectorSize, &bSeed)); diff --git a/webrtc/common_audio/signal_processing/spl_init.c b/webrtc/common_audio/signal_processing/spl_init.c index 454e13ba9..762f9e420 100644 --- a/webrtc/common_audio/signal_processing/spl_init.c +++ b/webrtc/common_audio/signal_processing/spl_init.c @@ -65,8 +65,10 @@ static void InitPointersToNeon() { WebRtcSpl_MinValueW32 = WebRtcSpl_MinValueW32Neon; WebRtcSpl_CrossCorrelation = WebRtcSpl_CrossCorrelationNeon; WebRtcSpl_DownsampleFast = WebRtcSpl_DownsampleFastNeon; + /* TODO(henrik.lundin): re-enable NEON when the crash from bug 3243 is + understood. */ WebRtcSpl_ScaleAndAddVectorsWithRound = - WebRtcSpl_ScaleAndAddVectorsWithRoundNeon; + WebRtcSpl_ScaleAndAddVectorsWithRoundC; WebRtcSpl_CreateRealFFT = WebRtcSpl_CreateRealFFTNeon; WebRtcSpl_FreeRealFFT = WebRtcSpl_FreeRealFFTNeon; WebRtcSpl_RealForwardFFT = WebRtcSpl_RealForwardFFTNeon; diff --git a/webrtc/common_audio/vad/include/webrtc_vad.h b/webrtc/common_audio/vad/include/webrtc_vad.h index f6e959fa3..053827303 100644 --- a/webrtc/common_audio/vad/include/webrtc_vad.h +++ b/webrtc/common_audio/vad/include/webrtc_vad.h @@ -34,9 +34,7 @@ int WebRtcVad_Create(VadInst** handle); // Frees the dynamic memory of a specified VAD instance. // // - handle [i] : Pointer to VAD instance that should be freed. -// -// returns : 0 - (OK), -1 - (NULL pointer in) -int WebRtcVad_Free(VadInst* handle); +void WebRtcVad_Free(VadInst* handle); // Initializes a VAD instance. // @@ -71,7 +69,7 @@ int WebRtcVad_set_mode(VadInst* handle, int mode); // returns : 1 - (Active Voice), // 0 - (Non-active Voice), // -1 - (Error) -int WebRtcVad_Process(VadInst* handle, int fs, int16_t* audio_frame, +int WebRtcVad_Process(VadInst* handle, int fs, const int16_t* audio_frame, int frame_length); // Checks for valid combinations of |rate| and |frame_length|. We support 10, diff --git a/webrtc/common_audio/vad/vad_core.c b/webrtc/common_audio/vad/vad_core.c index 80c31f481..98da6eaf0 100644 --- a/webrtc/common_audio/vad/vad_core.c +++ b/webrtc/common_audio/vad/vad_core.c @@ -603,7 +603,7 @@ int WebRtcVad_set_mode_core(VadInstT* self, int mode) { // Calculate VAD decision by first extracting feature values and then calculate // probability for both speech and background noise. -int WebRtcVad_CalcVad48khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad48khz(VadInstT* inst, const int16_t* speech_frame, int frame_length) { int vad; int i; @@ -628,7 +628,7 @@ int WebRtcVad_CalcVad48khz(VadInstT* inst, int16_t* speech_frame, return vad; } -int WebRtcVad_CalcVad32khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad32khz(VadInstT* inst, const int16_t* speech_frame, int frame_length) { int len, vad; @@ -650,7 +650,7 @@ int WebRtcVad_CalcVad32khz(VadInstT* inst, int16_t* speech_frame, return vad; } -int WebRtcVad_CalcVad16khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad16khz(VadInstT* inst, const int16_t* speech_frame, int frame_length) { int len, vad; @@ -666,7 +666,7 @@ int WebRtcVad_CalcVad16khz(VadInstT* inst, int16_t* speech_frame, return vad; } -int WebRtcVad_CalcVad8khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad8khz(VadInstT* inst, const int16_t* speech_frame, int frame_length) { int16_t feature_vector[kNumChannels], total_power; diff --git a/webrtc/common_audio/vad/vad_core.h b/webrtc/common_audio/vad/vad_core.h index d6c1da271..202963d8c 100644 --- a/webrtc/common_audio/vad/vad_core.h +++ b/webrtc/common_audio/vad/vad_core.h @@ -85,9 +85,9 @@ int WebRtcVad_set_mode_core(VadInstT* self, int mode); /**************************************************************************** * WebRtcVad_CalcVad48khz(...) - * WebRtcVad_CalcVad32khz(...) - * WebRtcVad_CalcVad16khz(...) - * WebRtcVad_CalcVad8khz(...) + * WebRtcVad_CalcVad32khz(...) + * WebRtcVad_CalcVad16khz(...) + * WebRtcVad_CalcVad8khz(...) * * Calculate probability for active speech and make VAD decision. * @@ -103,13 +103,13 @@ int WebRtcVad_set_mode_core(VadInstT* self, int mode); * 0 - No active speech * 1-6 - Active speech */ -int WebRtcVad_CalcVad48khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad48khz(VadInstT* inst, const int16_t* speech_frame, int frame_length); -int WebRtcVad_CalcVad32khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad32khz(VadInstT* inst, const int16_t* speech_frame, int frame_length); -int WebRtcVad_CalcVad16khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad16khz(VadInstT* inst, const int16_t* speech_frame, int frame_length); -int WebRtcVad_CalcVad8khz(VadInstT* inst, int16_t* speech_frame, +int WebRtcVad_CalcVad8khz(VadInstT* inst, const int16_t* speech_frame, int frame_length); #endif // WEBRTC_COMMON_AUDIO_VAD_VAD_CORE_H_ diff --git a/webrtc/common_audio/vad/vad_sp.c b/webrtc/common_audio/vad/vad_sp.c index 41deb3d05..e981ad23e 100644 --- a/webrtc/common_audio/vad/vad_sp.c +++ b/webrtc/common_audio/vad/vad_sp.c @@ -24,7 +24,7 @@ static const int16_t kSmoothingUp = 32439; // 0.99 in Q15. // TODO(bjornv): Move this function to vad_filterbank.c. // Downsampling filter based on splitting filter and allpass functions. -void WebRtcVad_Downsampling(int16_t* signal_in, +void WebRtcVad_Downsampling(const int16_t* signal_in, int16_t* signal_out, int32_t* filter_state, int in_length) { diff --git a/webrtc/common_audio/vad/vad_sp.h b/webrtc/common_audio/vad/vad_sp.h index f84876a01..b5e62593c 100644 --- a/webrtc/common_audio/vad/vad_sp.h +++ b/webrtc/common_audio/vad/vad_sp.h @@ -30,7 +30,7 @@ // // Output: // - signal_out : Downsampled signal (of length |in_length| / 2). -void WebRtcVad_Downsampling(int16_t* signal_in, +void WebRtcVad_Downsampling(const int16_t* signal_in, int16_t* signal_out, int32_t* filter_state, int in_length); diff --git a/webrtc/common_audio/vad/vad_unittest.cc b/webrtc/common_audio/vad/vad_unittest.cc index 1d73d34a4..a1127ad24 100644 --- a/webrtc/common_audio/vad/vad_unittest.cc +++ b/webrtc/common_audio/vad/vad_unittest.cc @@ -70,7 +70,6 @@ TEST_F(VadTest, ApiTest) { // NULL instance tests EXPECT_EQ(-1, WebRtcVad_Create(NULL)); EXPECT_EQ(-1, WebRtcVad_Init(NULL)); - EXPECT_EQ(-1, WebRtcVad_Free(NULL)); EXPECT_EQ(-1, WebRtcVad_set_mode(NULL, kModes[0])); EXPECT_EQ(-1, WebRtcVad_Process(NULL, kRates[0], speech, kFrameLengths[0])); @@ -121,7 +120,7 @@ TEST_F(VadTest, ApiTest) { } } - EXPECT_EQ(0, WebRtcVad_Free(handle)); + WebRtcVad_Free(handle); } TEST_F(VadTest, ValidRatesFrameLengths) { diff --git a/webrtc/common_audio/vad/webrtc_vad.c b/webrtc/common_audio/vad/webrtc_vad.c index 3acd3c37d..8a9b9317d 100644 --- a/webrtc/common_audio/vad/webrtc_vad.c +++ b/webrtc/common_audio/vad/webrtc_vad.c @@ -44,14 +44,8 @@ int WebRtcVad_Create(VadInst** handle) { return 0; } -int WebRtcVad_Free(VadInst* handle) { - if (handle == NULL) { - return -1; - } - +void WebRtcVad_Free(VadInst* handle) { free(handle); - - return 0; } // TODO(bjornv): Move WebRtcVad_InitCore() code here. @@ -74,7 +68,7 @@ int WebRtcVad_set_mode(VadInst* handle, int mode) { return WebRtcVad_set_mode_core(self, mode); } -int WebRtcVad_Process(VadInst* handle, int fs, int16_t* audio_frame, +int WebRtcVad_Process(VadInst* handle, int fs, const int16_t* audio_frame, int frame_length) { int vad = -1; VadInstT* self = (VadInstT*) handle; diff --git a/webrtc/common_types.h b/webrtc/common_types.h index 5d63a61df..6892a83f0 100644 --- a/webrtc/common_types.h +++ b/webrtc/common_types.h @@ -13,6 +13,8 @@ #include #include + +#include #include #include "webrtc/typedefs.h" @@ -93,7 +95,6 @@ enum TraceModule kTraceAudioDevice = 0x0012, kTraceVideoRenderer = 0x0014, kTraceVideoCapture = 0x0015, - kTraceVideoPreocessing = 0x0016, kTraceRemoteBitrateEstimator = 0x0017, }; @@ -467,6 +468,7 @@ enum AudioLayers kAudioLinuxPulse = 4 }; +// TODO(henrika): to be removed. enum NetEqModes // NetEQ playout configurations { // Optimized trade-off between low delay and jitter robustness for two-way @@ -483,6 +485,7 @@ enum NetEqModes // NetEQ playout configurations kNetEqOff = 3, }; +// TODO(henrika): to be removed. enum OnHoldModes // On Hold direction { kHoldSendAndPlay = 0, // Put both sending and playing in on-hold state. @@ -490,6 +493,7 @@ enum OnHoldModes // On Hold direction kHoldPlayOnly // Put only playing in on-hold state. }; +// TODO(henrika): to be removed. enum AmrMode { kRfc3267BwEfficient = 0, @@ -718,17 +722,17 @@ struct OverUseDetectorOptions { // This structure will have the information about when packet is actually // received by socket. struct PacketTime { - PacketTime() : timestamp(-1), max_error_us(-1) {} - PacketTime(int64_t timestamp, int64_t max_error_us) - : timestamp(timestamp), max_error_us(max_error_us) { + PacketTime() : timestamp(-1), not_before(-1) {} + PacketTime(int64_t timestamp, int64_t not_before) + : timestamp(timestamp), not_before(not_before) { } - int64_t timestamp; // Receive time after socket delivers the data. - int64_t max_error_us; // Earliest possible time the data could have arrived, - // indicating the potential error in the |timestamp| - // value,in case the system is busy. - // For example, the time of the last select() call. - // If unknown, this value will be set to zero. + int64_t timestamp; // Receive time after socket delivers the data. + int64_t not_before; // Earliest possible time the data could have arrived, + // indicating the potential error in the |timestamp| + // value,in case the system is busy. + // For example, the time of the last select() call. + // If unknown, this value will be set to zero. }; struct RTPHeaderExtension { @@ -779,30 +783,6 @@ struct RTPHeader { RTPHeaderExtension extension; }; -struct VideoStream { - VideoStream() - : width(0), - height(0), - max_framerate(-1), - min_bitrate_bps(-1), - target_bitrate_bps(-1), - max_bitrate_bps(-1), - max_qp(-1) {} - - size_t width; - size_t height; - int max_framerate; - - int min_bitrate_bps; - int target_bitrate_bps; - int max_bitrate_bps; - - int max_qp; - - // Bitrate thresholds for enabling additional temporal layers. - std::vector temporal_layers; -}; - } // namespace webrtc #endif // WEBRTC_COMMON_TYPES_H_ diff --git a/webrtc/common_video/BUILD.gn b/webrtc/common_video/BUILD.gn new file mode 100644 index 000000000..2e877e6b9 --- /dev/null +++ b/webrtc/common_video/BUILD.gn @@ -0,0 +1,13 @@ +# Copyright (c) 2014 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. + +import("../build/webrtc.gni") + +source_set("common_video") { + # TODO(pbos): Implement. +} diff --git a/webrtc/common_video/OWNERS b/webrtc/common_video/OWNERS index 7183cf213..ed9ac54d5 100644 --- a/webrtc/common_video/OWNERS +++ b/webrtc/common_video/OWNERS @@ -2,3 +2,12 @@ stefan@webrtc.org mikhal@webrtc.org marpan@webrtc.org henrik.lundin@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/common_video/common_video_unittests.gyp b/webrtc/common_video/common_video_unittests.gyp index 9523361cd..91a11edac 100644 --- a/webrtc/common_video/common_video_unittests.gyp +++ b/webrtc/common_video/common_video_unittests.gyp @@ -32,7 +32,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], @@ -43,7 +43,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'targets': [ { 'target_name': 'common_video_unittests_apk_target', diff --git a/webrtc/common_video/common_video_unittests.isolate b/webrtc/common_video/common_video_unittests.isolate index a95bd4144..d33366c27 100644 --- a/webrtc/common_video/common_video_unittests.isolate +++ b/webrtc/common_video/common_video_unittests.isolate @@ -8,29 +8,27 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../data/', - '../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/common_video_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../DEPS', - '../../resources/foreman_cif.yuv', - '../../testing/test_env.py', + '<(DEPTH)/DEPS', + '<(DEPTH)/resources/foreman_cif.yuv', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/common_video_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/common_video/i420_video_frame.cc b/webrtc/common_video/i420_video_frame.cc index e369ffe10..fdc2bbc23 100644 --- a/webrtc/common_video/i420_video_frame.cc +++ b/webrtc/common_video/i420_video_frame.cc @@ -10,6 +10,8 @@ #include "webrtc/common_video/interface/i420_video_frame.h" +#include + #include // swap namespace webrtc { @@ -18,6 +20,7 @@ I420VideoFrame::I420VideoFrame() : width_(0), height_(0), timestamp_(0), + ntp_time_ms_(0), render_time_ms_(0) {} I420VideoFrame::~I420VideoFrame() {} @@ -37,6 +40,7 @@ int I420VideoFrame::CreateEmptyFrame(int width, int height, v_plane_.CreateEmptyPlane(size_v, stride_v, size_v); // Creating empty frame - reset all values. timestamp_ = 0; + ntp_time_ms_ = 0; render_time_ms_ = 0; return 0; } @@ -71,10 +75,20 @@ int I420VideoFrame::CopyFrame(const I420VideoFrame& videoFrame) { if (ret < 0) return ret; timestamp_ = videoFrame.timestamp_; + ntp_time_ms_ = videoFrame.ntp_time_ms_; render_time_ms_ = videoFrame.render_time_ms_; return 0; } +I420VideoFrame* I420VideoFrame::CloneFrame() const { + scoped_ptr new_frame(new I420VideoFrame()); + if (new_frame->CopyFrame(*this) == -1) { + // CopyFrame failed. + return NULL; + } + return new_frame.release(); +} + void I420VideoFrame::SwapFrame(I420VideoFrame* videoFrame) { y_plane_.Swap(videoFrame->y_plane_); u_plane_.Swap(videoFrame->u_plane_); @@ -82,6 +96,7 @@ void I420VideoFrame::SwapFrame(I420VideoFrame* videoFrame) { std::swap(width_, videoFrame->width_); std::swap(height_, videoFrame->height_); std::swap(timestamp_, videoFrame->timestamp_); + std::swap(ntp_time_ms_, videoFrame->ntp_time_ms_); std::swap(render_time_ms_, videoFrame->render_time_ms_); } diff --git a/webrtc/common_video/i420_video_frame_unittest.cc b/webrtc/common_video/i420_video_frame_unittest.cc index 5c738bd79..ca01fd0cd 100644 --- a/webrtc/common_video/i420_video_frame_unittest.cc +++ b/webrtc/common_video/i420_video_frame_unittest.cc @@ -19,8 +19,8 @@ namespace webrtc { -bool EqualFrames(const I420VideoFrame& videoFrame1, - const I420VideoFrame& videoFrame2); +bool EqualFrames(const I420VideoFrame& frame1, + const I420VideoFrame& frame2); bool EqualFramesExceptSize(const I420VideoFrame& frame1, const I420VideoFrame& frame2); int ExpectedSize(int plane_stride, int image_height, PlaneType type); @@ -49,10 +49,12 @@ TEST(TestI420VideoFrame, WidthHeightValues) { EXPECT_EQ(valid_value, frame.height()); EXPECT_EQ(invalid_value, frame.set_height(0)); EXPECT_EQ(valid_value, frame.height()); - frame.set_timestamp(100u); - EXPECT_EQ(100u, frame.timestamp()); - frame.set_render_time_ms(100); - EXPECT_EQ(100, frame.render_time_ms()); + frame.set_timestamp(123u); + EXPECT_EQ(123u, frame.timestamp()); + frame.set_ntp_time_ms(456); + EXPECT_EQ(456, frame.ntp_time_ms()); + frame.set_render_time_ms(789); + EXPECT_EQ(789, frame.render_time_ms()); } TEST(TestI420VideoFrame, SizeAllocation) { @@ -82,7 +84,8 @@ TEST(TestI420VideoFrame, ResetSize) { TEST(TestI420VideoFrame, CopyFrame) { I420VideoFrame frame1, frame2; uint32_t timestamp = 1; - int64_t render_time_ms = 1; + int64_t ntp_time_ms = 2; + int64_t render_time_ms = 3; int stride_y = 15; int stride_u = 10; int stride_v = 10; @@ -92,6 +95,7 @@ TEST(TestI420VideoFrame, CopyFrame) { EXPECT_EQ(0, frame1.CreateEmptyFrame(width, height, stride_y, stride_u, stride_v)); frame1.set_timestamp(timestamp); + frame1.set_ntp_time_ms(ntp_time_ms); frame1.set_render_time_ms(render_time_ms); const int kSizeY = 225; const int kSizeU = 80; @@ -118,6 +122,29 @@ TEST(TestI420VideoFrame, CopyFrame) { EXPECT_TRUE(EqualFrames(frame1, frame2)); } +TEST(TestI420VideoFrame, CloneFrame) { + I420VideoFrame frame1; + scoped_ptr frame2; + const int kSizeY = 225; + const int kSizeU = 80; + const int kSizeV = 80; + uint8_t buffer_y[kSizeY]; + uint8_t buffer_u[kSizeU]; + uint8_t buffer_v[kSizeV]; + memset(buffer_y, 16, kSizeY); + memset(buffer_u, 8, kSizeU); + memset(buffer_v, 4, kSizeV); + frame1.CreateFrame( + kSizeY, buffer_y, kSizeU, buffer_u, kSizeV, buffer_v, 20, 20, 20, 10, 10); + frame1.set_timestamp(1); + frame1.set_ntp_time_ms(2); + frame1.set_render_time_ms(3); + + frame2.reset(frame1.CloneFrame()); + EXPECT_TRUE(frame2.get() != NULL); + EXPECT_TRUE(EqualFrames(frame1, *frame2)); +} + TEST(TestI420VideoFrame, CopyBuffer) { I420VideoFrame frame1, frame2; int width = 15; @@ -151,7 +178,8 @@ TEST(TestI420VideoFrame, CopyBuffer) { TEST(TestI420VideoFrame, FrameSwap) { I420VideoFrame frame1, frame2; uint32_t timestamp1 = 1; - int64_t render_time_ms1 = 1; + int64_t ntp_time_ms1 = 2; + int64_t render_time_ms1 = 3; int stride_y1 = 15; int stride_u1 = 10; int stride_v1 = 10; @@ -160,8 +188,9 @@ TEST(TestI420VideoFrame, FrameSwap) { const int kSizeY1 = 225; const int kSizeU1 = 80; const int kSizeV1 = 80; - uint32_t timestamp2 = 2; - int64_t render_time_ms2 = 4; + uint32_t timestamp2 = 4; + int64_t ntp_time_ms2 = 5; + int64_t render_time_ms2 = 6; int stride_y2 = 30; int stride_u2 = 20; int stride_v2 = 20; @@ -174,6 +203,7 @@ TEST(TestI420VideoFrame, FrameSwap) { EXPECT_EQ(0, frame1.CreateEmptyFrame(width1, height1, stride_y1, stride_u1, stride_v1)); frame1.set_timestamp(timestamp1); + frame1.set_ntp_time_ms(ntp_time_ms1); frame1.set_render_time_ms(render_time_ms1); // Set memory for frame1. uint8_t buffer_y1[kSizeY1]; @@ -190,6 +220,7 @@ TEST(TestI420VideoFrame, FrameSwap) { EXPECT_EQ(0, frame2.CreateEmptyFrame(width2, height2, stride_y2, stride_u2, stride_v2)); frame2.set_timestamp(timestamp2); + frame1.set_ntp_time_ms(ntp_time_ms2); frame2.set_render_time_ms(render_time_ms2); // Set memory for frame2. uint8_t buffer_y2[kSizeY2]; @@ -226,28 +257,24 @@ TEST(TestI420VideoFrame, RefCountedInstantiation) { bool EqualFrames(const I420VideoFrame& frame1, const I420VideoFrame& frame2) { - if (!EqualFramesExceptSize(frame1, frame2)) - return false; - // Compare allocated memory size. - bool ret = true; - ret |= (frame1.allocated_size(kYPlane) == frame2.allocated_size(kYPlane)); - ret |= (frame1.allocated_size(kUPlane) == frame2.allocated_size(kUPlane)); - ret |= (frame1.allocated_size(kVPlane) == frame2.allocated_size(kVPlane)); - return ret; + return (EqualFramesExceptSize(frame1, frame2) && + (frame1.allocated_size(kYPlane) == frame2.allocated_size(kYPlane)) && + (frame1.allocated_size(kUPlane) == frame2.allocated_size(kUPlane)) && + (frame1.allocated_size(kVPlane) == frame2.allocated_size(kVPlane))); } bool EqualFramesExceptSize(const I420VideoFrame& frame1, const I420VideoFrame& frame2) { - bool ret = true; - ret |= (frame1.width() == frame2.width()); - ret |= (frame1.height() == frame2.height()); - ret |= (frame1.stride(kYPlane) == frame2.stride(kYPlane)); - ret |= (frame1.stride(kUPlane) == frame2.stride(kUPlane)); - ret |= (frame1.stride(kVPlane) == frame2.stride(kVPlane)); - ret |= (frame1.timestamp() == frame2.timestamp()); - ret |= (frame1.render_time_ms() == frame2.render_time_ms()); - if (!ret) + if ((frame1.width() != frame2.width()) || + (frame1.height() != frame2.height()) || + (frame1.stride(kYPlane) != frame2.stride(kYPlane)) || + (frame1.stride(kUPlane) != frame2.stride(kUPlane)) || + (frame1.stride(kVPlane) != frame2.stride(kVPlane)) || + (frame1.timestamp() != frame2.timestamp()) || + (frame1.ntp_time_ms() != frame2.ntp_time_ms()) || + (frame1.render_time_ms() != frame2.render_time_ms())) { return false; + } // Memory should be the equal for the minimum of the two sizes. int size_y = std::min(frame1.allocated_size(kYPlane), frame2.allocated_size(kYPlane)); @@ -255,13 +282,9 @@ bool EqualFramesExceptSize(const I420VideoFrame& frame1, frame2.allocated_size(kUPlane)); int size_v = std::min(frame1.allocated_size(kVPlane), frame2.allocated_size(kVPlane)); - int ret_val = 0; - ret_val += memcmp(frame1.buffer(kYPlane), frame2.buffer(kYPlane), size_y); - ret_val += memcmp(frame1.buffer(kUPlane), frame2.buffer(kUPlane), size_u); - ret_val += memcmp(frame1.buffer(kVPlane), frame2.buffer(kVPlane), size_v); - if (ret_val == 0) - return true; - return false; + return (memcmp(frame1.buffer(kYPlane), frame2.buffer(kYPlane), size_y) == 0 && + memcmp(frame1.buffer(kUPlane), frame2.buffer(kUPlane), size_u) == 0 && + memcmp(frame1.buffer(kVPlane), frame2.buffer(kVPlane), size_v) == 0); } int ExpectedSize(int plane_stride, int image_height, PlaneType type) { diff --git a/webrtc/common_video/interface/i420_video_frame.h b/webrtc/common_video/interface/i420_video_frame.h index 45f2ec303..5f7a572bd 100644 --- a/webrtc/common_video/interface/i420_video_frame.h +++ b/webrtc/common_video/interface/i420_video_frame.h @@ -15,6 +15,8 @@ // // Storing and handling of YUV (I420) video frames. +#include + #include "webrtc/common_video/plane.h" #include "webrtc/system_wrappers/interface/scoped_refptr.h" #include "webrtc/typedefs.h" @@ -49,13 +51,13 @@ class I420VideoFrame { // on set dimensions - height and plane stride. // If required size is bigger than the allocated one, new buffers of adequate // size will be allocated. - // Return value: 0 on success ,-1 on error. + // Return value: 0 on success, -1 on error. virtual int CreateEmptyFrame(int width, int height, int stride_y, int stride_u, int stride_v); // CreateFrame: Sets the frame's members and buffers. If required size is // bigger than allocated one, new buffers of adequate size will be allocated. - // Return value: 0 on success ,-1 on error. + // Return value: 0 on success, -1 on error. virtual int CreateFrame(int size_y, const uint8_t* buffer_y, int size_u, const uint8_t* buffer_u, int size_v, const uint8_t* buffer_v, @@ -64,9 +66,13 @@ class I420VideoFrame { // Copy frame: If required size is bigger than allocated one, new buffers of // adequate size will be allocated. - // Return value: 0 on success ,-1 on error. + // Return value: 0 on success, -1 on error. virtual int CopyFrame(const I420VideoFrame& videoFrame); + // Make a copy of |this|. The caller owns the returned frame. + // Return value: a new frame on success, NULL on error. + virtual I420VideoFrame* CloneFrame() const; + // Swap Frame. virtual void SwapFrame(I420VideoFrame* videoFrame); @@ -99,6 +105,14 @@ class I420VideoFrame { // Get frame timestamp (90kHz). virtual uint32_t timestamp() const {return timestamp_;} + // Set capture ntp time in miliseconds. + virtual void set_ntp_time_ms(int64_t ntp_time_ms) { + ntp_time_ms_ = ntp_time_ms; + } + + // Get capture ntp time in miliseconds. + virtual int64_t ntp_time_ms() const {return ntp_time_ms_;} + // Set render time in miliseconds. virtual void set_render_time_ms(int64_t render_time_ms) {render_time_ms_ = render_time_ms;} @@ -136,6 +150,7 @@ class I420VideoFrame { int width_; int height_; uint32_t timestamp_; + int64_t ntp_time_ms_; int64_t render_time_ms_; }; // I420VideoFrame diff --git a/webrtc/common_video/interface/texture_video_frame.h b/webrtc/common_video/interface/texture_video_frame.h index e905ea733..2c625ab57 100644 --- a/webrtc/common_video/interface/texture_video_frame.h +++ b/webrtc/common_video/interface/texture_video_frame.h @@ -49,6 +49,7 @@ class TextureVideoFrame : public I420VideoFrame { int stride_u, int stride_v) OVERRIDE; virtual int CopyFrame(const I420VideoFrame& videoFrame) OVERRIDE; + virtual I420VideoFrame* CloneFrame() const OVERRIDE; virtual void SwapFrame(I420VideoFrame* videoFrame) OVERRIDE; virtual uint8_t* buffer(PlaneType type) OVERRIDE; virtual const uint8_t* buffer(PlaneType type) const OVERRIDE; diff --git a/webrtc/common_video/interface/video_image.h b/webrtc/common_video/interface/video_image.h index a7b65fd2c..c8df436b3 100644 --- a/webrtc/common_video/interface/video_image.h +++ b/webrtc/common_video/interface/video_image.h @@ -46,6 +46,7 @@ class EncodedImage : _encodedWidth(0), _encodedHeight(0), _timeStamp(0), + ntp_time_ms_(0), capture_time_ms_(0), _frameType(kDeltaFrame), _buffer(buffer), @@ -56,6 +57,8 @@ class EncodedImage uint32_t _encodedWidth; uint32_t _encodedHeight; uint32_t _timeStamp; + // NTP time of the capture time in local timebase in milliseconds. + int64_t ntp_time_ms_; int64_t capture_time_ms_; VideoFrameType _frameType; uint8_t* _buffer; diff --git a/webrtc/common_video/libyuv/libyuv_unittest.cc b/webrtc/common_video/libyuv/libyuv_unittest.cc index 3df520ed6..0abe7f3cc 100644 --- a/webrtc/common_video/libyuv/libyuv_unittest.cc +++ b/webrtc/common_video/libyuv/libyuv_unittest.cc @@ -84,7 +84,7 @@ class TestLibYuv : public ::testing::Test { FILE* source_file_; I420VideoFrame orig_frame_; - scoped_array orig_buffer_; + scoped_ptr orig_buffer_; const int width_; const int height_; const int size_y_; @@ -147,7 +147,7 @@ TEST_F(TestLibYuv, ConvertTest) { (width_ + 1) / 2, (width_ + 1) / 2)); printf("\nConvert #%d I420 <-> I420 \n", j); - scoped_array out_i420_buffer(new uint8_t[frame_length_]); + scoped_ptr out_i420_buffer(new uint8_t[frame_length_]); EXPECT_EQ(0, ConvertFromI420(orig_frame_, kI420, 0, out_i420_buffer.get())); EXPECT_EQ(0, ConvertToI420(kI420, out_i420_buffer.get(), 0, 0, @@ -162,7 +162,7 @@ TEST_F(TestLibYuv, ConvertTest) { j++; printf("\nConvert #%d I420 <-> RGB24\n", j); - scoped_array res_rgb_buffer2(new uint8_t[width_ * height_ * 3]); + scoped_ptr res_rgb_buffer2(new uint8_t[width_ * height_ * 3]); // Align the stride values for the output frame. int stride_y = 0; int stride_uv = 0; @@ -184,7 +184,7 @@ TEST_F(TestLibYuv, ConvertTest) { j++; printf("\nConvert #%d I420 <-> UYVY\n", j); - scoped_array out_uyvy_buffer(new uint8_t[width_ * height_ * 2]); + scoped_ptr out_uyvy_buffer(new uint8_t[width_ * height_ * 2]); EXPECT_EQ(0, ConvertFromI420(orig_frame_, kUYVY, 0, out_uyvy_buffer.get())); EXPECT_EQ(0, ConvertToI420(kUYVY, out_uyvy_buffer.get(), 0, 0, width_, height_, 0, kRotateNone, &res_i420_frame)); @@ -196,8 +196,8 @@ TEST_F(TestLibYuv, ConvertTest) { j++; printf("\nConvert #%d I420 <-> YV12\n", j); - scoped_array outYV120Buffer(new uint8_t[frame_length_]); - scoped_array res_i420_buffer(new uint8_t[frame_length_]); + scoped_ptr outYV120Buffer(new uint8_t[frame_length_]); + scoped_ptr res_i420_buffer(new uint8_t[frame_length_]); I420VideoFrame yv12_frame; EXPECT_EQ(0, ConvertFromI420(orig_frame_, kYV12, 0, outYV120Buffer.get())); yv12_frame.CreateFrame(size_y_, outYV120Buffer.get(), @@ -218,7 +218,7 @@ TEST_F(TestLibYuv, ConvertTest) { j++; printf("\nConvert #%d I420 <-> YUY2\n", j); - scoped_array out_yuy2_buffer(new uint8_t[width_ * height_ * 2]); + scoped_ptr out_yuy2_buffer(new uint8_t[width_ * height_ * 2]); EXPECT_EQ(0, ConvertFromI420(orig_frame_, kYUY2, 0, out_yuy2_buffer.get())); EXPECT_EQ(0, ConvertToI420(kYUY2, out_yuy2_buffer.get(), 0, 0, width_, @@ -231,7 +231,7 @@ TEST_F(TestLibYuv, ConvertTest) { psnr = I420PSNR(&orig_frame_, &res_i420_frame); EXPECT_EQ(48.0, psnr); printf("\nConvert #%d I420 <-> RGB565\n", j); - scoped_array out_rgb565_buffer(new uint8_t[width_ * height_ * 2]); + scoped_ptr out_rgb565_buffer(new uint8_t[width_ * height_ * 2]); EXPECT_EQ(0, ConvertFromI420(orig_frame_, kRGB565, 0, out_rgb565_buffer.get())); @@ -250,7 +250,7 @@ TEST_F(TestLibYuv, ConvertTest) { EXPECT_GT(ceil(psnr), 40); printf("\nConvert #%d I420 <-> ARGB8888\n", j); - scoped_array out_argb8888_buffer(new uint8_t[width_ * height_ * 4]); + scoped_ptr out_argb8888_buffer(new uint8_t[width_ * height_ * 4]); EXPECT_EQ(0, ConvertFromI420(orig_frame_, kARGB, 0, out_argb8888_buffer.get())); @@ -283,7 +283,7 @@ TEST_F(TestLibYuv, ConvertAlignedFrame) { Calc16ByteAlignedStride(width_, &stride_y, &stride_uv); EXPECT_EQ(0,res_i420_frame.CreateEmptyFrame(width_, height_, stride_y, stride_uv, stride_uv)); - scoped_array out_i420_buffer(new uint8_t[frame_length_]); + scoped_ptr out_i420_buffer(new uint8_t[frame_length_]); EXPECT_EQ(0, ConvertFromI420(orig_frame_, kI420, 0, out_i420_buffer.get())); EXPECT_EQ(0, ConvertToI420(kI420, out_i420_buffer.get(), 0, 0, diff --git a/webrtc/common_video/libyuv/scaler_unittest.cc b/webrtc/common_video/libyuv/scaler_unittest.cc index fee10df71..f186d82d8 100644 --- a/webrtc/common_video/libyuv/scaler_unittest.cc +++ b/webrtc/common_video/libyuv/scaler_unittest.cc @@ -99,7 +99,7 @@ TEST_F(TestScaler, ScaleSendingBufferTooSmall) { kI420, kI420, kScalePoint)); I420VideoFrame test_frame2; - scoped_array orig_buffer(new uint8_t[frame_length_]); + scoped_ptr orig_buffer(new uint8_t[frame_length_]); EXPECT_GT(fread(orig_buffer.get(), 1, frame_length_, source_file_), 0U); test_frame_.CreateFrame(size_y_, orig_buffer.get(), size_uv_, orig_buffer.get() + size_y_, @@ -442,7 +442,7 @@ void TestScaler::ScaleSequence(ScaleMethod method, total_clock = 0; int frame_count = 0; int src_required_size = CalcBufferSize(kI420, src_width, src_height); - scoped_array frame_buffer(new uint8_t[src_required_size]); + scoped_ptr frame_buffer(new uint8_t[src_required_size]); int size_y = src_width * src_height; int size_uv = ((src_width + 1) / 2) * ((src_height + 1) / 2); diff --git a/webrtc/common_video/texture_video_frame.cc b/webrtc/common_video/texture_video_frame.cc index 2dd6cadbe..f301d19c8 100644 --- a/webrtc/common_video/texture_video_frame.cc +++ b/webrtc/common_video/texture_video_frame.cc @@ -57,6 +57,11 @@ int TextureVideoFrame::CopyFrame(const I420VideoFrame& videoFrame) { return -1; } +I420VideoFrame* TextureVideoFrame::CloneFrame() const { + return new TextureVideoFrame( + handle_, width(), height(), timestamp(), render_time_ms()); +} + void TextureVideoFrame::SwapFrame(I420VideoFrame* videoFrame) { assert(false); // Should not be called. } diff --git a/webrtc/common_video/texture_video_frame_unittest.cc b/webrtc/common_video/texture_video_frame_unittest.cc index 04e09a67d..408f5f612 100644 --- a/webrtc/common_video/texture_video_frame_unittest.cc +++ b/webrtc/common_video/texture_video_frame_unittest.cc @@ -8,9 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "webrtc/common_video/interface/texture_video_frame.h" + #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/common_video/interface/native_handle.h" -#include "webrtc/common_video/interface/texture_video_frame.h" namespace webrtc { @@ -27,6 +28,9 @@ class NativeHandleImpl : public NativeHandle { int32_t ref_count_; }; +bool EqualTextureFrames(const I420VideoFrame& frame1, + const I420VideoFrame& frame2); + TEST(TestTextureVideoFrame, InitialValues) { NativeHandleImpl handle; TextureVideoFrame frame(&handle, 640, 480, 100, 10); @@ -55,4 +59,21 @@ TEST(TestTextureVideoFrame, RefCount) { EXPECT_EQ(0, handle.ref_count()); } +TEST(TestTextureVideoFrame, CloneFrame) { + NativeHandleImpl handle; + TextureVideoFrame frame1(&handle, 640, 480, 100, 200); + scoped_ptr frame2(frame1.CloneFrame()); + EXPECT_TRUE(frame2.get() != NULL); + EXPECT_TRUE(EqualTextureFrames(frame1, *frame2)); +} + +bool EqualTextureFrames(const I420VideoFrame& frame1, + const I420VideoFrame& frame2) { + return ((frame1.native_handle() == frame2.native_handle()) && + (frame1.width() == frame2.width()) && + (frame1.height() == frame2.height()) && + (frame1.timestamp() == frame2.timestamp()) && + (frame1.render_time_ms() == frame2.render_time_ms())); +} + } // namespace webrtc diff --git a/webrtc/config.cc b/webrtc/config.cc new file mode 100644 index 000000000..e0324b9e4 --- /dev/null +++ b/webrtc/config.cc @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014 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 "webrtc/config.h" + +#include +#include + +namespace webrtc { +std::string FecConfig::ToString() const { + std::stringstream ss; + ss << "{ulpfec_payload_type: " << ulpfec_payload_type; + ss << ", red_payload_type: " << red_payload_type; + ss << '}'; + return ss.str(); +} + +std::string RtpExtension::ToString() const { + std::stringstream ss; + ss << "{name: " << name; + ss << ", id: " << id; + ss << '}'; + return ss.str(); +} + +std::string VideoStream::ToString() const { + std::stringstream ss; + ss << "{width: " << width; + ss << ", height: " << height; + ss << ", max_framerate: " << max_framerate; + ss << ", min_bitrate_bps:" << min_bitrate_bps; + ss << ", target_bitrate_bps:" << target_bitrate_bps; + ss << ", max_bitrate_bps:" << max_bitrate_bps; + ss << ", max_qp: " << max_qp; + + ss << ", temporal_layers: {"; + for (size_t i = 0; i < temporal_layers.size(); ++i) { + ss << temporal_layers[i]; + if (i != temporal_layers.size() - 1) + ss << "}, {"; + } + ss << '}'; + + ss << '}'; + return ss.str(); +} +} // namespace webrtc diff --git a/webrtc/config.h b/webrtc/config.h index 105d9a546..7717bbad9 100644 --- a/webrtc/config.h +++ b/webrtc/config.h @@ -57,6 +57,7 @@ struct NackConfig { // payload types to '-1' to disable. struct FecConfig { FecConfig() : ulpfec_payload_type(-1), red_payload_type(-1) {} + std::string ToString() const; // Payload type used for ULPFEC packets. int ulpfec_payload_type; @@ -66,13 +67,40 @@ struct FecConfig { // RTP header extension to use for the video stream, see RFC 5285. struct RtpExtension { - static const char* kTOffset; - static const char* kAbsSendTime; RtpExtension(const char* name, int id) : name(name), id(id) {} + std::string ToString() const; // TODO(mflodman) Add API to query supported extensions. + static const char* kTOffset; + static const char* kAbsSendTime; std::string name; int id; }; + +struct VideoStream { + VideoStream() + : width(0), + height(0), + max_framerate(-1), + min_bitrate_bps(-1), + target_bitrate_bps(-1), + max_bitrate_bps(-1), + max_qp(-1) {} + std::string ToString() const; + + size_t width; + size_t height; + int max_framerate; + + int min_bitrate_bps; + int target_bitrate_bps; + int max_bitrate_bps; + + int max_qp; + + // Bitrate thresholds for enabling additional temporal layers. + std::vector temporal_layers; +}; + } // namespace webrtc #endif // WEBRTC_VIDEO_ENGINE_NEW_INCLUDE_CONFIG_H_ diff --git a/webrtc/engine_configurations.h b/webrtc/engine_configurations.h index 0fb09496e..be858b8e5 100644 --- a/webrtc/engine_configurations.h +++ b/webrtc/engine_configurations.h @@ -63,7 +63,6 @@ #define WEBRTC_VOICE_ENGINE_AGC // Near-end AGC #define WEBRTC_VOICE_ENGINE_ECHO // Near-end AEC #define WEBRTC_VOICE_ENGINE_NR // Near-end NS -#define WEBRTC_VOE_EXTERNAL_REC_AND_PLAYOUT #if !defined(WEBRTC_ANDROID) && !defined(WEBRTC_IOS) #define WEBRTC_VOICE_ENGINE_TYPING_DETECTION // Typing detection @@ -84,10 +83,6 @@ #define WEBRTC_VOICE_ENGINE_VIDEO_SYNC_API #define WEBRTC_VOICE_ENGINE_VOLUME_CONTROL_API -#ifndef WEBRTC_CHROMIUM_BUILD -#define WEBRTC_VOICE_ENGINE_CALL_REPORT_API -#endif - // ============================================================================ // VideoEngine // ============================================================================ diff --git a/webrtc/examples/android/media_demo/jni/on_load.cc b/webrtc/examples/android/media_demo/jni/on_load.cc index 27a2394b3..9fc4ca92b 100644 --- a/webrtc/examples/android/media_demo/jni/on_load.cc +++ b/webrtc/examples/android/media_demo/jni/on_load.cc @@ -38,7 +38,7 @@ JOWW(void, NativeWebRtcContextRegistry_register)( jobject context) { webrtc_examples::SetVoeDeviceObjects(g_vm); webrtc_examples::SetVieDeviceObjects(g_vm); - CHECK(webrtc::VideoEngine::SetAndroidObjects(g_vm) == 0, + CHECK(webrtc::VideoEngine::SetAndroidObjects(g_vm, context) == 0, "Failed to register android objects to video engine"); CHECK(webrtc::VoiceEngine::SetAndroidObjects(g_vm, jni, context) == 0, "Failed to register android objects to voice engine"); @@ -47,6 +47,8 @@ JOWW(void, NativeWebRtcContextRegistry_register)( JOWW(void, NativeWebRtcContextRegistry_unRegister)( JNIEnv* jni, jclass) { + CHECK(webrtc::VideoEngine::SetAndroidObjects(NULL, NULL) == 0, + "Failed to unregister android objects from video engine"); CHECK(webrtc::VoiceEngine::SetAndroidObjects(NULL, NULL, NULL) == 0, "Failed to unregister android objects from voice engine"); webrtc_examples::ClearVieDeviceObjects(); diff --git a/webrtc/examples/android/media_demo/jni/video_engine_jni.cc b/webrtc/examples/android/media_demo/jni/video_engine_jni.cc index 712e17866..c6589ece6 100644 --- a/webrtc/examples/android/media_demo/jni/video_engine_jni.cc +++ b/webrtc/examples/android/media_demo/jni/video_engine_jni.cc @@ -631,7 +631,8 @@ JOWW(jint, VideoEngine_setTraceFile)(JNIEnv* jni, jobject, jstring j_filename, return webrtc::VideoEngine::SetTraceFile(filename.c_str(), file_counter); } -JOWW(jint, VideoEngine_setTraceFilter)(JNIEnv* jni, jobject, jint filter) { +JOWW(jint, VideoEngine_nativeSetTraceFilter)(JNIEnv* jni, jobject, + jint filter) { return webrtc::VideoEngine::SetTraceFilter(filter); } diff --git a/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaEngine.java b/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaEngine.java index cf2e2be88..f62843158 100644 --- a/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaEngine.java +++ b/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/MediaEngine.java @@ -11,6 +11,7 @@ package org.webrtc.webrtcdemo; import org.webrtc.videoengine.ViERenderer; +import org.webrtc.videoengine.VideoCaptureAndroid; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -18,9 +19,10 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.hardware.Camera; import android.hardware.Camera.CameraInfo; +import android.hardware.Camera; import android.hardware.SensorManager; +import android.media.AudioManager; import android.os.Environment; import android.util.Log; import android.view.OrientationEventListener; @@ -193,6 +195,10 @@ public void onOrientationChanged (int orientation) { } }; orientationListener.enable(); + // Set audio mode to communication + AudioManager audioManager = + ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); + audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); // Listen to headset being plugged in/out. IntentFilter receiverFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); headsetListener = new BroadcastReceiver() { @@ -422,8 +428,9 @@ public void setIncomingVoeRtpDump(boolean enable) { private void updateAudioOutput() { boolean useSpeaker = !headsetPluggedIn && speakerEnabled; - check(voe.setLoudspeakerStatus(useSpeaker) == 0, - "Failed updating loudspeaker"); + AudioManager audioManager = + ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); + audioManager.setSpeakerphoneOn(useSpeaker); } public void startViE() { @@ -589,9 +596,9 @@ private void startCamera() { cameraInfo.dispose(); check(vie.connectCaptureDevice(currentCameraHandle, videoChannel) == 0, "Failed to connect capture device"); - // Camera and preview surface. Note, renderer must be created before - // calling StartCapture or |svLocal| won't be able to render. - svLocal = ViERenderer.CreateLocalRenderer(context); + // Camera and preview surface. + svLocal = new SurfaceView(context); + VideoCaptureAndroid.setLocalPreview(svLocal.getHolder()); check(vie.startCapture(currentCameraHandle) == 0, "Failed StartCapture"); compensateRotation(); } @@ -732,4 +739,4 @@ private void compensateRotation() { "Failed setRotateCapturedFrames: camera " + currentCameraHandle + "rotation " + cameraRotation); } -} \ No newline at end of file +} diff --git a/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/VideoEngine.java b/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/VideoEngine.java index 885f88be2..7cfa5856d 100644 --- a/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/VideoEngine.java +++ b/webrtc/examples/android/media_demo/src/org/webrtc/webrtcdemo/VideoEngine.java @@ -29,7 +29,7 @@ public enum TraceLevel { TRACE_DEBUG(0x0800), TRACE_INFO(0x1000), TRACE_TERSE_INFO(0x2000), - TRACE_ALL(0xfff); + TRACE_ALL(0xffff); public final int level; TraceLevel(int level) { @@ -103,9 +103,9 @@ public native int registerObserver(int channel, public native int setTraceFile(String fileName, boolean fileCounter); public int setTraceFilter(TraceLevel filter) { - return filter.level; + return nativeSetTraceFilter(filter.level); } - private native int setTraceFilter(int filter); + private native int nativeSetTraceFilter(int filter); public int startRtpDump(int channel, String file, RtpDirections direction) { return startRtpDump(channel, file, direction.ordinal()); diff --git a/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.cc b/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.cc index 81adc8ff9..23b60eebf 100644 --- a/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.cc +++ b/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.cc @@ -22,7 +22,7 @@ FakeAudioDeviceBuffer::FakeAudioDeviceBuffer() next_available_buffer_(0), record_channels_(0), play_channels_(0) { - buf_.reset(new scoped_array[kNumBuffers]); + buf_.reset(new scoped_ptr[kNumBuffers]); for (int i = 0; i < kNumBuffers; ++i) { buf_[i].reset(new int8_t[buffer_size_bytes()]); } diff --git a/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.h b/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.h index b98ee1e81..1ef866cb3 100644 --- a/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.h +++ b/webrtc/examples/android/opensl_loopback/fake_audio_device_buffer.h @@ -55,7 +55,7 @@ class FakeAudioDeviceBuffer : public AudioDeviceBuffer { AudioManagerJni audio_manager_; SingleRwFifo fifo_; - scoped_array > buf_; + scoped_ptr[]> buf_; int next_available_buffer_; uint8_t record_channels_; diff --git a/webrtc/modules/OWNERS b/webrtc/modules/OWNERS new file mode 100644 index 000000000..bbffda7e4 --- /dev/null +++ b/webrtc/modules/OWNERS @@ -0,0 +1,6 @@ +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/BUILD.gn b/webrtc/modules/audio_coding/BUILD.gn new file mode 100644 index 000000000..e4ad4cb35 --- /dev/null +++ b/webrtc/modules/audio_coding/BUILD.gn @@ -0,0 +1,13 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("audio_coding") { + # TODO(andrew): Implement. +} diff --git a/webrtc/modules/audio_coding/OWNERS b/webrtc/modules/audio_coding/OWNERS new file mode 100644 index 000000000..d5ae8473c --- /dev/null +++ b/webrtc/modules/audio_coding/OWNERS @@ -0,0 +1 @@ +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/audio_coding/codecs/cng/OWNERS b/webrtc/modules/audio_coding/codecs/cng/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/cng/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/g711/OWNERS b/webrtc/modules/audio_coding/codecs/g711/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/g711/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/g722/OWNERS b/webrtc/modules/audio_coding/codecs/g722/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/g722/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/ilbc/OWNERS b/webrtc/modules/audio_coding/codecs/ilbc/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/ilbc/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/isac/OWNERS b/webrtc/modules/audio_coding/codecs/isac/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/isac/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/OWNERS b/webrtc/modules/audio_coding/codecs/isac/fix/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_hist.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_hist.c index 5c23f7ab7..c66be2e48 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_hist.c +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_hist.c @@ -67,9 +67,9 @@ int WebRtcIsacfix_EncHistMulti(Bitstr_enc *streamData, W_upper_LSB = W_upper & 0x0000FFFF; W_upper_MSB = WEBRTC_SPL_RSHIFT_W32(W_upper, 16); W_lower = WEBRTC_SPL_UMUL(W_upper_MSB, cdfLo); - W_lower += WEBRTC_SPL_UMUL_RSFT16(W_upper_LSB, cdfLo); + W_lower += ((W_upper_LSB * cdfLo) >> 16); W_upper = WEBRTC_SPL_UMUL(W_upper_MSB, cdfHi); - W_upper += WEBRTC_SPL_UMUL_RSFT16(W_upper_LSB, cdfHi); + W_upper += ((W_upper_LSB * cdfHi) >> 16); /* shift interval such that it begins at zero */ W_upper -= ++W_lower; diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_logist.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_logist.c index b540ed5ee..9391fb3c1 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_logist.c +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routines_logist.c @@ -17,7 +17,6 @@ #include "arith_routins.h" - /* Tables for piecewise linear cdf functions: y = k*x */ /* x Points for function piecewise() in Q15 */ @@ -248,7 +247,7 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, int16_t envCount; uint16_t tmpARSpecQ8 = 0; int k, i; - + int offset = 0; /* point to beginning of stream buffer */ streamPtr = streamData->stream + streamData->stream_index; @@ -304,7 +303,7 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, cdfTmp = WebRtcIsacfix_Piecewise(WEBRTC_SPL_MUL_16_U16(candQ7, tmpARSpecQ8)); W_tmp = WEBRTC_SPL_UMUL_16_16(cdfTmp, W_upper_MSB); - W_tmp += WEBRTC_SPL_UMUL_16_16_RSFT16(cdfTmp, W_upper_LSB); + W_tmp += ((uint32_t)cdfTmp * (uint32_t)W_upper_LSB) >> 16; if (streamVal > W_tmp) { @@ -313,7 +312,7 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, cdfTmp = WebRtcIsacfix_Piecewise(WEBRTC_SPL_MUL_16_U16(candQ7, tmpARSpecQ8)); W_tmp = WEBRTC_SPL_UMUL_16_16(cdfTmp, W_upper_MSB); - W_tmp += WEBRTC_SPL_UMUL_16_16_RSFT16(cdfTmp, W_upper_LSB); + W_tmp += ((uint32_t)cdfTmp * (uint32_t)W_upper_LSB) >> 16; while (streamVal > W_tmp) { @@ -323,7 +322,7 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, WEBRTC_SPL_MUL_16_U16(candQ7, tmpARSpecQ8)); W_tmp = WEBRTC_SPL_UMUL_16_16(cdfTmp, W_upper_MSB); - W_tmp += WEBRTC_SPL_UMUL_16_16_RSFT16(cdfTmp, W_upper_LSB); + W_tmp += ((uint32_t)cdfTmp * (uint32_t)W_upper_LSB) >> 16; /* error check */ if (W_lower == W_tmp) { @@ -342,7 +341,7 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, cdfTmp = WebRtcIsacfix_Piecewise(WEBRTC_SPL_MUL_16_U16(candQ7, tmpARSpecQ8)); W_tmp = WEBRTC_SPL_UMUL_16_16(cdfTmp, W_upper_MSB); - W_tmp += WEBRTC_SPL_UMUL_16_16_RSFT16(cdfTmp, W_upper_LSB); + W_tmp += ((uint32_t)cdfTmp * (uint32_t)W_upper_LSB) >> 16; while ( !(streamVal > W_tmp) ) { @@ -352,7 +351,7 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, WEBRTC_SPL_MUL_16_U16(candQ7, tmpARSpecQ8)); W_tmp = WEBRTC_SPL_UMUL_16_16(cdfTmp, W_upper_MSB); - W_tmp += WEBRTC_SPL_UMUL_16_16_RSFT16(cdfTmp, W_upper_LSB); + W_tmp += ((uint32_t)cdfTmp * (uint32_t)W_upper_LSB) >> 16; /* error check */ if (W_upper == W_tmp){ @@ -377,14 +376,27 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, * W_upper < 2^24 */ while ( !(W_upper & 0xFF000000) ) { - /* read next byte from stream */ - if (streamData->full == 0) { - streamVal = WEBRTC_SPL_LSHIFT_W32(streamVal, 8) | (*streamPtr++ & 0x00FF); - streamData->full = 1; + if (streamPtr < streamData->stream + streamData->stream_size) { + /* read next byte from stream */ + if (streamData->full == 0) { + streamVal = WEBRTC_SPL_LSHIFT_W32(streamVal, 8) | (*streamPtr++ & 0x00FF); + streamData->full = 1; + } else { + streamVal = WEBRTC_SPL_LSHIFT_W32(streamVal, 8) | + ((*streamPtr) >> 8); + streamData->full = 0; + } } else { - streamVal = WEBRTC_SPL_LSHIFT_W32(streamVal, 8) | - WEBRTC_SPL_RSHIFT_U16(*streamPtr, 8); - streamData->full = 0; + /* Intending to read outside the stream. This can happen for the last + * two or three bytes. It is how the algorithm is implemented. Do + * not read from the bit stream and insert zeros instead. */ + streamVal = WEBRTC_SPL_LSHIFT_W32(streamVal, 8); + if (streamData->full == 0) { + offset++; // We would have incremented the pointer in this case. + streamData->full = 1; + } else { + streamData->full = 0; + } } W_upper = WEBRTC_SPL_LSHIFT_W32(W_upper, 8); } @@ -392,7 +404,7 @@ int16_t WebRtcIsacfix_DecLogisticMulti2(int16_t *dataQ7, envCount++; } - streamData->stream_index = streamPtr - streamData->stream; + streamData->stream_index = streamPtr + offset - streamData->stream; streamData->W_upper = W_upper; streamData->streamval = streamVal; diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/codec.h b/webrtc/modules/audio_coding/codecs/isac/fix/source/codec.h index 88c7e1abe..2f649324e 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/codec.h +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/codec.h @@ -179,6 +179,21 @@ void WebRtcIsacfix_FilterMaLoopNeon(int16_t input0, int32_t* ptr2); #endif +#if defined(MIPS32_LE) +int WebRtcIsacfix_AutocorrMIPS(int32_t* __restrict r, + const int16_t* __restrict x, + int16_t N, + int16_t order, + int16_t* __restrict scale); + +void WebRtcIsacfix_FilterMaLoopMIPS(int16_t input0, + int16_t input1, + int32_t input2, + int32_t* ptr0, + int32_t* ptr1, + int32_t* ptr2); +#endif + /* Function pointers associated with the above functions. */ typedef int (*AutocorrFix)(int32_t* __restrict r, diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/decode_plc.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/decode_plc.c index 6bccb8c83..6bd5843cd 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/decode_plc.c +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/decode_plc.c @@ -326,7 +326,7 @@ int16_t WebRtcIsacfix_DecodePlcImpl(int16_t *signal_out16, for( k = 0; k < lag0; k++ ) { corr = WEBRTC_SPL_ADD_SAT_W32( corr, WEBRTC_SPL_ABS_W32( - WEBRTC_SPL_SUB_SAT_W16( + WebRtcSpl_SubSatW16( (ISACdec_obj->plcstr_obj).lastPitchLP[k], (ISACdec_obj->plcstr_obj).prevPitchInvIn[ FRAMESAMPLES_HALF - 2*lag0 - 10 + i + k ] ) ) ); diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/encode.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/encode.c index e209c0ee5..daf0d6299 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/encode.c +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/encode.c @@ -15,18 +15,21 @@ * */ -#include "arith_routins.h" -#include "bandwidth_estimator.h" -#include "codec.h" -#include "pitch_gain_tables.h" -#include "pitch_lag_tables.h" -#include "entropy_coding.h" -#include "lpc_tables.h" -#include "lpc_masking_model.h" -#include "pitch_estimator.h" -#include "structs.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/codec.h" + +#include #include +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/arith_routins.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/bandwidth_estimator.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/entropy_coding.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/lpc_masking_model.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/lpc_tables.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/pitch_estimator.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/pitch_gain_tables.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/pitch_lag_tables.h" +#include "webrtc/modules/audio_coding/codecs/isac/fix/source/structs.h" + int WebRtcIsacfix_EncodeImpl(int16_t *in, ISACFIX_EncInst_t *ISACenc_obj, @@ -450,12 +453,14 @@ int WebRtcIsacfix_EncodeImpl(int16_t *in, while (stream_length < MinBytes) { + assert(stream_length >= 0); if (stream_length & 0x0001){ ISACenc_obj->bitstr_seed = WEBRTC_SPL_RAND( ISACenc_obj->bitstr_seed ); ISACenc_obj->bitstr_obj.stream[ WEBRTC_SPL_RSHIFT_W16(stream_length, 1) ] |= (uint16_t)(ISACenc_obj->bitstr_seed & 0xFF); } else { ISACenc_obj->bitstr_seed = WEBRTC_SPL_RAND( ISACenc_obj->bitstr_seed ); - ISACenc_obj->bitstr_obj.stream[ WEBRTC_SPL_RSHIFT_W16(stream_length, 1) ] = WEBRTC_SPL_LSHIFT_U16(ISACenc_obj->bitstr_seed, 8); + ISACenc_obj->bitstr_obj.stream[stream_length / 2] = + ((uint16_t)ISACenc_obj->bitstr_seed << 8); } stream_length++; } @@ -467,7 +472,8 @@ int WebRtcIsacfix_EncodeImpl(int16_t *in, } else { ISACenc_obj->bitstr_obj.stream[usefulstr_len>>1] &= 0x00FF; - ISACenc_obj->bitstr_obj.stream[usefulstr_len>>1] += WEBRTC_SPL_LSHIFT_U16((MinBytes - usefulstr_len) & 0x00FF, 8); + ISACenc_obj->bitstr_obj.stream[usefulstr_len >> 1] += + ((uint16_t)((MinBytes - usefulstr_len) & 0x00FF) << 8); } } else diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbank_internal.h b/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbank_internal.h index 28d103572..3fefc1a5d 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbank_internal.h +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbank_internal.h @@ -58,6 +58,17 @@ void WebRtcIsacfix_AllpassFilter2FixDec16Neon( int32_t *filter_state_ch2); #endif +#if defined(MIPS_DSP_R1_LE) +void WebRtcIsacfix_AllpassFilter2FixDec16MIPS( + int16_t *data_ch1, + int16_t *data_ch2, + const int16_t *factor_ch1, + const int16_t *factor_ch2, + const int length, + int32_t *filter_state_ch1, + int32_t *filter_state_ch2); +#endif + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbanks.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbanks.c index 9c9d098ae..64557e132 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbanks.c +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbanks.c @@ -102,8 +102,8 @@ void WebRtcIsacfix_HighpassFilterFixDec32(int16_t *io, #ifdef WEBRTC_ARCH_ARM_V7 { - int tmp_coeff0 = 0; - int tmp_coeff1 = 0; + register int tmp_coeff0; + register int tmp_coeff1; __asm __volatile( "ldr %[tmp_coeff0], [%[coeff]]\n\t" "ldr %[tmp_coeff1], [%[coeff], #4]\n\t" @@ -113,12 +113,12 @@ void WebRtcIsacfix_HighpassFilterFixDec32(int16_t *io, "ldr %[tmp_coeff1], [%[coeff], #12]\n\t" "smmulr %[a1], %[tmp_coeff0], %[state0]\n\t" "smmulr %[b1], %[tmp_coeff1], %[state1]\n\t" - :[a2]"+r"(a2), - [b2]"+r"(b2), - [a1]"+r"(a1), - [b1]"+r"(b1), - [tmp_coeff0]"+r"(tmp_coeff0), - [tmp_coeff1]"+r"(tmp_coeff1) + :[a2]"=&r"(a2), + [b2]"=&r"(b2), + [a1]"=&r"(a1), + [b1]"=r"(b1), + [tmp_coeff0]"=&r"(tmp_coeff0), + [tmp_coeff1]"=&r"(tmp_coeff1) :[coeff]"r"(coefficient), [state0]"r"(state0), [state1]"r"(state1) diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbanks_mips.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbanks_mips.c new file mode 100644 index 000000000..1887745b7 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/filterbanks_mips.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/codecs/isac/fix/source/filterbank_internal.h" + +// WebRtcIsacfix_AllpassFilter2FixDec16 function optimized for MIPSDSP platform +// Bit-exact with WebRtcIsacfix_AllpassFilter2FixDec16C from filterbanks.c +void WebRtcIsacfix_AllpassFilter2FixDec16MIPS( + int16_t *data_ch1, // Input and output in channel 1, in Q0 + int16_t *data_ch2, // Input and output in channel 2, in Q0 + const int16_t *factor_ch1, // Scaling factor for channel 1, in Q15 + const int16_t *factor_ch2, // Scaling factor for channel 2, in Q15 + const int length, // Length of the data buffers + int32_t *filter_state_ch1, // Filter state for channel 1, in Q16 + int32_t *filter_state_ch2) { // Filter state for channel 2, in Q16 + + int32_t st0_ch1, st1_ch1; // channel1 state variables + int32_t st0_ch2, st1_ch2; // channel2 state variables + int32_t f_ch10, f_ch11, f_ch20, f_ch21; // factor variables + int32_t r0, r1, r2, r3, r4, r5; // temporary ragister variables + + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + // Load all the state and factor variables + "lh %[f_ch10], 0(%[factor_ch1]) \n\t" + "lh %[f_ch20], 0(%[factor_ch2]) \n\t" + "lh %[f_ch11], 2(%[factor_ch1]) \n\t" + "lh %[f_ch21], 2(%[factor_ch2]) \n\t" + "lw %[st0_ch1], 0(%[filter_state_ch1]) \n\t" + "lw %[st1_ch1], 4(%[filter_state_ch1]) \n\t" + "lw %[st0_ch2], 0(%[filter_state_ch2]) \n\t" + "lw %[st1_ch2], 4(%[filter_state_ch2]) \n\t" + // Allpass filtering loop + "1: \n\t" + "lh %[r0], 0(%[data_ch1]) \n\t" + "lh %[r1], 0(%[data_ch2]) \n\t" + "addiu %[length], %[length], -1 \n\t" + "mul %[r2], %[r0], %[f_ch10] \n\t" + "mul %[r3], %[r1], %[f_ch20] \n\t" + "sll %[r0], %[r0], 16 \n\t" + "sll %[r1], %[r1], 16 \n\t" + "sll %[r2], %[r2], 1 \n\t" + "addq_s.w %[r2], %[r2], %[st0_ch1] \n\t" + "sll %[r3], %[r3], 1 \n\t" + "addq_s.w %[r3], %[r3], %[st0_ch2] \n\t" + "sra %[r2], %[r2], 16 \n\t" + "mul %[st0_ch1], %[f_ch10], %[r2] \n\t" + "sra %[r3], %[r3], 16 \n\t" + "mul %[st0_ch2], %[f_ch20], %[r3] \n\t" + "mul %[r4], %[r2], %[f_ch11] \n\t" + "mul %[r5], %[r3], %[f_ch21] \n\t" + "sll %[st0_ch1], %[st0_ch1], 1 \n\t" + "subq_s.w %[st0_ch1], %[r0], %[st0_ch1] \n\t" + "sll %[st0_ch2], %[st0_ch2], 1 \n\t" + "subq_s.w %[st0_ch2], %[r1], %[st0_ch2] \n\t" + "sll %[r4], %[r4], 1 \n\t" + "addq_s.w %[r4], %[r4], %[st1_ch1] \n\t" + "sll %[r5], %[r5], 1 \n\t" + "addq_s.w %[r5], %[r5], %[st1_ch2] \n\t" + "sra %[r4], %[r4], 16 \n\t" + "mul %[r0], %[r4], %[f_ch11] \n\t" + "sra %[r5], %[r5], 16 \n\t" + "mul %[r1], %[r5], %[f_ch21] \n\t" + "sh %[r4], 0(%[data_ch1]) \n\t" + "sh %[r5], 0(%[data_ch2]) \n\t" + "addiu %[data_ch1], %[data_ch1], 2 \n\t" + "sll %[r2], %[r2], 16 \n\t" + "sll %[r0], %[r0], 1 \n\t" + "subq_s.w %[st1_ch1], %[r2], %[r0] \n\t" + "sll %[r3], %[r3], 16 \n\t" + "sll %[r1], %[r1], 1 \n\t" + "subq_s.w %[st1_ch2], %[r3], %[r1] \n\t" + "bgtz %[length], 1b \n\t" + " addiu %[data_ch2], %[data_ch2], 2 \n\t" + // Store channel states + "sw %[st0_ch1], 0(%[filter_state_ch1]) \n\t" + "sw %[st1_ch1], 4(%[filter_state_ch1]) \n\t" + "sw %[st0_ch2], 0(%[filter_state_ch2]) \n\t" + "sw %[st1_ch2], 4(%[filter_state_ch2]) \n\t" + ".set pop \n\t" + : [f_ch10] "=&r" (f_ch10), [f_ch20] "=&r" (f_ch20), + [f_ch11] "=&r" (f_ch11), [f_ch21] "=&r" (f_ch21), + [st0_ch1] "=&r" (st0_ch1), [st1_ch1] "=&r" (st1_ch1), + [st0_ch2] "=&r" (st0_ch2), [st1_ch2] "=&r" (st1_ch2), + [r0] "=&r" (r0), [r1] "=&r" (r1), [r2] "=&r" (r2), + [r3] "=&r" (r3), [r4] "=&r" (r4), [r5] "=&r" (r5) + : [factor_ch1] "r" (factor_ch1), [factor_ch2] "r" (factor_ch2), + [filter_state_ch1] "r" (filter_state_ch1), + [filter_state_ch2] "r" (filter_state_ch2), + [data_ch1] "r" (data_ch1), [data_ch2] "r" (data_ch2), + [length] "r" (length) + : "memory", "hi", "lo" + ); +} diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/filters_mips.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/filters_mips.c new file mode 100644 index 000000000..056dc275d --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/filters_mips.c @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/codecs/isac/fix/source/codec.h" + +// MIPS optimized implementation of the Autocorrelation function in fixed point. +// NOTE! Different from SPLIB-version in how it scales the signal. +int WebRtcIsacfix_AutocorrMIPS(int32_t* __restrict r, + const int16_t* __restrict x, + int16_t N, + int16_t order, + int16_t* __restrict scale) { + int i = 0; + int16_t scaling = 0; + int16_t* in = (int16_t*)x; + int loop_size = (int)(N >> 3); + int count = (int)(N & 7); + // Declare temporary variables used as registry values. + int32_t r0, r1, r2, r3; +#if !defined(MIPS_DSP_R2_LE) + // For non-DSPR2 optimizations 4 more registers are used. + int32_t r4, r5, r6, r7; +#endif + + // Calculate r[0] and scaling needed. + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "mult $0, $0 \n\t" + // Loop is unrolled 8 times, set accumulator to zero in branch delay slot. + "beqz %[loop_size], 2f \n\t" + " mult $0, $0 \n\t" + "1: \n\t" + // Load 8 samples per loop iteration. +#if defined(MIPS_DSP_R2_LE) + "ulw %[r0], 0(%[in]) \n\t" + "ulw %[r1], 4(%[in]) \n\t" + "ulw %[r2], 8(%[in]) \n\t" + "ulw %[r3], 12(%[in]) \n\t" +#else + "lh %[r0], 0(%[in]) \n\t" + "lh %[r1], 2(%[in]) \n\t" + "lh %[r2], 4(%[in]) \n\t" + "lh %[r3], 6(%[in]) \n\t" + "lh %[r4], 8(%[in]) \n\t" + "lh %[r5], 10(%[in]) \n\t" + "lh %[r6], 12(%[in]) \n\t" + "lh %[r7], 14(%[in]) \n\t" +#endif + "addiu %[loop_size], %[loop_size], -1 \n\t" + // Multiply and accumulate. +#if defined(MIPS_DSP_R2_LE) + "dpa.w.ph $ac0, %[r0], %[r0] \n\t" + "dpa.w.ph $ac0, %[r1], %[r1] \n\t" + "dpa.w.ph $ac0, %[r2], %[r2] \n\t" + "dpa.w.ph $ac0, %[r3], %[r3] \n\t" +#else + "madd %[r0], %[r0] \n\t" + "madd %[r1], %[r1] \n\t" + "madd %[r2], %[r2] \n\t" + "madd %[r3], %[r3] \n\t" + "madd %[r4], %[r4] \n\t" + "madd %[r5], %[r5] \n\t" + "madd %[r6], %[r6] \n\t" + "madd %[r7], %[r7] \n\t" +#endif + "bnez %[loop_size], 1b \n\t" + " addiu %[in], %[in], 16 \n\t" + "2: \n\t" + "beqz %[count], 4f \n\t" +#if defined(MIPS_DSP_R1_LE) + " extr.w %[r0], $ac0, 31 \n\t" +#else + " mfhi %[r2] \n\t" +#endif + // Process remaining samples (if any). + "3: \n\t" + "lh %[r0], 0(%[in]) \n\t" + "addiu %[count], %[count], -1 \n\t" + "madd %[r0], %[r0] \n\t" + "bnez %[count], 3b \n\t" + " addiu %[in], %[in], 2 \n\t" +#if defined(MIPS_DSP_R1_LE) + "extr.w %[r0], $ac0, 31 \n\t" +#else + "mfhi %[r2] \n\t" +#endif + "4: \n\t" +#if !defined(MIPS_DSP_R1_LE) + "mflo %[r3] \n\t" + "sll %[r0], %[r2], 1 \n\t" + "srl %[r1], %[r3], 31 \n\t" + "addu %[r0], %[r0], %[r1] \n\t" +#endif + // Calculate scaling (the value of shifting). + "clz %[r1], %[r0] \n\t" + "addiu %[r1], %[r1], -32 \n\t" + "subu %[scaling], $0, %[r1] \n\t" + "slti %[r1], %[r0], 0x1 \n\t" + "movn %[scaling], $0, %[r1] \n\t" +#if defined(MIPS_DSP_R1_LE) + "extrv.w %[r0], $ac0, %[scaling] \n\t" + "mfhi %[r2], $ac0 \n\t" +#else + "addiu %[r1], %[scaling], -32 \n\t" + "subu %[r1], $0, %[r1] \n\t" + "sllv %[r1], %[r2], %[r1] \n\t" + "srlv %[r0], %[r3], %[scaling] \n\t" + "addu %[r0], %[r0], %[r1] \n\t" +#endif + "slti %[r1], %[scaling], 32 \n\t" + "movz %[r0], %[r2], %[r1] \n\t" + ".set pop \n\t" + : [loop_size] "+r" (loop_size), [in] "+r" (in), [r0] "=&r" (r0), + [r1] "=&r" (r1), [r2] "=&r" (r2), [r3] "=&r" (r3), +#if !defined(MIPS_DSP_R2_LE) + [r4] "=&r" (r4), [r5] "=&r" (r5), [r6] "=&r" (r6), [r7] "=&r" (r7), +#endif + [count] "+r" (count), [scaling] "=r" (scaling) + : [N] "r" (N) + : "memory", "hi", "lo" + ); + r[0] = r0; + + // Correlation calculation is divided in 3 cases depending on the scaling + // value (different accumulator manipulation needed). Three slightly different + // loops are written in order to avoid branches inside the loop. + if (scaling == 0) { + // In this case, the result will be in low part of the accumulator. + for (i = 1; i < order + 1; i++) { + in = (int16_t*)x; + int16_t* in1 = (int16_t*)x + i; + count = N - i; + loop_size = (count) >> 2; + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "mult $0, $0 \n\t" + "beqz %[loop_size], 2f \n\t" + " andi %[count], %[count], 0x3 \n\t" + // Loop processing 4 pairs of samples per iteration. + "1: \n\t" +#if defined(MIPS_DSP_R2_LE) + "ulw %[r0], 0(%[in]) \n\t" + "ulw %[r1], 0(%[in1]) \n\t" + "ulw %[r2], 4(%[in]) \n\t" + "ulw %[r3], 4(%[in1]) \n\t" +#else + "lh %[r0], 0(%[in]) \n\t" + "lh %[r1], 0(%[in1]) \n\t" + "lh %[r2], 2(%[in]) \n\t" + "lh %[r3], 2(%[in1]) \n\t" + "lh %[r4], 4(%[in]) \n\t" + "lh %[r5], 4(%[in1]) \n\t" + "lh %[r6], 6(%[in]) \n\t" + "lh %[r7], 6(%[in1]) \n\t" +#endif + "addiu %[loop_size], %[loop_size], -1 \n\t" +#if defined(MIPS_DSP_R2_LE) + "dpa.w.ph $ac0, %[r0], %[r1] \n\t" + "dpa.w.ph $ac0, %[r2], %[r3] \n\t" +#else + "madd %[r0], %[r1] \n\t" + "madd %[r2], %[r3] \n\t" + "madd %[r4], %[r5] \n\t" + "madd %[r6], %[r7] \n\t" +#endif + "addiu %[in], %[in], 8 \n\t" + "bnez %[loop_size], 1b \n\t" + " addiu %[in1], %[in1], 8 \n\t" + "2: \n\t" + "beqz %[count], 4f \n\t" + " mflo %[r0] \n\t" + // Process remaining samples (if any). + "3: \n\t" + "lh %[r0], 0(%[in]) \n\t" + "lh %[r1], 0(%[in1]) \n\t" + "addiu %[count], %[count], -1 \n\t" + "addiu %[in], %[in], 2 \n\t" + "madd %[r0], %[r1] \n\t" + "bnez %[count], 3b \n\t" + " addiu %[in1], %[in1], 2 \n\t" + "mflo %[r0] \n\t" + "4: \n\t" + ".set pop \n\t" + : [loop_size] "+r" (loop_size), [in] "+r" (in), [in1] "+r" (in1), +#if !defined(MIPS_DSP_R2_LE) + [r4] "=&r" (r4), [r5] "=&r" (r5), [r6] "=&r" (r6), [r7] "=&r" (r7), +#endif + [r0] "=&r" (r0), [r1] "=&r" (r1), [r2] "=&r" (r2), [r3] "=&r" (r3), + [count] "+r" (count) + : + : "memory", "hi", "lo" + ); + r[i] = r0; + } + } else if (scaling == 32) { + // In this case, the result will be high part of the accumulator. + for (i = 1; i < order + 1; i++) { + in = (int16_t*)x; + int16_t* in1 = (int16_t*)x + i; + count = N - i; + loop_size = (count) >> 2; + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "mult $0, $0 \n\t" + "beqz %[loop_size], 2f \n\t" + " andi %[count], %[count], 0x3 \n\t" + // Loop processing 4 pairs of samples per iteration. + "1: \n\t" +#if defined(MIPS_DSP_R2_LE) + "ulw %[r0], 0(%[in]) \n\t" + "ulw %[r1], 0(%[in1]) \n\t" + "ulw %[r2], 4(%[in]) \n\t" + "ulw %[r3], 4(%[in1]) \n\t" +#else + "lh %[r0], 0(%[in]) \n\t" + "lh %[r1], 0(%[in1]) \n\t" + "lh %[r2], 2(%[in]) \n\t" + "lh %[r3], 2(%[in1]) \n\t" + "lh %[r4], 4(%[in]) \n\t" + "lh %[r5], 4(%[in1]) \n\t" + "lh %[r6], 6(%[in]) \n\t" + "lh %[r7], 6(%[in1]) \n\t" +#endif + "addiu %[loop_size], %[loop_size], -1 \n\t" +#if defined(MIPS_DSP_R2_LE) + "dpa.w.ph $ac0, %[r0], %[r1] \n\t" + "dpa.w.ph $ac0, %[r2], %[r3] \n\t" +#else + "madd %[r0], %[r1] \n\t" + "madd %[r2], %[r3] \n\t" + "madd %[r4], %[r5] \n\t" + "madd %[r6], %[r7] \n\t" +#endif + "addiu %[in], %[in], 8 \n\t" + "bnez %[loop_size], 1b \n\t" + " addiu %[in1], %[in1], 8 \n\t" + "2: \n\t" + "beqz %[count], 4f \n\t" + " mfhi %[r0] \n\t" + // Process remaining samples (if any). + "3: \n\t" + "lh %[r0], 0(%[in]) \n\t" + "lh %[r1], 0(%[in1]) \n\t" + "addiu %[count], %[count], -1 \n\t" + "addiu %[in], %[in], 2 \n\t" + "madd %[r0], %[r1] \n\t" + "bnez %[count], 3b \n\t" + " addiu %[in1], %[in1], 2 \n\t" + "mfhi %[r0] \n\t" + "4: \n\t" + ".set pop \n\t" + : [loop_size] "+r" (loop_size), [in] "+r" (in), [in1] "+r" (in1), +#if !defined(MIPS_DSP_R2_LE) + [r4] "=&r" (r4), [r5] "=&r" (r5), [r6] "=&r" (r6), [r7] "=&r" (r7), +#endif + [r0] "=&r" (r0), [r1] "=&r" (r1), [r2] "=&r" (r2), [r3] "=&r" (r3), + [count] "+r" (count) + : + : "memory", "hi", "lo" + ); + r[i] = r0; + } + } else { + // In this case, the result is obtained by combining low and high parts + // of the accumulator. +#if !defined(MIPS_DSP_R1_LE) + int32_t tmp_shift = 32 - scaling; +#endif + for (i = 1; i < order + 1; i++) { + in = (int16_t*)x; + int16_t* in1 = (int16_t*)x + i; + count = N - i; + loop_size = (count) >> 2; + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "mult $0, $0 \n\t" + "beqz %[loop_size], 2f \n\t" + " andi %[count], %[count], 0x3 \n\t" + "1: \n\t" +#if defined(MIPS_DSP_R2_LE) + "ulw %[r0], 0(%[in]) \n\t" + "ulw %[r1], 0(%[in1]) \n\t" + "ulw %[r2], 4(%[in]) \n\t" + "ulw %[r3], 4(%[in1]) \n\t" +#else + "lh %[r0], 0(%[in]) \n\t" + "lh %[r1], 0(%[in1]) \n\t" + "lh %[r2], 2(%[in]) \n\t" + "lh %[r3], 2(%[in1]) \n\t" + "lh %[r4], 4(%[in]) \n\t" + "lh %[r5], 4(%[in1]) \n\t" + "lh %[r6], 6(%[in]) \n\t" + "lh %[r7], 6(%[in1]) \n\t" +#endif + "addiu %[loop_size], %[loop_size], -1 \n\t" +#if defined(MIPS_DSP_R2_LE) + "dpa.w.ph $ac0, %[r0], %[r1] \n\t" + "dpa.w.ph $ac0, %[r2], %[r3] \n\t" +#else + "madd %[r0], %[r1] \n\t" + "madd %[r2], %[r3] \n\t" + "madd %[r4], %[r5] \n\t" + "madd %[r6], %[r7] \n\t" +#endif + "addiu %[in], %[in], 8 \n\t" + "bnez %[loop_size], 1b \n\t" + " addiu %[in1], %[in1], 8 \n\t" + "2: \n\t" + "beqz %[count], 4f \n\t" +#if defined(MIPS_DSP_R1_LE) + " extrv.w %[r0], $ac0, %[scaling] \n\t" +#else + " mfhi %[r0] \n\t" +#endif + "3: \n\t" + "lh %[r0], 0(%[in]) \n\t" + "lh %[r1], 0(%[in1]) \n\t" + "addiu %[count], %[count], -1 \n\t" + "addiu %[in], %[in], 2 \n\t" + "madd %[r0], %[r1] \n\t" + "bnez %[count], 3b \n\t" + " addiu %[in1], %[in1], 2 \n\t" +#if defined(MIPS_DSP_R1_LE) + "extrv.w %[r0], $ac0, %[scaling] \n\t" +#else + "mfhi %[r0] \n\t" +#endif + "4: \n\t" +#if !defined(MIPS_DSP_R1_LE) + "mflo %[r1] \n\t" + "sllv %[r0], %[r0], %[tmp_shift] \n\t" + "srlv %[r1], %[r1], %[scaling] \n\t" + "addu %[r0], %[r0], %[r1] \n\t" +#endif + ".set pop \n\t" + : [loop_size] "+r" (loop_size), [in] "+r" (in), [in1] "+r" (in1), +#if !defined(MIPS_DSP_R2_LE) + [r4] "=&r" (r4), [r5] "=&r" (r5), [r6] "=&r" (r6), [r7] "=&r" (r7), +#endif + [r0] "=&r" (r0), [r1] "=&r" (r1), [r2] "=&r" (r2), [r3] "=&r" (r3), + [count] "+r" (count) + : [scaling] "r" (scaling) +#if !defined(MIPS_DSP_R1_LE) + , [tmp_shift] "r" (tmp_shift) +#endif + : "memory", "hi", "lo" + ); + r[i] = r0; + } + } + *scale = scaling; + + return (order + 1); +} diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.c index 8baa30738..763590809 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.c +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.c @@ -179,7 +179,7 @@ int16_t WebRtcIsacfix_FreeInternal(ISACFIX_MainStruct *ISAC_main_inst) } /**************************************************************************** - * WebRtcAecm_InitNeon(...) + * WebRtcIsacfix_InitNeon(...) * * This function initializes function pointers for ARM Neon platform. */ @@ -199,6 +199,23 @@ static void WebRtcIsacfix_InitNeon(void) { } #endif +/**************************************************************************** + * WebRtcIsacfix_InitMIPS(...) + * + * This function initializes function pointers for MIPS platform. + */ + +#if defined(MIPS32_LE) +static void WebRtcIsacfix_InitMIPS(void) { + WebRtcIsacfix_AutocorrFix = WebRtcIsacfix_AutocorrMIPS; + WebRtcIsacfix_FilterMaLoopFix = WebRtcIsacfix_FilterMaLoopMIPS; +#if defined(MIPS_DSP_R1_LE) + WebRtcIsacfix_AllpassFilter2FixDec16 = + WebRtcIsacfix_AllpassFilter2FixDec16MIPS; +#endif +} +#endif + /**************************************************************************** * WebRtcIsacfix_EncoderInit(...) * @@ -296,6 +313,10 @@ int16_t WebRtcIsacfix_EncoderInit(ISACFIX_MainStruct *ISAC_main_inst, WebRtcIsacfix_InitNeon(); #endif +#if defined(MIPS32_LE) + WebRtcIsacfix_InitMIPS(); +#endif + return statusInit; } @@ -587,15 +608,11 @@ int16_t WebRtcIsacfix_UpdateBwEstimate1(ISACFIX_MainStruct *ISAC_main_inst, { ISACFIX_SubStruct *ISAC_inst; Bitstr_dec streamdata; - uint16_t partOfStream[5]; #ifndef WEBRTC_ARCH_BIG_ENDIAN int k; #endif int16_t err; - /* Set stream pointer to point at partOfStream */ - streamdata.stream = (uint16_t *)partOfStream; - /* typecast pointer to real structure */ ISAC_inst = (ISACFIX_SubStruct *)ISAC_main_inst; @@ -675,15 +692,11 @@ int16_t WebRtcIsacfix_UpdateBwEstimate(ISACFIX_MainStruct *ISAC_main_inst, { ISACFIX_SubStruct *ISAC_inst; Bitstr_dec streamdata; - uint16_t partOfStream[5]; #ifndef WEBRTC_ARCH_BIG_ENDIAN int k; #endif int16_t err; - /* Set stream pointer to point at partOfStream */ - streamdata.stream = (uint16_t *)partOfStream; - /* typecast pointer to real structure */ ISAC_inst = (ISACFIX_SubStruct *)ISAC_main_inst; @@ -790,7 +803,7 @@ int16_t WebRtcIsacfix_Decode(ISACFIX_MainStruct *ISAC_main_inst, return -1; } - (ISAC_inst->ISACdec_obj.bitstr_obj).stream = (uint16_t *)encoded; + ISAC_inst->ISACdec_obj.bitstr_obj.stream_size = (len + 1) >> 1; /* convert bitstream from int16_t to bytes */ #ifndef WEBRTC_ARCH_BIG_ENDIAN @@ -891,7 +904,7 @@ int16_t WebRtcIsacfix_DecodeNb(ISACFIX_MainStruct *ISAC_main_inst, return -1; } - (ISAC_inst->ISACdec_obj.bitstr_obj).stream = (uint16_t *)encoded; + ISAC_inst->ISACdec_obj.bitstr_obj.stream_size = (len + 1) >> 1; /* convert bitstream from int16_t to bytes */ #ifndef WEBRTC_ARCH_BIG_ENDIAN @@ -1266,15 +1279,11 @@ int16_t WebRtcIsacfix_ReadFrameLen(const int16_t* encoded, int16_t* frameLength) { Bitstr_dec streamdata; - uint16_t partOfStream[5]; #ifndef WEBRTC_ARCH_BIG_ENDIAN int k; #endif int16_t err; - /* Set stream pointer to point at partOfStream */ - streamdata.stream = (uint16_t *)partOfStream; - streamdata.W_upper = 0xFFFFFFFF; streamdata.streamval = 0; streamdata.stream_index = 0; @@ -1315,15 +1324,11 @@ int16_t WebRtcIsacfix_ReadBwIndex(const int16_t* encoded, int16_t* rateIndex) { Bitstr_dec streamdata; - uint16_t partOfStream[5]; #ifndef WEBRTC_ARCH_BIG_ENDIAN int k; #endif int16_t err; - /* Set stream pointer to point at partOfStream */ - streamdata.stream = (uint16_t *)partOfStream; - streamdata.W_upper = 0xFFFFFFFF; streamdata.streamval = 0; streamdata.stream_index = 0; diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.gypi b/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.gypi index 87c98606a..a18a803d6 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.gypi +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/isacfix.gypi @@ -85,6 +85,30 @@ 'pitch_filter_c.c', ], }], + ['target_arch=="mipsel"', { + 'sources': [ + 'filters_mips.c', + 'lattice_mips.c', + ], + 'sources!': [ + 'lattice_c.c', + ], + 'conditions': [ + ['mips_dsp_rev>0', { + 'sources': [ + 'filterbanks_mips.c', + ], + }], + ['mips_dsp_rev>1', { + 'sources': [ + 'pitch_filter_mips.c', + ], + 'sources!': [ + 'pitch_filter_c.c', + ], + }], + ], + }], ], }, ], diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/lattice_mips.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/lattice_mips.c new file mode 100644 index 000000000..c59692216 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/lattice_mips.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/codecs/isac/fix/source/settings.h" +#include "webrtc/typedefs.h" + +// Filter ar_g_Q0[] and ar_f_Q0[] through an AR filter with coefficients +// cth_Q15[] and sth_Q15[]. +void WebRtcIsacfix_FilterArLoop(int16_t* ar_g_Q0, // Input samples + int16_t* ar_f_Q0, // Input samples + int16_t* cth_Q15, // Filter coefficients + int16_t* sth_Q15, // Filter coefficients + int16_t order_coef) { // order of the filter + int n = 0; + + for (n = 0; n < HALF_SUBFRAMELEN - 1; n++) { + int count = order_coef - 1; + int offset; +#if !defined(MIPS_DSP_R1_LE) + int16_t* tmp_cth; + int16_t* tmp_sth; + int16_t* tmp_arg; + int32_t max_q16 = 0x7fff; + int32_t min_q16 = 0xffff8000; +#endif + // Declare variables used as temporary registers. + int32_t r0, r1, r2, t0, t1, t2, t_ar; + + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "bltz %[count], 2f \n\t" + " lh %[t_ar], 0(%[tmp]) \n\t" + // Inner loop + "1: \n\t" + "sll %[offset], %[count], 1 \n\t" +#if defined(MIPS_DSP_R1_LE) + "lhx %[r0], %[offset](%[cth_Q15]) \n\t" + "lhx %[r1], %[offset](%[sth_Q15]) \n\t" + "lhx %[r2], %[offset](%[ar_g_Q0]) \n\t" +#else + "addu %[tmp_cth], %[cth_Q15], %[offset] \n\t" + "addu %[tmp_sth], %[sth_Q15], %[offset] \n\t" + "addu %[tmp_arg], %[ar_g_Q0], %[offset] \n\t" + "lh %[r0], 0(%[tmp_cth]) \n\t" + "lh %[r1], 0(%[tmp_sth]) \n\t" + "lh %[r2], 0(%[tmp_arg]) \n\t" +#endif + "mul %[t0], %[r0], %[t_ar] \n\t" + "mul %[t1], %[r1], %[t_ar] \n\t" + "mul %[t2], %[r1], %[r2] \n\t" + "mul %[r0], %[r0], %[r2] \n\t" + "subu %[t0], %[t0], %[t2] \n\t" + "addu %[t1], %[t1], %[r0] \n\t" +#if defined(MIPS_DSP_R1_LE) + "shra_r.w %[t1], %[t1], 15 \n\t" + "shra_r.w %[t0], %[t0], 15 \n\t" +#else + "addiu %[t1], %[t1], 0x4000 \n\t" + "sra %[t1], %[t1], 15 \n\t" + "addiu %[t0], %[t0], 0x4000 \n\t" + "sra %[t0], %[t0], 15 \n\t" +#endif + "addiu %[offset], %[offset], 2 \n\t" +#if defined(MIPS_DSP_R1_LE) + "shll_s.w %[t1], %[t1], 16 \n\t" + "shll_s.w %[t_ar], %[t0], 16 \n\t" +#else + "slt %[r0], %[t1], %[max_q16] \n\t" + "slt %[r1], %[t0], %[max_q16] \n\t" + "movz %[t1], %[max_q16], %[r0] \n\t" + "movz %[t0], %[max_q16], %[r1] \n\t" +#endif + "addu %[offset], %[offset], %[ar_g_Q0] \n\t" +#if defined(MIPS_DSP_R1_LE) + "sra %[t1], %[t1], 16 \n\t" + "sra %[t_ar], %[t_ar], 16 \n\t" +#else + "slt %[r0], %[t1], %[min_q16] \n\t" + "slt %[r1], %[t0], %[min_q16] \n\t" + "movn %[t1], %[min_q16], %[r0] \n\t" + "movn %[t0], %[min_q16], %[r1] \n\t" + "addu %[t_ar], $zero, %[t0] \n\t" +#endif + "sh %[t1], 0(%[offset]) \n\t" + "bgtz %[count], 1b \n\t" + " addiu %[count], %[count], -1 \n\t" + "2: \n\t" + "sh %[t_ar], 0(%[tmp]) \n\t" + "sh %[t_ar], 0(%[ar_g_Q0]) \n\t" + ".set pop \n\t" + : [t_ar] "=&r" (t_ar), [count] "+r" (count), [offset] "=&r" (offset), + [r0] "=&r" (r0), [r1] "=&r" (r1), [r2] "=&r" (r2), [t0] "=&r" (t0), +#if !defined(MIPS_DSP_R1_LE) + [tmp_cth] "=&r" (tmp_cth), [tmp_sth] "=&r" (tmp_sth), + [tmp_arg] "=&r" (tmp_arg), +#endif + [t1] "=&r" (t1), [t2] "=&r" (t2) + : [tmp] "r" (&ar_f_Q0[n+1]), [cth_Q15] "r" (cth_Q15), +#if !defined(MIPS_DSP_R1_LE) + [max_q16] "r" (max_q16), [min_q16] "r" (min_q16), +#endif + [sth_Q15] "r" (sth_Q15), [ar_g_Q0] "r" (ar_g_Q0) + : "memory", "hi", "lo" + ); + } +} + +// MIPS optimization of the inner loop used for function +// WebRtcIsacfix_NormLatticeFilterMa(). It does: +// +// for 0 <= n < HALF_SUBFRAMELEN - 1: +// *ptr2 = input2 * (*ptr2) + input0 * (*ptr0)); +// *ptr1 = input1 * (*ptr0) + input0 * (*ptr2); +// +// Note, function WebRtcIsacfix_FilterMaLoopMIPS and WebRtcIsacfix_FilterMaLoopC +// are not bit-exact. The accuracy of the MIPS function is same or better. +void WebRtcIsacfix_FilterMaLoopMIPS(int16_t input0, // Filter coefficient + int16_t input1, // Filter coefficient + int32_t input2, // Inverse coeff (1/input1) + int32_t* ptr0, // Sample buffer + int32_t* ptr1, // Sample buffer + int32_t* ptr2) { // Sample buffer +#if defined(MIPS_DSP_R2_LE) + // MIPS DSPR2 version. 4 available accumulators allows loop unrolling 4 times. + // This variant is not bit-exact with WebRtcIsacfix_FilterMaLoopC, since we + // are exploiting 64-bit accumulators. The accuracy of the MIPS DSPR2 function + // is same or better. + int n = (HALF_SUBFRAMELEN - 1) >> 2; + int m = (HALF_SUBFRAMELEN - 1) & 3; + + int r0, r1, r2, r3; + int t0, t1, t2, t3; + int s0, s1, s2, s3; + + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "1: \n\t" + "lw %[r0], 0(%[ptr0]) \n\t" + "lw %[r1], 4(%[ptr0]) \n\t" + "lw %[r2], 8(%[ptr0]) \n\t" + "lw %[r3], 12(%[ptr0]) \n\t" + "mult $ac0, %[r0], %[input0] \n\t" + "mult $ac1, %[r1], %[input0] \n\t" + "mult $ac2, %[r2], %[input0] \n\t" + "mult $ac3, %[r3], %[input0] \n\t" + "lw %[t0], 0(%[ptr2]) \n\t" + "extr_rs.w %[s0], $ac0, 15 \n\t" + "extr_rs.w %[s1], $ac1, 15 \n\t" + "extr_rs.w %[s2], $ac2, 15 \n\t" + "extr_rs.w %[s3], $ac3, 15 \n\t" + "lw %[t1], 4(%[ptr2]) \n\t" + "lw %[t2], 8(%[ptr2]) \n\t" + "lw %[t3], 12(%[ptr2]) \n\t" + "addu %[t0], %[t0], %[s0] \n\t" + "addu %[t1], %[t1], %[s1] \n\t" + "addu %[t2], %[t2], %[s2] \n\t" + "addu %[t3], %[t3], %[s3] \n\t" + "mult $ac0, %[t0], %[input2] \n\t" + "mult $ac1, %[t1], %[input2] \n\t" + "mult $ac2, %[t2], %[input2] \n\t" + "mult $ac3, %[t3], %[input2] \n\t" + "addiu %[ptr0], %[ptr0], 16 \n\t" + "extr_rs.w %[t0], $ac0, 16 \n\t" + "extr_rs.w %[t1], $ac1, 16 \n\t" + "extr_rs.w %[t2], $ac2, 16 \n\t" + "extr_rs.w %[t3], $ac3, 16 \n\t" + "addiu %[n], %[n], -1 \n\t" + "mult $ac0, %[r0], %[input1] \n\t" + "mult $ac1, %[r1], %[input1] \n\t" + "mult $ac2, %[r2], %[input1] \n\t" + "mult $ac3, %[r3], %[input1] \n\t" + "sw %[t0], 0(%[ptr2]) \n\t" + "extr_rs.w %[s0], $ac0, 15 \n\t" + "extr_rs.w %[s1], $ac1, 15 \n\t" + "extr_rs.w %[s2], $ac2, 15 \n\t" + "extr_rs.w %[s3], $ac3, 15 \n\t" + "sw %[t1], 4(%[ptr2]) \n\t" + "sw %[t2], 8(%[ptr2]) \n\t" + "sw %[t3], 12(%[ptr2]) \n\t" + "mult $ac0, %[t0], %[input0] \n\t" + "mult $ac1, %[t1], %[input0] \n\t" + "mult $ac2, %[t2], %[input0] \n\t" + "mult $ac3, %[t3], %[input0] \n\t" + "addiu %[ptr2], %[ptr2], 16 \n\t" + "extr_rs.w %[t0], $ac0, 15 \n\t" + "extr_rs.w %[t1], $ac1, 15 \n\t" + "extr_rs.w %[t2], $ac2, 15 \n\t" + "extr_rs.w %[t3], $ac3, 15 \n\t" + "addu %[t0], %[t0], %[s0] \n\t" + "addu %[t1], %[t1], %[s1] \n\t" + "addu %[t2], %[t2], %[s2] \n\t" + "addu %[t3], %[t3], %[s3] \n\t" + "sw %[t0], 0(%[ptr1]) \n\t" + "sw %[t1], 4(%[ptr1]) \n\t" + "sw %[t2], 8(%[ptr1]) \n\t" + "sw %[t3], 12(%[ptr1]) \n\t" + "bgtz %[n], 1b \n\t" + " addiu %[ptr1], %[ptr1], 16 \n\t" + "beq %[m], %0, 3f \n\t" + " nop \n\t" + "2: \n\t" + "lw %[r0], 0(%[ptr0]) \n\t" + "lw %[t0], 0(%[ptr2]) \n\t" + "addiu %[ptr0], %[ptr0], 4 \n\t" + "mult $ac0, %[r0], %[input0] \n\t" + "mult $ac1, %[r0], %[input1] \n\t" + "extr_rs.w %[r1], $ac0, 15 \n\t" + "extr_rs.w %[t1], $ac1, 15 \n\t" + "addu %[t0], %[t0], %[r1] \n\t" + "mult $ac0, %[t0], %[input2] \n\t" + "extr_rs.w %[t0], $ac0, 16 \n\t" + "sw %[t0], 0(%[ptr2]) \n\t" + "mult $ac0, %[t0], %[input0] \n\t" + "addiu %[ptr2], %[ptr2], 4 \n\t" + "addiu %[m], %[m], -1 \n\t" + "extr_rs.w %[t0], $ac0, 15 \n\t" + "addu %[t0], %[t0], %[t1] \n\t" + "sw %[t0], 0(%[ptr1]) \n\t" + "bgtz %[m], 2b \n\t" + " addiu %[ptr1], %[ptr1], 4 \n\t" + "3: \n\t" + ".set pop \n\t" + : [r0] "=&r" (r0), [r1] "=&r" (r1), [r2] "=&r" (r2), + [r3] "=&r" (r3), [t0] "=&r" (t0), [t1] "=&r" (t1), + [t2] "=&r" (t2), [t3] "=&r" (t3), [s0] "=&r" (s0), + [s1] "=&r" (s1), [s2] "=&r" (s2), [s3] "=&r" (s3), + [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), [m] "+r" (m), + [ptr2] "+r" (ptr2), [n] "+r" (n) + : [input0] "r" (input0), [input1] "r" (input1), + [input2] "r" (input2) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", "$ac2hi", + "$ac2lo", "$ac3hi", "$ac3lo" + ); +#else + // Non-DSPR2 version of the function. Avoiding the accumulator usage due to + // large latencies. This variant is bit-exact with C code. + int n = HALF_SUBFRAMELEN - 1; + int32_t t16a, t16b; + int32_t r0, r1, r2, r3, r4; + + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "sra %[t16a], %[input2], 16 \n\t" + "andi %[t16b], %[input2], 0xFFFF \n\t" +#if defined(MIPS32R2_LE) + "seh %[t16b], %[t16b] \n\t" + "seh %[input0], %[input0] \n\t" + "seh %[input1], %[input1] \n\t" +#else + "sll %[t16b], %[t16b], 16 \n\t" + "sra %[t16b], %[t16b], 16 \n\t" + "sll %[input0], %[input0], 16 \n\t" + "sra %[input0], %[input0], 16 \n\t" + "sll %[input1], %[input1], 16 \n\t" + "sra %[input1], %[input1], 16 \n\t" +#endif + "addiu %[r0], %[t16a], 1 \n\t" + "slt %[r1], %[t16b], $zero \n\t" + "movn %[t16a], %[r0], %[r1] \n\t" + "1: \n\t" + "lw %[r0], 0(%[ptr0]) \n\t" + "lw %[r1], 0(%[ptr2]) \n\t" + "addiu %[ptr0], %[ptr0], 4 \n\t" + "sra %[r2], %[r0], 16 \n\t" + "andi %[r0], %[r0], 0xFFFF \n\t" + "mul %[r3], %[r2], %[input0] \n\t" + "mul %[r4], %[r0], %[input0] \n\t" + "mul %[r2], %[r2], %[input1] \n\t" + "mul %[r0], %[r0], %[input1] \n\t" + "addiu %[ptr2], %[ptr2], 4 \n\t" + "sll %[r3], %[r3], 1 \n\t" + "sra %[r4], %[r4], 1 \n\t" + "addiu %[r4], %[r4], 0x2000 \n\t" + "sra %[r4], %[r4], 14 \n\t" + "addu %[r3], %[r3], %[r4] \n\t" + "addu %[r1], %[r1], %[r3] \n\t" + "sra %[r3], %[r1], 16 \n\t" + "andi %[r4], %[r1], 0xFFFF \n\t" + "sra %[r4], %[r4], 1 \n\t" + "mul %[r1], %[r1], %[t16a] \n\t" + "mul %[r3], %[r3], %[t16b] \n\t" + "mul %[r4], %[r4], %[t16b] \n\t" + "sll %[r2], %[r2], 1 \n\t" + "sra %[r0], %[r0], 1 \n\t" + "addiu %[r0], %[r0], 0x2000 \n\t" + "sra %[r0], %[r0], 14 \n\t" + "addu %[r0], %[r0], %[r2] \n\t" + "addiu %[n], %[n], -1 \n\t" + "addu %[r1], %[r1], %[r3] \n\t" + "addiu %[r4], %[r4], 0x4000 \n\t" + "sra %[r4], %[r4], 15 \n\t" + "addu %[r1], %[r1], %[r4] \n\t" + "sra %[r2], %[r1], 16 \n\t" + "andi %[r3], %[r1], 0xFFFF \n\t" + "mul %[r3], %[r3], %[input0] \n\t" + "mul %[r2], %[r2], %[input0] \n\t" + "sw %[r1], -4(%[ptr2]) \n\t" + "sra %[r3], %[r3], 1 \n\t" + "addiu %[r3], %[r3], 0x2000 \n\t" + "sra %[r3], %[r3], 14 \n\t" + "addu %[r0], %[r0], %[r3] \n\t" + "sll %[r2], %[r2], 1 \n\t" + "addu %[r0], %[r0], %[r2] \n\t" + "sw %[r0], 0(%[ptr1]) \n\t" + "bgtz %[n], 1b \n\t" + " addiu %[ptr1], %[ptr1], 4 \n\t" + ".set pop \n\t" + : [t16a] "=&r" (t16a), [t16b] "=&r" (t16b), [r0] "=&r" (r0), + [r1] "=&r" (r1), [r2] "=&r" (r2), [r3] "=&r" (r3), + [r4] "=&r" (r4), [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), + [ptr2] "+r" (ptr2), [n] "+r" (n) + : [input0] "r" (input0), [input1] "r" (input1), + [input2] "r" (input2) + : "hi", "lo", "memory" + ); +#endif +} diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/lpc_masking_model.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/lpc_masking_model.c index 0dc817439..deba0d5e2 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/lpc_masking_model.c +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/lpc_masking_model.c @@ -834,13 +834,15 @@ void WebRtcIsacfix_GetLpcCoef(int16_t *inLoQ0, /* bandwidth expansion */ for (n = 1; n <= ORDERLO; n++) { - a_LOQ11[n] = (int16_t) WEBRTC_SPL_MUL_16_16_RSFT_WITH_FIXROUND(kPolyVecLo[n-1], a_LOQ11[n]); + a_LOQ11[n] = (int16_t) ((WEBRTC_SPL_MUL_16_16( + kPolyVecLo[n-1], a_LOQ11[n]) + ((int32_t) (1 << 14))) >> 15); } polyHI[0] = a_HIQ12[0]; for (n = 1; n <= ORDERHI; n++) { - a_HIQ12[n] = (int16_t) WEBRTC_SPL_MUL_16_16_RSFT_WITH_FIXROUND(kPolyVecHi[n-1], a_HIQ12[n]); + a_HIQ12[n] = (int16_t) ((WEBRTC_SPL_MUL_16_16( + kPolyVecHi[n-1], a_HIQ12[n]) + ((int32_t) (1 << 14))) >> 15); polyHI[n] = a_HIQ12[n]; } diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/pitch_filter_mips.c b/webrtc/modules/audio_coding/codecs/isac/fix/source/pitch_filter_mips.c new file mode 100644 index 000000000..8334f7eb1 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/pitch_filter_mips.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/codecs/isac/fix/source/pitch_estimator.h" + +void WebRtcIsacfix_PitchFilterCore(int loopNumber, + int16_t gain, + int index, + int16_t sign, + int16_t* inputState, + int16_t* outputBuf2, + const int16_t* coefficient, + int16_t* inputBuf, + int16_t* outputBuf, + int* index2) { + int ind2t = *index2; + int i = 0; + int16_t* out2_pos2 = &outputBuf2[PITCH_BUFFSIZE - (index + 2)] + ind2t; + int32_t w1, w2, w3, w4, w5, gain32, sign32; + int32_t coef1, coef2, coef3, coef4, coef5 = 0; + // Define damp factors as int32_t (pair of int16_t) + int32_t kDampF0 = 0x0000F70A; + int32_t kDampF1 = 0x51EC2000; + int32_t kDampF2 = 0xF70A2000; + int16_t* input1 = inputBuf + ind2t; + int16_t* output1 = outputBuf + ind2t; + int16_t* output2 = outputBuf2 + ind2t + PITCH_BUFFSIZE; + + // Load coefficients outside the loop and sign-extend gain and sign + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "lwl %[coef1], 3(%[coefficient]) \n\t" + "lwl %[coef2], 7(%[coefficient]) \n\t" + "lwl %[coef3], 11(%[coefficient]) \n\t" + "lwl %[coef4], 15(%[coefficient]) \n\t" + "lwr %[coef1], 0(%[coefficient]) \n\t" + "lwr %[coef2], 4(%[coefficient]) \n\t" + "lwr %[coef3], 8(%[coefficient]) \n\t" + "lwr %[coef4], 12(%[coefficient]) \n\t" + "lhu %[coef5], 16(%[coefficient]) \n\t" + "seh %[gain32], %[gain] \n\t" + "seh %[sign32], %[sign] \n\t" + ".set pop \n\t" + : [coef1] "=&r" (coef1), [coef2] "=&r" (coef2), [coef3] "=&r" (coef3), + [coef4] "=&r" (coef4), [coef5] "=&r" (coef5), [gain32] "=&r" (gain32), + [sign32] "=&r" (sign32) + : [coefficient] "r" (coefficient), [gain] "r" (gain), + [sign] "r" (sign) + : "memory" + ); + + for (i = 0; i < loopNumber; i++) { + __asm __volatile ( + ".set push \n\t" + ".set noreorder \n\t" + // Filter to get fractional pitch + "li %[w1], 8192 \n\t" + "mtlo %[w1] \n\t" + "mthi $0 \n\t" + "lwl %[w1], 3(%[out2_pos2]) \n\t" + "lwl %[w2], 7(%[out2_pos2]) \n\t" + "lwl %[w3], 11(%[out2_pos2]) \n\t" + "lwl %[w4], 15(%[out2_pos2]) \n\t" + "lwr %[w1], 0(%[out2_pos2]) \n\t" + "lwr %[w2], 4(%[out2_pos2]) \n\t" + "lwr %[w3], 8(%[out2_pos2]) \n\t" + "lwr %[w4], 12(%[out2_pos2]) \n\t" + "lhu %[w5], 16(%[out2_pos2]) \n\t" + "dpa.w.ph $ac0, %[w1], %[coef1] \n\t" + "dpa.w.ph $ac0, %[w2], %[coef2] \n\t" + "dpa.w.ph $ac0, %[w3], %[coef3] \n\t" + "dpa.w.ph $ac0, %[w4], %[coef4] \n\t" + "dpa.w.ph $ac0, %[w5], %[coef5] \n\t" + "addiu %[out2_pos2], %[out2_pos2], 2 \n\t" + "mthi $0, $ac1 \n\t" + "lwl %[w2], 3(%[inputState]) \n\t" + "lwl %[w3], 7(%[inputState]) \n\t" + // Fractional pitch shift & saturation + "extr_s.h %[w1], $ac0, 14 \n\t" + "li %[w4], 16384 \n\t" + "lwr %[w2], 0(%[inputState]) \n\t" + "lwr %[w3], 4(%[inputState]) \n\t" + "mtlo %[w4], $ac1 \n\t" + // Shift low pass filter state + "swl %[w2], 5(%[inputState]) \n\t" + "swl %[w3], 9(%[inputState]) \n\t" + "mul %[w1], %[gain32], %[w1] \n\t" + "swr %[w2], 2(%[inputState]) \n\t" + "swr %[w3], 6(%[inputState]) \n\t" + // Low pass filter accumulation + "dpa.w.ph $ac1, %[kDampF1], %[w2] \n\t" + "dpa.w.ph $ac1, %[kDampF2], %[w3] \n\t" + "lh %[w4], 0(%[input1]) \n\t" + "addiu %[input1], %[input1], 2 \n\t" + "shra_r.w %[w1], %[w1], 12 \n\t" + "sh %[w1], 0(%[inputState]) \n\t" + "dpa.w.ph $ac1, %[kDampF0], %[w1] \n\t" + // Low pass filter shift & saturation + "extr_s.h %[w2], $ac1, 15 \n\t" + "mul %[w2], %[w2], %[sign32] \n\t" + // Buffer update + "subu %[w2], %[w4], %[w2] \n\t" + "shll_s.w %[w2], %[w2], 16 \n\t" + "sra %[w2], %[w2], 16 \n\t" + "sh %[w2], 0(%[output1]) \n\t" + "addu %[w2], %[w2], %[w4] \n\t" + "shll_s.w %[w2], %[w2], 16 \n\t" + "addiu %[output1], %[output1], 2 \n\t" + "sra %[w2], %[w2], 16 \n\t" + "sh %[w2], 0(%[output2]) \n\t" + "addiu %[output2], %[output2], 2 \n\t" + ".set pop \n\t" + : [w1] "=&r" (w1), [w2] "=&r" (w2), [w3] "=&r" (w3), [w4] "=&r" (w4), + [w5] "=&r" (w5), [input1] "+r" (input1), [out2_pos2] "+r" (out2_pos2), + [output1] "+r" (output1), [output2] "+r" (output2) + : [coefficient] "r" (coefficient), [inputState] "r" (inputState), + [gain32] "r" (gain32), [sign32] "r" (sign32), [kDampF0] "r" (kDampF0), + [kDampF1] "r" (kDampF1), [kDampF2] "r" (kDampF2), + [coef1] "r" (coef1), [coef2] "r" (coef2), [coef3] "r" (coef3), + [coef4] "r" (coef4), [coef5] "r" (coef5) + : "hi", "lo", "$ac1hi", "$ac1lo", "memory" + ); + } + (*index2) += loopNumber; +} diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/structs.h b/webrtc/modules/audio_coding/codecs/isac/fix/source/structs.h index 4d0435663..bd20ba016 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/structs.h +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/structs.h @@ -26,13 +26,14 @@ /* Bitstream struct for decoder */ typedef struct Bitstreamstruct_dec { - uint16_t *stream; /* Pointer to bytestream to decode */ + uint16_t stream[STREAM_MAXW16_60MS]; /* Array bytestream to decode */ uint32_t W_upper; /* Upper boundary of interval W */ uint32_t streamval; uint16_t stream_index; /* Index to the current position in bytestream */ int16_t full; /* 0 - first byte in memory filled, second empty*/ /* 1 - both bytes are empty (we just filled the previous memory */ + int stream_size; /* The size of stream. */ } Bitstr_dec; /* Bitstream struct for encoder */ diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/source/transform_neon.S b/webrtc/modules/audio_coding/codecs/isac/fix/source/transform_neon.S index 46682ac55..6713b2869 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/source/transform_neon.S +++ b/webrtc/modules/audio_coding/codecs/isac/fix/source/transform_neon.S @@ -42,7 +42,11 @@ DEFINE_FUNCTION WebRtcIsacfix_Time2SpecNeon add r5, sp, #(16 + FRAMESAMPLES * 2) @ tmpimQ16; adr r9, WebRtcIsacfix_kCosTab1 +#if defined(__APPLE__) + mov r6, #:lower16:(WebRtcIsacfix_kSinTab1 - WebRtcIsacfix_kCosTab1) +#else mov r6, #(WebRtcIsacfix_kSinTab1 - WebRtcIsacfix_kCosTab1) +#endif add r10, r9, r6 @ WebRtcIsacfix_kSinTab1 vmov.u32 q14, #0 @ Initialize the maximum values for tmpInIm. @@ -455,7 +459,12 @@ TransformAndFindMax: bgt TransformAndFindMax adr r10, WebRtcIsacfix_kSinTab1 +#if defined(__APPLE__) + mov r2, #:lower16:(WebRtcIsacfix_kSinTab1 - WebRtcIsacfix_kCosTab1) +#else mov r2, #(WebRtcIsacfix_kSinTab1 - WebRtcIsacfix_kCosTab1) +#endif + sub r11, r10, r2 @ WebRtcIsacfix_kCosTab1 @ Find the maximum value in the Neon registers diff --git a/webrtc/modules/audio_coding/codecs/isac/fix/test/isac_speed_test.cc b/webrtc/modules/audio_coding/codecs/isac/fix/test/isac_speed_test.cc index 348a0c19f..4acb09ae1 100644 --- a/webrtc/modules/audio_coding/codecs/isac/fix/test/isac_speed_test.cc +++ b/webrtc/modules/audio_coding/codecs/isac/fix/test/isac_speed_test.cc @@ -12,6 +12,7 @@ #include "webrtc/modules/audio_coding/codecs/isac/fix/source/settings.h" #include "webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.h" +using ::std::string; using ::std::tr1::make_tuple; using ::testing::ValuesIn; diff --git a/webrtc/modules/audio_coding/codecs/isac/main/source/OWNERS b/webrtc/modules/audio_coding/codecs/isac/main/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/isac/main/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/opus/OWNERS b/webrtc/modules/audio_coding/codecs/opus/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/opus/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/opus/opus_speed_test.cc b/webrtc/modules/audio_coding/codecs/opus/opus_speed_test.cc index 401474ef5..16099c6d9 100644 --- a/webrtc/modules/audio_coding/codecs/opus/opus_speed_test.cc +++ b/webrtc/modules/audio_coding/codecs/opus/opus_speed_test.cc @@ -11,6 +11,7 @@ #include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" #include "webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.h" +using ::std::string; using ::std::tr1::make_tuple; using ::testing::ValuesIn; diff --git a/webrtc/modules/audio_coding/codecs/pcm16b/OWNERS b/webrtc/modules/audio_coding/codecs/pcm16b/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/pcm16b/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/tools/OWNERS b/webrtc/modules/audio_coding/codecs/tools/OWNERS new file mode 100644 index 000000000..bbffda7e4 --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/tools/OWNERS @@ -0,0 +1,6 @@ +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.cc b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.cc index 2563c412e..c7cafdff9 100644 --- a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.cc +++ b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.cc @@ -117,8 +117,8 @@ void AudioCodecSpeedTest::EncodeDecode(size_t audio_duration_sec) { } printf("Encoding: %.2f%% real time,\nDecoding: %.2f%% real time.\n", - encoding_time_ms_ / audio_duration_sec / 10.0, - decoding_time_ms_ / audio_duration_sec / 10.0); + (encoding_time_ms_ / audio_duration_sec) / 10.0, + (decoding_time_ms_ / audio_duration_sec) / 10.0); } } // namespace webrtc diff --git a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.h b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.h index b385aa29a..2c9b45e4f 100644 --- a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.h +++ b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_test.h @@ -16,17 +16,13 @@ #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" -using ::std::string; -using ::std::tr1::tuple; -using ::testing::TestWithParam; - namespace webrtc { // Define coding parameter as // . -typedef tuple coding_param; +typedef std::tr1::tuple coding_param; -class AudioCodecSpeedTest : public TestWithParam { +class AudioCodecSpeedTest : public testing::TestWithParam { protected: AudioCodecSpeedTest(int block_duration_ms, int input_sampling_khz, @@ -83,7 +79,7 @@ class AudioCodecSpeedTest : public TestWithParam { // Bit rate is in bit-per-second. int bit_rate_; - string in_filename_; + std::string in_filename_; // Determines whether to save the output to file. bool save_out_data_; diff --git a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.gypi b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.gypi index 4d746e010..4d675e10c 100644 --- a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.gypi +++ b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.gypi @@ -28,7 +28,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], @@ -38,7 +38,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'targets': [ { 'target_name': 'audio_codec_speed_tests_apk_target', diff --git a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.isolate b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.isolate index 82aaac052..8c5a2bd0e 100644 --- a/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.isolate +++ b/webrtc/modules/audio_coding/codecs/tools/audio_codec_speed_tests.isolate @@ -8,33 +8,31 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../../../../resources/', - '../../../../../../data/', + '<(DEPTH)/resources/', + '<(DEPTH)/data/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/audio_codec_speed_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_touched': [ - '../../../../../DEPS', + '<(DEPTH)/DEPS', ], 'isolate_dependency_tracked': [ - '../../../../../resources/audio_coding/music_stereo_48kHz.pcm', - '../../../../../resources/audio_coding/speech_mono_16kHz.pcm', - '../../../../../resources/audio_coding/speech_mono_32_48kHz.pcm', - '../../../../../testing/test_env.py', + '<(DEPTH)/resources/audio_coding/music_stereo_48kHz.pcm', + '<(DEPTH)/resources/audio_coding/speech_mono_16kHz.pcm', + '<(DEPTH)/resources/audio_coding/speech_mono_32_48kHz.pcm', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/audio_codec_speed_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../../../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/modules/audio_coding/main/OWNERS b/webrtc/modules/audio_coding/main/OWNERS index e1e6256ca..83880d21d 100644 --- a/webrtc/modules/audio_coding/main/OWNERS +++ b/webrtc/modules/audio_coding/main/OWNERS @@ -1,3 +1,4 @@ tina.legrand@webrtc.org turaj@webrtc.org jan.skoglund@webrtc.org +henrik.lundin@webrtc.org diff --git a/webrtc/modules/audio_coding/main/acm2/OWNERS b/webrtc/modules/audio_coding/main/acm2/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_coding/main/acm2/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/main/acm2/acm_cng.h b/webrtc/modules/audio_coding/main/acm2/acm_cng.h index a0c1c5544..8b0a39283 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_cng.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_cng.h @@ -35,7 +35,8 @@ class ACMCNG: public ACMGenericCodec { int16_t InternalInitEncoder(WebRtcACMCodecParams *codec_params); protected: - void DestructEncoderSafe(); + void DestructEncoderSafe() OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalCreateEncoder(); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_codec_database.cc b/webrtc/modules/audio_coding/main/acm2/acm_codec_database.cc index fd30a137a..e55b6c466 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_codec_database.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_codec_database.cc @@ -20,7 +20,7 @@ #include #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include "webrtc/system_wrappers/interface/trace.h" // Includes needed to create the codecs. diff --git a/webrtc/modules/audio_coding/main/acm2/acm_codec_database.h b/webrtc/modules/audio_coding/main/acm2/acm_codec_database.h index 98869efee..65be793e3 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_codec_database.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_codec_database.h @@ -18,7 +18,7 @@ #include "webrtc/common_types.h" #include "webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/main/acm2/acm_g722.h b/webrtc/modules/audio_coding/main/acm2/acm_g722.h index 7216a574a..6cdd79d44 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_g722.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_g722.h @@ -12,6 +12,7 @@ #define WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_ACM_G722_H_ #include "webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" typedef struct WebRtcG722EncInst G722EncInst; typedef struct WebRtcG722DecInst G722DecInst; @@ -32,7 +33,9 @@ class ACMG722 : public ACMGenericCodec { // For FEC. ACMGenericCodec* CreateInstance(void); - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); + int16_t InternalEncode(uint8_t* bitstream, + int16_t* bitstream_len_byte) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params); @@ -40,9 +43,11 @@ class ACMG722 : public ACMGenericCodec { int32_t Add10MsDataSafe(const uint32_t timestamp, const int16_t* data, const uint16_t length_smpl, - const uint8_t audio_channel); + const uint8_t audio_channel) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); - void DestructEncoderSafe(); + void DestructEncoderSafe() OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalCreateEncoder(); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc index aa8e8be06..d16d1d388 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc @@ -26,7 +26,7 @@ namespace acm2 { // Enum for CNG enum { kMaxPLCParamsCNG = WEBRTC_CNG_MAX_LPC_ORDER, - kNewCNGNumPLCParams = 8 + kNewCNGNumLPCParams = 8 }; // Interval for sending new CNG parameters (SID frames) is 100 msec. @@ -56,10 +56,10 @@ ACMGenericCodec::ACMGenericCodec() vad_mode_(VADNormal), dtx_enabled_(false), ptr_dtx_inst_(NULL), - num_lpc_params_(kNewCNGNumPLCParams), + num_lpc_params_(kNewCNGNumLPCParams), sent_cn_previous_(false), prev_frame_cng_(0), - neteq_decode_lock_(NULL), + has_internal_fec_(false), codec_wrapper_lock_(*RWLockWrapper::CreateRWLock()), last_timestamp_(0xD87F3F9F), unique_id_(0) { @@ -209,7 +209,6 @@ int16_t ACMGenericCodec::Encode(uint8_t* bitstream, return 0; } WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); // Not all codecs accept the whole frame to be pushed into encoder at once. // Some codecs needs to be feed with a specific number of samples different @@ -393,7 +392,6 @@ int16_t ACMGenericCodec::EncoderParamsSafe(WebRtcACMCodecParams* enc_params) { int16_t ACMGenericCodec::ResetEncoder() { WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); return ResetEncoderSafe(); } @@ -442,7 +440,6 @@ int16_t ACMGenericCodec::InternalResetEncoder() { int16_t ACMGenericCodec::InitEncoder(WebRtcACMCodecParams* codec_params, bool force_initialization) { WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); return InitEncoderSafe(codec_params, force_initialization); } @@ -546,7 +543,7 @@ void ACMGenericCodec::DestructEncoder() { WebRtcCng_FreeEnc(ptr_dtx_inst_); ptr_dtx_inst_ = NULL; } - num_lpc_params_ = kNewCNGNumPLCParams; + num_lpc_params_ = kNewCNGNumLPCParams; DestructEncoderSafe(); } @@ -628,7 +625,6 @@ int16_t ACMGenericCodec::CreateEncoder() { void ACMGenericCodec::DestructEncoderInst(void* ptr_inst) { if (ptr_inst != NULL) { WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); InternalDestructEncoderInst(ptr_inst); } } diff --git a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h index d41580fff..80f239ac6 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h @@ -13,9 +13,10 @@ #include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" #include "webrtc/system_wrappers/interface/trace.h" #define MAX_FRAME_SIZE_10MSEC 6 @@ -292,17 +293,6 @@ class ACMGenericCodec { // int32_t IsInternalDTXReplaced(bool* internal_dtx_replaced); - /////////////////////////////////////////////////////////////////////////// - // void SetNetEqDecodeLock() - // Passes the NetEq lock to the codec. - // - // Input: - // -neteq_decode_lock : pointer to the lock associated with NetEQ of ACM. - // - void SetNetEqDecodeLock(RWLockWrapper* neteq_decode_lock) { - neteq_decode_lock_ = neteq_decode_lock; - } - /////////////////////////////////////////////////////////////////////////// // bool HasInternalDTX() // Used to check if the codec has internal DTX. @@ -311,7 +301,10 @@ class ACMGenericCodec { // true if the codec has an internal DTX, e.g. G729, // false otherwise. // - bool HasInternalDTX() const { return has_internal_dtx_; } + bool HasInternalDTX() const { + ReadLockScoped rl(codec_wrapper_lock_); + return has_internal_dtx_; + } /////////////////////////////////////////////////////////////////////////// // int32_t GetEstimatedBandwidth() @@ -435,7 +428,8 @@ class ACMGenericCodec { // -1 if failed, or if this is meaningless for the given codec. // 0 if succeeded. // - virtual int16_t UpdateEncoderSampFreq(uint16_t samp_freq_hz); + virtual int16_t UpdateEncoderSampFreq(uint16_t samp_freq_hz) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // EncoderSampFreq() @@ -449,7 +443,8 @@ class ACMGenericCodec { // -1 if failed to output sampling rate. // 0 if the sample rate is returned successfully. // - virtual int16_t EncoderSampFreq(uint16_t* samp_freq_hz); + virtual int16_t EncoderSampFreq(uint16_t* samp_freq_hz) + SHARED_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int32_t ConfigISACBandwidthEstimator() @@ -514,8 +509,6 @@ class ACMGenericCodec { // virtual int32_t SetISACMaxRate(const uint32_t max_rate_bps); - int32_t FrameSize() { return frame_len_smpl_; } - /////////////////////////////////////////////////////////////////////////// // REDPayloadISAC() // This is an iSAC-specific function. The function is called to get RED @@ -560,6 +553,49 @@ class ACMGenericCodec { // virtual AudioDecoder* Decoder(int /* codec_id */) { return NULL; } + /////////////////////////////////////////////////////////////////////////// + // bool HasInternalFEC() + // Used to check if the codec has internal FEC. + // + // Return value: + // true if the codec has an internal FEC, e.g. Opus. + // false otherwise. + // + bool HasInternalFEC() const { + ReadLockScoped rl(codec_wrapper_lock_); + return has_internal_fec_; + } + + /////////////////////////////////////////////////////////////////////////// + // int SetFEC(); + // Sets the codec internal FEC. No effects on codecs that do not provide + // internal FEC. + // + // Input: + // -enable_fec : if true FEC will be enabled otherwise the FEC is + // disabled. + // + // Return value: + // -1 if failed, or the codec does not support FEC + // 0 if succeeded. + // + virtual int SetFEC(bool /* enable_fec */) { return -1; } + + /////////////////////////////////////////////////////////////////////////// + // int SetPacketLossRate() + // Sets expected packet loss rate for encoding. Some encoders provide packet + // loss gnostic encoding to make stream less sensitive to packet losses, + // through e.g., FEC. No effects on codecs that do not provide such encoding. + // + // Input: + // -loss_rate : expected packet loss rate (0 -- 100 inclusive). + // + // Return value: + // -1 if failed, + // 0 if succeeded or packet loss rate is ignored. + // + virtual int SetPacketLossRate(int /* loss_rate */) { return 0; } + protected: /////////////////////////////////////////////////////////////////////////// // All the functions with FunctionNameSafe(...) contain the actual @@ -576,26 +612,29 @@ class ACMGenericCodec { virtual int32_t Add10MsDataSafe(const uint32_t timestamp, const int16_t* data, const uint16_t length, - const uint8_t audio_channel); + const uint8_t audio_channel) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // See EncoderParam() for the description of function, input(s)/output(s) // and return value. // - int16_t EncoderParamsSafe(WebRtcACMCodecParams* enc_params); + int16_t EncoderParamsSafe(WebRtcACMCodecParams* enc_params) + SHARED_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // See ResetEncoder() for the description of function, input(s)/output(s) // and return value. // - int16_t ResetEncoderSafe(); + int16_t ResetEncoderSafe() EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // See InitEncoder() for the description of function, input(s)/output(s) // and return value. // int16_t InitEncoderSafe(WebRtcACMCodecParams* codec_params, - bool force_initialization); + bool force_initialization) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // See InitDecoder() for the description of function, input(s)/output(s) @@ -608,7 +647,8 @@ class ACMGenericCodec { // See DestructEncoder() for the description of function, // input(s)/output(s) and return value. // - virtual void DestructEncoderSafe() = 0; + virtual void DestructEncoderSafe() + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_) = 0; /////////////////////////////////////////////////////////////////////////// // See SetBitRate() for the description of function, input(s)/output(s) @@ -616,7 +656,8 @@ class ACMGenericCodec { // // Any codec that can change the bit-rate has to implement this. // - virtual int16_t SetBitRateSafe(const int32_t bitrate_bps); + virtual int16_t SetBitRateSafe(const int32_t bitrate_bps) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // See GetEstimatedBandwidth() for the description of function, @@ -641,7 +682,8 @@ class ACMGenericCodec { // See SetVAD() for the description of function, input(s)/output(s) and // return value. // - int16_t SetVADSafe(bool* enable_dtx, bool* enable_vad, ACMVADMode* mode); + int16_t SetVADSafe(bool* enable_dtx, bool* enable_vad, ACMVADMode* mode) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // See ReplaceInternalDTX() for the description of function, input and @@ -663,7 +705,7 @@ class ACMGenericCodec { // -1 if failed, // 0 if succeeded. // - int16_t CreateEncoder(); + int16_t CreateEncoder() EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int16_t EnableVAD(); @@ -678,7 +720,8 @@ class ACMGenericCodec { // -1 if failed, // 0 if succeeded. // - int16_t EnableVAD(ACMVADMode mode); + int16_t EnableVAD(ACMVADMode mode) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int16_t DisableVAD() @@ -688,7 +731,7 @@ class ACMGenericCodec { // -1 if failed, // 0 if succeeded. // - int16_t DisableVAD(); + int16_t DisableVAD() EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int16_t EnableDTX() @@ -699,7 +742,7 @@ class ACMGenericCodec { // -1 if failed, // 0 if succeeded. // - virtual int16_t EnableDTX(); + virtual int16_t EnableDTX() EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int16_t DisableDTX() @@ -710,7 +753,7 @@ class ACMGenericCodec { // -1 if failed, // 0 if succeeded. // - virtual int16_t DisableDTX(); + virtual int16_t DisableDTX() EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int16_t InternalEncode() @@ -728,7 +771,8 @@ class ACMGenericCodec { // otherwise the length of the bit-stream is returned. // virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) = 0; + int16_t* bitstream_len_byte) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_) = 0; /////////////////////////////////////////////////////////////////////////// // int16_t InternalInitEncoder() @@ -749,7 +793,8 @@ class ACMGenericCodec { // -1 if failed, // 0 if succeeded. // - virtual int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params) = 0; + virtual int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_) = 0; /////////////////////////////////////////////////////////////////////////// // void IncreaseNoMissedSamples() @@ -760,7 +805,8 @@ class ACMGenericCodec { // -num_samples : the number of overwritten samples is incremented // by this value. // - void IncreaseNoMissedSamples(const int16_t num_samples); + void IncreaseNoMissedSamples(const int16_t num_samples) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int16_t InternalCreateEncoder() @@ -804,7 +850,8 @@ class ACMGenericCodec { // -1 if failed, // 0 if succeeded. // - virtual int16_t InternalResetEncoder(); + virtual int16_t InternalResetEncoder() + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // int16_t ProcessFrameVADDTX() @@ -838,7 +885,8 @@ class ACMGenericCodec { // int16_t ProcessFrameVADDTX(uint8_t* bitstream, int16_t* bitstream_len_byte, - int16_t* samples_processed); + int16_t* samples_processed) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); /////////////////////////////////////////////////////////////////////////// // CurrentRate() @@ -854,62 +902,63 @@ class ACMGenericCodec { // &in_audio_[in_audio_ix_write_] always point to where new audio can be // written to - int16_t in_audio_ix_write_; + int16_t in_audio_ix_write_ GUARDED_BY(codec_wrapper_lock_); // &in_audio_[in_audio_ix_read_] points to where audio has to be read from - int16_t in_audio_ix_read_; + int16_t in_audio_ix_read_ GUARDED_BY(codec_wrapper_lock_); - int16_t in_timestamp_ix_write_; + int16_t in_timestamp_ix_write_ GUARDED_BY(codec_wrapper_lock_); // Where the audio is stored before encoding, // To save memory the following buffer can be allocated // dynamically for 80 ms depending on the sampling frequency // of the codec. - int16_t* in_audio_; - uint32_t* in_timestamp_; + int16_t* in_audio_ GUARDED_BY(codec_wrapper_lock_); + uint32_t* in_timestamp_ GUARDED_BY(codec_wrapper_lock_); - int16_t frame_len_smpl_; - uint16_t num_channels_; + int16_t frame_len_smpl_ GUARDED_BY(codec_wrapper_lock_); + uint16_t num_channels_ GUARDED_BY(codec_wrapper_lock_); // This will point to a static database of the supported codecs - int16_t codec_id_; + int16_t codec_id_ GUARDED_BY(codec_wrapper_lock_); // This will account for the number of samples were not encoded // the case is rare, either samples are missed due to overwrite // at input buffer or due to encoding error - uint32_t num_missed_samples_; + uint32_t num_missed_samples_ GUARDED_BY(codec_wrapper_lock_); // True if the encoder instance created - bool encoder_exist_; + bool encoder_exist_ GUARDED_BY(codec_wrapper_lock_); // True if the encoder instance initialized - bool encoder_initialized_; + bool encoder_initialized_ GUARDED_BY(codec_wrapper_lock_); - bool registered_in_neteq_; + const bool registered_in_neteq_ + GUARDED_BY(codec_wrapper_lock_); // TODO(henrik.lundin) Remove? // VAD/DTX - bool has_internal_dtx_; - WebRtcVadInst* ptr_vad_inst_; - bool vad_enabled_; - ACMVADMode vad_mode_; - int16_t vad_label_[MAX_FRAME_SIZE_10MSEC]; - bool dtx_enabled_; - WebRtcCngEncInst* ptr_dtx_inst_; - uint8_t num_lpc_params_; - bool sent_cn_previous_; - int16_t prev_frame_cng_; - - WebRtcACMCodecParams encoder_params_; - - // Used as a global lock for all available decoders - // so that no decoder is used when NetEQ decodes. - RWLockWrapper* neteq_decode_lock_; + bool has_internal_dtx_ GUARDED_BY(codec_wrapper_lock_); + WebRtcVadInst* ptr_vad_inst_ GUARDED_BY(codec_wrapper_lock_); + bool vad_enabled_ GUARDED_BY(codec_wrapper_lock_); + ACMVADMode vad_mode_ GUARDED_BY(codec_wrapper_lock_); + int16_t vad_label_[MAX_FRAME_SIZE_10MSEC] GUARDED_BY(codec_wrapper_lock_); + bool dtx_enabled_ GUARDED_BY(codec_wrapper_lock_); + WebRtcCngEncInst* ptr_dtx_inst_ GUARDED_BY(codec_wrapper_lock_); + uint8_t num_lpc_params_ // TODO(henrik.lundin) Delete and + GUARDED_BY(codec_wrapper_lock_); // replace with kNewCNGNumLPCParams. + bool sent_cn_previous_ GUARDED_BY(codec_wrapper_lock_); + int16_t prev_frame_cng_ GUARDED_BY(codec_wrapper_lock_); + + // FEC. + bool has_internal_fec_ GUARDED_BY(codec_wrapper_lock_); + + WebRtcACMCodecParams encoder_params_ GUARDED_BY(codec_wrapper_lock_); // Used to lock wrapper internal data // such as buffers and state variables. RWLockWrapper& codec_wrapper_lock_; - uint32_t last_timestamp_; + uint32_t last_timestamp_ GUARDED_BY(codec_wrapper_lock_); uint32_t unique_id_; }; diff --git a/webrtc/modules/audio_coding/main/acm2/acm_ilbc.h b/webrtc/modules/audio_coding/main/acm2/acm_ilbc.h index fd6e85379..714c90046 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_ilbc.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_ilbc.h @@ -29,14 +29,18 @@ class ACMILBC : public ACMGenericCodec { // for FEC ACMGenericCodec* CreateInstance(void); - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); + int16_t InternalEncode(uint8_t* bitstream, + int16_t* bitstream_len_byte) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params); protected: - int16_t SetBitRateSafe(const int32_t rate); + int16_t SetBitRateSafe(const int32_t rate) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); - void DestructEncoderSafe(); + void DestructEncoderSafe() OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalCreateEncoder(); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_isac.cc b/webrtc/modules/audio_coding/main/acm2/acm_isac.cc index e27284212..2adae861b 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_isac.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_isac.cc @@ -14,7 +14,8 @@ #include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" #include "webrtc/modules/audio_coding/main/acm2/acm_codec_database.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/trace.h" #ifdef WEBRTC_CODEC_ISAC @@ -59,14 +60,15 @@ static const int32_t kIsacRatesSwb[NR_ISAC_BANDWIDTHS] = { #if (!defined(WEBRTC_CODEC_ISAC) && !defined(WEBRTC_CODEC_ISACFX)) ACMISAC::ACMISAC(int16_t /* codec_id */) - : codec_inst_ptr_(NULL), + : codec_inst_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + codec_inst_ptr_(NULL), is_enc_initialized_(false), isac_coding_mode_(CHANNEL_INDEPENDENT), enforce_frame_size_(false), isac_currentBN_(32000), samples_in10MsAudio_(160), // Initiates to 16 kHz mode. - audio_decoder_(NULL), - decoder_initialized_(false) {} + decoder_initialized_(false) { +} ACMISAC::~ACMISAC() { return; @@ -261,81 +263,14 @@ static uint16_t ACMISACFixGetDecSampRate(ACM_ISAC_STRUCT* /* inst */) { #endif -// Decoder class to be injected into NetEq. -class AcmAudioDecoderIsac : public AudioDecoder { - public: - AcmAudioDecoderIsac(int codec_id, void* state) - : AudioDecoder(ACMCodecDB::neteq_decoders_[codec_id]) { - state_ = state; - } - - // ACMISAC is the owner of the object where |state_| is pointing to. - // Therefore, it should not be deleted in this destructor. - virtual ~AcmAudioDecoderIsac() {} - - virtual int Decode(const uint8_t* encoded, size_t encoded_len, - int16_t* decoded, SpeechType* speech_type) { - int16_t temp_type; - int ret = ACM_ISAC_DECODE_B(static_cast(state_), - reinterpret_cast(encoded), - static_cast(encoded_len), decoded, - &temp_type); - *speech_type = ConvertSpeechType(temp_type); - return ret; - } - - virtual bool HasDecodePlc() const { return true; } - - virtual int DecodePlc(int num_frames, int16_t* decoded) { - return ACM_ISAC_DECODEPLC(static_cast(state_), - decoded, static_cast(num_frames)); - } - - virtual int Init() { - return 0; // We expect that the initialized instance is injected in the - // constructor. - } - - virtual int IncomingPacket(const uint8_t* payload, - size_t payload_len, - uint16_t rtp_sequence_number, - uint32_t rtp_timestamp, - uint32_t arrival_timestamp) { - return ACM_ISAC_DECODE_BWE(static_cast(state_), - reinterpret_cast(payload), - static_cast(payload_len), - rtp_sequence_number, - rtp_timestamp, - arrival_timestamp); - } - - virtual int DecodeRedundant(const uint8_t* encoded, - size_t encoded_len, int16_t* decoded, - SpeechType* speech_type) { - int16_t temp_type = 1; // Default is speech. - int16_t ret = ACM_ISAC_DECODERCU(static_cast(state_), - reinterpret_cast(encoded), - static_cast(encoded_len), decoded, - &temp_type); - *speech_type = ConvertSpeechType(temp_type); - return ret; - } - - virtual int ErrorCode() { - return ACM_ISAC_GETERRORCODE(static_cast(state_)); - } - - private: - DISALLOW_COPY_AND_ASSIGN(AcmAudioDecoderIsac); -}; - ACMISAC::ACMISAC(int16_t codec_id) - : is_enc_initialized_(false), + : AudioDecoder(ACMCodecDB::neteq_decoders_[codec_id]), + codec_inst_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + is_enc_initialized_(false), isac_coding_mode_(CHANNEL_INDEPENDENT), enforce_frame_size_(false), isac_current_bn_(32000), samples_in_10ms_audio_(160), // Initiates to 16 kHz mode. - audio_decoder_(NULL), decoder_initialized_(false) { codec_id_ = codec_id; @@ -345,14 +280,10 @@ ACMISAC::ACMISAC(int16_t codec_id) return; } codec_inst_ptr_->inst = NULL; + state_ = codec_inst_ptr_; } ACMISAC::~ACMISAC() { - if (audio_decoder_ != NULL) { - delete audio_decoder_; - audio_decoder_ = NULL; - } - if (codec_inst_ptr_ != NULL) { if (codec_inst_ptr_->inst != NULL) { ACM_ISAC_FREE(codec_inst_ptr_->inst); @@ -364,6 +295,34 @@ ACMISAC::~ACMISAC() { return; } +int16_t ACMISAC::InternalInitDecoder(WebRtcACMCodecParams* codec_params) { + // set decoder sampling frequency. + if (codec_params->codec_inst.plfreq == 32000 || + codec_params->codec_inst.plfreq == 48000) { + UpdateDecoderSampFreq(ACMCodecDB::kISACSWB); + } else { + UpdateDecoderSampFreq(ACMCodecDB::kISAC); + } + + // in a one-way communication we may never register send-codec. + // However we like that the BWE to work properly so it has to + // be initialized. The BWE is initialized when iSAC encoder is initialized. + // Therefore, we need this. + if (!encoder_initialized_) { + // Since we don't require a valid rate or a valid packet size when + // initializing the decoder, we set valid values before initializing encoder + codec_params->codec_inst.rate = kIsacWbDefaultRate; + codec_params->codec_inst.pacsize = kIsacPacSize960; + if (InternalInitEncoder(codec_params) < 0) { + return -1; + } + encoder_initialized_ = true; + } + + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); + return ACM_ISAC_DECODERINIT(codec_inst_ptr_->inst); +} + ACMGenericCodec* ACMISAC::CreateInstance(void) { return NULL; } int16_t ACMISAC::InternalEncode(uint8_t* bitstream, @@ -375,6 +334,7 @@ int16_t ACMISAC::InternalEncode(uint8_t* bitstream, // at the first 10ms pushed in to iSAC if the bit-rate is low, this is // sort of a bug in iSAC. to address this we treat iSAC as the // following. + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); if (codec_inst_ptr_ == NULL) { return -1; } @@ -428,6 +388,7 @@ int16_t ACMISAC::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { if (UpdateEncoderSampFreq((uint16_t)codec_params->codec_inst.plfreq) < 0) { return -1; } + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); if (ACM_ISAC_ENCODERINIT(codec_inst_ptr_->inst, isac_coding_mode_) < 0) { return -1; } @@ -450,38 +411,8 @@ int16_t ACMISAC::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { return 0; } -int16_t ACMISAC::InternalInitDecoder(WebRtcACMCodecParams* codec_params) { - if (codec_inst_ptr_ == NULL) { - return -1; - } - - // set decoder sampling frequency. - if (codec_params->codec_inst.plfreq == 32000 || - codec_params->codec_inst.plfreq == 48000) { - UpdateDecoderSampFreq(ACMCodecDB::kISACSWB); - } else { - UpdateDecoderSampFreq(ACMCodecDB::kISAC); - } - - // in a one-way communication we may never register send-codec. - // However we like that the BWE to work properly so it has to - // be initialized. The BWE is initialized when iSAC encoder is initialized. - // Therefore, we need this. - if (!encoder_initialized_) { - // Since we don't require a valid rate or a valid packet size when - // initializing the decoder, we set valid values before initializing encoder - codec_params->codec_inst.rate = kIsacWbDefaultRate; - codec_params->codec_inst.pacsize = kIsacPacSize960; - if (InternalInitEncoder(codec_params) < 0) { - return -1; - } - encoder_initialized_ = true; - } - - return ACM_ISAC_DECODERINIT(codec_inst_ptr_->inst); -} - int16_t ACMISAC::InternalCreateEncoder() { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); if (codec_inst_ptr_ == NULL) { return -1; } @@ -493,19 +424,6 @@ int16_t ACMISAC::InternalCreateEncoder() { return status; } -void ACMISAC::DestructEncoderSafe() { - // codec with shared instance cannot delete. - encoder_initialized_ = false; - return; -} - -void ACMISAC::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - ACM_ISAC_FREE(static_cast(ptr_inst)); - } - return; -} - int16_t ACMISAC::Transcode(uint8_t* bitstream, int16_t* bitstream_len_byte, int16_t q_bwe, @@ -513,6 +431,7 @@ int16_t ACMISAC::Transcode(uint8_t* bitstream, bool is_red) { int16_t jitter_info = 0; // transcode from a higher rate to lower rate sanity check + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); if (codec_inst_ptr_ == NULL) { return -1; } @@ -530,7 +449,27 @@ int16_t ACMISAC::Transcode(uint8_t* bitstream, } } +void ACMISAC::UpdateFrameLen() { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); + frame_len_smpl_ = ACM_ISAC_GETNEWFRAMELEN(codec_inst_ptr_->inst); + encoder_params_.codec_inst.pacsize = frame_len_smpl_; +} + +void ACMISAC::DestructEncoderSafe() { + // codec with shared instance cannot delete. + encoder_initialized_ = false; + return; +} + +void ACMISAC::InternalDestructEncoderInst(void* ptr_inst) { + if (ptr_inst != NULL) { + ACM_ISAC_FREE(static_cast(ptr_inst)); + } + return; +} + int16_t ACMISAC::SetBitRateSafe(int32_t bit_rate) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); if (codec_inst_ptr_ == NULL) { return -1; } @@ -594,6 +533,7 @@ int32_t ACMISAC::GetEstimatedBandwidthSafe() { int samp_rate; // Get bandwidth information + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); ACM_ISAC_GETSENDBWE(codec_inst_ptr_->inst, &bandwidth_index, &delay_index); // Validy check of index @@ -615,6 +555,7 @@ int32_t ACMISAC::SetEstimatedBandwidthSafe(int32_t estimated_bandwidth) { int16_t bandwidth_index; // Check sample frequency and choose appropriate table + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); samp_rate = ACM_ISAC_GETENCSAMPRATE(codec_inst_ptr_->inst); if (samp_rate == 16000) { @@ -657,6 +598,7 @@ int32_t ACMISAC::GetRedPayloadSafe( return -1; #else uint8_t* red_payload, int16_t* payload_bytes) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); int16_t bytes = WebRtcIsac_GetRedPayload( codec_inst_ptr_->inst, reinterpret_cast(red_payload)); @@ -672,6 +614,7 @@ int16_t ACMISAC::UpdateDecoderSampFreq( #ifdef WEBRTC_CODEC_ISAC int16_t codec_id) { // The decoder supports only wideband and super-wideband. + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); if (ACMCodecDB::kISAC == codec_id) { return WebRtcIsac_SetDecSampRate(codec_inst_ptr_->inst, 16000); } else if (ACMCodecDB::kISACSWB == codec_id || @@ -700,6 +643,7 @@ int16_t ACMISAC::UpdateEncoderSampFreq( in_audio_ix_read_ = 0; in_audio_ix_write_ = 0; in_timestamp_ix_write_ = 0; + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); if (WebRtcIsac_SetEncSampRate(codec_inst_ptr_->inst, encoder_samp_freq_hz) < 0) { return -1; @@ -718,6 +662,7 @@ int16_t ACMISAC::UpdateEncoderSampFreq( } int16_t ACMISAC::EncoderSampFreq(uint16_t* samp_freq_hz) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); *samp_freq_hz = ACM_ISAC_GETENCSAMPRATE(codec_inst_ptr_->inst); return 0; } @@ -730,6 +675,7 @@ int32_t ACMISAC::ConfigISACBandwidthEstimator( { uint16_t samp_freq_hz; EncoderSampFreq(&samp_freq_hz); + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); // TODO(turajs): at 32kHz we hardcode calling with 30ms and enforce // the frame-size otherwise we might get error. Revise if // control-bwe is changed. @@ -748,27 +694,29 @@ int32_t ACMISAC::ConfigISACBandwidthEstimator( "Couldn't config iSAC BWE."); return -1; } - UpdateFrameLen(); + { + WriteLockScoped wl(codec_wrapper_lock_); + UpdateFrameLen(); + } + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); ACM_ISAC_GETSENDBITRATE(codec_inst_ptr_->inst, &isac_current_bn_); return 0; } int32_t ACMISAC::SetISACMaxPayloadSize(const uint16_t max_payload_len_bytes) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); return ACM_ISAC_SETMAXPAYLOADSIZE(codec_inst_ptr_->inst, max_payload_len_bytes); } int32_t ACMISAC::SetISACMaxRate(const uint32_t max_rate_bit_per_sec) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); return ACM_ISAC_SETMAXRATE(codec_inst_ptr_->inst, max_rate_bit_per_sec); } -void ACMISAC::UpdateFrameLen() { - frame_len_smpl_ = ACM_ISAC_GETNEWFRAMELEN(codec_inst_ptr_->inst); - encoder_params_.codec_inst.pacsize = frame_len_smpl_; -} - void ACMISAC::CurrentRate(int32_t* rate_bit_per_sec) { if (isac_coding_mode_ == ADAPTIVE) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); ACM_ISAC_GETSENDBITRATE(codec_inst_ptr_->inst, rate_bit_per_sec); } } @@ -784,12 +732,72 @@ int16_t ACMISAC::REDPayloadISAC(const int32_t isac_rate, return status; } -AudioDecoder* ACMISAC::Decoder(int codec_id) { - if (audio_decoder_) - return audio_decoder_; +int ACMISAC::Decode(const uint8_t* encoded, + size_t encoded_len, + int16_t* decoded, + SpeechType* speech_type) { + int16_t temp_type; + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); + int ret = + ACM_ISAC_DECODE_B(static_cast(codec_inst_ptr_->inst), + reinterpret_cast(encoded), + static_cast(encoded_len), + decoded, + &temp_type); + *speech_type = ConvertSpeechType(temp_type); + return ret; +} + +int ACMISAC::DecodePlc(int num_frames, int16_t* decoded) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); + return ACM_ISAC_DECODEPLC( + static_cast(codec_inst_ptr_->inst), + decoded, + static_cast(num_frames)); +} + +int ACMISAC::IncomingPacket(const uint8_t* payload, + size_t payload_len, + uint16_t rtp_sequence_number, + uint32_t rtp_timestamp, + uint32_t arrival_timestamp) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); + return ACM_ISAC_DECODE_BWE( + static_cast(codec_inst_ptr_->inst), + reinterpret_cast(payload), + static_cast(payload_len), + rtp_sequence_number, + rtp_timestamp, + arrival_timestamp); +} + +int ACMISAC::DecodeRedundant(const uint8_t* encoded, + size_t encoded_len, + int16_t* decoded, + SpeechType* speech_type) { + int16_t temp_type = 1; // Default is speech. + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); + int16_t ret = + ACM_ISAC_DECODERCU(static_cast(codec_inst_ptr_->inst), + reinterpret_cast(encoded), + static_cast(encoded_len), + decoded, + &temp_type); + *speech_type = ConvertSpeechType(temp_type); + return ret; +} + +int ACMISAC::ErrorCode() { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); + return ACM_ISAC_GETERRORCODE( + static_cast(codec_inst_ptr_->inst)); +} +AudioDecoder* ACMISAC::Decoder(int codec_id) { // Create iSAC instance if it does not exist. + WriteLockScoped wl(codec_wrapper_lock_); if (!encoder_exist_) { + CriticalSectionScoped lock(codec_inst_crit_sect_.get()); assert(codec_inst_ptr_->inst == NULL); encoder_initialized_ = false; decoder_initialized_ = false; @@ -822,8 +830,7 @@ AudioDecoder* ACMISAC::Decoder(int codec_id) { decoder_initialized_ = true; } - audio_decoder_ = new AcmAudioDecoderIsac(codec_id, codec_inst_ptr_->inst); - return audio_decoder_; + return this; } #endif diff --git a/webrtc/modules/audio_coding/main/acm2/acm_isac.h b/webrtc/modules/audio_coding/main/acm2/acm_isac.h index a3227d5d0..31263c709 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_isac.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_isac.h @@ -12,86 +12,125 @@ #define WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_ACM_ISAC_H_ #include "webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" namespace webrtc { +class CriticalSectionWrapper; + namespace acm2 { struct ACMISACInst; -class AcmAudioDecoderIsac; enum IsacCodingMode { ADAPTIVE, CHANNEL_INDEPENDENT }; -class ACMISAC : public ACMGenericCodec { +class ACMISAC : public ACMGenericCodec, AudioDecoder { public: explicit ACMISAC(int16_t codec_id); ~ACMISAC(); - // for FEC - ACMGenericCodec* CreateInstance(void); + int16_t InternalInitDecoder(WebRtcACMCodecParams* codec_params) + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); + // Methods below are inherited from ACMGenericCodec. + ACMGenericCodec* CreateInstance(void) OVERRIDE; - int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params); + int16_t InternalEncode(uint8_t* bitstream, + int16_t* bitstream_len_byte) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); - int16_t InternalInitDecoder(WebRtcACMCodecParams* codec_params); + int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); - int16_t UpdateDecoderSampFreq(int16_t codec_id); + int16_t UpdateDecoderSampFreq(int16_t codec_id) OVERRIDE; - int16_t UpdateEncoderSampFreq(uint16_t samp_freq_hz); + int16_t UpdateEncoderSampFreq(uint16_t samp_freq_hz) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); - int16_t EncoderSampFreq(uint16_t* samp_freq_hz); + int16_t EncoderSampFreq(uint16_t* samp_freq_hz) OVERRIDE; int32_t ConfigISACBandwidthEstimator(const uint8_t init_frame_size_msec, const uint16_t init_rate_bit_per_sec, - const bool enforce_frame_size); + const bool enforce_frame_size) OVERRIDE; - int32_t SetISACMaxPayloadSize(const uint16_t max_payload_len_bytes); + int32_t SetISACMaxPayloadSize(const uint16_t max_payload_len_bytes) OVERRIDE; - int32_t SetISACMaxRate(const uint32_t max_rate_bit_per_sec); + int32_t SetISACMaxRate(const uint32_t max_rate_bit_per_sec) OVERRIDE; int16_t REDPayloadISAC(const int32_t isac_rate, const int16_t isac_bw_estimate, uint8_t* payload, - int16_t* payload_len_bytes); + int16_t* payload_len_bytes) OVERRIDE; - protected: - void DestructEncoderSafe(); + // Methods below are inherited from AudioDecoder. + virtual int Decode(const uint8_t* encoded, + size_t encoded_len, + int16_t* decoded, + SpeechType* speech_type) OVERRIDE; - int16_t SetBitRateSafe(const int32_t bit_rate); + virtual bool HasDecodePlc() const OVERRIDE { return true; } - int32_t GetEstimatedBandwidthSafe(); + virtual int DecodePlc(int num_frames, int16_t* decoded) OVERRIDE; - int32_t SetEstimatedBandwidthSafe(int32_t estimated_bandwidth); + virtual int Init() OVERRIDE { return 0; } - int32_t GetRedPayloadSafe(uint8_t* red_payload, int16_t* payload_bytes); + virtual int IncomingPacket(const uint8_t* payload, + size_t payload_len, + uint16_t rtp_sequence_number, + uint32_t rtp_timestamp, + uint32_t arrival_timestamp) OVERRIDE; - int16_t InternalCreateEncoder(); + virtual int DecodeRedundant(const uint8_t* encoded, + size_t encoded_len, + int16_t* decoded, + SpeechType* speech_type) OVERRIDE; - void InternalDestructEncoderInst(void* ptr_inst); + virtual int ErrorCode() OVERRIDE; + protected: int16_t Transcode(uint8_t* bitstream, int16_t* bitstream_len_byte, int16_t q_bwe, int32_t rate, bool is_red); - void CurrentRate(int32_t* rate_bit_per_sec); + void UpdateFrameLen() EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); + + // Methods below are inherited from ACMGenericCodec. + void DestructEncoderSafe() OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); + + int16_t SetBitRateSafe(const int32_t bit_rate) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); + + int32_t GetEstimatedBandwidthSafe() OVERRIDE; + + int32_t SetEstimatedBandwidthSafe(int32_t estimated_bandwidth) OVERRIDE; + + int32_t GetRedPayloadSafe(uint8_t* red_payload, + int16_t* payload_bytes) OVERRIDE; + + int16_t InternalCreateEncoder() OVERRIDE; + + void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - void UpdateFrameLen(); + void CurrentRate(int32_t* rate_bit_per_sec) OVERRIDE; - virtual AudioDecoder* Decoder(int codec_id); + virtual AudioDecoder* Decoder(int codec_id) OVERRIDE; - ACMISACInst* codec_inst_ptr_; + // |codec_inst_crit_sect_| protects |codec_inst_ptr_|. + const scoped_ptr codec_inst_crit_sect_; + ACMISACInst* codec_inst_ptr_ GUARDED_BY(codec_inst_crit_sect_); bool is_enc_initialized_; IsacCodingMode isac_coding_mode_; bool enforce_frame_size_; int32_t isac_current_bn_; uint16_t samples_in_10ms_audio_; - AcmAudioDecoderIsac* audio_decoder_; bool decoder_initialized_; }; diff --git a/webrtc/modules/audio_coding/main/acm2/acm_opus.cc b/webrtc/modules/audio_coding/main/acm2/acm_opus.cc index 5830f6b40..544c932f3 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_opus.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_opus.cc @@ -75,6 +75,8 @@ ACMOpus::ACMOpus(int16_t codec_id) // Opus has internal DTX, but we dont use it for now. has_internal_dtx_ = false; + has_internal_fec_ = true; + if (codec_id_ != ACMCodecDB::kOpus) { WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, "Wrong codec id for Opus."); @@ -198,6 +200,31 @@ int16_t ACMOpus::SetBitRateSafe(const int32_t rate) { return -1; } +int ACMOpus::SetFEC(bool enable_fec) { + // Ask the encoder to enable FEC. + if (enable_fec) { + if (WebRtcOpus_EnableFec(encoder_inst_ptr_) == 0) { + fec_enabled_ = true; + return 0; + } + } else { + if (WebRtcOpus_DisableFec(encoder_inst_ptr_) == 0) { + fec_enabled_ = false; + return 0; + } + } + return -1; +} + +int ACMOpus::SetPacketLossRate(int loss_rate) { + // Ask the encoder to change the target packet loss rate. + if (WebRtcOpus_SetPacketLossRate(encoder_inst_ptr_, loss_rate) == 0) { + packet_loss_rate_ = loss_rate; + return 0; + } + return -1; +} + #endif // WEBRTC_CODEC_OPUS } // namespace acm2 diff --git a/webrtc/modules/audio_coding/main/acm2/acm_opus.h b/webrtc/modules/audio_coding/main/acm2/acm_opus.h index a346e3c8f..b94adc471 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_opus.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_opus.h @@ -28,10 +28,16 @@ class ACMOpus : public ACMGenericCodec { ACMGenericCodec* CreateInstance(void); - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); + int16_t InternalEncode(uint8_t* bitstream, + int16_t* bitstream_len_byte) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalInitEncoder(WebRtcACMCodecParams *codec_params); + virtual int SetFEC(bool enable_fec) OVERRIDE; + + virtual int SetPacketLossRate(int loss_rate) OVERRIDE; + protected: void DestructEncoderSafe(); @@ -39,12 +45,16 @@ class ACMOpus : public ACMGenericCodec { void InternalDestructEncoderInst(void* ptr_inst); - int16_t SetBitRateSafe(const int32_t rate); + int16_t SetBitRateSafe(const int32_t rate) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); WebRtcOpusEncInst* encoder_inst_ptr_; uint16_t sample_freq_; - uint16_t bitrate_; + int32_t bitrate_; int channels_; + + bool fec_enabled_; + int packet_loss_rate_; }; } // namespace acm2 diff --git a/webrtc/modules/audio_coding/main/acm2/acm_pcm16b.h b/webrtc/modules/audio_coding/main/acm2/acm_pcm16b.h index 23b8c121e..de4330d56 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_pcm16b.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_pcm16b.h @@ -25,12 +25,15 @@ class ACMPCM16B : public ACMGenericCodec { // For FEC. ACMGenericCodec* CreateInstance(void); - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); + int16_t InternalEncode(uint8_t* bitstream, + int16_t* bitstream_len_byte) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params); protected: - void DestructEncoderSafe(); + void DestructEncoderSafe() OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalCreateEncoder(); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_pcma.h b/webrtc/modules/audio_coding/main/acm2/acm_pcma.h index 2da873cb7..a2d8874a6 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_pcma.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_pcma.h @@ -25,7 +25,9 @@ class ACMPCMA : public ACMGenericCodec { // For FEC. ACMGenericCodec* CreateInstance(void); - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); + int16_t InternalEncode(uint8_t* bitstream, + int16_t* bitstream_len_byte) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_pcmu.h b/webrtc/modules/audio_coding/main/acm2/acm_pcmu.h index 18d8279dc..7aa83b756 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_pcmu.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_pcmu.h @@ -25,12 +25,15 @@ class ACMPCMU : public ACMGenericCodec { // For FEC. ACMGenericCodec* CreateInstance(void); - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); + int16_t InternalEncode(uint8_t* bitstream, + int16_t* bitstream_len_byte) OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalInitEncoder(WebRtcACMCodecParams* codec_params); protected: - void DestructEncoderSafe(); + void DestructEncoderSafe() OVERRIDE + EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_); int16_t InternalCreateEncoder(); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc b/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc index 7c124c7e3..f40250f2d 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc @@ -21,11 +21,11 @@ #include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" #include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" #include "webrtc/modules/audio_coding/main/acm2/nack.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/logging.h" -#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" #include "webrtc/system_wrappers/interface/tick_util.h" #include "webrtc/system_wrappers/interface/trace.h" @@ -35,7 +35,6 @@ namespace acm2 { namespace { -const int kNeteqInitSampleRateHz = 16000; const int kNackThresholdPackets = 2; // |vad_activity_| field of |audio_frame| is set to |previous_audio_activity_| @@ -117,21 +116,22 @@ bool IsCng(int codec_id) { } // namespace -AcmReceiver::AcmReceiver() - : id_(0), - neteq_(NetEq::Create(kNeteqInitSampleRateHz)), +AcmReceiver::AcmReceiver(const AudioCodingModule::Config& config) + : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + id_(config.id), last_audio_decoder_(-1), // Invalid value. - decode_lock_(RWLockWrapper::CreateRWLock()), - neteq_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - vad_enabled_(true), previous_audio_activity_(AudioFrame::kVadPassive), - current_sample_rate_hz_(kNeteqInitSampleRateHz), + current_sample_rate_hz_(config.neteq_config.sample_rate_hz), nack_(), nack_enabled_(false), + neteq_(NetEq::Create(config.neteq_config)), + vad_enabled_(true), + clock_(config.clock), av_sync_(false), initial_delay_manager_(), missing_packets_sync_stream_(), late_packets_sync_stream_() { + assert(clock_); for (int n = 0; n < ACMCodecDB::kMaxNumCodecs; ++n) { decoders_[n].registered = false; } @@ -147,8 +147,6 @@ AcmReceiver::AcmReceiver() AcmReceiver::~AcmReceiver() { delete neteq_; - delete decode_lock_; - delete neteq_crit_sect_; } int AcmReceiver::SetMinimumDelay(int delay_ms) { @@ -162,7 +160,7 @@ int AcmReceiver::SetInitialDelay(int delay_ms) { if (delay_ms < 0 || delay_ms > 10000) { return -1; } - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); if (delay_ms == 0) { av_sync_ = false; @@ -206,7 +204,7 @@ int AcmReceiver::LeastRequiredDelayMs() const { } int AcmReceiver::current_sample_rate_hz() const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); return current_sample_rate_hz_; } @@ -269,7 +267,7 @@ int AcmReceiver::InsertPacket(const WebRtcRTPHeader& rtp_header, const RTPHeader* header = &rtp_header.header; // Just a shorthand. { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); int codec_id = RtpHeaderToCodecIndex(*header, incoming_payload); if (codec_id < 0) { @@ -328,24 +326,20 @@ int AcmReceiver::InsertPacket(const WebRtcRTPHeader& rtp_header, rtp_header, receive_timestamp, packet_type, new_codec, sample_rate_hz, missing_packets_sync_stream_.get()); } - } + } // |crit_sect_| is released. - { - WriteLockScoped lock_codecs(*decode_lock_); // Lock to prevent an encoding. - - // If |missing_packets_sync_stream_| is allocated then we are in AV-sync and - // we may need to insert sync-packets. We don't check |av_sync_| as we are - // outside AcmReceiver's critical section. - if (missing_packets_sync_stream_.get()) { - InsertStreamOfSyncPackets(missing_packets_sync_stream_.get()); - } + // If |missing_packets_sync_stream_| is allocated then we are in AV-sync and + // we may need to insert sync-packets. We don't check |av_sync_| as we are + // outside AcmReceiver's critical section. + if (missing_packets_sync_stream_.get()) { + InsertStreamOfSyncPackets(missing_packets_sync_stream_.get()); + } - if (neteq_->InsertPacket(rtp_header, incoming_payload, length_payload, - receive_timestamp) < 0) { - LOG_FERR1(LS_ERROR, "AcmReceiver::InsertPacket", header->payloadType) << - " Failed to insert packet"; - return -1; - } + if (neteq_->InsertPacket(rtp_header, incoming_payload, length_payload, + receive_timestamp) < 0) { + LOG_FERR1(LS_ERROR, "AcmReceiver::InsertPacket", header->payloadType) << + " Failed to insert packet"; + return -1; } return 0; } @@ -359,7 +353,7 @@ int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) { { // Accessing members, take the lock. - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); if (av_sync_) { assert(initial_delay_manager_.get()); @@ -383,28 +377,24 @@ int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) { } } - { - WriteLockScoped lock_codecs(*decode_lock_); // Lock to prevent an encoding. - - // If |late_packets_sync_stream_| is allocated then we have been in AV-sync - // mode and we might have to insert sync-packets. - if (late_packets_sync_stream_.get()) { - InsertStreamOfSyncPackets(late_packets_sync_stream_.get()); - if (return_silence) // Silence generated, don't pull from NetEq. - return 0; - } + // If |late_packets_sync_stream_| is allocated then we have been in AV-sync + // mode and we might have to insert sync-packets. + if (late_packets_sync_stream_.get()) { + InsertStreamOfSyncPackets(late_packets_sync_stream_.get()); + if (return_silence) // Silence generated, don't pull from NetEq. + return 0; + } - if (neteq_->GetAudio(AudioFrame::kMaxDataSizeSamples, - ptr_audio_buffer, - &samples_per_channel, - &num_channels, &type) != NetEq::kOK) { - LOG_FERR0(LS_ERROR, "AcmReceiver::GetAudio") << "NetEq Failed."; - return -1; - } + if (neteq_->GetAudio(AudioFrame::kMaxDataSizeSamples, + ptr_audio_buffer, + &samples_per_channel, + &num_channels, &type) != NetEq::kOK) { + LOG_FERR0(LS_ERROR, "AcmReceiver::GetAudio") << "NetEq Failed."; + return -1; } // Accessing members, take the lock. - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); // Update NACK. int decoded_sequence_num = 0; @@ -426,9 +416,13 @@ int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) { if (ptr_audio_buffer == audio_buffer_) { // Data is written to local buffer. if (need_resampling) { - samples_per_channel = resampler_.Resample10Msec( - audio_buffer_, current_sample_rate_hz_, desired_freq_hz, - num_channels, audio_frame->data_); + samples_per_channel = + resampler_.Resample10Msec(audio_buffer_, + current_sample_rate_hz_, + desired_freq_hz, + num_channels, + AudioFrame::kMaxDataSizeSamples, + audio_frame->data_); if (samples_per_channel < 0) { LOG_FERR0(LS_ERROR, "AcmReceiver::GetAudio") << "Resampler Failed."; return -1; @@ -442,9 +436,13 @@ int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) { // Data is written into |audio_frame|. if (need_resampling) { // We might end up here ONLY if codec is changed. - samples_per_channel = resampler_.Resample10Msec( - audio_frame->data_, current_sample_rate_hz_, desired_freq_hz, - num_channels, audio_buffer_); + samples_per_channel = + resampler_.Resample10Msec(audio_frame->data_, + current_sample_rate_hz_, + desired_freq_hz, + num_channels, + AudioFrame::kMaxDataSizeSamples, + audio_buffer_); if (samples_per_channel < 0) { LOG_FERR0(LS_ERROR, "AcmReceiver::GetAudio") << "Resampler Failed."; return -1; @@ -463,6 +461,19 @@ int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) { SetAudioFrameActivityAndType(vad_enabled_, type, audio_frame); previous_audio_activity_ = audio_frame->vad_activity_; call_stats_.DecodedByNetEq(audio_frame->speech_type_); + + // Computes the RTP timestamp of the first sample in |audio_frame| from + // |GetPlayoutTimestamp|, which is the timestamp of the last sample of + // |audio_frame|. + uint32_t playout_timestamp = 0; + if (GetPlayoutTimestamp(&playout_timestamp)) { + audio_frame->timestamp_ = + playout_timestamp - audio_frame->samples_per_channel_; + } else { + // Remain 0 until we have a valid |playout_timestamp|. + audio_frame->timestamp_ = 0; + } + return 0; } @@ -478,7 +489,7 @@ int32_t AcmReceiver::AddCodec(int acm_codec_id, neteq_decoder = kDecoderOpus_2ch; } - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); // The corresponding NetEq decoder ID. // If this coder has been registered before. @@ -505,8 +516,7 @@ int32_t AcmReceiver::AddCodec(int acm_codec_id, ret_val = neteq_->RegisterPayloadType(neteq_decoder, payload_type); } else { ret_val = neteq_->RegisterExternalDecoder( - audio_decoder, neteq_decoder, - ACMCodecDB::database_[acm_codec_id].plfreq, payload_type); + audio_decoder, neteq_decoder, payload_type); } if (ret_val != NetEq::kOK) { LOG_FERR3(LS_ERROR, "AcmReceiver::AddCodec", acm_codec_id, payload_type, @@ -525,13 +535,13 @@ int32_t AcmReceiver::AddCodec(int acm_codec_id, void AcmReceiver::EnableVad() { neteq_->EnableVad(); - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); vad_enabled_ = true; } void AcmReceiver::DisableVad() { neteq_->DisableVad(); - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); vad_enabled_ = false; } @@ -543,7 +553,7 @@ void AcmReceiver::FlushBuffers() { // many as it can. int AcmReceiver::RemoveAllCodecs() { int ret_val = 0; - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); for (int n = 0; n < ACMCodecDB::kMaxNumCodecs; ++n) { if (decoders_[n].registered) { if (neteq_->RemovePayloadType(decoders_[n].payload_type) == 0) { @@ -569,7 +579,7 @@ int AcmReceiver::RemoveCodec(uint8_t payload_type) { LOG_FERR1(LS_ERROR, "AcmReceiver::RemoveCodec", payload_type); return -1; } - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); decoders_[codec_index].registered = false; if (last_audio_decoder_ == codec_index) last_audio_decoder_ = -1; // Codec is removed, invalidate last decoder. @@ -577,26 +587,27 @@ int AcmReceiver::RemoveCodec(uint8_t payload_type) { } void AcmReceiver::set_id(int id) { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); id_ = id; } -uint32_t AcmReceiver::PlayoutTimestamp() { +bool AcmReceiver::GetPlayoutTimestamp(uint32_t* timestamp) { if (av_sync_) { assert(initial_delay_manager_.get()); - if (initial_delay_manager_->buffering()) - return initial_delay_manager_->playout_timestamp(); + if (initial_delay_manager_->buffering()) { + return initial_delay_manager_->GetPlayoutTimestamp(timestamp); + } } - return neteq_->PlayoutTimestamp(); + return neteq_->GetPlayoutTimestamp(timestamp); } int AcmReceiver::last_audio_codec_id() const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); return last_audio_decoder_; } int AcmReceiver::last_audio_payload_type() const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); if (last_audio_decoder_ < 0) return -1; assert(decoders_[last_audio_decoder_].registered); @@ -604,7 +615,7 @@ int AcmReceiver::last_audio_payload_type() const { } int AcmReceiver::RedPayloadType() const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); if (ACMCodecDB::kRED < 0 || !decoders_[ACMCodecDB::kRED].registered) { LOG_F(LS_WARNING) << "RED is not registered."; @@ -614,7 +625,7 @@ int AcmReceiver::RedPayloadType() const { } int AcmReceiver::LastAudioCodec(CodecInst* codec) const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); if (last_audio_decoder_ < 0) { return -1; } @@ -639,6 +650,7 @@ void AcmReceiver::NetworkStatistics(ACMNetworkStatistics* acm_stat) { acm_stat->currentPreemptiveRate = neteq_stat.preemptive_rate; acm_stat->currentAccelerateRate = neteq_stat.accelerate_rate; acm_stat->clockDriftPPM = neteq_stat.clockdrift_ppm; + acm_stat->addedSamples = neteq_stat.added_zero_samples; std::vector waiting_times; neteq_->WaitingTimes(&waiting_times); @@ -668,7 +680,7 @@ void AcmReceiver::NetworkStatistics(ACMNetworkStatistics* acm_stat) { int AcmReceiver::DecoderByPayloadType(uint8_t payload_type, CodecInst* codec) const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); int codec_index = PayloadType2CodecIndex(payload_type); if (codec_index < 0) { LOG_FERR1(LS_ERROR, "AcmReceiver::DecoderByPayloadType", payload_type); @@ -694,7 +706,7 @@ int AcmReceiver::EnableNack(size_t max_nack_list_size) { if (max_nack_list_size == 0 || max_nack_list_size > Nack::kNackListSizeLimit) return -1; - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); if (!nack_enabled_) { nack_.reset(Nack::Create(kNackThresholdPackets)); nack_enabled_ = true; @@ -710,14 +722,14 @@ int AcmReceiver::EnableNack(size_t max_nack_list_size) { } void AcmReceiver::DisableNack() { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); nack_.reset(); // Memory is released. nack_enabled_ = false; } std::vector AcmReceiver::GetNackList( int round_trip_time_ms) const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); if (round_trip_time_ms < 0) { WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, "GetNackList: round trip time cannot be negative." @@ -733,7 +745,7 @@ std::vector AcmReceiver::GetNackList( void AcmReceiver::ResetInitialDelay() { { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); av_sync_ = false; initial_delay_manager_.reset(NULL); missing_packets_sync_stream_.reset(NULL); @@ -755,13 +767,9 @@ bool AcmReceiver::GetSilence(int desired_sample_rate_hz, AudioFrame* frame) { // exceeds a threshold. int num_packets; int max_num_packets; - int buffer_size_byte; - int max_buffer_size_byte; const float kBufferingThresholdScale = 0.9f; - neteq_->PacketBufferStatistics(&num_packets, &max_num_packets, - &buffer_size_byte, &max_buffer_size_byte); - if (num_packets > max_num_packets * kBufferingThresholdScale || - buffer_size_byte > max_buffer_size_byte * kBufferingThresholdScale) { + neteq_->PacketBufferStatistics(&num_packets, &max_num_packets); + if (num_packets > max_num_packets * kBufferingThresholdScale) { initial_delay_manager_->DisableBuffering(); return false; } @@ -774,7 +782,6 @@ bool AcmReceiver::GetSilence(int desired_sample_rate_hz, AudioFrame* frame) { current_sample_rate_hz_ = ACMCodecDB::database_[last_audio_decoder_].plfreq; frame->num_channels_ = decoders_[last_audio_decoder_].channels; } else { - current_sample_rate_hz_ = kNeteqInitSampleRateHz; frame->num_channels_ = 1; } @@ -788,7 +795,6 @@ bool AcmReceiver::GetSilence(int desired_sample_rate_hz, AudioFrame* frame) { frame->samples_per_channel_ = frame->sample_rate_hz_ / 100; // Always 10 ms. frame->speech_type_ = AudioFrame::kCNG; frame->vad_activity_ = AudioFrame::kVadPassive; - frame->energy_ = 0; int samples = frame->samples_per_channel_ * frame->num_channels_; memset(frame->data_, 0, samples * sizeof(int16_t)); return true; @@ -818,7 +824,7 @@ uint32_t AcmReceiver::NowInTimestamp(int decoder_sampling_rate) const { // We masked 6 most significant bits of 32-bit so there is no overflow in // the conversion from milliseconds to timestamp. const uint32_t now_in_ms = static_cast( - TickTime::MillisecondTimestamp() & 0x03ffffff); + clock_->TimeInMilliseconds() & 0x03ffffff); return static_cast( (decoder_sampling_rate / 1000) * now_in_ms); } @@ -842,7 +848,7 @@ void AcmReceiver::InsertStreamOfSyncPackets( void AcmReceiver::GetDecodingCallStatistics( AudioDecodingCallStats* stats) const { - CriticalSectionScoped lock(neteq_crit_sect_); + CriticalSectionScoped lock(crit_sect_.get()); *stats = call_stats_.GetDecodingStatistics(); } diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receiver.h b/webrtc/modules/audio_coding/main/acm2/acm_receiver.h index 81eb5206b..dd6de528f 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_receiver.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_receiver.h @@ -20,16 +20,16 @@ #include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" #include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" #include "webrtc/modules/audio_coding/main/acm2/initial_delay_manager.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" #include "webrtc/typedefs.h" namespace webrtc { struct CodecInst; class CriticalSectionWrapper; -class RWLockWrapper; class NetEq; namespace acm2 { @@ -47,9 +47,7 @@ class AcmReceiver { }; // Constructor of the class - AcmReceiver(); - - explicit AcmReceiver(NetEq* neteq); + explicit AcmReceiver(const AudioCodingModule::Config& config); // Destructor of the class. ~AcmReceiver(); @@ -210,13 +208,6 @@ class AcmReceiver { // bool vad_enabled() const { return vad_enabled_; } - // - // Get the decode lock used to protect decoder instances while decoding. - // - // Return value : Pointer to the decode lock. - // - RWLockWrapper* DecodeLock() const { return decode_lock_; } - // // Flushes the NetEq packet and speech buffers. // @@ -244,9 +235,10 @@ class AcmReceiver { void set_id(int id); // TODO(turajs): can be inline. // - // Returns the RTP timestamp of the last sample delivered by GetAudio(). + // Gets the RTP timestamp of the last sample delivered by GetAudio(). + // Returns true if the RTP timestamp is valid, otherwise false. // - uint32_t PlayoutTimestamp(); + bool GetPlayoutTimestamp(uint32_t* timestamp); // // Return the index of the codec associated with the last non-CNG/non-DTMF @@ -328,7 +320,8 @@ class AcmReceiver { private: int PayloadType2CodecIndex(uint8_t payload_type) const; - bool GetSilence(int desired_sample_rate_hz, AudioFrame* frame); + bool GetSilence(int desired_sample_rate_hz, AudioFrame* frame) + EXCLUSIVE_LOCKS_REQUIRED(crit_sect_); int GetNumSyncPacketToInsert(uint16_t received_squence_number); @@ -339,20 +332,22 @@ class AcmReceiver { void InsertStreamOfSyncPackets(InitialDelayManager::SyncStream* sync_stream); - int id_; + scoped_ptr crit_sect_; + int id_; // TODO(henrik.lundin) Make const. + int last_audio_decoder_ GUARDED_BY(crit_sect_); + AudioFrame::VADActivity previous_audio_activity_ GUARDED_BY(crit_sect_); + int current_sample_rate_hz_ GUARDED_BY(crit_sect_); + ACMResampler resampler_ GUARDED_BY(crit_sect_); + // Used in GetAudio, declared as member to avoid allocating every 10ms. + // TODO(henrik.lundin) Stack-allocate in GetAudio instead? + int16_t audio_buffer_[AudioFrame::kMaxDataSizeSamples] GUARDED_BY(crit_sect_); + scoped_ptr nack_ GUARDED_BY(crit_sect_); + bool nack_enabled_ GUARDED_BY(crit_sect_); + CallStatistics call_stats_ GUARDED_BY(crit_sect_); NetEq* neteq_; Decoder decoders_[ACMCodecDB::kMaxNumCodecs]; - int last_audio_decoder_; - RWLockWrapper* decode_lock_; - CriticalSectionWrapper* neteq_crit_sect_; bool vad_enabled_; - AudioFrame::VADActivity previous_audio_activity_; - int current_sample_rate_hz_; - ACMResampler resampler_; - // Used in GetAudio, declared as member to avoid allocating every 10ms. - int16_t audio_buffer_[AudioFrame::kMaxDataSizeSamples]; - scoped_ptr nack_; - bool nack_enabled_; + Clock* clock_; // TODO(henrik.lundin) Make const if possible. // Indicates if a non-zero initial delay is set, and the receiver is in // AV-sync mode. @@ -366,8 +361,6 @@ class AcmReceiver { // initial delay is set. scoped_ptr missing_packets_sync_stream_; scoped_ptr late_packets_sync_stream_; - - CallStatistics call_stats_; }; } // namespace acm2 diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receiver_unittest.cc b/webrtc/modules/audio_coding/main/acm2/acm_receiver_unittest.cc index 712eeb268..4234f1464 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_receiver_unittest.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_receiver_unittest.cc @@ -16,7 +16,8 @@ #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #include "webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h" #include "webrtc/modules/audio_coding/main/acm2/acm_codec_database.h" -#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h" +#include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/test/test_suite.h" #include "webrtc/test/testsupport/fileutils.h" @@ -42,12 +43,14 @@ class AcmReceiverTest : public AudioPacketizationCallback, public ::testing::Test { protected: AcmReceiverTest() - : receiver_(new AcmReceiver), - acm_(new AudioCodingModuleImpl(0)), - timestamp_(0), + : timestamp_(0), packet_sent_(false), last_packet_send_timestamp_(timestamp_), - last_frame_type_(kFrameEmpty) {} + last_frame_type_(kFrameEmpty) { + AudioCodingModule::Config config; + acm_.reset(new AudioCodingModuleImpl(config)); + receiver_.reset(new AcmReceiver(config)); + } ~AcmReceiverTest() {} @@ -302,55 +305,6 @@ TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(PostdecodingVad)) { EXPECT_EQ(AudioFrame::kVadUnknown, frame.vad_activity_); } -TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(FlushBuffer)) { - const int id = ACMCodecDB::kISAC; - EXPECT_EQ(0, receiver_->AddCodec(id, codecs_[id].pltype, codecs_[id].channels, - NULL)); - const int kNumPackets = 5; - const int num_10ms_frames = codecs_[id].pacsize / (codecs_[id].plfreq / 100); - for (int n = 0; n < kNumPackets; ++n) - InsertOnePacketOfSilence(id); - ACMNetworkStatistics statistics; - receiver_->NetworkStatistics(&statistics); - ASSERT_EQ(num_10ms_frames * kNumPackets * 10, statistics.currentBufferSize); - - receiver_->FlushBuffers(); - receiver_->NetworkStatistics(&statistics); - ASSERT_EQ(0, statistics.currentBufferSize); -} - -TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(PlayoutTimestamp)) { - const int id = ACMCodecDB::kPCM16Bwb; - EXPECT_EQ(0, receiver_->AddCodec(id, codecs_[id].pltype, codecs_[id].channels, - NULL)); - receiver_->SetPlayoutMode(fax); - const int kNumPackets = 5; - const int num_10ms_frames = codecs_[id].pacsize / (codecs_[id].plfreq / 100); - uint32_t expected_timestamp; - AudioFrame frame; - int ts_offset = 0; - bool first_audio_frame = true; - for (int n = 0; n < kNumPackets; ++n) { - packet_sent_ = false; - InsertOnePacketOfSilence(id); - ASSERT_TRUE(packet_sent_); - expected_timestamp = last_packet_send_timestamp_; - for (int k = 0; k < num_10ms_frames; ++k) { - ASSERT_EQ(0, receiver_->GetAudio(codecs_[id].plfreq, &frame)); - if (first_audio_frame) { - // There is an offset in playout timestamps. Perhaps, it is related to - // initial delay that NetEq applies - ts_offset = receiver_->PlayoutTimestamp() - expected_timestamp; - first_audio_frame = false; - } else { - EXPECT_EQ(expected_timestamp + ts_offset, - receiver_->PlayoutTimestamp()); - } - expected_timestamp += codecs_[id].plfreq / 100; // Increment by 10 ms. - } - } -} - TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(LastAudioCodec)) { const int kCodecId[] = { ACMCodecDB::kISAC, ACMCodecDB::kPCMA, ACMCodecDB::kISACSWB, diff --git a/webrtc/modules/audio_coding/main/acm2/acm_resampler.cc b/webrtc/modules/audio_coding/main/acm2/acm_resampler.cc index 294092258..97d87b1b3 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_resampler.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_resampler.cc @@ -10,6 +10,7 @@ #include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" +#include #include #include "webrtc/common_audio/resampler/include/resampler.h" @@ -28,10 +29,15 @@ int ACMResampler::Resample10Msec(const int16_t* in_audio, int in_freq_hz, int out_freq_hz, int num_audio_channels, + int out_capacity_samples, int16_t* out_audio) { int in_length = in_freq_hz * num_audio_channels / 100; int out_length = out_freq_hz * num_audio_channels / 100; if (in_freq_hz == out_freq_hz) { + if (out_capacity_samples < in_length) { + assert(false); + return -1; + } memcpy(out_audio, in_audio, in_length * sizeof(int16_t)); return in_length / num_audio_channels; } @@ -43,9 +49,15 @@ int ACMResampler::Resample10Msec(const int16_t* in_audio, return -1; } - out_length = resampler_.Resample(in_audio, in_length, out_audio, out_length); + out_length = + resampler_.Resample(in_audio, in_length, out_audio, out_capacity_samples); if (out_length == -1) { - LOG_FERR4(LS_ERROR, Resample, in_audio, in_length, out_audio, out_length); + LOG_FERR4(LS_ERROR, + Resample, + in_audio, + in_length, + out_audio, + out_capacity_samples); return -1; } diff --git a/webrtc/modules/audio_coding/main/acm2/acm_resampler.h b/webrtc/modules/audio_coding/main/acm2/acm_resampler.h index 5d952e54b..a8fc6b6f2 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_resampler.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_resampler.h @@ -26,10 +26,11 @@ class ACMResampler { int in_freq_hz, int out_freq_hz, int num_audio_channels, + int out_capacity_samples, int16_t* out_audio); private: - PushResampler resampler_; + PushResampler resampler_; }; } // namespace acm2 diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.cc b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.cc index 60ed69cb2..eca909cc4 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.cc +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.cc @@ -13,22 +13,21 @@ #include "webrtc/common_types.h" #include "webrtc/modules/audio_coding/main/acm2/acm_codec_database.h" #include "webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h" -#include "webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h" #include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { -const char kLegacyAcmVersion[] = "acm1"; -const char kExperimentalAcmVersion[] = "acm2"; - // Create module AudioCodingModule* AudioCodingModule::Create(int id) { - return new acm1::AudioCodingModuleImpl(id, Clock::GetRealTimeClock()); + return Create(id, Clock::GetRealTimeClock()); } AudioCodingModule* AudioCodingModule::Create(int id, Clock* clock) { - return new acm1::AudioCodingModuleImpl(id, clock); + AudioCodingModule::Config config; + config.id = id; + config.clock = clock; + return new acm2::AudioCodingModuleImpl(config); } // Get number of supported codecs @@ -95,13 +94,4 @@ bool AudioCodingModule::IsCodecValid(const CodecInst& codec) { } } -AudioCodingModule* AudioCodingModuleFactory::Create(int id) const { - return new acm1::AudioCodingModuleImpl(static_cast(id), - Clock::GetRealTimeClock()); -} - -AudioCodingModule* NewAudioCodingModuleFactory::Create(int id) const { - return new acm2::AudioCodingModuleImpl(id); -} - } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi index f51c3bf7d..90dad6c55 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi @@ -7,16 +7,36 @@ # be found in the AUTHORS file in the root of the source tree. { + 'variables': { + 'audio_coding_dependencies': [ + 'CNG', + 'G711', + 'G722', + 'iLBC', + 'iSAC', + 'iSACFix', + 'PCM16B', + '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + ], + 'audio_coding_defines': [], + 'conditions': [ + ['include_opus==1', { + 'audio_coding_dependencies': ['webrtc_opus',], + 'audio_coding_defines': ['WEBRTC_CODEC_OPUS',], + }], + ], + }, 'targets': [ { - 'target_name': 'acm2', + 'target_name': 'audio_coding_module', 'type': 'static_library', 'defines': [ '<@(audio_coding_defines)', ], 'dependencies': [ '<@(audio_coding_dependencies)', - 'NetEq4', + 'neteq', ], 'include_dirs': [ '../interface', @@ -93,4 +113,45 @@ ], }, ], + 'conditions': [ + ['include_tests==1', { + 'targets': [ + { + 'target_name': 'delay_test', + 'type': 'executable', + 'dependencies': [ + 'audio_coding_module', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(webrtc_root)/test/test.gyp:test_support', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + ], + 'sources': [ + '../test/delay_test.cc', + '../test/Channel.cc', + '../test/PCMFile.cc', + '../test/utility.cc', + ], + }, # delay_test + { + 'target_name': 'insert_packet_with_timing', + 'type': 'executable', + 'dependencies': [ + 'audio_coding_module', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(webrtc_root)/test/test.gyp:test_support', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + ], + 'sources': [ + '../test/insert_packet_with_timing.cc', + '../test/Channel.cc', + '../test/PCMFile.cc', + ], + }, # delay_test + ], + }], + ], } diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc index 92a69cf8f..f2410b7df 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc @@ -39,11 +39,11 @@ enum { kMaxPacketSize = 2560 }; -// Maximum number of payloads that can be packed in one RED payload. For -// regular FEC, we only pack two payloads. In case of dual-streaming, in worst -// case we might pack 3 payloads in one RED payload. +// Maximum number of payloads that can be packed in one RED packet. For +// regular RED, we only pack two payloads. In case of dual-streaming, in worst +// case we might pack 3 payloads in one RED packet. enum { - kNumFecFragmentationVectors = 2, + kNumRedFragmentationVectors = 2, kMaxNumFragmentationVectors = 3 }; @@ -114,9 +114,10 @@ static int TimestampLessThan(uint32_t t1, uint32_t t2) { } // namespace -AudioCodingModuleImpl::AudioCodingModuleImpl(int id) - : packetization_callback_(NULL), - id_(id), +AudioCodingModuleImpl::AudioCodingModuleImpl( + const AudioCodingModule::Config& config) + : acm_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + id_(config.id), expected_codec_ts_(0xD87F3F9F), expected_in_ts_(0xD87F3F9F), send_codec_inst_(), @@ -131,18 +132,20 @@ AudioCodingModuleImpl::AudioCodingModuleImpl(int id) stereo_send_(false), current_send_codec_idx_(-1), send_codec_registered_(false), - acm_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - vad_callback_(NULL), + receiver_(config), is_first_red_(true), - fec_enabled_(false), - last_fec_timestamp_(0), + red_enabled_(false), + last_red_timestamp_(0), + codec_fec_enabled_(false), previous_pltype_(255), aux_rtp_header_(NULL), receiver_initialized_(false), - callback_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), secondary_send_codec_inst_(), codec_timestamp_(expected_codec_ts_), - first_10ms_data_(false) { + first_10ms_data_(false), + callback_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + packetization_callback_(NULL), + vad_callback_(NULL) { // Nullify send codec memory, set payload type and set codec name to // invalid values. @@ -159,8 +162,6 @@ AudioCodingModuleImpl::AudioCodingModuleImpl(int id) mirror_codec_idx_[i] = -1; } - receiver_.set_id(id_); - // Allocate memory for RED. red_buffer_ = new uint8_t[MAX_PAYLOAD_SIZE_BYTE]; @@ -201,7 +202,7 @@ AudioCodingModuleImpl::AudioCodingModuleImpl(int id) WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, "Cannot initialize receiver"); } - WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceAudioCoding, id, "Created"); + WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceAudioCoding, id_, "Created"); } AudioCodingModuleImpl::~AudioCodingModuleImpl() { @@ -349,7 +350,7 @@ int AudioCodingModuleImpl::ProcessDualStream() { int16_t len_bytes = MAX_PAYLOAD_SIZE_BYTE; WebRtcACMEncodingType encoding_type; if (secondary_encoder_->Encode(red_buffer_, &len_bytes, - &last_fec_timestamp_, + &last_red_timestamp_, &encoding_type) < 0) { WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, "ProcessDual(): Encoding of secondary encoder Failed"); @@ -372,7 +373,7 @@ int AudioCodingModuleImpl::ProcessDualStream() { index_primary = secondary_ready_to_encode ? TimestampLessThan(primary_timestamp, secondary_timestamp) : 0; index_primary += has_previous_payload ? - TimestampLessThan(primary_timestamp, last_fec_timestamp_) : 0; + TimestampLessThan(primary_timestamp, last_red_timestamp_) : 0; } if (secondary_ready_to_encode) { @@ -384,7 +385,7 @@ int AudioCodingModuleImpl::ProcessDualStream() { if (has_previous_payload) { index_previous_secondary = primary_ready_to_encode ? - (1 - TimestampLessThan(primary_timestamp, last_fec_timestamp_)) : 0; + (1 - TimestampLessThan(primary_timestamp, last_red_timestamp_)) : 0; // If secondary is ready it always have a timestamp larger than previous // secondary. So the index is either 0 or 1. index_previous_secondary += secondary_ready_to_encode ? 1 : 0; @@ -405,7 +406,7 @@ int AudioCodingModuleImpl::ProcessDualStream() { } else if (index_secondary == 0) { current_timestamp = secondary_timestamp; } else { - current_timestamp = last_fec_timestamp_; + current_timestamp = last_red_timestamp_; } fragmentation_.fragmentationVectorSize = 0; @@ -420,7 +421,7 @@ int AudioCodingModuleImpl::ProcessDualStream() { fragmentation_.fragmentationPlType[index_previous_secondary] = secondary_send_codec_inst_.pltype; fragmentation_.fragmentationTimeDiff[index_previous_secondary] = - static_cast(current_timestamp - last_fec_timestamp_); + static_cast(current_timestamp - last_red_timestamp_); fragmentation_.fragmentationVectorSize++; } @@ -462,7 +463,7 @@ int AudioCodingModuleImpl::ProcessDualStream() { { CriticalSectionScoped lock(callback_crit_sect_); if (packetization_callback_ != NULL) { - // Callback with payload data, including redundant data (FEC/RED). + // Callback with payload data, including redundant data (RED). if (packetization_callback_->SendData(kAudioFrameSpeech, my_red_payload_type, current_timestamp, stream, @@ -495,7 +496,7 @@ int AudioCodingModuleImpl::ProcessSingleStream() { FrameType frame_type = kAudioFrameSpeech; uint8_t current_payload_type = 0; bool has_data_to_send = false; - bool fec_active = false; + bool red_active = false; RTPFragmentationHeader my_fragmentation; // Keep the scope of the ACM critical section limited. @@ -562,15 +563,15 @@ int AudioCodingModuleImpl::ProcessSingleStream() { // Redundancy encode is done here. The two bitstreams packetized into // one RTP packet and the fragmentation points are set. // Only apply RED on speech data. - if ((fec_enabled_) && + if ((red_enabled_) && ((encoding_type == kActiveNormalEncoded) || (encoding_type == kPassiveNormalEncoded))) { - // FEC is enabled within this scope. + // RED is enabled within this scope. // // Note that, a special solution exists for iSAC since it is the only // codec for which GetRedPayload has a non-empty implementation. // - // Summary of the FEC scheme below (use iSAC as example): + // Summary of the RED scheme below (use iSAC as example): // // 1st (is_first_red_ is true) encoded iSAC frame (primary #1) => // - call GetRedPayload() and store redundancy for packet #1 in @@ -581,7 +582,7 @@ int AudioCodingModuleImpl::ProcessSingleStream() { // - store primary #2 in 1st fragment of RED buffer and send the // combined packet // - the transmitted packet contains primary #2 (new) and - // reduncancy for packet #1 (old) + // redundancy for packet #1 (old) // - call GetRed_Payload() and store redundancy for packet #2 in // second fragment of RED buffer // @@ -604,19 +605,19 @@ int AudioCodingModuleImpl::ProcessSingleStream() { // // Hence, even if every second packet is dropped, perfect // reconstruction is possible. - fec_active = true; + red_active = true; has_data_to_send = false; // Skip the following part for the first packet in a RED session. if (!is_first_red_) { - // Rearrange stream such that FEC packets are included. + // Rearrange stream such that RED packets are included. // Replace stream now that we have stored current stream. memcpy(stream + fragmentation_.fragmentationOffset[1], red_buffer_, fragmentation_.fragmentationLength[1]); // Update the fragmentation time difference vector, in number of // timestamps. uint16_t time_since_last = static_cast( - rtp_timestamp - last_fec_timestamp_); + rtp_timestamp - last_red_timestamp_); // Update fragmentation vectors. fragmentation_.fragmentationPlType[1] = @@ -630,7 +631,7 @@ int AudioCodingModuleImpl::ProcessSingleStream() { // Insert new packet payload type. fragmentation_.fragmentationPlType[0] = current_payload_type; - last_fec_timestamp_ = rtp_timestamp; + last_red_timestamp_ = rtp_timestamp; // Can be modified by the GetRedPayload() call if iSAC is utilized. red_length_bytes = length_bytes; @@ -650,7 +651,7 @@ int AudioCodingModuleImpl::ProcessSingleStream() { if (codecs_[current_send_codec_idx_]->GetRedPayload( red_buffer_, &red_length_bytes) == -1) { // The codec was not iSAC => use current encoder output as redundant - // data instead (trivial FEC scheme). + // data instead (trivial RED scheme). memcpy(red_buffer_, stream, red_length_bytes); } @@ -658,7 +659,7 @@ int AudioCodingModuleImpl::ProcessSingleStream() { // Update payload type with RED payload type. current_payload_type = red_pltype_; // We have packed 2 payloads. - fragmentation_.fragmentationVectorSize = kNumFecFragmentationVectors; + fragmentation_.fragmentationVectorSize = kNumRedFragmentationVectors; // Copy to local variable, as it will be used outside ACM lock. my_fragmentation.CopyFrom(fragmentation_); @@ -672,8 +673,8 @@ int AudioCodingModuleImpl::ProcessSingleStream() { CriticalSectionScoped lock(callback_crit_sect_); if (packetization_callback_ != NULL) { - if (fec_active) { - // Callback with payload data, including redundant data (FEC/RED). + if (red_active) { + // Callback with payload data, including redundant data (RED). packetization_callback_->SendData(frame_type, current_payload_type, rtp_timestamp, stream, length_bytes, &my_fragmentation); @@ -713,14 +714,14 @@ int AudioCodingModuleImpl::InitializeSender() { } } - // Initialize FEC/RED. + // Initialize RED. is_first_red_ = true; - if (fec_enabled_ || secondary_encoder_.get() != NULL) { + if (red_enabled_ || secondary_encoder_.get() != NULL) { if (red_buffer_ != NULL) { memset(red_buffer_, 0, MAX_PAYLOAD_SIZE_BYTE); } - if (fec_enabled_) { - ResetFragmentation(kNumFecFragmentationVectors); + if (red_enabled_) { + ResetFragmentation(kNumRedFragmentationVectors); } else { ResetFragmentation(0); } @@ -748,7 +749,6 @@ ACMGenericCodec* AudioCodingModuleImpl::CreateCodec(const CodecInst& codec) { return my_codec; } my_codec->SetUniqueID(id_); - my_codec->SetNetEqDecodeLock(receiver_.DecodeLock()); return my_codec; } @@ -1031,10 +1031,20 @@ int AudioCodingModuleImpl::RegisterSendCodec(const CodecInst& send_codec) { // Everything is fine so we can replace the previous codec with this one. if (send_codec_registered_) { - // If we change codec we start fresh with FEC. + // If we change codec we start fresh with RED. // This is not strictly required by the standard. is_first_red_ = true; codec_ptr->SetVAD(&dtx_enabled_, &vad_enabled_, &vad_mode_); + + if (!codec_ptr->HasInternalFEC()) { + codec_fec_enabled_ = false; + } else { + if (codec_ptr->SetFEC(codec_fec_enabled_) < 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot set codec FEC"); + return -1; + } + } } current_send_codec_idx_ = codec_id; @@ -1120,8 +1130,18 @@ int AudioCodingModuleImpl::RegisterSendCodec(const CodecInst& send_codec) { } send_codec_inst_.rate = send_codec.rate; } - previous_pltype_ = send_codec_inst_.pltype; + if (!codecs_[codec_id]->HasInternalFEC()) { + codec_fec_enabled_ = false; + } else { + if (codecs_[codec_id]->SetFEC(codec_fec_enabled_) < 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Cannot set codec FEC"); + return -1; + } + } + + previous_pltype_ = send_codec_inst_.pltype; return 0; } } @@ -1182,6 +1202,7 @@ int AudioCodingModuleImpl::SendBitrate() const { // Set available bandwidth, inform the encoder about the estimated bandwidth // received from the remote party. int AudioCodingModuleImpl::SetReceivedEstimatedBandwidth(int bw) { + CriticalSectionScoped lock(acm_crit_sect_); return codecs_[current_send_codec_idx_]->SetEstimatedBandwidth(bw); } @@ -1361,9 +1382,13 @@ int AudioCodingModuleImpl::PreprocessToAddData(const AudioFrame& in_frame, // The result of the resampler is written to output frame. dest_ptr_audio = preprocess_frame_.data_; - preprocess_frame_.samples_per_channel_ = resampler_.Resample10Msec( - src_ptr_audio, in_frame.sample_rate_hz_, send_codec_inst_.plfreq, - preprocess_frame_.num_channels_, dest_ptr_audio); + preprocess_frame_.samples_per_channel_ = + resampler_.Resample10Msec(src_ptr_audio, + in_frame.sample_rate_hz_, + send_codec_inst_.plfreq, + preprocess_frame_.num_channels_, + AudioFrame::kMaxDataSizeSamples, + dest_ptr_audio); if (preprocess_frame_.samples_per_channel_ < 0) { WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, @@ -1380,41 +1405,88 @@ int AudioCodingModuleImpl::PreprocessToAddData(const AudioFrame& in_frame, } ///////////////////////////////////////// -// (FEC) Forward Error Correction +// (RED) Redundant Coding // -bool AudioCodingModuleImpl::FECStatus() const { +bool AudioCodingModuleImpl::REDStatus() const { CriticalSectionScoped lock(acm_crit_sect_); - return fec_enabled_; + + return red_enabled_; } -// Configure FEC status i.e on/off. -int AudioCodingModuleImpl::SetFECStatus( +// Configure RED status i.e on/off. +int AudioCodingModuleImpl::SetREDStatus( #ifdef WEBRTC_CODEC_RED - bool enable_fec) { + bool enable_red) { CriticalSectionScoped lock(acm_crit_sect_); - if (fec_enabled_ != enable_fec) { + if (enable_red == true && codec_fec_enabled_ == true) { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, + "Codec internal FEC and RED cannot be co-enabled."); + return -1; + } + + if (red_enabled_ != enable_red) { // Reset the RED buffer. memset(red_buffer_, 0, MAX_PAYLOAD_SIZE_BYTE); // Reset fragmentation buffers. - ResetFragmentation(kNumFecFragmentationVectors); - // Set fec_enabled_. - fec_enabled_ = enable_fec; + ResetFragmentation(kNumRedFragmentationVectors); + // Set red_enabled_. + red_enabled_ = enable_red; } - is_first_red_ = true; // Make sure we restart FEC. + is_first_red_ = true; // Make sure we restart RED. return 0; #else - bool /* enable_fec */) { - fec_enabled_ = false; + bool /* enable_red */) { + red_enabled_ = false; WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - " WEBRTC_CODEC_RED is undefined => fec_enabled_ = %d", - fec_enabled_); + " WEBRTC_CODEC_RED is undefined => red_enabled_ = %d", + red_enabled_); return -1; #endif } +///////////////////////////////////////// +// (FEC) Forward Error Correction (codec internal) +// + +bool AudioCodingModuleImpl::CodecFEC() const { + CriticalSectionScoped lock(acm_crit_sect_); + return codec_fec_enabled_; +} + +int AudioCodingModuleImpl::SetCodecFEC(bool enable_codec_fec) { + CriticalSectionScoped lock(acm_crit_sect_); + + if (enable_codec_fec == true && red_enabled_ == true) { + WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, + "Codec internal FEC and RED cannot be co-enabled."); + return -1; + } + + // Set codec FEC. + if (HaveValidEncoder("SetCodecFEC") && + codecs_[current_send_codec_idx_]->SetFEC(enable_codec_fec) < 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Set codec internal FEC failed."); + return -1; + } + codec_fec_enabled_ = enable_codec_fec; + return 0; +} + +int AudioCodingModuleImpl::SetPacketLossRate(int loss_rate) { + CriticalSectionScoped lock(acm_crit_sect_); + if (HaveValidEncoder("SetPacketLossRate") && + codecs_[current_send_codec_idx_]->SetPacketLossRate(loss_rate) < 0) { + WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, + "Set packet loss rate failed."); + return -1; + } + return 0; +} + ///////////////////////////////////////// // (VAD) Voice Activity Detection // @@ -1706,8 +1778,6 @@ int AudioCodingModuleImpl::PlayoutData10Ms(int desired_freq_hz, } audio_frame->id_ = id_; - audio_frame->energy_ = 0; - audio_frame->timestamp_ = 0; return 0; } @@ -1766,6 +1836,7 @@ int AudioCodingModuleImpl::IncomingPayload(const uint8_t* incoming_payload, aux_rtp_header_->type.Audio.channel = 1; } + aux_rtp_header_->header.timestamp = timestamp; IncomingPacket(incoming_payload, payload_length, *aux_rtp_header_); // Get ready for the next payload. aux_rtp_header_->header.sequenceNumber++; @@ -1847,8 +1918,7 @@ int AudioCodingModuleImpl::ConfigISACBandwidthEstimator( } int AudioCodingModuleImpl::PlayoutTimestamp(uint32_t* timestamp) { - *timestamp = receiver_.PlayoutTimestamp(); - return 0; + return receiver_.GetPlayoutTimestamp(timestamp) ? 0 : -1; } bool AudioCodingModuleImpl::HaveValidEncoder(const char* caller_name) const { @@ -1882,6 +1952,7 @@ int AudioCodingModuleImpl::REDPayloadISAC(int isac_rate, int isac_bw_estimate, uint8_t* payload, int16_t* length_bytes) { + CriticalSectionScoped lock(acm_crit_sect_); if (!HaveValidEncoder("EncodeData")) { return -1; } @@ -1972,10 +2043,6 @@ int AudioCodingModuleImpl::LeastRequiredDelayMs() const { return receiver_.LeastRequiredDelayMs(); } -const char* AudioCodingModuleImpl::Version() const { - return kExperimentalAcmVersion; -} - void AudioCodingModuleImpl::GetDecodingCallStatistics( AudioDecodingCallStats* call_stats) const { receiver_.GetDecodingCallStatistics(call_stats); diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h index bc4ea0f7a..02290da04 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h @@ -19,11 +19,11 @@ #include "webrtc/modules/audio_coding/main/acm2/acm_receiver.h" #include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" namespace webrtc { class CriticalSectionWrapper; -class RWLockWrapper; namespace acm2 { @@ -32,11 +32,9 @@ class ACMGenericCodec; class AudioCodingModuleImpl : public AudioCodingModule { public: - explicit AudioCodingModuleImpl(int id); + explicit AudioCodingModuleImpl(const AudioCodingModule::Config& config); ~AudioCodingModuleImpl(); - virtual const char* Version() const; - // Change the unique identifier of this object. virtual int32_t ChangeUniqueId(const int32_t id); @@ -94,14 +92,27 @@ class AudioCodingModuleImpl : public AudioCodingModule { int Add10MsData(const AudioFrame& audio_frame); ///////////////////////////////////////// - // (FEC) Forward Error Correction + // (RED) Redundant Coding + // + + // Configure RED status i.e. on/off. + int SetREDStatus(bool enable_red); + + // Get RED status. + bool REDStatus() const; + + ///////////////////////////////////////// + // (FEC) Forward Error Correction (codec internal) // - // Configure FEC status i.e on/off. - int SetFECStatus(bool enable_fec); + // Configure FEC status i.e. on/off. + int SetCodecFEC(bool enabled_codec_fec); // Get FEC status. - bool FECStatus() const; + bool CodecFEC() const; + + // Set target packet loss rate + int SetPacketLossRate(int loss_rate); ///////////////////////////////////////// // (VAD) Voice Activity Detection @@ -203,6 +214,7 @@ class AudioCodingModuleImpl : public AudioCodingModule { // GET RED payload for iSAC. The method id called when 'this' ACM is // the default ACM. + // TODO(henrik.lundin) Not used. Remove? int REDPayloadISAC(int isac_rate, int isac_bw_estimate, uint8_t* payload, @@ -235,13 +247,15 @@ class AudioCodingModuleImpl : public AudioCodingModule { ACMGenericCodec* CreateCodec(const CodecInst& codec); - int InitializeReceiverSafe(); + int InitializeReceiverSafe() EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); - bool HaveValidEncoder(const char* caller_name) const; + bool HaveValidEncoder(const char* caller_name) const + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); // Set VAD/DTX status. This function does not acquire a lock, and it is // created to be called only from inside a critical section. - int SetVADSafe(bool enable_dtx, bool enable_vad, ACMVADMode mode); + int SetVADSafe(bool enable_dtx, bool enable_vad, ACMVADMode mode) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); // Process buffered audio when dual-streaming is not enabled (When RED is // enabled still this function is used.) @@ -263,18 +277,22 @@ class AudioCodingModuleImpl : public AudioCodingModule { // -1: if encountering an error. // 0: otherwise. int PreprocessToAddData(const AudioFrame& in_frame, - const AudioFrame** ptr_out); + const AudioFrame** ptr_out) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); // Change required states after starting to receive the codec corresponding // to |index|. int UpdateUponReceivingCodec(int index); - int EncodeFragmentation(int fragmentation_index, int payload_type, + int EncodeFragmentation(int fragmentation_index, + int payload_type, uint32_t current_timestamp, ACMGenericCodec* encoder, - uint8_t* stream); + uint8_t* stream) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); - void ResetFragmentation(int vector_size); + void ResetFragmentation(int vector_size) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); // Get a pointer to AudioDecoder of the given codec. For some codecs, e.g. // iSAC, encoding and decoding have to be performed on a shared @@ -287,52 +305,54 @@ class AudioCodingModuleImpl : public AudioCodingModule { // codec owns the decoder-instance. For such codecs |*decoder| should be a // valid pointer, otherwise it will be NULL. int GetAudioDecoder(const CodecInst& codec, int codec_id, - int mirror_id, AudioDecoder** decoder); - - AudioPacketizationCallback* packetization_callback_; - - int id_; - uint32_t expected_codec_ts_; - uint32_t expected_in_ts_; - CodecInst send_codec_inst_; - - uint8_t cng_nb_pltype_; - uint8_t cng_wb_pltype_; - uint8_t cng_swb_pltype_; - uint8_t cng_fb_pltype_; - - uint8_t red_pltype_; - bool vad_enabled_; - bool dtx_enabled_; - ACMVADMode vad_mode_; - ACMGenericCodec* codecs_[ACMCodecDB::kMaxNumCodecs]; - int mirror_codec_idx_[ACMCodecDB::kMaxNumCodecs]; - bool stereo_send_; - int current_send_codec_idx_; - bool send_codec_registered_; - ACMResampler resampler_; - AcmReceiver receiver_; - CriticalSectionWrapper* acm_crit_sect_; - ACMVADCallback* vad_callback_; + int mirror_id, AudioDecoder** decoder) + EXCLUSIVE_LOCKS_REQUIRED(acm_crit_sect_); - // RED/FEC. - bool is_first_red_; - bool fec_enabled_; + CriticalSectionWrapper* acm_crit_sect_; + int id_; // TODO(henrik.lundin) Make const. + uint32_t expected_codec_ts_ GUARDED_BY(acm_crit_sect_); + uint32_t expected_in_ts_ GUARDED_BY(acm_crit_sect_); + CodecInst send_codec_inst_ GUARDED_BY(acm_crit_sect_); + + uint8_t cng_nb_pltype_ GUARDED_BY(acm_crit_sect_); + uint8_t cng_wb_pltype_ GUARDED_BY(acm_crit_sect_); + uint8_t cng_swb_pltype_ GUARDED_BY(acm_crit_sect_); + uint8_t cng_fb_pltype_ GUARDED_BY(acm_crit_sect_); + + uint8_t red_pltype_ GUARDED_BY(acm_crit_sect_); + bool vad_enabled_ GUARDED_BY(acm_crit_sect_); + bool dtx_enabled_ GUARDED_BY(acm_crit_sect_); + ACMVADMode vad_mode_ GUARDED_BY(acm_crit_sect_); + ACMGenericCodec* codecs_[ACMCodecDB::kMaxNumCodecs] + GUARDED_BY(acm_crit_sect_); + int mirror_codec_idx_[ACMCodecDB::kMaxNumCodecs] GUARDED_BY(acm_crit_sect_); + bool stereo_send_ GUARDED_BY(acm_crit_sect_); + int current_send_codec_idx_ GUARDED_BY(acm_crit_sect_); + bool send_codec_registered_ GUARDED_BY(acm_crit_sect_); + ACMResampler resampler_ GUARDED_BY(acm_crit_sect_); + AcmReceiver receiver_; // AcmReceiver has it's own internal lock. + + // RED. + bool is_first_red_ GUARDED_BY(acm_crit_sect_); + bool red_enabled_ GUARDED_BY(acm_crit_sect_); // TODO(turajs): |red_buffer_| is allocated in constructor, why having them // as pointers and not an array. If concerned about the memory, then make a // set-up function to allocate them only when they are going to be used, i.e. - // FEC or Dual-streaming is enabled. - uint8_t* red_buffer_; + // RED or Dual-streaming is enabled. + uint8_t* red_buffer_ GUARDED_BY(acm_crit_sect_); // TODO(turajs): we actually don't need |fragmentation_| as a member variable. // It is sufficient to keep the length & payload type of previous payload in // member variables. - RTPFragmentationHeader fragmentation_; - uint32_t last_fec_timestamp_; + RTPFragmentationHeader fragmentation_ GUARDED_BY(acm_crit_sect_); + uint32_t last_red_timestamp_ GUARDED_BY(acm_crit_sect_); + + // Codec internal FEC + bool codec_fec_enabled_ GUARDED_BY(acm_crit_sect_); // This is to keep track of CN instances where we can send DTMFs. - uint8_t previous_pltype_; + uint8_t previous_pltype_ GUARDED_BY(acm_crit_sect_); // Used when payloads are pushed into ACM without any RTP info // One example is when pre-encoded bit-stream is pushed from @@ -342,15 +362,18 @@ class AudioCodingModuleImpl : public AudioCodingModule { // be used in other methods, locks need to be taken. WebRtcRTPHeader* aux_rtp_header_; - bool receiver_initialized_; + bool receiver_initialized_ GUARDED_BY(acm_crit_sect_); - CriticalSectionWrapper* callback_crit_sect_; + AudioFrame preprocess_frame_ GUARDED_BY(acm_crit_sect_); + CodecInst secondary_send_codec_inst_ GUARDED_BY(acm_crit_sect_); + scoped_ptr secondary_encoder_ GUARDED_BY(acm_crit_sect_); + uint32_t codec_timestamp_ GUARDED_BY(acm_crit_sect_); + bool first_10ms_data_ GUARDED_BY(acm_crit_sect_); - AudioFrame preprocess_frame_; - CodecInst secondary_send_codec_inst_; - scoped_ptr secondary_encoder_; - uint32_t codec_timestamp_; - bool first_10ms_data_; + CriticalSectionWrapper* callback_crit_sect_; + AudioPacketizationCallback* packetization_callback_ + GUARDED_BY(callback_crit_sect_); + ACMVADCallback* vad_callback_ GUARDED_BY(callback_crit_sect_); }; } // namespace acm2 diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_unittest.cc b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_unittest.cc new file mode 100644 index 000000000..37cd70e5e --- /dev/null +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_unittest.cc @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2014 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 "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" +#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h" +#include "webrtc/modules/interface/module_common_types.h" +#include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/system_wrappers/interface/compile_assert.h" +#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" +#include "webrtc/system_wrappers/interface/event_wrapper.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/sleep.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" +#include "webrtc/system_wrappers/interface/thread_wrapper.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/testsupport/gtest_disable.h" + +namespace webrtc { + +const int kSampleRateHz = 16000; +const int kNumSamples10ms = kSampleRateHz / 100; +const int kFrameSizeMs = 10; // Multiple of 10. +const int kFrameSizeSamples = kFrameSizeMs / 10 * kNumSamples10ms; +const int kPayloadSizeBytes = kFrameSizeSamples * sizeof(int16_t); +const uint8_t kPayloadType = 111; + +class RtpUtility { + public: + RtpUtility(int samples_per_packet, uint8_t payload_type) + : samples_per_packet_(samples_per_packet), payload_type_(payload_type) {} + + virtual ~RtpUtility() {} + + void Populate(WebRtcRTPHeader* rtp_header) { + rtp_header->header.sequenceNumber = 0xABCD; + rtp_header->header.timestamp = 0xABCDEF01; + rtp_header->header.payloadType = payload_type_; + rtp_header->header.markerBit = false; + rtp_header->header.ssrc = 0x1234; + rtp_header->header.numCSRCs = 0; + rtp_header->frameType = kAudioFrameSpeech; + + rtp_header->header.payload_type_frequency = kSampleRateHz; + rtp_header->type.Audio.channel = 1; + rtp_header->type.Audio.isCNG = false; + } + + void Forward(WebRtcRTPHeader* rtp_header) { + ++rtp_header->header.sequenceNumber; + rtp_header->header.timestamp += samples_per_packet_; + } + + private: + int samples_per_packet_; + uint8_t payload_type_; +}; + +class PacketizationCallbackStub : public AudioPacketizationCallback { + public: + PacketizationCallbackStub() + : num_calls_(0), + crit_sect_(CriticalSectionWrapper::CreateCriticalSection()) {} + + virtual int32_t SendData( + FrameType frame_type, + uint8_t payload_type, + uint32_t timestamp, + const uint8_t* payload_data, + uint16_t payload_len_bytes, + const RTPFragmentationHeader* fragmentation) OVERRIDE { + CriticalSectionScoped lock(crit_sect_.get()); + ++num_calls_; + last_payload_vec_.assign(payload_data, payload_data + payload_len_bytes); + return 0; + } + + int num_calls() const { + CriticalSectionScoped lock(crit_sect_.get()); + return num_calls_; + } + + int last_payload_len_bytes() const { + CriticalSectionScoped lock(crit_sect_.get()); + return last_payload_vec_.size(); + } + + void SwapBuffers(std::vector* payload) { + CriticalSectionScoped lock(crit_sect_.get()); + last_payload_vec_.swap(*payload); + } + + private: + int num_calls_ GUARDED_BY(crit_sect_); + std::vector last_payload_vec_ GUARDED_BY(crit_sect_); + const scoped_ptr crit_sect_; +}; + +class AudioCodingModuleTest : public ::testing::Test { + protected: + AudioCodingModuleTest() + : id_(1), + rtp_utility_(new RtpUtility(kFrameSizeSamples, kPayloadType)), + clock_(Clock::GetRealTimeClock()) {} + + ~AudioCodingModuleTest() {} + + void TearDown() {} + + void SetUp() { + acm_.reset(AudioCodingModule::Create(id_, clock_)); + + RegisterCodec(); + + rtp_utility_->Populate(&rtp_header_); + + input_frame_.sample_rate_hz_ = kSampleRateHz; + input_frame_.num_channels_ = 1; + input_frame_.samples_per_channel_ = kSampleRateHz * 10 / 1000; // 10 ms. + COMPILE_ASSERT(kSampleRateHz * 10 / 1000 <= AudioFrame::kMaxDataSizeSamples, + audio_frame_too_small); + memset(input_frame_.data_, + 0, + input_frame_.samples_per_channel_ * sizeof(input_frame_.data_[0])); + + ASSERT_EQ(0, acm_->RegisterTransportCallback(&packet_cb_)); + } + + virtual void RegisterCodec() { + AudioCodingModule::Codec("L16", &codec_, kSampleRateHz, 1); + codec_.pltype = kPayloadType; + + // Register L16 codec in ACM. + ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec_)); + ASSERT_EQ(0, acm_->RegisterSendCodec(codec_)); + } + + virtual void InsertPacketAndPullAudio() { + InsertPacket(); + PullAudio(); + } + + virtual void InsertPacket() { + const uint8_t kPayload[kPayloadSizeBytes] = {0}; + ASSERT_EQ(0, + acm_->IncomingPacket(kPayload, kPayloadSizeBytes, rtp_header_)); + rtp_utility_->Forward(&rtp_header_); + } + + virtual void PullAudio() { + AudioFrame audio_frame; + ASSERT_EQ(0, acm_->PlayoutData10Ms(-1, &audio_frame)); + } + + virtual void InsertAudio() { + ASSERT_EQ(0, acm_->Add10MsData(input_frame_)); + input_frame_.timestamp_ += kNumSamples10ms; + } + + virtual void Encode() { + int32_t encoded_bytes = acm_->Process(); + // Expect to get one packet with two bytes per sample, or no packet at all, + // depending on how many 10 ms blocks go into |codec_.pacsize|. + EXPECT_TRUE(encoded_bytes == 2 * codec_.pacsize || encoded_bytes == 0); + } + + const int id_; + scoped_ptr rtp_utility_; + scoped_ptr acm_; + PacketizationCallbackStub packet_cb_; + WebRtcRTPHeader rtp_header_; + AudioFrame input_frame_; + CodecInst codec_; + Clock* clock_; +}; + +// Check if the statistics are initialized correctly. Before any call to ACM +// all fields have to be zero. +TEST_F(AudioCodingModuleTest, DISABLED_ON_ANDROID(InitializedToZero)) { + AudioDecodingCallStats stats; + acm_->GetDecodingCallStatistics(&stats); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); +} + +// Apply an initial playout delay. Calls to AudioCodingModule::PlayoutData10ms() +// should result in generating silence, check the associated field. +TEST_F(AudioCodingModuleTest, DISABLED_ON_ANDROID(SilenceGeneratorCalled)) { + AudioDecodingCallStats stats; + const int kInitialDelay = 100; + + acm_->SetInitialPlayoutDelay(kInitialDelay); + + int num_calls = 0; + for (int time_ms = 0; time_ms < kInitialDelay; + time_ms += kFrameSizeMs, ++num_calls) { + InsertPacketAndPullAudio(); + } + acm_->GetDecodingCallStatistics(&stats); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(num_calls, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); +} + +// Insert some packets and pull audio. Check statistics are valid. Then, +// simulate packet loss and check if PLC and PLC-to-CNG statistics are +// correctly updated. +TEST_F(AudioCodingModuleTest, DISABLED_ON_ANDROID(NetEqCalls)) { + AudioDecodingCallStats stats; + const int kNumNormalCalls = 10; + + for (int num_calls = 0; num_calls < kNumNormalCalls; ++num_calls) { + InsertPacketAndPullAudio(); + } + acm_->GetDecodingCallStatistics(&stats); + EXPECT_EQ(kNumNormalCalls, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); + + const int kNumPlc = 3; + const int kNumPlcCng = 5; + + // Simulate packet-loss. NetEq first performs PLC then PLC fades to CNG. + for (int n = 0; n < kNumPlc + kNumPlcCng; ++n) { + PullAudio(); + } + acm_->GetDecodingCallStatistics(&stats); + EXPECT_EQ(kNumNormalCalls + kNumPlc + kNumPlcCng, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(kNumPlc, stats.decoded_plc); + EXPECT_EQ(kNumPlcCng, stats.decoded_plc_cng); +} + +TEST_F(AudioCodingModuleTest, VerifyOutputFrame) { + AudioFrame audio_frame; + const int kSampleRateHz = 32000; + EXPECT_EQ(0, acm_->PlayoutData10Ms(kSampleRateHz, &audio_frame)); + EXPECT_EQ(id_, audio_frame.id_); + EXPECT_EQ(0u, audio_frame.timestamp_); + EXPECT_GT(audio_frame.num_channels_, 0); + EXPECT_EQ(kSampleRateHz / 100, audio_frame.samples_per_channel_); + EXPECT_EQ(kSampleRateHz, audio_frame.sample_rate_hz_); +} + +TEST_F(AudioCodingModuleTest, FailOnZeroDesiredFrequency) { + AudioFrame audio_frame; + EXPECT_EQ(-1, acm_->PlayoutData10Ms(0, &audio_frame)); +} + +// A multi-threaded test for ACM. This base class is using the PCM16b 16 kHz +// codec, while the derive class AcmIsacMtTest is using iSAC. +class AudioCodingModuleMtTest : public AudioCodingModuleTest { + protected: + static const int kNumPackets = 500; + static const int kNumPullCalls = 500; + + AudioCodingModuleMtTest() + : AudioCodingModuleTest(), + send_thread_(ThreadWrapper::CreateThread(CbSendThread, + this, + kRealtimePriority, + "send")), + insert_packet_thread_(ThreadWrapper::CreateThread(CbInsertPacketThread, + this, + kRealtimePriority, + "insert_packet")), + pull_audio_thread_(ThreadWrapper::CreateThread(CbPullAudioThread, + this, + kRealtimePriority, + "pull_audio")), + test_complete_(EventWrapper::Create()), + send_count_(0), + insert_packet_count_(0), + pull_audio_count_(0), + crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + next_insert_packet_time_ms_(0), + fake_clock_(new SimulatedClock(0)) { + clock_ = fake_clock_.get(); + } + + void SetUp() { + AudioCodingModuleTest::SetUp(); + StartThreads(); + } + + void StartThreads() { + unsigned int thread_id = 0; + ASSERT_TRUE(send_thread_->Start(thread_id)); + ASSERT_TRUE(insert_packet_thread_->Start(thread_id)); + ASSERT_TRUE(pull_audio_thread_->Start(thread_id)); + } + + void TearDown() { + AudioCodingModuleTest::TearDown(); + pull_audio_thread_->Stop(); + send_thread_->Stop(); + insert_packet_thread_->Stop(); + } + + EventTypeWrapper RunTest() { + return test_complete_->Wait(10 * 60 * 1000); // 10 minutes' timeout. + } + + virtual bool TestDone() { + if (packet_cb_.num_calls() > kNumPackets) { + CriticalSectionScoped lock(crit_sect_.get()); + if (pull_audio_count_ > kNumPullCalls) { + // Both conditions for completion are met. End the test. + return true; + } + } + return false; + } + + static bool CbSendThread(void* context) { + return reinterpret_cast(context)->CbSendImpl(); + } + + // The send thread doesn't have to care about the current simulated time, + // since only the AcmReceiver is using the clock. + bool CbSendImpl() { + SleepMs(1); + if (HasFatalFailure()) { + // End the test early if a fatal failure (ASSERT_*) has occurred. + test_complete_->Set(); + } + ++send_count_; + InsertAudio(); + Encode(); + if (TestDone()) { + test_complete_->Set(); + } + return true; + } + + static bool CbInsertPacketThread(void* context) { + return reinterpret_cast(context) + ->CbInsertPacketImpl(); + } + + bool CbInsertPacketImpl() { + SleepMs(1); + { + CriticalSectionScoped lock(crit_sect_.get()); + if (clock_->TimeInMilliseconds() < next_insert_packet_time_ms_) { + return true; + } + next_insert_packet_time_ms_ += 10; + } + // Now we're not holding the crit sect when calling ACM. + ++insert_packet_count_; + InsertPacket(); + return true; + } + + static bool CbPullAudioThread(void* context) { + return reinterpret_cast(context) + ->CbPullAudioImpl(); + } + + bool CbPullAudioImpl() { + SleepMs(1); + { + CriticalSectionScoped lock(crit_sect_.get()); + // Don't let the insert thread fall behind. + if (next_insert_packet_time_ms_ < clock_->TimeInMilliseconds()) { + return true; + } + ++pull_audio_count_; + } + // Now we're not holding the crit sect when calling ACM. + PullAudio(); + fake_clock_->AdvanceTimeMilliseconds(10); + return true; + } + + scoped_ptr send_thread_; + scoped_ptr insert_packet_thread_; + scoped_ptr pull_audio_thread_; + const scoped_ptr test_complete_; + int send_count_; + int insert_packet_count_; + int pull_audio_count_ GUARDED_BY(crit_sect_); + const scoped_ptr crit_sect_; + int64_t next_insert_packet_time_ms_ GUARDED_BY(crit_sect_); + scoped_ptr fake_clock_; +}; + +TEST_F(AudioCodingModuleMtTest, DoTest) { + EXPECT_EQ(kEventSignaled, RunTest()); +} + +// This is a multi-threaded ACM test using iSAC. The test encodes audio +// from a PCM file. The most recent encoded frame is used as input to the +// receiving part. Depending on timing, it may happen that the same RTP packet +// is inserted into the receiver multiple times, but this is a valid use-case, +// and simplifies the test code a lot. +class AcmIsacMtTest : public AudioCodingModuleMtTest { + protected: + static const int kNumPackets = 500; + static const int kNumPullCalls = 500; + + AcmIsacMtTest() + : AudioCodingModuleMtTest(), + last_packet_number_(0) {} + + ~AcmIsacMtTest() {} + + void SetUp() { + AudioCodingModuleTest::SetUp(); + + // Set up input audio source to read from specified file, loop after 5 + // seconds, and deliver blocks of 10 ms. + const std::string input_file_name = + webrtc::test::ResourcePath("audio_coding/speech_mono_16kHz", "pcm"); + audio_loop_.Init(input_file_name, 5 * kSampleRateHz, kNumSamples10ms); + + // Generate one packet to have something to insert. + int loop_counter = 0; + while (packet_cb_.last_payload_len_bytes() == 0) { + InsertAudio(); + Encode(); + ASSERT_LT(loop_counter++, 10); + } + // Set |last_packet_number_| to one less that |num_calls| so that the packet + // will be fetched in the next InsertPacket() call. + last_packet_number_ = packet_cb_.num_calls() - 1; + + StartThreads(); + } + + virtual void RegisterCodec() { + COMPILE_ASSERT(kSampleRateHz == 16000, test_designed_for_isac_16khz); + AudioCodingModule::Codec("ISAC", &codec_, kSampleRateHz, 1); + codec_.pltype = kPayloadType; + + // Register iSAC codec in ACM, effectively unregistering the PCM16B codec + // registered in AudioCodingModuleTest::SetUp(); + ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec_)); + ASSERT_EQ(0, acm_->RegisterSendCodec(codec_)); + } + + void InsertPacket() { + int num_calls = packet_cb_.num_calls(); // Store locally for thread safety. + if (num_calls > last_packet_number_) { + // Get the new payload out from the callback handler. + // Note that since we swap buffers here instead of directly inserting + // a pointer to the data in |packet_cb_|, we avoid locking the callback + // for the duration of the IncomingPacket() call. + packet_cb_.SwapBuffers(&last_payload_vec_); + ASSERT_GT(last_payload_vec_.size(), 0u); + rtp_utility_->Forward(&rtp_header_); + last_packet_number_ = num_calls; + } + ASSERT_GT(last_payload_vec_.size(), 0u); + ASSERT_EQ( + 0, + acm_->IncomingPacket( + &last_payload_vec_[0], last_payload_vec_.size(), rtp_header_)); + } + + void InsertAudio() { + memcpy(input_frame_.data_, audio_loop_.GetNextBlock(), kNumSamples10ms); + AudioCodingModuleTest::InsertAudio(); + } + + void Encode() { ASSERT_GE(acm_->Process(), 0); } + + // This method is the same as AudioCodingModuleMtTest::TestDone(), but here + // it is using the constants defined in this class (i.e., shorter test run). + virtual bool TestDone() { + if (packet_cb_.num_calls() > kNumPackets) { + CriticalSectionScoped lock(crit_sect_.get()); + if (pull_audio_count_ > kNumPullCalls) { + // Both conditions for completion are met. End the test. + return true; + } + } + return false; + } + + int last_packet_number_; + std::vector last_payload_vec_; + test::AudioLoop audio_loop_; +}; + +TEST_F(AcmIsacMtTest, DoTest) { + EXPECT_EQ(kEventSignaled, RunTest()); +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.cc b/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.cc index c2b218cb6..786fb2e52 100644 --- a/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.cc +++ b/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.cc @@ -219,6 +219,14 @@ void InitialDelayManager::LatePackets( return; } +bool InitialDelayManager::GetPlayoutTimestamp(uint32_t* playout_timestamp) { + if (!buffering_) { + return false; + } + *playout_timestamp = playout_timestamp_; + return true; +} + void InitialDelayManager::DisableBuffering() { buffering_ = false; } diff --git a/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.h b/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.h index 3c5ba3c01..6edc11508 100644 --- a/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.h +++ b/webrtc/modules/audio_coding/main/acm2/initial_delay_manager.h @@ -65,8 +65,9 @@ class InitialDelayManager { // sequence of late (or perhaps missing) packets is computed. void LatePackets(uint32_t timestamp_now, SyncStream* sync_stream); - // Playout timestamp, valid when buffering. - uint32_t playout_timestamp() { return playout_timestamp_; } + // Get playout timestamp. + // Returns true if the timestamp is valid (when buffering), otherwise false. + bool GetPlayoutTimestamp(uint32_t* playout_timestamp); // True if buffered audio is less than the given initial delay (specified at // the constructor). Buffering might be disabled by the client of this class. diff --git a/webrtc/modules/audio_coding/main/acm2/initial_delay_manager_unittest.cc b/webrtc/modules/audio_coding/main/acm2/initial_delay_manager_unittest.cc index 15e88a539..38b7cfc27 100644 --- a/webrtc/modules/audio_coding/main/acm2/initial_delay_manager_unittest.cc +++ b/webrtc/modules/audio_coding/main/acm2/initial_delay_manager_unittest.cc @@ -359,7 +359,9 @@ TEST_F(InitialDelayManagerTest, BufferingAudio) { EXPECT_TRUE(manager_->buffering()); const uint32_t expected_playout_timestamp = rtp_info_.header.timestamp - kInitDelayMs * kSamplingRateHz / 1000; - EXPECT_EQ(expected_playout_timestamp, manager_->playout_timestamp()); + uint32_t actual_playout_timestamp = 0; + EXPECT_TRUE(manager_->GetPlayoutTimestamp(&actual_playout_timestamp)); + EXPECT_EQ(expected_playout_timestamp, actual_playout_timestamp); NextRtpHeader(&rtp_info_, &rtp_receive_timestamp_); } diff --git a/webrtc/modules/audio_coding/main/acm2/nack_unittest.cc b/webrtc/modules/audio_coding/main/acm2/nack_unittest.cc index 8011d8856..5837c31a8 100644 --- a/webrtc/modules/audio_coding/main/acm2/nack_unittest.cc +++ b/webrtc/modules/audio_coding/main/acm2/nack_unittest.cc @@ -398,7 +398,7 @@ TEST(NackTest, ChangeOfListSizeAppliedAndOldElementsRemoved) { // Packet lost more than NACK-list size limit. uint16_t num_lost_packets = kNackThreshold + kNackListSize + 5; - scoped_array seq_num_lost(new uint16_t[num_lost_packets]); + scoped_ptr seq_num_lost(new uint16_t[num_lost_packets]); for (int n = 0; n < num_lost_packets; ++n) { seq_num_lost[n] = ++seq_num; } diff --git a/webrtc/modules/audio_coding/main/interface/audio_coding_module.h b/webrtc/modules/audio_coding/main/interface/audio_coding_module.h index db45addde..482700474 100644 --- a/webrtc/modules/audio_coding/main/interface/audio_coding_module.h +++ b/webrtc/modules/audio_coding/main/interface/audio_coding_module.h @@ -15,7 +15,9 @@ #include "webrtc/common_types.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" #include "webrtc/modules/interface/module.h" +#include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -25,7 +27,6 @@ struct CodecInst; struct WebRtcRTPHeader; class AudioFrame; class RTPFragmentationHeader; -class Clock; #define WEBRTC_10MS_PCM_AUDIO 960 // 16 bits super wideband 48 kHz @@ -73,15 +74,22 @@ class ACMVQMonCallback { const uint16_t delayMS) = 0; // average delay in ms }; -// Version string for testing, to distinguish instances of ACM1 from ACM2. -extern const char kLegacyAcmVersion[]; -extern const char kExperimentalAcmVersion[]; - class AudioCodingModule: public Module { protected: AudioCodingModule() {} public: + struct Config { + Config() + : id(0), + neteq_config(), + clock(Clock::GetRealTimeClock()) {} + + int id; + NetEq::Config neteq_config; + Clock* clock; + }; + /////////////////////////////////////////////////////////////////////////// // Creation and destruction of a ACM. // @@ -178,11 +186,6 @@ class AudioCodingModule: public Module { // static bool IsCodecValid(const CodecInst& codec); - // Returns the version of ACM. This facilitates distinguishing instances of - // ACM1 from ACM2 while testing. This API will be removed when ACM1 is - // completely removed. - virtual const char* Version() const = 0; - /////////////////////////////////////////////////////////////////////////// // Sender // @@ -327,6 +330,7 @@ class AudioCodingModule: public Module { // -1 if error occurred in setting the bandwidth, // 0 bandwidth is set successfully. // + // TODO(henrik.lundin) Unused. Remove? virtual int32_t SetReceivedEstimatedBandwidth( const int32_t bw) = 0; @@ -370,12 +374,12 @@ class AudioCodingModule: public Module { virtual int32_t Add10MsData(const AudioFrame& audio_frame) = 0; /////////////////////////////////////////////////////////////////////////// - // (FEC) Forward Error Correction + // (RED) Redundant Coding // /////////////////////////////////////////////////////////////////////////// - // int32_t SetFECStatus(const bool enable) - // configure FEC status i.e. on/off. + // int32_t SetREDStatus() + // configure RED status i.e. on/off. // // RFC 2198 describes a solution which has a single payload type which // signifies a packet with redundancy. That packet then becomes a container, @@ -385,27 +389,69 @@ class AudioCodingModule: public Module { // since each encapsulated payload must be preceded by a header indicating // the type of data enclosed. // - // This means that FEC is actually a RED scheme. + // Input: + // -enable_red : if true RED is enabled, otherwise RED is + // disabled. + // + // Return value: + // -1 if failed to set RED status, + // 0 if succeeded. + // + virtual int32_t SetREDStatus(bool enable_red) = 0; + + /////////////////////////////////////////////////////////////////////////// + // bool REDStatus() + // Get RED status + // + // Return value: + // true if RED is enabled, + // false if RED is disabled. + // + virtual bool REDStatus() const = 0; + + /////////////////////////////////////////////////////////////////////////// + // (FEC) Forward Error Correction (codec internal) + // + + /////////////////////////////////////////////////////////////////////////// + // int32_t SetCodecFEC() + // Configures codec internal FEC status i.e. on/off. No effects on codecs that + // do not provide internal FEC. // // Input: - // -enable_fec : if true FEC is enabled, otherwise FEC is + // -enable_fec : if true FEC will be enabled otherwise the FEC is // disabled. // // Return value: - // -1 if failed to set FEC status, + // -1 if failed, or the codec does not support FEC // 0 if succeeded. // - virtual int32_t SetFECStatus(const bool enable_fec) = 0; + virtual int SetCodecFEC(bool enable_codec_fec) = 0; /////////////////////////////////////////////////////////////////////////// - // bool FECStatus() - // Get FEC status + // bool CodecFEC() + // Gets status of codec internal FEC. // - // Return value + // Return value: // true if FEC is enabled, // false if FEC is disabled. // - virtual bool FECStatus() const = 0; + virtual bool CodecFEC() const = 0; + + /////////////////////////////////////////////////////////////////////////// + // int SetPacketLossRate() + // Sets expected packet loss rate for encoding. Some encoders provide packet + // loss gnostic encoding to make stream less sensitive to packet losses, + // through e.g., FEC. No effects on codecs that do not provide such encoding. + // + // Input: + // -packet_loss_rate : expected packet loss rate (0 -- 100 inclusive). + // + // Return value + // -1 if failed to set packet loss rate, + // 0 if succeeded. + // + virtual int SetPacketLossRate(int packet_loss_rate) = 0; /////////////////////////////////////////////////////////////////////////// // (VAD) Voice Activity Detection @@ -936,20 +982,6 @@ class AudioCodingModule: public Module { AudioDecodingCallStats* call_stats) const = 0; }; -struct AudioCodingModuleFactory { - AudioCodingModuleFactory() {} - virtual ~AudioCodingModuleFactory() {} - - virtual AudioCodingModule* Create(int id) const; -}; - -struct NewAudioCodingModuleFactory : AudioCodingModuleFactory { - NewAudioCodingModuleFactory() {} - virtual ~NewAudioCodingModuleFactory() {} - - virtual AudioCodingModule* Create(int id) const; -}; - } // namespace webrtc #endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_INTERFACE_AUDIO_CODING_MODULE_H_ diff --git a/webrtc/modules/audio_coding/main/source/Android.mk b/webrtc/modules/audio_coding/main/source/Android.mk deleted file mode 100644 index 90214a9c4..000000000 --- a/webrtc/modules/audio_coding/main/source/Android.mk +++ /dev/null @@ -1,67 +0,0 @@ -# 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. - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -include $(LOCAL_PATH)/../../../../../android-webrtc.mk - -LOCAL_ARM_MODE := arm -LOCAL_MODULE_CLASS := STATIC_LIBRARIES -LOCAL_MODULE := libwebrtc_audio_coding -LOCAL_MODULE_TAGS := optional -LOCAL_CPP_EXTENSION := .cc -LOCAL_SRC_FILES := \ - acm_cng.cc \ - acm_codec_database.cc \ - acm_dtmf_detection.cc \ - acm_dtmf_playout.cc \ - acm_g722.cc \ - acm_generic_codec.cc \ - acm_ilbc.cc \ - acm_isac.cc \ - acm_neteq.cc \ - acm_pcm16b.cc \ - acm_pcma.cc \ - acm_pcmu.cc \ - acm_red.cc \ - acm_resampler.cc \ - audio_coding_module.cc \ - audio_coding_module_impl.cc - -# Flags passed to both C and C++ files. -LOCAL_CFLAGS := \ - $(MY_WEBRTC_COMMON_DEFS) - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/../interface \ - $(LOCAL_PATH)/../../codecs/cng/include \ - $(LOCAL_PATH)/../../codecs/g711/include \ - $(LOCAL_PATH)/../../codecs/g722/include \ - $(LOCAL_PATH)/../../codecs/ilbc/interface \ - $(LOCAL_PATH)/../../codecs/iSAC/main/interface \ - $(LOCAL_PATH)/../../codecs/iSAC/fix/interface \ - $(LOCAL_PATH)/../../codecs/pcm16b/include \ - $(LOCAL_PATH)/../../neteq/interface \ - $(LOCAL_PATH)/../../../.. \ - $(LOCAL_PATH)/../../../interface \ - $(LOCAL_PATH)/../../../../common_audio/resampler/include \ - $(LOCAL_PATH)/../../../../common_audio/signal_processing/include \ - $(LOCAL_PATH)/../../../../common_audio/vad/include \ - $(LOCAL_PATH)/../../../../system_wrappers/interface - -LOCAL_SHARED_LIBRARIES := \ - libcutils \ - libdl \ - libstlport - -ifndef NDK_ROOT -include external/stlport/libstlport.mk -endif -include $(BUILD_STATIC_LIBRARY) diff --git a/webrtc/modules/audio_coding/main/source/acm_amr.cc b/webrtc/modules/audio_coding/main/source/acm_amr.cc deleted file mode 100644 index d39860778..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_amr.cc +++ /dev/null @@ -1,430 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_amr.h" - -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_AMR -// NOTE! GSM AMR is not included in the open-source package. The following -// interface file is needed: -// -// /modules/audio_coding/codecs/amr/main/interface/amr_interface.h -// -// The API in the header file should match the one below. -// -// int16_t WebRtcAmr_CreateEnc(AMR_encinst_t_** enc_inst); -// int16_t WebRtcAmr_CreateDec(AMR_decinst_t_** dec_inst); -// int16_t WebRtcAmr_FreeEnc(AMR_encinst_t_* enc_inst); -// int16_t WebRtcAmr_FreeDec(AMR_decinst_t_* dec_inst); -// int16_t WebRtcAmr_Encode(AMR_encinst_t_* enc_inst, -// int16_t* input, -// int16_t len, -// int16_t*output, -// int16_t mode); -// int16_t WebRtcAmr_EncoderInit(AMR_encinst_t_* enc_inst, -// int16_t dtx_mode); -// int16_t WebRtcAmr_EncodeBitmode(AMR_encinst_t_* enc_inst, -// int format); -// int16_t WebRtcAmr_Decode(AMR_decinst_t_* dec_inst); -// int16_t WebRtcAmr_DecodePlc(AMR_decinst_t_* dec_inst); -// int16_t WebRtcAmr_DecoderInit(AMR_decinst_t_* dec_inst); -// int16_t WebRtcAmr_DecodeBitmode(AMR_decinst_t_* dec_inst, -// int format); -#include "amr_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_AMR -ACMAMR::ACMAMR(int16_t /* codec_id */) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - encoding_mode_(-1), // Invalid value. - encoding_rate_(0), // Invalid value. - encoder_packing_format_(AMRBandwidthEfficient), - decoder_packing_format_(AMRBandwidthEfficient) { - return; -} - -ACMAMR::~ACMAMR() { - return; -} - -int16_t ACMAMR::InternalEncode(uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMAMR::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMAMR::EnableDTX() { - return -1; -} - -int16_t ACMAMR::DisableDTX() { - return -1; -} - -int16_t ACMAMR::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMAMR::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMAMR::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMAMR::CreateInstance(void) { - return NULL; -} - -int16_t ACMAMR::InternalCreateEncoder() { - return -1; -} - -void ACMAMR::DestructEncoderSafe() { - return; -} - -int16_t ACMAMR::InternalCreateDecoder() { - return -1; -} - -void ACMAMR::DestructDecoderSafe() { - return; -} - -int16_t ACMAMR::SetBitRateSafe(const int32_t /* rate */) { - return -1; -} - -void ACMAMR::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -int16_t ACMAMR::SetAMREncoderPackingFormat( - ACMAMRPackingFormat /* packing_format */) { - return -1; -} - -ACMAMRPackingFormat ACMAMR::AMREncoderPackingFormat() const { - return AMRUndefined; -} - -int16_t ACMAMR::SetAMRDecoderPackingFormat( - ACMAMRPackingFormat /* packing_format */) { - return -1; -} - -ACMAMRPackingFormat ACMAMR::AMRDecoderPackingFormat() const { - return AMRUndefined; -} - -#else //===================== Actual Implementation ======================= - -#define WEBRTC_AMR_MR475 0 -#define WEBRTC_AMR_MR515 1 -#define WEBRTC_AMR_MR59 2 -#define WEBRTC_AMR_MR67 3 -#define WEBRTC_AMR_MR74 4 -#define WEBRTC_AMR_MR795 5 -#define WEBRTC_AMR_MR102 6 -#define WEBRTC_AMR_MR122 7 - -ACMAMR::ACMAMR(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - encoding_mode_(-1), // invalid value - encoding_rate_(0) { // invalid value - codec_id_ = codec_id; - has_internal_dtx_ = true; - encoder_packing_format_ = AMRBandwidthEfficient; - decoder_packing_format_ = AMRBandwidthEfficient; - return; -} - -ACMAMR::~ACMAMR() { - if (encoder_inst_ptr_ != NULL) { - WebRtcAmr_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcAmr_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMAMR::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - int16_t vad_decision = 1; - // sanity check, if the rate is set correctly. we might skip this - // sanity check. if rate is not set correctly, initialization flag - // should be false and should not be here. - if ((encoding_mode_ < WEBRTC_AMR_MR475) || - (encoding_mode_ > WEBRTC_AMR_MR122)) { - *bitstream_len_byte = 0; - return -1; - } - *bitstream_len_byte = WebRtcAmr_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], - frame_len_smpl_, - (int16_t*)bitstream, - encoding_mode_); - - // Update VAD, if internal DTX is used - if (has_internal_dtx_ && dtx_enabled_) { - if (*bitstream_len_byte <= (7 * frame_len_smpl_ / 160)) { - vad_decision = 0; - } - for (int16_t n = 0; n < MAX_FRAME_SIZE_10MSEC; n++) { - vad_label_[n] = vad_decision; - } - } - // increment the read index - in_audio_ix_read_ += frame_len_smpl_; - return *bitstream_len_byte; -} - -int16_t ACMAMR::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMAMR::EnableDTX() { - if (dtx_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // enable DTX - if (WebRtcAmr_EncoderInit(encoder_inst_ptr_, 1) < 0) { - return -1; - } - dtx_enabled_ = true; - return 0; - } else { - return -1; - } -} - -int16_t ACMAMR::DisableDTX() { - if (!dtx_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // disable DTX - if (WebRtcAmr_EncoderInit(encoder_inst_ptr_, 0) < 0) { - return -1; - } - dtx_enabled_ = false; - return 0; - } else { - // encoder doesn't exists, therefore disabling is harmless - return 0; - } -} - -int16_t ACMAMR::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { - int16_t status = SetBitRateSafe((codec_params->codec_inst).rate); - status += (WebRtcAmr_EncoderInit( - encoder_inst_ptr_, ((codec_params->enable_dtx) ? 1 : 0)) < 0) ? -1 : 0; - status += (WebRtcAmr_EncodeBitmode( - encoder_inst_ptr_, encoder_packing_format_) < 0) ? -1 : 0; - return (status < 0) ? -1 : 0; -} - -int16_t ACMAMR::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - int16_t status = - ((WebRtcAmr_DecoderInit(decoder_inst_ptr_) < 0) ? -1 : 0); - status += WebRtcAmr_DecodeBitmode(decoder_inst_ptr_, decoder_packing_format_); - return (status < 0) ? -1 : 0; -} - -int32_t ACMAMR::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - // Todo: - // log error - return -1; - } - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_AMR_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderAMR, codec_inst.pltype, decoder_inst_ptr_, - 8000); - SET_AMR_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMAMR::CreateInstance(void) { - return NULL; -} - -int16_t ACMAMR::InternalCreateEncoder() { - return WebRtcAmr_CreateEnc(&encoder_inst_ptr_); -} - -void ACMAMR::DestructEncoderSafe() { - if (encoder_inst_ptr_ != NULL) { - WebRtcAmr_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - // there is no encoder set the following - encoder_exist_ = false; - encoder_initialized_ = false; - encoding_mode_ = -1; // invalid value - encoding_rate_ = 0; // invalid value -} - -int16_t ACMAMR::InternalCreateDecoder() { - return WebRtcAmr_CreateDec(&decoder_inst_ptr_); -} - -void ACMAMR::DestructDecoderSafe() { - if (decoder_inst_ptr_ != NULL) { - WebRtcAmr_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - // there is no encoder instance set the followings - decoder_exist_ = false; - decoder_initialized_ = false; -} - -int16_t ACMAMR::SetBitRateSafe(const int32_t rate) { - switch (rate) { - case 4750: { - encoding_mode_ = WEBRTC_AMR_MR475; - encoding_rate_ = 4750; - break; - } - case 5150: { - encoding_mode_ = WEBRTC_AMR_MR515; - encoding_rate_ = 5150; - break; - } - case 5900: { - encoding_mode_ = WEBRTC_AMR_MR59; - encoding_rate_ = 5900; - break; - } - case 6700: { - encoding_mode_ = WEBRTC_AMR_MR67; - encoding_rate_ = 6700; - break; - } - case 7400: { - encoding_mode_ = WEBRTC_AMR_MR74; - encoding_rate_ = 7400; - break; - } - case 7950: { - encoding_mode_ = WEBRTC_AMR_MR795; - encoding_rate_ = 7950; - break; - } - case 10200: { - encoding_mode_ = WEBRTC_AMR_MR102; - encoding_rate_ = 10200; - break; - } - case 12200: { - encoding_mode_ = WEBRTC_AMR_MR122; - encoding_rate_ = 12200; - break; - } - default: { - return -1; - } - } - return 0; -} - -void ACMAMR::InternalDestructEncoderInst(void* ptr_inst) { - // Free the memory where ptr_inst is pointing to - if (ptr_inst != NULL) { - WebRtcAmr_FreeEnc(reinterpret_cast(ptr_inst)); - } - return; -} - -int16_t ACMAMR::SetAMREncoderPackingFormat( - ACMAMRPackingFormat packing_format) { - if ((packing_format != AMRBandwidthEfficient) && - (packing_format != AMROctetAlligned) && - (packing_format != AMRFileStorage)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Invalid AMR Encoder packing-format."); - return -1; - } else { - if (WebRtcAmr_EncodeBitmode(encoder_inst_ptr_, packing_format) < 0) { - return -1; - } else { - encoder_packing_format_ = packing_format; - return 0; - } - } -} - -ACMAMRPackingFormat ACMAMR::AMREncoderPackingFormat() const { - return encoder_packing_format_; -} - -int16_t ACMAMR::SetAMRDecoderPackingFormat( - ACMAMRPackingFormat packing_format) { - if ((packing_format != AMRBandwidthEfficient) && - (packing_format != AMROctetAlligned) && - (packing_format != AMRFileStorage)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Invalid AMR decoder packing-format."); - return -1; - } else { - if (WebRtcAmr_DecodeBitmode(decoder_inst_ptr_, packing_format) < 0) { - return -1; - } else { - decoder_packing_format_ = packing_format; - return 0; - } - } -} - -ACMAMRPackingFormat ACMAMR::AMRDecoderPackingFormat() const { - return decoder_packing_format_; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc - diff --git a/webrtc/modules/audio_coding/main/source/acm_amr.h b/webrtc/modules/audio_coding/main/source/acm_amr.h deleted file mode 100644 index 19c657246..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_amr.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_AMR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_AMR_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct AMR_encinst_t_; -struct AMR_decinst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMAMR : public ACMGenericCodec { - public: - explicit ACMAMR(int16_t codec_id); - virtual ~ACMAMR(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - int16_t SetAMREncoderPackingFormat(const ACMAMRPackingFormat packing_format); - - ACMAMRPackingFormat AMREncoderPackingFormat() const; - - int16_t SetAMRDecoderPackingFormat(const ACMAMRPackingFormat packing_format); - - ACMAMRPackingFormat AMRDecoderPackingFormat() const; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual int16_t SetBitRateSafe(const int32_t rate) OVERRIDE; - - virtual int16_t EnableDTX() OVERRIDE; - - virtual int16_t DisableDTX() OVERRIDE; - - AMR_encinst_t_* encoder_inst_ptr_; - AMR_decinst_t_* decoder_inst_ptr_; - int16_t encoding_mode_; - int16_t encoding_rate_; - ACMAMRPackingFormat encoder_packing_format_; - ACMAMRPackingFormat decoder_packing_format_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_AMR_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_amrwb.cc b/webrtc/modules/audio_coding/main/source/acm_amrwb.cc deleted file mode 100644 index 8b1b58d03..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_amrwb.cc +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_amrwb.h" - -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_AMRWB -// NOTE! GSM AMR-wb is not included in the open-source package. The -// following interface file is needed: -// -// /modules/audio_coding/codecs/amrwb/main/interface/amrwb_interface.h -// -// The API in the header file should match the one below. -// -// int16_t WebRtcAmrWb_CreateEnc(AMRWB_encinst_t_** enc_inst); -// int16_t WebRtcAmrWb_CreateDec(AMRWB_decinst_t_** dec_inst); -// int16_t WebRtcAmrWb_FreeEnc(AMRWB_encinst_t_* enc_inst); -// int16_t WebRtcAmrWb_FreeDec(AMRWB_decinst_t_* dec_inst); -// int16_t WebRtcAmrWb_Encode(AMRWB_encinst_t_* enc_inst, int16_t* input, -// int16_t len, int16_t* output, int16_t mode); -// int16_t WebRtcAmrWb_EncoderInit(AMRWB_encinst_t_* enc_inst, -// int16_t dtx_mode); -// int16_t WebRtcAmrWb_EncodeBitmode(AMRWB_encinst_t_* enc_inst, -// int format); -// int16_t WebRtcAmrWb_Decode(AMRWB_decinst_t_* dec_inst); -// int16_t WebRtcAmrWb_DecodePlc(AMRWB_decinst_t_* dec_inst); -// int16_t WebRtcAmrWb_DecoderInit(AMRWB_decinst_t_* dec_inst); -// int16_t WebRtcAmrWb_DecodeBitmode(AMRWB_decinst_t_* dec_inst, -// int format); -#include "amrwb_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_AMRWB -ACMAMRwb::ACMAMRwb(int16_t /* codec_id */) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - encoding_mode_(-1), // invalid value - encoding_rate_(0), // invalid value - encoder_packing_format_(AMRBandwidthEfficient), - decoder_packing_format_(AMRBandwidthEfficient) { -} - -ACMAMRwb::~ACMAMRwb() { -} - -int16_t ACMAMRwb::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMAMRwb::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMAMRwb::EnableDTX() { - return -1; -} - -int16_t ACMAMRwb::DisableDTX() { - return -1; -} - -int16_t ACMAMRwb::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMAMRwb::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMAMRwb::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* -ACMAMRwb::CreateInstance(void) { - return NULL; -} - -int16_t ACMAMRwb::InternalCreateEncoder() { - return -1; -} - -void ACMAMRwb::DestructEncoderSafe() { - return; -} - -int16_t ACMAMRwb::InternalCreateDecoder() { - return -1; -} - -void ACMAMRwb::DestructDecoderSafe() { - return; -} - -int16_t ACMAMRwb::SetBitRateSafe(const int32_t /* rate */) { - return -1; -} - -void ACMAMRwb::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -int16_t ACMAMRwb::SetAMRwbEncoderPackingFormat( - ACMAMRPackingFormat /* packing_format */) { - return -1; -} - -ACMAMRPackingFormat ACMAMRwb::AMRwbEncoderPackingFormat() const { - return AMRUndefined; -} - -int16_t ACMAMRwb::SetAMRwbDecoderPackingFormat( - ACMAMRPackingFormat /* packing_format */) { - return -1; -} - -ACMAMRPackingFormat ACMAMRwb::AMRwbDecoderPackingFormat() const { - return AMRUndefined; -} - -#else //===================== Actual Implementation ======================= - -#define AMRWB_MODE_7k 0 -#define AMRWB_MODE_9k 1 -#define AMRWB_MODE_12k 2 -#define AMRWB_MODE_14k 3 -#define AMRWB_MODE_16k 4 -#define AMRWB_MODE_18k 5 -#define AMRWB_MODE_20k 6 -#define AMRWB_MODE_23k 7 -#define AMRWB_MODE_24k 8 - -ACMAMRwb::ACMAMRwb(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - encoding_mode_(-1), // invalid value - encoding_rate_(0) { // invalid value - codec_id_ = codec_id; - has_internal_dtx_ = true; - encoder_packing_format_ = AMRBandwidthEfficient; - decoder_packing_format_ = AMRBandwidthEfficient; - return; -} - -ACMAMRwb::~ACMAMRwb() { - if (encoder_inst_ptr_ != NULL) { - WebRtcAmrWb_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcAmrWb_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMAMRwb::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - int16_t vad_decision = 1; - // sanity check, if the rate is set correctly. we might skip this - // sanity check. if rate is not set correctly, initialization flag - // should be false and should not be here. - if ((encoding_mode_ < AMRWB_MODE_7k) || (encoding_mode_ > AMRWB_MODE_24k)) { - *bitstream_len_byte = 0; - return -1; - } - *bitstream_len_byte = WebRtcAmrWb_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], - frame_len_smpl_, - (int16_t*)bitstream, - encoding_mode_); - - // Update VAD, if internal DTX is used - if (has_internal_dtx_ && dtx_enabled_) { - if (*bitstream_len_byte <= (7 * frame_len_smpl_ / 160)) { - vad_decision = 0; - } - for (int16_t n = 0; n < MAX_FRAME_SIZE_10MSEC; n++) { - vad_label_[n] = vad_decision; - } - } - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += frame_len_smpl_; - return *bitstream_len_byte; -} - -int16_t ACMAMRwb::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMAMRwb::EnableDTX() { - if (dtx_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // enable DTX - if (WebRtcAmrWb_EncoderInit(encoder_inst_ptr_, 1) < 0) { - return -1; - } - dtx_enabled_ = true; - return 0; - } else { - return -1; - } -} - -int16_t ACMAMRwb::DisableDTX() { - if (!dtx_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // disable DTX - if (WebRtcAmrWb_EncoderInit(encoder_inst_ptr_, 0) < 0) { - return -1; - } - dtx_enabled_ = false; - return 0; - } else { - // encoder doesn't exists, therefore disabling is harmless - return 0; - } -} - -int16_t ACMAMRwb::InternalInitEncoder( - WebRtcACMCodecParams* codec_params) { - // sanity check - if (encoder_inst_ptr_ == NULL) { - return -1; - } - - int16_t status = SetBitRateSafe((codec_params->codec_inst).rate); - status += (WebRtcAmrWb_EncoderInit( - encoder_inst_ptr_, ((codec_params->enable_dtx) ? 1 : 0)) < 0) ? -1 : 0; - status += (WebRtcAmrWb_EncodeBitmode( - encoder_inst_ptr_, encoder_packing_format_) < 0) ? -1 : 0; - return (status < 0) ? -1 : 0; -} - -int16_t ACMAMRwb::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - int16_t status = WebRtcAmrWb_DecodeBitmode(decoder_inst_ptr_, - decoder_packing_format_); - status += ((WebRtcAmrWb_DecoderInit(decoder_inst_ptr_) < 0) ? -1 : 0); - return (status < 0) ? -1 : 0; -} - -int32_t ACMAMRwb::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - return -1; - } - - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_AMRWB_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderAMRWB, codec_inst.pltype, - decoder_inst_ptr_, 16000); - SET_AMRWB_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMAMRwb::CreateInstance(void) { - return NULL; -} - -int16_t ACMAMRwb::InternalCreateEncoder() { - return WebRtcAmrWb_CreateEnc(&encoder_inst_ptr_); -} - -void ACMAMRwb::DestructEncoderSafe() { - if (encoder_inst_ptr_ != NULL) { - WebRtcAmrWb_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - // there is no encoder set the following - encoder_exist_ = false; - encoder_initialized_ = false; - encoding_mode_ = -1; // invalid value - encoding_rate_ = 0; -} - -int16_t ACMAMRwb::InternalCreateDecoder() { - return WebRtcAmrWb_CreateDec(&decoder_inst_ptr_); -} - -void ACMAMRwb::DestructDecoderSafe() { - if (decoder_inst_ptr_ != NULL) { - WebRtcAmrWb_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - // there is no encoder instance set the followings - decoder_exist_ = false; - decoder_initialized_ = false; -} - -int16_t ACMAMRwb::SetBitRateSafe(const int32_t rate) { - switch (rate) { - case 7000: { - encoding_mode_ = AMRWB_MODE_7k; - encoding_rate_ = 7000; - break; - } - case 9000: { - encoding_mode_ = AMRWB_MODE_9k; - encoding_rate_ = 9000; - break; - } - case 12000: { - encoding_mode_ = AMRWB_MODE_12k; - encoding_rate_ = 12000; - break; - } - case 14000: { - encoding_mode_ = AMRWB_MODE_14k; - encoding_rate_ = 14000; - break; - } - case 16000: { - encoding_mode_ = AMRWB_MODE_16k; - encoding_rate_ = 16000; - break; - } - case 18000: { - encoding_mode_ = AMRWB_MODE_18k; - encoding_rate_ = 18000; - break; - } - case 20000: { - encoding_mode_ = AMRWB_MODE_20k; - encoding_rate_ = 20000; - break; - } - case 23000: { - encoding_mode_ = AMRWB_MODE_23k; - encoding_rate_ = 23000; - break; - } - case 24000: { - encoding_mode_ = AMRWB_MODE_24k; - encoding_rate_ = 24000; - break; - } - default: { - return -1; - } - } - return 0; -} - -void ACMAMRwb::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcAmrWb_FreeEnc(static_cast(ptr_inst)); - } - return; -} - -int16_t ACMAMRwb::SetAMRwbEncoderPackingFormat( - ACMAMRPackingFormat packing_format) { - if ((packing_format != AMRBandwidthEfficient) && - (packing_format != AMROctetAlligned) && - (packing_format != AMRFileStorage)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Invalid AMRwb encoder packing-format."); - return -1; - } else { - if (WebRtcAmrWb_EncodeBitmode(encoder_inst_ptr_, packing_format) < 0) { - return -1; - } else { - encoder_packing_format_ = packing_format; - return 0; - } - } -} - -ACMAMRPackingFormat ACMAMRwb::AMRwbEncoderPackingFormat() const { - return encoder_packing_format_; -} - -int16_t ACMAMRwb::SetAMRwbDecoderPackingFormat( - ACMAMRPackingFormat packing_format) { - if ((packing_format != AMRBandwidthEfficient) && - (packing_format != AMROctetAlligned) && - (packing_format != AMRFileStorage)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Invalid AMRwb decoder packing-format."); - return -1; - } else { - if (WebRtcAmrWb_DecodeBitmode(decoder_inst_ptr_, packing_format) < 0) { - return -1; - } else { - decoder_packing_format_ = packing_format; - return 0; - } - } -} - -ACMAMRPackingFormat ACMAMRwb::AMRwbDecoderPackingFormat() const { - return decoder_packing_format_; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_amrwb.h b/webrtc/modules/audio_coding/main/source/acm_amrwb.h deleted file mode 100644 index 25934187e..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_amrwb.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_AMRWB_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_AMRWB_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct AMRWB_encinst_t_; -struct AMRWB_decinst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMAMRwb : public ACMGenericCodec { - public: - explicit ACMAMRwb(int16_t codec_id); - virtual ~ACMAMRwb(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t SetAMRwbEncoderPackingFormat( - const ACMAMRPackingFormat packing_format); - - virtual ACMAMRPackingFormat AMRwbEncoderPackingFormat() const; - - virtual int16_t SetAMRwbDecoderPackingFormat( - const ACMAMRPackingFormat packing_format); - - virtual ACMAMRPackingFormat AMRwbDecoderPackingFormat() const; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual int16_t SetBitRateSafe(const int32_t rate) OVERRIDE; - - virtual int16_t EnableDTX() OVERRIDE; - - virtual int16_t DisableDTX() OVERRIDE; - - AMRWB_encinst_t_* encoder_inst_ptr_; - AMRWB_decinst_t_* decoder_inst_ptr_; - - int16_t encoding_mode_; - int16_t encoding_rate_; - ACMAMRPackingFormat encoder_packing_format_; - ACMAMRPackingFormat decoder_packing_format_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_AMRWB_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_celt.cc b/webrtc/modules/audio_coding/main/source/acm_celt.cc deleted file mode 100644 index 3b838143d..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_celt.cc +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_celt.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_CELT -// NOTE! Celt is not included in the open-source package. Modify this file or -// your codec API to match the function call and name of used Celt API file. -#include "celt_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_CELT - -ACMCELT::ACMCELT(int16_t /* codec_id */) - : enc_inst_ptr_(NULL), - dec_inst_ptr_(NULL), - sampling_freq_(0), - bitrate_(0), - channels_(1), - dec_channels_(1) { - return; -} - -ACMCELT::~ACMCELT() { - return; -} - -int16_t ACMCELT::InternalEncode(uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMCELT::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMCELT::InternalInitEncoder(WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMCELT::InternalInitDecoder(WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMCELT::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMCELT::CreateInstance(void) { - return NULL; -} - -int16_t ACMCELT::InternalCreateEncoder() { - return -1; -} - -void ACMCELT::DestructEncoderSafe() { - return; -} - -int16_t ACMCELT::InternalCreateDecoder() { - return -1; -} - -void ACMCELT::DestructDecoderSafe() { - return; -} - -void ACMCELT::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -bool ACMCELT::IsTrueStereoCodec() { - return true; -} - -int16_t ACMCELT::SetBitRateSafe(const int32_t /*rate*/) { - return -1; -} - -void ACMCELT::SplitStereoPacket(uint8_t* /*payload*/, - int32_t* /*payload_length*/) {} - -#else //===================== Actual Implementation ======================= - -ACMCELT::ACMCELT(int16_t codec_id) - : enc_inst_ptr_(NULL), - dec_inst_ptr_(NULL), - sampling_freq_(32000), // Default sampling frequency. - bitrate_(64000), // Default rate. - channels_(1), // Default send mono. - dec_channels_(1) { // Default receive mono. - // TODO(tlegrand): remove later when ACMGenericCodec has a new constructor. - codec_id_ = codec_id; - - return; -} - -ACMCELT::~ACMCELT() { - if (enc_inst_ptr_ != NULL) { - WebRtcCelt_FreeEnc(enc_inst_ptr_); - enc_inst_ptr_ = NULL; - } - if (dec_inst_ptr_ != NULL) { - WebRtcCelt_FreeDec(dec_inst_ptr_); - dec_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMCELT::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - *bitstream_len_byte = 0; - - // Call Encoder. - *bitstream_len_byte = WebRtcCelt_Encode(enc_inst_ptr_, - &in_audio_[in_audio_ix_read_], - bitstream); - - // Increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer. - in_audio_ix_read_ += frame_len_smpl_ * channels_; - - if (*bitstream_len_byte < 0) { - // Error reported from the encoder. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalEncode: Encode error for Celt"); - *bitstream_len_byte = 0; - return -1; - } - - return *bitstream_len_byte; -} - -int16_t ACMCELT::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMCELT::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { - // Set bitrate and check that it is within the valid range. - int16_t status = SetBitRateSafe((codec_params->codec_inst).rate); - if (status < 0) { - return -1; - } - - // If number of channels changed we need to re-create memory. - if (codec_params->codec_inst.channels != channels_) { - WebRtcCelt_FreeEnc(enc_inst_ptr_); - enc_inst_ptr_ = NULL; - // Store new number of channels. - channels_ = codec_params->codec_inst.channels; - if (WebRtcCelt_CreateEnc(&enc_inst_ptr_, channels_) < 0) { - return -1; - } - } - - // Initiate encoder. - if (WebRtcCelt_EncoderInit(enc_inst_ptr_, channels_, bitrate_) >= 0) { - return 0; - } else { - return -1; - } -} - -int16_t ACMCELT::InternalInitDecoder(WebRtcACMCodecParams* codec_params) { - // If number of channels changed we need to re-create memory. - if (codec_params->codec_inst.channels != dec_channels_) { - WebRtcCelt_FreeDec(dec_inst_ptr_); - dec_inst_ptr_ = NULL; - // Store new number of channels. - dec_channels_ = codec_params->codec_inst.channels; - if (WebRtcCelt_CreateDec(&dec_inst_ptr_, dec_channels_) < 0) { - return -1; - } - } - - // Initiate decoder, both master and slave parts. - if (WebRtcCelt_DecoderInit(dec_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitDecoder: init decoder failed for Celt."); - return -1; - } - if (WebRtcCelt_DecoderInitSlave(dec_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitDecoder: init decoder failed for Celt."); - return -1; - } - return 0; -} - -int32_t ACMCELT::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodecDef: Decoder uninitialized for Celt"); - return -1; - } - - // Fill up the structure by calling - // "SET_CODEC_PAR" and "SET_CELT_FUNCTIONS" or "SET_CELTSLAVE_FUNCTIONS". - // Then call NetEQ to add the codec to it's - // database. - if (codec_inst.channels == 1) { - SET_CODEC_PAR(codec_def, kDecoderCELT_32, codec_inst.pltype, dec_inst_ptr_, - 32000); - } else { - SET_CODEC_PAR(codec_def, kDecoderCELT_32_2ch, codec_inst.pltype, - dec_inst_ptr_, 32000); - } - - // If this is the master of NetEQ, regular decoder will be added, otherwise - // the slave decoder will be used. - if (is_master_) { - SET_CELT_FUNCTIONS(codec_def); - } else { - SET_CELTSLAVE_FUNCTIONS(codec_def); - } - return 0; -} - -ACMGenericCodec* ACMCELT::CreateInstance(void) { - return NULL; -} - -int16_t ACMCELT::InternalCreateEncoder() { - if (WebRtcCelt_CreateEnc(&enc_inst_ptr_, num_channels_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: create encoder failed for Celt"); - return -1; - } - channels_ = num_channels_; - return 0; -} - -void ACMCELT::DestructEncoderSafe() { - encoder_exist_ = false; - encoder_initialized_ = false; - if (enc_inst_ptr_ != NULL) { - WebRtcCelt_FreeEnc(enc_inst_ptr_); - enc_inst_ptr_ = NULL; - } -} - -int16_t ACMCELT::InternalCreateDecoder() { - if (WebRtcCelt_CreateDec(&dec_inst_ptr_, dec_channels_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateDecoder: create decoder failed for Celt"); - return -1; - } - - return 0; -} - -void ACMCELT::DestructDecoderSafe() { - decoder_exist_ = false; - decoder_initialized_ = false; - if (dec_inst_ptr_ != NULL) { - WebRtcCelt_FreeDec(dec_inst_ptr_); - dec_inst_ptr_ = NULL; - } -} - -void ACMCELT::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcCelt_FreeEnc(static_cast(ptr_inst)); - } - return; -} - -bool ACMCELT::IsTrueStereoCodec() { - return true; -} - -int16_t ACMCELT::SetBitRateSafe(const int32_t rate) { - // Check that rate is in the valid range. - if ((rate >= 48000) && (rate <= 128000)) { - // Store new rate. - bitrate_ = rate; - - // Initiate encoder with new rate. - if (WebRtcCelt_EncoderInit(enc_inst_ptr_, channels_, bitrate_) >= 0) { - return 0; - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetBitRateSafe: Failed to initiate Celt with rate %d", - rate); - return -1; - } - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetBitRateSafe: Invalid rate Celt, %d", rate); - return -1; - } -} - -// Copy the stereo packet so that NetEq will insert into both master and slave. -void ACMCELT::SplitStereoPacket(uint8_t* payload, int32_t* payload_length) { - // Check for valid inputs. - assert(payload != NULL); - assert(*payload_length > 0); - - // Duplicate the payload. - memcpy(&payload[*payload_length], &payload[0], - sizeof(uint8_t) * (*payload_length)); - // Double the size of the packet. - *payload_length *= 2; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_celt.h b/webrtc/modules/audio_coding/main/source/acm_celt.h deleted file mode 100644 index 4a4610e0d..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_celt.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CELT_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CELT_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct CELT_encinst_t_; -struct CELT_decinst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMCELT : public ACMGenericCodec { - public: - explicit ACMCELT(int16_t codec_id); - virtual ~ACMCELT(); - - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual bool IsTrueStereoCodec() OVERRIDE; - - virtual int16_t SetBitRateSafe(const int32_t rate) OVERRIDE; - - virtual void SplitStereoPacket(uint8_t* payload, - int32_t* payload_length) OVERRIDE; - - CELT_encinst_t_* enc_inst_ptr_; - CELT_decinst_t_* dec_inst_ptr_; - uint16_t sampling_freq_; - int32_t bitrate_; - uint16_t channels_; - uint16_t dec_channels_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CELT_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_cng.cc b/webrtc/modules/audio_coding/main/source/acm_cng.cc deleted file mode 100644 index 6f3a5057e..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_cng.cc +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_cng.h" - -#include "webrtc/modules/audio_coding/codecs/cng/include/webrtc_cng.h" -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -namespace webrtc { - -namespace acm1 { - -ACMCNG::ACMCNG(int16_t codec_id) { - encoder_inst_ptr_ = NULL; - decoder_inst_ptr_ = NULL; - codec_id_ = codec_id; - samp_freq_hz_ = ACMCodecDB::CodecFreq(codec_id_); - return; -} - -ACMCNG::~ACMCNG() { - if (encoder_inst_ptr_ != NULL) { - WebRtcCng_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcCng_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -// CNG is not like a regular encoder, this function -// should not be called normally -// instead the following function is called from inside -// ACMGenericCodec::ProcessFrameVADDTX -int16_t ACMCNG::InternalEncode(uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMCNG::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -// CNG is not like a regular encoder, -// this function should not be called normally -// instead the following function is called from inside -// ACMGenericCodec::ProcessFrameVADDTX -int16_t ACMCNG::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMCNG::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return WebRtcCng_InitDec(decoder_inst_ptr_); -} - -int32_t ACMCNG::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - // TODO(tlegrand): log error - return -1; - } - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_CNG_FUNCTION." - // Then return the structure back to NetEQ to add the codec to it's - // database. - - if (samp_freq_hz_ == 8000 || samp_freq_hz_ == 16000 || - samp_freq_hz_ == 32000 || samp_freq_hz_ == 48000) { - SET_CODEC_PAR((codec_def), kDecoderCNG, codec_inst.pltype, - decoder_inst_ptr_, samp_freq_hz_); - SET_CNG_FUNCTIONS((codec_def)); - return 0; - } else { - return -1; - } -} - -ACMGenericCodec* ACMCNG::CreateInstance(void) { - return NULL; -} - -int16_t ACMCNG::InternalCreateEncoder() { - if (WebRtcCng_CreateEnc(&encoder_inst_ptr_) < 0) { - encoder_inst_ptr_ = NULL; - return -1; - } else { - return 0; - } -} - -void ACMCNG::DestructEncoderSafe() { - if (encoder_inst_ptr_ != NULL) { - WebRtcCng_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - encoder_exist_ = false; - encoder_initialized_ = false; -} - -int16_t ACMCNG::InternalCreateDecoder() { - if (WebRtcCng_CreateDec(&decoder_inst_ptr_) < 0) { - decoder_inst_ptr_ = NULL; - return -1; - } else { - return 0; - } -} - -void ACMCNG::DestructDecoderSafe() { - if (decoder_inst_ptr_ != NULL) { - WebRtcCng_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - decoder_exist_ = false; - decoder_initialized_ = false; -} - -void ACMCNG::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcCng_FreeEnc(static_cast(ptr_inst)); - } - return; -} - -int16_t ACMCNG::EnableDTX() { return -1; } -int16_t ACMCNG::DisableDTX() { return -1; } - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_cng.h b/webrtc/modules/audio_coding/main/source/acm_cng.h deleted file mode 100644 index 728312d55..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_cng.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CNG_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CNG_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct WebRtcCngEncInst; -struct WebRtcCngDecInst; - -namespace webrtc { - -namespace acm1 { - -class ACMCNG: public ACMGenericCodec { - public: - explicit ACMCNG(int16_t codec_id); - virtual ~ACMCNG(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual int16_t EnableDTX() OVERRIDE; - virtual int16_t DisableDTX() OVERRIDE; - - WebRtcCngEncInst* encoder_inst_ptr_; - WebRtcCngDecInst* decoder_inst_ptr_; - uint16_t samp_freq_hz_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CNG_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_codec_database.cc b/webrtc/modules/audio_coding/main/source/acm_codec_database.cc deleted file mode 100644 index 138effd6a..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_codec_database.cc +++ /dev/null @@ -1,956 +0,0 @@ -/* - * Copyright (c) 2012 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 file generates databases with information about all supported audio - * codecs. - */ - -// TODO(tlegrand): Change constant input pointers in all functions to constant -// references, where appropriate. -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/system_wrappers/interface/trace.h" - -// Includes needed to create the codecs. -// G.711, PCM mu-law and A-law. -#include "webrtc/modules/audio_coding/codecs/g711/include/g711_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_pcma.h" -#include "webrtc/modules/audio_coding/main/source/acm_pcmu.h" -// CNG. -#include "webrtc/modules/audio_coding/codecs/cng/include/webrtc_cng.h" -#include "webrtc/modules/audio_coding/main/source/acm_cng.h" -// NetEQ. -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#ifdef WEBRTC_CODEC_ISAC -#include "webrtc/modules/audio_coding/codecs/isac/main/interface/isac.h" -#endif -#ifdef WEBRTC_CODEC_ISACFX -#include "webrtc/modules/audio_coding/codecs/isac/fix/interface/isacfix.h" -#endif -#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) -#include "webrtc/modules/audio_coding/main/source/acm_isac.h" -#include "webrtc/modules/audio_coding/main/source/acm_isac_macros.h" -#endif -#ifdef WEBRTC_CODEC_PCM16 -#include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#include "webrtc/modules/audio_coding/main/source/acm_pcm16b.h" -#endif -#ifdef WEBRTC_CODEC_ILBC -#include "webrtc/modules/audio_coding/codecs/ilbc/interface/ilbc.h" -#include "webrtc/modules/audio_coding/main/source/acm_ilbc.h" -#endif -#ifdef WEBRTC_CODEC_AMR -#include "webrtc/modules/audio_coding/codecs/amr/include/amr_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_amr.h" -#endif -#ifdef WEBRTC_CODEC_AMRWB -#include "webrtc/modules/audio_coding/codecs/amrwb/include/amrwb_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_amrwb.h" -#endif -#ifdef WEBRTC_CODEC_CELT -#include "webrtc/modules/audio_coding/codecs/celt/include/celt_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_celt.h" -#endif -#ifdef WEBRTC_CODEC_G722 -#include "webrtc/modules/audio_coding/codecs/g722/include/g722_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_g722.h" -#endif -#ifdef WEBRTC_CODEC_G722_1 -#include "webrtc/modules/audio_coding/codecs/g7221/include/g7221_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_g7221.h" -#endif -#ifdef WEBRTC_CODEC_G722_1C -#include "webrtc/modules/audio_coding/codecs/g7221c/include/g7221c_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_g7221c.h" -#endif -#ifdef WEBRTC_CODEC_G729 -#include "webrtc/modules/audio_coding/codecs/g729/include/g729_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_g729.h" -#endif -#ifdef WEBRTC_CODEC_G729_1 -#include "webrtc/modules/audio_coding/codecs/g7291/include/g7291_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_g7291.h" -#endif -#ifdef WEBRTC_CODEC_GSMFR -#include "webrtc/modules/audio_coding/codecs/gsmfr/include/gsmfr_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_gsmfr.h" -#endif -#ifdef WEBRTC_CODEC_OPUS -#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_opus.h" -#endif -#ifdef WEBRTC_CODEC_SPEEX -#include "webrtc/modules/audio_coding/codecs/speex/include/speex_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_speex.h" -#endif -#ifdef WEBRTC_CODEC_AVT -#include "webrtc/modules/audio_coding/main/source/acm_dtmf_playout.h" -#endif -#ifdef WEBRTC_CODEC_RED -#include "webrtc/modules/audio_coding/main/source/acm_red.h" -#endif - -namespace webrtc { - -namespace acm1 { - -// Not yet used payload-types. -// 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, -// 67, 66, 65 - -const CodecInst ACMCodecDB::database_[] = { -#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) - {103, "ISAC", 16000, kIsacPacSize480, 1, kIsacWbDefaultRate}, -# if (defined(WEBRTC_CODEC_ISAC)) - {104, "ISAC", 32000, kIsacPacSize960, 1, kIsacSwbDefaultRate}, - {105, "ISAC", 48000, kIsacPacSize1440, 1, kIsacSwbDefaultRate}, -# endif -#endif -#ifdef WEBRTC_CODEC_PCM16 - // Mono - {107, "L16", 8000, 80, 1, 128000}, - {108, "L16", 16000, 160, 1, 256000}, - {109, "L16", 32000, 320, 1, 512000}, - // Stereo - {111, "L16", 8000, 80, 2, 128000}, - {112, "L16", 16000, 160, 2, 256000}, - {113, "L16", 32000, 320, 2, 512000}, -#endif - // G.711, PCM mu-law and A-law. - // Mono - {0, "PCMU", 8000, 160, 1, 64000}, - {8, "PCMA", 8000, 160, 1, 64000}, - // Stereo - {110, "PCMU", 8000, 160, 2, 64000}, - {118, "PCMA", 8000, 160, 2, 64000}, -#ifdef WEBRTC_CODEC_ILBC - {102, "ILBC", 8000, 240, 1, 13300}, -#endif -#ifdef WEBRTC_CODEC_AMR - {114, "AMR", 8000, 160, 1, 12200}, -#endif -#ifdef WEBRTC_CODEC_AMRWB - {115, "AMR-WB", 16000, 320, 1, 20000}, -#endif -#ifdef WEBRTC_CODEC_CELT - // Mono - {116, "CELT", 32000, 640, 1, 64000}, - // Stereo - {117, "CELT", 32000, 640, 2, 64000}, -#endif -#ifdef WEBRTC_CODEC_G722 - // Mono - {9, "G722", 16000, 320, 1, 64000}, - // Stereo - {119, "G722", 16000, 320, 2, 64000}, -#endif -#ifdef WEBRTC_CODEC_G722_1 - {92, "G7221", 16000, 320, 1, 32000}, - {91, "G7221", 16000, 320, 1, 24000}, - {90, "G7221", 16000, 320, 1, 16000}, -#endif -#ifdef WEBRTC_CODEC_G722_1C - {89, "G7221", 32000, 640, 1, 48000}, - {88, "G7221", 32000, 640, 1, 32000}, - {87, "G7221", 32000, 640, 1, 24000}, -#endif -#ifdef WEBRTC_CODEC_G729 - {18, "G729", 8000, 240, 1, 8000}, -#endif -#ifdef WEBRTC_CODEC_G729_1 - {86, "G7291", 16000, 320, 1, 32000}, -#endif -#ifdef WEBRTC_CODEC_GSMFR - {3, "GSM", 8000, 160, 1, 13200}, -#endif -#ifdef WEBRTC_CODEC_OPUS - // Opus internally supports 48, 24, 16, 12, 8 kHz. - // Mono and stereo. - {120, "opus", 48000, 960, 2, 64000}, -#endif -#ifdef WEBRTC_CODEC_SPEEX - {85, "speex", 8000, 160, 1, 11000}, - {84, "speex", 16000, 320, 1, 22000}, -#endif - // Comfort noise for four different sampling frequencies. - {13, "CN", 8000, 240, 1, 0}, - {98, "CN", 16000, 480, 1, 0}, - {99, "CN", 32000, 960, 1, 0}, -#ifdef ENABLE_48000_HZ - {100, "CN", 48000, 1440, 1, 0}, -#endif -#ifdef WEBRTC_CODEC_AVT - {106, "telephone-event", 8000, 240, 1, 0}, -#endif -#ifdef WEBRTC_CODEC_RED - {127, "red", 8000, 0, 1, 0}, -#endif - // To prevent compile errors due to trailing commas. - {-1, "Null", -1, -1, -1, -1} -}; - -// Create database with all codec settings at compile time. -// Each entry needs the following parameters in the given order: -// Number of allowed packet sizes, a vector with the allowed packet sizes, -// Basic block samples, max number of channels that are supported. -const ACMCodecDB::CodecSettings ACMCodecDB::codec_settings_[] = { -#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) - {2, {kIsacPacSize480, kIsacPacSize960}, 0, 1}, -# if (defined(WEBRTC_CODEC_ISAC)) - {1, {kIsacPacSize960}, 0, 1}, - {1, {kIsacPacSize1440}, 0, 1}, -# endif -#endif -#ifdef WEBRTC_CODEC_PCM16 - // Mono - {4, {80, 160, 240, 320}, 0, 2}, - {4, {160, 320, 480, 640}, 0, 2}, - {2, {320, 640}, 0, 2}, - // Stereo - {4, {80, 160, 240, 320}, 0, 2}, - {4, {160, 320, 480, 640}, 0, 2}, - {2, {320, 640}, 0, 2}, -#endif - // G.711, PCM mu-law and A-law. - // Mono - {6, {80, 160, 240, 320, 400, 480}, 0, 2}, - {6, {80, 160, 240, 320, 400, 480}, 0, 2}, - // Stereo - {6, {80, 160, 240, 320, 400, 480}, 0, 2}, - {6, {80, 160, 240, 320, 400, 480}, 0, 2}, -#ifdef WEBRTC_CODEC_ILBC - {4, {160, 240, 320, 480}, 0, 1}, -#endif -#ifdef WEBRTC_CODEC_AMR - {3, {160, 320, 480}, 0, 1}, -#endif -#ifdef WEBRTC_CODEC_AMRWB - {3, {320, 640, 960}, 0, 1}, -#endif -#ifdef WEBRTC_CODEC_CELT - // Mono - {1, {640}, 0, 2}, - // Stereo - {1, {640}, 0, 2}, -#endif -#ifdef WEBRTC_CODEC_G722 - // Mono - {6, {160, 320, 480, 640, 800, 960}, 0, 2}, - // Stereo - {6, {160, 320, 480, 640, 800, 960}, 0, 2}, -#endif -#ifdef WEBRTC_CODEC_G722_1 - {1, {320}, 320, 1}, - {1, {320}, 320, 1}, - {1, {320}, 320, 1}, -#endif -#ifdef WEBRTC_CODEC_G722_1C - {1, {640}, 640, 1}, - {1, {640}, 640, 1}, - {1, {640}, 640, 1}, -#endif -#ifdef WEBRTC_CODEC_G729 - {6, {80, 160, 240, 320, 400, 480}, 0, 1}, -#endif -#ifdef WEBRTC_CODEC_G729_1 - {3, {320, 640, 960}, 0, 1}, -#endif -#ifdef WEBRTC_CODEC_GSMFR - {3, {160, 320, 480}, 160, 1}, -#endif -#ifdef WEBRTC_CODEC_OPUS - // Opus supports frames shorter than 10ms, - // but it doesn't help us to use them. - // Mono and stereo. - {4, {480, 960, 1920, 2880}, 0, 2}, -#endif -#ifdef WEBRTC_CODEC_SPEEX - {3, {160, 320, 480}, 0, 1}, - {3, {320, 640, 960}, 0, 1}, -#endif - // Comfort noise for three different sampling frequencies. - {1, {240}, 240, 1}, - {1, {480}, 480, 1}, - {1, {960}, 960, 1}, -#ifdef ENABLE_48000_HZ - {1, {1440}, 1440, 1}, -#endif -#ifdef WEBRTC_CODEC_AVT - {1, {240}, 240, 1}, -#endif -#ifdef WEBRTC_CODEC_RED - {1, {0}, 0, 1}, -#endif - // To prevent compile errors due to trailing commas. - {-1, {-1}, -1, -1} -}; - -// Create a database of all NetEQ decoders at compile time. -const WebRtcNetEQDecoder ACMCodecDB::neteq_decoders_[] = { -#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) - kDecoderISAC, -# if (defined(WEBRTC_CODEC_ISAC)) - kDecoderISACswb, - kDecoderISACfb, -# endif -#endif -#ifdef WEBRTC_CODEC_PCM16 - // Mono - kDecoderPCM16B, - kDecoderPCM16Bwb, - kDecoderPCM16Bswb32kHz, - // Stereo - kDecoderPCM16B_2ch, - kDecoderPCM16Bwb_2ch, - kDecoderPCM16Bswb32kHz_2ch, -#endif - // G.711, PCM mu-las and A-law. - // Mono - kDecoderPCMu, - kDecoderPCMa, - // Stereo - kDecoderPCMu_2ch, - kDecoderPCMa_2ch, -#ifdef WEBRTC_CODEC_ILBC - kDecoderILBC, -#endif -#ifdef WEBRTC_CODEC_AMR - kDecoderAMR, -#endif -#ifdef WEBRTC_CODEC_AMRWB - kDecoderAMRWB, -#endif -#ifdef WEBRTC_CODEC_CELT - // Mono - kDecoderCELT_32, - // Stereo - kDecoderCELT_32_2ch, -#endif -#ifdef WEBRTC_CODEC_G722 - // Mono - kDecoderG722, - // Stereo - kDecoderG722_2ch, -#endif -#ifdef WEBRTC_CODEC_G722_1 - kDecoderG722_1_32, - kDecoderG722_1_24, - kDecoderG722_1_16, -#endif -#ifdef WEBRTC_CODEC_G722_1C - kDecoderG722_1C_48, - kDecoderG722_1C_32, - kDecoderG722_1C_24, -#endif -#ifdef WEBRTC_CODEC_G729 - kDecoderG729, -#endif -#ifdef WEBRTC_CODEC_G729_1 - kDecoderG729_1, -#endif -#ifdef WEBRTC_CODEC_GSMFR - kDecoderGSMFR, -#endif -#ifdef WEBRTC_CODEC_OPUS - // Mono and stereo. - kDecoderOpus, -#endif -#ifdef WEBRTC_CODEC_SPEEX - kDecoderSPEEX_8, - kDecoderSPEEX_16, -#endif - // Comfort noise for three different sampling frequencies. - kDecoderCNG, - kDecoderCNG, - kDecoderCNG, -#ifdef ENABLE_48000_HZ - kDecoderCNG, -#endif -#ifdef WEBRTC_CODEC_AVT - kDecoderAVT, -#endif -#ifdef WEBRTC_CODEC_RED - kDecoderRED, -#endif - kDecoderReservedEnd -}; - -// Get codec information from database. -// TODO(tlegrand): replace memcpy with a pointer to the data base memory. -int ACMCodecDB::Codec(int codec_id, CodecInst* codec_inst) { - // Error check to see that codec_id is not out of bounds. - if ((codec_id < 0) || (codec_id >= kNumCodecs)) { - return -1; - } - - // Copy database information for the codec to the output. - memcpy(codec_inst, &database_[codec_id], sizeof(CodecInst)); - - return 0; -} - -// Enumerator for error codes when asking for codec database id. -enum { - kInvalidCodec = -10, - kInvalidPayloadtype = -30, - kInvalidPacketSize = -40, - kInvalidRate = -50 -}; - -// Gets the codec id number from the database. If there is some mismatch in -// the codec settings, the function will return an error code. -// NOTE! The first mismatch found will generate the return value. -int ACMCodecDB::CodecNumber(const CodecInst* codec_inst, int* mirror_id) { - // Look for a matching codec in the database. - int codec_id = CodecId(codec_inst); - - // Checks if we found a matching codec. - if (codec_id == -1) { - return kInvalidCodec; - } - - // Checks the validity of payload type - if (!ValidPayloadType(codec_inst->pltype)) { - return kInvalidPayloadtype; - } - - // Comfort Noise is special case, packet-size & rate is not checked. - if (STR_CASE_CMP(database_[codec_id].plname, "CN") == 0) { - *mirror_id = codec_id; - return codec_id; - } - - // RED is special case, packet-size & rate is not checked. - if (STR_CASE_CMP(database_[codec_id].plname, "red") == 0) { - *mirror_id = codec_id; - return codec_id; - } - - // Checks the validity of packet size. - if (codec_settings_[codec_id].num_packet_sizes > 0) { - bool packet_size_ok = false; - int i; - int packet_size_samples; - for (i = 0; i < codec_settings_[codec_id].num_packet_sizes; i++) { - packet_size_samples = - codec_settings_[codec_id].packet_sizes_samples[i]; - if (codec_inst->pacsize == packet_size_samples) { - packet_size_ok = true; - break; - } - } - - if (!packet_size_ok) { - return kInvalidPacketSize; - } - } - - if (codec_inst->pacsize < 1) { - return kInvalidPacketSize; - } - - // Check the validity of rate. Codecs with multiple rates have their own - // function for this. - *mirror_id = codec_id; - if (STR_CASE_CMP("isac", codec_inst->plname) == 0) { - if (IsISACRateValid(codec_inst->rate)) { - // Set mirrorID to iSAC WB which is only created once to be used both for - // iSAC WB and SWB, because they need to share struct. - *mirror_id = kISAC; - return codec_id; - } else { - return kInvalidRate; - } - } else if (STR_CASE_CMP("ilbc", codec_inst->plname) == 0) { - return IsILBCRateValid(codec_inst->rate, codec_inst->pacsize) - ? codec_id : kInvalidRate; - } else if (STR_CASE_CMP("amr", codec_inst->plname) == 0) { - return IsAMRRateValid(codec_inst->rate) - ? codec_id : kInvalidRate; - } else if (STR_CASE_CMP("amr-wb", codec_inst->plname) == 0) { - return IsAMRwbRateValid(codec_inst->rate) - ? codec_id : kInvalidRate; - } else if (STR_CASE_CMP("g7291", codec_inst->plname) == 0) { - return IsG7291RateValid(codec_inst->rate) - ? codec_id : kInvalidRate; - } else if (STR_CASE_CMP("opus", codec_inst->plname) == 0) { - return IsOpusRateValid(codec_inst->rate) - ? codec_id : kInvalidRate; - } else if (STR_CASE_CMP("speex", codec_inst->plname) == 0) { - return IsSpeexRateValid(codec_inst->rate) - ? codec_id : kInvalidRate; - } else if (STR_CASE_CMP("celt", codec_inst->plname) == 0) { - return IsCeltRateValid(codec_inst->rate) - ? codec_id : kInvalidRate; - } - - return IsRateValid(codec_id, codec_inst->rate) ? - codec_id : kInvalidRate; -} - -// Looks for a matching payload name, frequency, and channels in the -// codec list. Need to check all three since some codecs have several codec -// entries with different frequencies and/or channels. -// Does not check other codec settings, such as payload type and packet size. -// Returns the id of the codec, or -1 if no match is found. -int ACMCodecDB::CodecId(const CodecInst* codec_inst) { - return (CodecId(codec_inst->plname, codec_inst->plfreq, - codec_inst->channels)); -} - -int ACMCodecDB::CodecId(const char* payload_name, int frequency, int channels) { - for (int id = 0; id < kNumCodecs; id++) { - bool name_match = false; - bool frequency_match = false; - bool channels_match = false; - - // Payload name, sampling frequency and number of channels need to match. - // NOTE! If |frequency| is -1, the frequency is not applicable, and is - // always treated as true, like for RED. - name_match = (STR_CASE_CMP(database_[id].plname, payload_name) == 0); - frequency_match = (frequency == database_[id].plfreq) || (frequency == -1); - // The number of channels must match for all codecs but Opus. - if (STR_CASE_CMP(payload_name, "opus") != 0) { - channels_match = (channels == database_[id].channels); - } else { - // For opus we just check that number of channels is valid. - channels_match = (channels == 1 || channels == 2); - } - - if (name_match && frequency_match && channels_match) { - // We have found a matching codec in the list. - return id; - } - } - - // We didn't find a matching codec. - return -1; -} -// Gets codec id number, and mirror id, from database for the receiver. -int ACMCodecDB::ReceiverCodecNumber(const CodecInst* codec_inst, - int* mirror_id) { - // Look for a matching codec in the database. - int codec_id = CodecId(codec_inst); - - // Set |mirror_id| to |codec_id|, except for iSAC. In case of iSAC we always - // set |mirror_id| to iSAC WB (kISAC) which is only created once to be used - // both for iSAC WB and SWB, because they need to share struct. - if (STR_CASE_CMP(codec_inst->plname, "ISAC") != 0) { - *mirror_id = codec_id; - } else { - *mirror_id = kISAC; - } - - return codec_id; -} - -// Returns the codec sampling frequency for codec with id = "codec_id" in -// database. -int ACMCodecDB::CodecFreq(int codec_id) { - // Error check to see that codec_id is not out of bounds. - if (codec_id < 0 || codec_id >= kNumCodecs) { - return -1; - } - - return database_[codec_id].plfreq; -} - -// Returns the codec's basic coding block size in samples. -int ACMCodecDB::BasicCodingBlock(int codec_id) { - // Error check to see that codec_id is not out of bounds. - if (codec_id < 0 || codec_id >= kNumCodecs) { - return -1; - } - - return codec_settings_[codec_id].basic_block_samples; -} - -// Returns the NetEQ decoder database. -const WebRtcNetEQDecoder* ACMCodecDB::NetEQDecoders() { - return neteq_decoders_; -} - -// Gets mirror id. The Id is used for codecs sharing struct for settings that -// need different payload types. -int ACMCodecDB::MirrorID(int codec_id) { - if (STR_CASE_CMP(database_[codec_id].plname, "isac") == 0) { - return kISAC; - } else { - return codec_id; - } -} - -// Creates memory/instance for storing codec state. -ACMGenericCodec* ACMCodecDB::CreateCodecInstance(const CodecInst* codec_inst) { - // All we have support for right now. - if (!STR_CASE_CMP(codec_inst->plname, "ISAC")) { -#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) - return new ACMISAC(kISAC); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "PCMU")) { - if (codec_inst->channels == 1) { - return new ACMPCMU(kPCMU); - } else { - return new ACMPCMU(kPCMU_2ch); - } - } else if (!STR_CASE_CMP(codec_inst->plname, "PCMA")) { - if (codec_inst->channels == 1) { - return new ACMPCMA(kPCMA); - } else { - return new ACMPCMA(kPCMA_2ch); - } - } else if (!STR_CASE_CMP(codec_inst->plname, "ILBC")) { -#ifdef WEBRTC_CODEC_ILBC - return new ACMILBC(kILBC); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "AMR")) { -#ifdef WEBRTC_CODEC_AMR - return new ACMAMR(kGSMAMR); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "AMR-WB")) { -#ifdef WEBRTC_CODEC_AMRWB - return new ACMAMRwb(kGSMAMRWB); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "CELT")) { -#ifdef WEBRTC_CODEC_CELT - if (codec_inst->channels == 1) { - return new ACMCELT(kCELT32); - } else { - return new ACMCELT(kCELT32_2ch); - } -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "G722")) { -#ifdef WEBRTC_CODEC_G722 - if (codec_inst->channels == 1) { - return new ACMG722(kG722); - } else { - return new ACMG722(kG722_2ch); - } -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "G7221")) { - switch (codec_inst->plfreq) { - case 16000: { -#ifdef WEBRTC_CODEC_G722_1 - int codec_id; - switch (codec_inst->rate) { - case 16000 : { - codec_id = kG722_1_16; - break; - } - case 24000 : { - codec_id = kG722_1_24; - break; - } - case 32000 : { - codec_id = kG722_1_32; - break; - } - default: { - return NULL; - } - } - return new ACMG722_1(codec_id); -#endif - } - case 32000: { -#ifdef WEBRTC_CODEC_G722_1C - int codec_id; - switch (codec_inst->rate) { - case 24000 : { - codec_id = kG722_1C_24; - break; - } - case 32000 : { - codec_id = kG722_1C_32; - break; - } - case 48000 : { - codec_id = kG722_1C_48; - break; - } - default: { - return NULL; - } - } - return new ACMG722_1C(codec_id); -#endif - } - } - } else if (!STR_CASE_CMP(codec_inst->plname, "CN")) { - // For CN we need to check sampling frequency to know what codec to create. - int codec_id; - switch (codec_inst->plfreq) { - case 8000: { - codec_id = kCNNB; - break; - } - case 16000: { - codec_id = kCNWB; - break; - } - case 32000: { - codec_id = kCNSWB; - break; - } -#ifdef ENABLE_48000_HZ - case 48000: { - codec_id = kCNFB; - break; - } -#endif - default: { - return NULL; - } - } - return new ACMCNG(codec_id); - } else if (!STR_CASE_CMP(codec_inst->plname, "G729")) { -#ifdef WEBRTC_CODEC_G729 - return new ACMG729(kG729); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "G7291")) { -#ifdef WEBRTC_CODEC_G729_1 - return new ACMG729_1(kG729_1); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "opus")) { -#ifdef WEBRTC_CODEC_OPUS - return new ACMOpus(kOpus); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "speex")) { -#ifdef WEBRTC_CODEC_SPEEX - int codec_id; - switch (codec_inst->plfreq) { - case 8000: { - codec_id = kSPEEX8; - break; - } - case 16000: { - codec_id = kSPEEX16; - break; - } - default: { - return NULL; - } - } - return new ACMSPEEX(codec_id); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "CN")) { - // For CN we need to check sampling frequency to know what codec to create. - int codec_id; - switch (codec_inst->plfreq) { - case 8000: { - codec_id = kCNNB; - break; - } - case 16000: { - codec_id = kCNWB; - break; - } - case 32000: { - codec_id = kCNSWB; - break; - } -#ifdef ENABLE_48000_HZ - case 48000: { - codec_id = kCNFB; - break; - } -#endif - default: { - return NULL; - } - } - return new ACMCNG(codec_id); - } else if (!STR_CASE_CMP(codec_inst->plname, "L16")) { -#ifdef WEBRTC_CODEC_PCM16 - // For L16 we need to check sampling frequency to know what codec to create. - int codec_id; - if (codec_inst->channels == 1) { - switch (codec_inst->plfreq) { - case 8000: { - codec_id = kPCM16B; - break; - } - case 16000: { - codec_id = kPCM16Bwb; - break; - } - case 32000: { - codec_id = kPCM16Bswb32kHz; - break; - } - default: { - return NULL; - } - } - } else { - switch (codec_inst->plfreq) { - case 8000: { - codec_id = kPCM16B_2ch; - break; - } - case 16000: { - codec_id = kPCM16Bwb_2ch; - break; - } - case 32000: { - codec_id = kPCM16Bswb32kHz_2ch; - break; - } - default: { - return NULL; - } - } - } - return new ACMPCM16B(codec_id); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "telephone-event")) { -#ifdef WEBRTC_CODEC_AVT - return new ACMDTMFPlayout(kAVT); -#endif - } else if (!STR_CASE_CMP(codec_inst->plname, "red")) { -#ifdef WEBRTC_CODEC_RED - return new ACMRED(kRED); -#endif - } - return NULL; -} - -// Checks if the bitrate is valid for the codec. -bool ACMCodecDB::IsRateValid(int codec_id, int rate) { - if (database_[codec_id].rate == rate) { - return true; - } else { - return false; - } -} - -// Checks if the bitrate is valid for iSAC. -bool ACMCodecDB::IsISACRateValid(int rate) { - if ((rate == -1) || ((rate <= 56000) && (rate >= 10000))) { - return true; - } else { - return false; - } -} - -// Checks if the bitrate is valid for iLBC. -bool ACMCodecDB::IsILBCRateValid(int rate, int frame_size_samples) { - if (((frame_size_samples == 240) || (frame_size_samples == 480)) && - (rate == 13300)) { - return true; - } else if (((frame_size_samples == 160) || (frame_size_samples == 320)) && - (rate == 15200)) { - return true; - } else { - return false; - } -} - -// Check if the bitrate is valid for the GSM-AMR. -bool ACMCodecDB::IsAMRRateValid(int rate) { - switch (rate) { - case 4750: - case 5150: - case 5900: - case 6700: - case 7400: - case 7950: - case 10200: - case 12200: { - return true; - } - default: { - return false; - } - } -} - -// Check if the bitrate is valid for GSM-AMR-WB. -bool ACMCodecDB::IsAMRwbRateValid(int rate) { - switch (rate) { - case 7000: - case 9000: - case 12000: - case 14000: - case 16000: - case 18000: - case 20000: - case 23000: - case 24000: { - return true; - } - default: { - return false; - } - } -} - -// Check if the bitrate is valid for G.729.1. -bool ACMCodecDB::IsG7291RateValid(int rate) { - switch (rate) { - case 8000: - case 12000: - case 14000: - case 16000: - case 18000: - case 20000: - case 22000: - case 24000: - case 26000: - case 28000: - case 30000: - case 32000: { - return true; - } - default: { - return false; - } - } -} - -// Checks if the bitrate is valid for Speex. -bool ACMCodecDB::IsSpeexRateValid(int rate) { - if (rate > 2000) { - return true; - } else { - return false; - } -} - -// Checks if the bitrate is valid for Opus. -bool ACMCodecDB::IsOpusRateValid(int rate) { - if ((rate < 6000) || (rate > 510000)) { - return false; - } - return true; -} - -// Checks if the bitrate is valid for Celt. -bool ACMCodecDB::IsCeltRateValid(int rate) { - if ((rate >= 48000) && (rate <= 128000)) { - return true; - } else { - return false; - } -} - -// Checks if the payload type is in the valid range. -bool ACMCodecDB::ValidPayloadType(int payload_type) { - if ((payload_type < 0) || (payload_type > 127)) { - return false; - } - return true; -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_codec_database.h b/webrtc/modules/audio_coding/main/source/acm_codec_database.h deleted file mode 100644 index 7a7054dd1..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_codec_database.h +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (c) 2012 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 file generates databases with information about all supported audio - * codecs. - */ - -#ifndef WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CODEC_DATABASE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CODEC_DATABASE_H_ - -#include "webrtc/common_types.h" -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" - -namespace webrtc { - -namespace acm1 { - -// TODO(tlegrand): replace class ACMCodecDB with a namespace. -class ACMCodecDB { - public: - // Enum with array indexes for the supported codecs. NOTE! The order MUST - // be the same as when creating the database in acm_codec_database.cc. - enum { - kNone = -1 -#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) - , kISAC -# if (defined(WEBRTC_CODEC_ISAC)) - , kISACSWB - , kISACFB -# endif -#endif -#ifdef WEBRTC_CODEC_PCM16 - // Mono - , kPCM16B - , kPCM16Bwb - , kPCM16Bswb32kHz - // Stereo - , kPCM16B_2ch - , kPCM16Bwb_2ch - , kPCM16Bswb32kHz_2ch -#endif - // Mono - , kPCMU - , kPCMA - // Stereo - , kPCMU_2ch - , kPCMA_2ch -#ifdef WEBRTC_CODEC_ILBC - , kILBC -#endif -#ifdef WEBRTC_CODEC_AMR - , kGSMAMR -#endif -#ifdef WEBRTC_CODEC_AMRWB - , kGSMAMRWB -#endif -#ifdef WEBRTC_CODEC_CELT - // Mono - , kCELT32 - // Stereo - , kCELT32_2ch -#endif -#ifdef WEBRTC_CODEC_G722 - // Mono - , kG722 - // Stereo - , kG722_2ch -#endif -#ifdef WEBRTC_CODEC_G722_1 - , kG722_1_32 - , kG722_1_24 - , kG722_1_16 -#endif -#ifdef WEBRTC_CODEC_G722_1C - , kG722_1C_48 - , kG722_1C_32 - , kG722_1C_24 -#endif -#ifdef WEBRTC_CODEC_G729 - , kG729 -#endif -#ifdef WEBRTC_CODEC_G729_1 - , kG729_1 -#endif -#ifdef WEBRTC_CODEC_GSMFR - , kGSMFR -#endif -#ifdef WEBRTC_CODEC_OPUS - // Mono and stereo - , kOpus -#endif -#ifdef WEBRTC_CODEC_SPEEX - , kSPEEX8 - , kSPEEX16 -#endif - , kCNNB - , kCNWB - , kCNSWB -#ifdef ENABLE_48000_HZ - , kCNFB -#endif -#ifdef WEBRTC_CODEC_AVT - , kAVT -#endif -#ifdef WEBRTC_CODEC_RED - , kRED -#endif - , kNumCodecs - }; - - // Set unsupported codecs to -1 -#ifndef WEBRTC_CODEC_ISAC - enum {kISACSWB = -1}; - enum {kISACFB = -1}; -# ifndef WEBRTC_CODEC_ISACFX - enum {kISAC = -1}; -# endif -#endif -#ifndef WEBRTC_CODEC_PCM16 - // Mono - enum {kPCM16B = -1}; - enum {kPCM16Bwb = -1}; - enum {kPCM16Bswb32kHz = -1}; - // Stereo - enum {kPCM16B_2ch = -1}; - enum {kPCM16Bwb_2ch = -1}; - enum {kPCM16Bswb32kHz_2ch = -1}; -#endif - // 48 kHz not supported, always set to -1. - enum {kPCM16Bswb48kHz = -1}; -#ifndef WEBRTC_CODEC_ILBC - enum {kILBC = -1}; -#endif -#ifndef WEBRTC_CODEC_AMR - enum {kGSMAMR = -1}; -#endif -#ifndef WEBRTC_CODEC_AMRWB - enum {kGSMAMRWB = -1}; -#endif -#ifndef WEBRTC_CODEC_CELT - // Mono - enum {kCELT32 = -1}; - // Stereo - enum {kCELT32_2ch = -1}; -#endif -#ifndef WEBRTC_CODEC_G722 - // Mono - enum {kG722 = -1}; - // Stereo - enum {kG722_2ch = -1}; -#endif -#ifndef WEBRTC_CODEC_G722_1 - enum {kG722_1_32 = -1}; - enum {kG722_1_24 = -1}; - enum {kG722_1_16 = -1}; -#endif -#ifndef WEBRTC_CODEC_G722_1C - enum {kG722_1C_48 = -1}; - enum {kG722_1C_32 = -1}; - enum {kG722_1C_24 = -1}; -#endif -#ifndef WEBRTC_CODEC_G729 - enum {kG729 = -1}; -#endif -#ifndef WEBRTC_CODEC_G729_1 - enum {kG729_1 = -1}; -#endif -#ifndef WEBRTC_CODEC_GSMFR - enum {kGSMFR = -1}; -#endif -#ifndef WEBRTC_CODEC_SPEEX - enum {kSPEEX8 = -1}; - enum {kSPEEX16 = -1}; -#endif -#ifndef WEBRTC_CODEC_OPUS - // Mono and stereo - enum {kOpus = -1}; -#endif -#ifndef WEBRTC_CODEC_AVT - enum {kAVT = -1}; -#endif -#ifndef WEBRTC_CODEC_RED - enum {kRED = -1}; -#endif - - // kMaxNumCodecs - Maximum number of codecs that can be activated in one - // build. - // kMaxNumPacketSize - Maximum number of allowed packet sizes for one codec. - // These might need to be increased if adding a new codec to the database - static const int kMaxNumCodecs = 50; - static const int kMaxNumPacketSize = 6; - - // Codec specific settings - // - // num_packet_sizes - number of allowed packet sizes. - // packet_sizes_samples - list of the allowed packet sizes. - // basic_block_samples - assigned a value different from 0 if the codec - // requires to be fed with a specific number of samples - // that can be different from packet size. - // channel_support - number of channels supported to encode; - // 1 = mono, 2 = stereo, etc. - struct CodecSettings { - int num_packet_sizes; - int packet_sizes_samples[kMaxNumPacketSize]; - int basic_block_samples; - int channel_support; - }; - - // Gets codec information from database at the position in database given by - // [codec_id]. - // Input: - // [codec_id] - number that specifies at what position in the database to - // get the information. - // Output: - // [codec_inst] - filled with information about the codec. - // Return: - // 0 if successful, otherwise -1. - static int Codec(int codec_id, CodecInst* codec_inst); - - // Returns codec id and mirror id from database, given the information - // received in the input [codec_inst]. Mirror id is a number that tells - // where to find the codec's memory (instance). The number is either the - // same as codec id (most common), or a number pointing at a different - // entry in the database, if the codec has several entries with different - // payload types. This is used for codecs that must share one struct even if - // the payload type differs. - // One example is the codec iSAC which has the same struct for both 16 and - // 32 khz, but they have different entries in the database. Let's say the - // function is called with iSAC 32kHz. The function will return 1 as that is - // the entry in the data base, and [mirror_id] = 0, as that is the entry for - // iSAC 16 kHz, which holds the shared memory. - // Input: - // [codec_inst] - Information about the codec for which we require the - // database id. - // Output: - // [mirror_id] - mirror id, which most often is the same as the return - // value, see above. - // Return: - // codec id if successful, otherwise < 0. - static int CodecNumber(const CodecInst* codec_inst, int* mirror_id); - static int CodecId(const CodecInst* codec_inst); - static int CodecId(const char* payload_name, int frequency, int channels); - static int ReceiverCodecNumber(const CodecInst* codec_inst, int* mirror_id); - - // Returns the codec sampling frequency for codec with id = "codec_id" in - // database. - // TODO(tlegrand): Check if function is needed, or if we can change - // to access database directly. - // Input: - // [codec_id] - number that specifies at what position in the database to - // get the information. - // Return: - // codec sampling frequency if successful, otherwise -1. - static int CodecFreq(int codec_id); - - // Return the codec's basic coding block size in samples. - // TODO(tlegrand): Check if function is needed, or if we can change - // to access database directly. - // Input: - // [codec_id] - number that specifies at what position in the database to - // get the information. - // Return: - // codec basic block size if successful, otherwise -1. - static int BasicCodingBlock(int codec_id); - - // Returns the NetEQ decoder database. - static const WebRtcNetEQDecoder* NetEQDecoders(); - - // Returns mirror id, which is a number that tells where to find the codec's - // memory (instance). It is either the same as codec id (most common), or a - // number pointing at a different entry in the database, if the codec have - // several entries with different payload types. This is used for codecs that - // must share struct even if the payload type differs. - // TODO(tlegrand): Check if function is needed, or if we can change - // to access database directly. - // Input: - // [codec_id] - number that specifies codec's position in the database. - // Return: - // Mirror id on success, otherwise -1. - static int MirrorID(int codec_id); - - // Create memory/instance for storing codec state. - // Input: - // [codec_inst] - information about codec. Only name of codec, "plname", is - // used in this function. - static ACMGenericCodec* CreateCodecInstance(const CodecInst* codec_inst); - - // Checks if the bitrate is valid for the codec. - // Input: - // [codec_id] - number that specifies codec's position in the database. - // [rate] - bitrate to check. - // [frame_size_samples] - (used for iLBC) specifies which frame size to go - // with the rate. - static bool IsRateValid(int codec_id, int rate); - static bool IsISACRateValid(int rate); - static bool IsILBCRateValid(int rate, int frame_size_samples); - static bool IsAMRRateValid(int rate); - static bool IsAMRwbRateValid(int rate); - static bool IsG7291RateValid(int rate); - static bool IsSpeexRateValid(int rate); - static bool IsOpusRateValid(int rate); - static bool IsCeltRateValid(int rate); - - // Check if the payload type is valid, meaning that it is in the valid range - // of 0 to 127. - // Input: - // [payload_type] - payload type. - static bool ValidPayloadType(int payload_type); - - // Databases with information about the supported codecs - // database_ - stored information about all codecs: payload type, name, - // sampling frequency, packet size in samples, default channel - // support, and default rate. - // codec_settings_ - stored codec settings: number of allowed packet sizes, - // a vector with the allowed packet sizes, basic block - // samples, and max number of channels that are supported. - // neteq_decoders_ - list of supported decoders in NetEQ. - static const CodecInst database_[kMaxNumCodecs]; - static const CodecSettings codec_settings_[kMaxNumCodecs]; - static const WebRtcNetEQDecoder neteq_decoders_[kMaxNumCodecs]; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_CODEC_DATABASE_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_dtmf_detection.cc b/webrtc/modules/audio_coding/main/source/acm_dtmf_detection.cc deleted file mode 100644 index edb629876..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_dtmf_detection.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_dtmf_detection.h" - -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" - -namespace webrtc { - -namespace acm1 { - -ACMDTMFDetection::ACMDTMFDetection() {} - -ACMDTMFDetection::~ACMDTMFDetection() {} - -int16_t ACMDTMFDetection::Enable(ACMCountries /* cpt */) { - return -1; -} - -int16_t ACMDTMFDetection::Disable() { - return -1; -} - -int16_t ACMDTMFDetection::Detect( - const int16_t* /* in_audio_buff */, - const uint16_t /* in_buff_len_word16 */, - const int32_t /* in_freq_hz */, - bool& /* tone_detected */, - int16_t& /* tone */) { - return -1; -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_dtmf_detection.h b/webrtc/modules/audio_coding/main/source/acm_dtmf_detection.h deleted file mode 100644 index 74553107a..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_dtmf_detection.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_DTMF_DETECTION_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_DTMF_DETECTION_H_ - -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" -#include "webrtc/modules/audio_coding/main/source/acm_resampler.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -namespace acm1 { - -class ACMDTMFDetection { - public: - ACMDTMFDetection(); - ~ACMDTMFDetection(); - int16_t Enable(ACMCountries cpt = ACMDisableCountryDetection); - int16_t Disable(); - int16_t Detect(const int16_t* in_audio_buff, - const uint16_t in_buff_len_word16, - const int32_t in_freq_hz, - bool& tone_detected, - int16_t& tone); - - private: - ACMResampler resampler_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_DTMF_DETECTION_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_dtmf_playout.cc b/webrtc/modules/audio_coding/main/source/acm_dtmf_playout.cc deleted file mode 100644 index 32195e6fe..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_dtmf_playout.cc +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_dtmf_playout.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_AVT - -ACMDTMFPlayout::ACMDTMFPlayout( - int16_t /* codec_id */) { - return; -} - -ACMDTMFPlayout::~ACMDTMFPlayout() { - return; -} - -int16_t ACMDTMFPlayout::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMDTMFPlayout::DecodeSafe( - uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMDTMFPlayout::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMDTMFPlayout::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMDTMFPlayout::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMDTMFPlayout::CreateInstance(void) { - return NULL; -} - -int16_t ACMDTMFPlayout::InternalCreateEncoder() { - return -1; -} - -int16_t ACMDTMFPlayout::InternalCreateDecoder() { - return -1; -} - -void ACMDTMFPlayout::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -void ACMDTMFPlayout::DestructEncoderSafe() { - return; -} - -void ACMDTMFPlayout::DestructDecoderSafe() { - return; -} - -#else //===================== Actual Implementation ======================= - -ACMDTMFPlayout::ACMDTMFPlayout(int16_t codec_id) { - codec_id_ = codec_id; -} - -ACMDTMFPlayout::~ACMDTMFPlayout() { - return; -} - -int16_t ACMDTMFPlayout::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return 0; -} - -int16_t ACMDTMFPlayout::DecodeSafe( - uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMDTMFPlayout::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, - // DTMFPlayout has no instance - return 0; -} - -int16_t ACMDTMFPlayout::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, - // DTMFPlayout has no instance - return 0; -} - -int32_t ACMDTMFPlayout::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_AVT_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderAVT, codec_inst.pltype, NULL, 8000); - SET_AVT_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMDTMFPlayout::CreateInstance(void) { - return NULL; -} - -int16_t ACMDTMFPlayout::InternalCreateEncoder() { - // DTMFPlayout has no instance - return 0; -} - -int16_t ACMDTMFPlayout::InternalCreateDecoder() { - // DTMFPlayout has no instance - return 0; -} - -void ACMDTMFPlayout::InternalDestructEncoderInst(void* /* ptr_inst */) { - // DTMFPlayout has no instance - return; -} - -void ACMDTMFPlayout::DestructEncoderSafe() { - // DTMFPlayout has no instance - return; -} - -void ACMDTMFPlayout::DestructDecoderSafe() { - // DTMFPlayout has no instance - return; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_dtmf_playout.h b/webrtc/modules/audio_coding/main/source/acm_dtmf_playout.h deleted file mode 100644 index 46175f59e..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_dtmf_playout.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_DTMF_PLAYOUT_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_DTMF_PLAYOUT_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -namespace webrtc { - -namespace acm1 { - -class ACMDTMFPlayout: public ACMGenericCodec { - public: - explicit ACMDTMFPlayout(int16_t codec_id); - virtual ~ACMDTMFPlayout(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_DTMF_PLAYOUT_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_g722.cc b/webrtc/modules/audio_coding/main/source/acm_g722.cc deleted file mode 100644 index 1c19109b6..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g722.cc +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_g722.h" - -#include "webrtc/modules/audio_coding/codecs/g722/include/g722_interface.h" -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_G722 - -ACMG722::ACMG722(int16_t /* codec_id */) - : ptr_enc_str_(NULL), - ptr_dec_str_(NULL), - encoder_inst_ptr_(NULL), - encoder_inst_ptr_right_(NULL), - decoder_inst_ptr_(NULL) {} - -ACMG722::~ACMG722() {} - -int32_t ACMG722::Add10MsDataSafe( - const uint32_t /* timestamp */, - const int16_t* /* data */, - const uint16_t /* length_smpl */, - const uint8_t /* audio_channel */) { - return -1; -} - -int16_t ACMG722::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMG722::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMG722::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMG722::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMG722::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMG722::CreateInstance(void) { - return NULL; -} - -int16_t ACMG722::InternalCreateEncoder() { - return -1; -} - -void ACMG722::DestructEncoderSafe() { - return; -} - -int16_t ACMG722::InternalCreateDecoder() { - return -1; -} - -void ACMG722::DestructDecoderSafe() { - return; -} - -void ACMG722::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -void ACMG722::SplitStereoPacket(uint8_t* /*payload*/, - int32_t* /*payload_length*/) {} - -#else //===================== Actual Implementation ======================= - -// Encoder and decoder memory -struct ACMG722EncStr { - G722EncInst* inst; // instance for left channel in case of stereo - G722EncInst* inst_right; // instance for right channel in case of stereo -}; -struct ACMG722DecStr { - G722DecInst* inst; // instance for left channel in case of stereo - G722DecInst* inst_right; // instance for right channel in case of stereo -}; - -ACMG722::ACMG722(int16_t codec_id) - : encoder_inst_ptr_(NULL), - encoder_inst_ptr_right_(NULL), - decoder_inst_ptr_(NULL) { - // Encoder - ptr_enc_str_ = new ACMG722EncStr; - if (ptr_enc_str_ != NULL) { - ptr_enc_str_->inst = NULL; - ptr_enc_str_->inst_right = NULL; - } - // Decoder - ptr_dec_str_ = new ACMG722DecStr; - if (ptr_dec_str_ != NULL) { - ptr_dec_str_->inst = NULL; - ptr_dec_str_->inst_right = NULL; // Not used - } - codec_id_ = codec_id; - return; -} - -ACMG722::~ACMG722() { - // Encoder - if (ptr_enc_str_ != NULL) { - if (ptr_enc_str_->inst != NULL) { - WebRtcG722_FreeEncoder(ptr_enc_str_->inst); - ptr_enc_str_->inst = NULL; - } - if (ptr_enc_str_->inst_right != NULL) { - WebRtcG722_FreeEncoder(ptr_enc_str_->inst_right); - ptr_enc_str_->inst_right = NULL; - } - delete ptr_enc_str_; - ptr_enc_str_ = NULL; - } - // Decoder - if (ptr_dec_str_ != NULL) { - if (ptr_dec_str_->inst != NULL) { - WebRtcG722_FreeDecoder(ptr_dec_str_->inst); - ptr_dec_str_->inst = NULL; - } - if (ptr_dec_str_->inst_right != NULL) { - WebRtcG722_FreeDecoder(ptr_dec_str_->inst_right); - ptr_dec_str_->inst_right = NULL; - } - delete ptr_dec_str_; - ptr_dec_str_ = NULL; - } - return; -} - -int32_t ACMG722::Add10MsDataSafe(const uint32_t timestamp, - const int16_t* data, - const uint16_t length_smpl, - const uint8_t audio_channel) { - return ACMGenericCodec::Add10MsDataSafe((timestamp >> 1), data, length_smpl, - audio_channel); -} - -int16_t ACMG722::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - // If stereo, split input signal in left and right channel before encoding - if (num_channels_ == 2) { - int16_t left_channel[960]; - int16_t right_channel[960]; - uint8_t out_left[480]; - uint8_t out_right[480]; - int16_t len_in_bytes; - for (int i = 0, j = 0; i < frame_len_smpl_ * 2; i += 2, j++) { - left_channel[j] = in_audio_[in_audio_ix_read_ + i]; - right_channel[j] = in_audio_[in_audio_ix_read_ + i + 1]; - } - len_in_bytes = WebRtcG722_Encode(encoder_inst_ptr_, left_channel, - frame_len_smpl_, - (int16_t*)out_left); - len_in_bytes += WebRtcG722_Encode(encoder_inst_ptr_right_, right_channel, - frame_len_smpl_, - (int16_t*)out_right); - *bitstream_len_byte = len_in_bytes; - - // Interleave the 4 bits per sample from left and right channel - for (int i = 0, j = 0; i < len_in_bytes; i += 2, j++) { - bitstream[i] = (out_left[j] & 0xF0) + (out_right[j] >> 4); - bitstream[i + 1] = ((out_left[j] & 0x0F) << 4) + (out_right[j] & 0x0F); - } - } else { - *bitstream_len_byte = WebRtcG722_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], - frame_len_smpl_, - (int16_t*)bitstream); - } - - // increment the read index this tell the caller how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += frame_len_smpl_ * num_channels_; - return *bitstream_len_byte; -} - -int16_t ACMG722::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMG722::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { - if (codec_params->codec_inst.channels == 2) { - // Create codec struct for right channel - if (ptr_enc_str_->inst_right == NULL) { - WebRtcG722_CreateEncoder(&ptr_enc_str_->inst_right); - if (ptr_enc_str_->inst_right == NULL) { - return -1; - } - } - encoder_inst_ptr_right_ = ptr_enc_str_->inst_right; - if (WebRtcG722_EncoderInit(encoder_inst_ptr_right_) < 0) { - return -1; - } - } - - return WebRtcG722_EncoderInit(encoder_inst_ptr_); -} - -int16_t ACMG722::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return WebRtcG722_DecoderInit(decoder_inst_ptr_); -} - -int32_t ACMG722::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - // TODO(turajs): log error - return -1; - } - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_G722_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - if (codec_inst.channels == 1) { - SET_CODEC_PAR(codec_def, kDecoderG722, codec_inst.pltype, decoder_inst_ptr_, - 16000); - } else { - SET_CODEC_PAR(codec_def, kDecoderG722_2ch, codec_inst.pltype, - decoder_inst_ptr_, 16000); - } - SET_G722_FUNCTIONS(codec_def); - return 0; -} - -ACMGenericCodec* ACMG722::CreateInstance(void) { - return NULL; -} - -int16_t ACMG722::InternalCreateEncoder() { - if (ptr_enc_str_ == NULL) { - // this structure must be created at the costructor - // if it is still NULL then there is a probelm and - // we dont continue - return -1; - } - WebRtcG722_CreateEncoder(&ptr_enc_str_->inst); - if (ptr_enc_str_->inst == NULL) { - return -1; - } - encoder_inst_ptr_ = ptr_enc_str_->inst; - return 0; -} - -void ACMG722::DestructEncoderSafe() { - if (ptr_enc_str_ != NULL) { - if (ptr_enc_str_->inst != NULL) { - WebRtcG722_FreeEncoder(ptr_enc_str_->inst); - ptr_enc_str_->inst = NULL; - } - } - encoder_exist_ = false; - encoder_initialized_ = false; -} - -int16_t ACMG722::InternalCreateDecoder() { - if (ptr_dec_str_ == NULL) { - // this structure must be created at the costructor - // if it is still NULL then there is a probelm and - // we dont continue - return -1; - } - - WebRtcG722_CreateDecoder(&ptr_dec_str_->inst); - if (ptr_dec_str_->inst == NULL) { - return -1; - } - decoder_inst_ptr_ = ptr_dec_str_->inst; - return 0; -} - -void ACMG722::DestructDecoderSafe() { - decoder_exist_ = false; - decoder_initialized_ = false; - if (ptr_dec_str_ != NULL) { - if (ptr_dec_str_->inst != NULL) { - WebRtcG722_FreeDecoder(ptr_dec_str_->inst); - ptr_dec_str_->inst = NULL; - } - } -} - -void ACMG722::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcG722_FreeEncoder(static_cast(ptr_inst)); - } - return; -} - -// Split the stereo packet and place left and right channel after each other -// in the payload vector. -void ACMG722::SplitStereoPacket(uint8_t* payload, int32_t* payload_length) { - uint8_t right_byte; - - // Check for valid inputs. - assert(payload != NULL); - assert(*payload_length > 0); - - // Regroup the 4 bits/sample so to |l1 l2| |r1 r2| |l3 l4| |r3 r4| ..., - // where "lx" is 4 bits representing left sample number x, and "rx" right - // sample. Two samples fits in one byte, represented with |...|. - for (int i = 0; i < *payload_length; i += 2) { - right_byte = ((payload[i] & 0x0F) << 4) + (payload[i + 1] & 0x0F); - payload[i] = (payload[i] & 0xF0) + (payload[i + 1] >> 4); - payload[i + 1] = right_byte; - } - - // Move one byte representing right channel each loop, and place it at the - // end of the bytestream vector. After looping the data is reordered to: - // |l1 l2| |l3 l4| ... |l(N-1) lN| |r1 r2| |r3 r4| ... |r(N-1) r(N)|, - // where N is the total number of samples. - for (int i = 0; i < *payload_length / 2; i++) { - right_byte = payload[i + 1]; - memmove(&payload[i + 1], &payload[i + 2], *payload_length - i - 2); - payload[*payload_length - 1] = right_byte; - } -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_g722.h b/webrtc/modules/audio_coding/main/source/acm_g722.h deleted file mode 100644 index cf7ebe1e2..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g722.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G722_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G722_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -typedef struct WebRtcG722EncInst G722EncInst; -typedef struct WebRtcG722DecInst G722DecInst; - -namespace webrtc { - -namespace acm1 { - -// forward declaration -struct ACMG722EncStr; -struct ACMG722DecStr; - -class ACMG722 : public ACMGenericCodec { - public: - explicit ACMG722(int16_t codec_id); - virtual ~ACMG722(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual int32_t Add10MsDataSafe(const uint32_t timestamp, - const int16_t* data, - const uint16_t length_smpl, - const uint8_t audio_channel) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual void SplitStereoPacket(uint8_t* payload, - int32_t* payload_length) OVERRIDE; - - ACMG722EncStr* ptr_enc_str_; - ACMG722DecStr* ptr_dec_str_; - - G722EncInst* encoder_inst_ptr_; - G722EncInst* encoder_inst_ptr_right_; // Prepared for stereo - G722DecInst* decoder_inst_ptr_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G722_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_g7221.cc b/webrtc/modules/audio_coding/main/source/acm_g7221.cc deleted file mode 100644 index ed172fd3e..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g7221.cc +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_g7221.h" - -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_G722_1 -// NOTE! G.722.1 is not included in the open-source package. The following -// interface file is needed: -// -// /modules/audio_coding/codecs/g7221/main/interface/g7221_interface.h -// -// The API in the header file should match the one below. -// -// int16_t WebRtcG7221_CreateEnc16(G722_1_16_encinst_t_** enc_inst); -// int16_t WebRtcG7221_CreateEnc24(G722_1_24_encinst_t_** enc_inst); -// int16_t WebRtcG7221_CreateEnc32(G722_1_32_encinst_t_** enc_inst); -// int16_t WebRtcG7221_CreateDec16(G722_1_16_decinst_t_** dec_inst); -// int16_t WebRtcG7221_CreateDec24(G722_1_24_decinst_t_** dec_inst); -// int16_t WebRtcG7221_CreateDec32(G722_1_32_decinst_t_** dec_inst); -// -// int16_t WebRtcG7221_FreeEnc16(G722_1_16_encinst_t_** enc_inst); -// int16_t WebRtcG7221_FreeEnc24(G722_1_24_encinst_t_** enc_inst); -// int16_t WebRtcG7221_FreeEnc32(G722_1_32_encinst_t_** enc_inst); -// int16_t WebRtcG7221_FreeDec16(G722_1_16_decinst_t_** dec_inst); -// int16_t WebRtcG7221_FreeDec24(G722_1_24_decinst_t_** dec_inst); -// int16_t WebRtcG7221_FreeDec32(G722_1_32_decinst_t_** dec_inst); -// -// int16_t WebRtcG7221_EncoderInit16(G722_1_16_encinst_t_* enc_inst); -// int16_t WebRtcG7221_EncoderInit24(G722_1_24_encinst_t_* enc_inst); -// int16_t WebRtcG7221_EncoderInit32(G722_1_32_encinst_t_* enc_inst); -// int16_t WebRtcG7221_DecoderInit16(G722_1_16_decinst_t_* dec_inst); -// int16_t WebRtcG7221_DecoderInit24(G722_1_24_decinst_t_* dec_inst); -// int16_t WebRtcG7221_DecoderInit32(G722_1_32_decinst_t_* dec_inst); -// -// int16_t WebRtcG7221_Encode16(G722_1_16_encinst_t_* enc_inst, -// int16_t* input, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221_Encode24(G722_1_24_encinst_t_* enc_inst, -// int16_t* input, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221_Encode32(G722_1_32_encinst_t_* enc_inst, -// int16_t* input, -// int16_t len, -// int16_t* output); -// -// int16_t WebRtcG7221_Decode16(G722_1_16_decinst_t_* dec_inst, -// int16_t* bitstream, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221_Decode24(G722_1_24_decinst_t_* dec_inst, -// int16_t* bitstream, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221_Decode32(G722_1_32_decinst_t_* dec_inst, -// int16_t* bitstream, -// int16_t len, -// int16_t* output); -// -// int16_t WebRtcG7221_DecodePlc16(G722_1_16_decinst_t_* dec_inst, -// int16_t* output, -// int16_t nr_lost_frames); -// int16_t WebRtcG7221_DecodePlc24(G722_1_24_decinst_t_* dec_inst, -// int16_t* output, -// int16_t nr_lost_frames); -// int16_t WebRtcG7221_DecodePlc32(G722_1_32_decinst_t_* dec_inst, -// int16_t* output, -// int16_t nr_lost_frames); -#include "g7221_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_G722_1 - -ACMG722_1::ACMG722_1(int16_t /* codec_id */) - : operational_rate_(-1), - encoder_inst_ptr_(NULL), - encoder_inst_ptr_right_(NULL), - decoder_inst_ptr_(NULL), - encoder_inst16_ptr_(NULL), - encoder_inst16_ptr_right_(NULL), - encoder_inst24_ptr_(NULL), - encoder_inst24_ptr_right_(NULL), - encoder_inst32_ptr_(NULL), - encoder_inst32_ptr_right_(NULL), - decoder_inst16_ptr_(NULL), - decoder_inst24_ptr_(NULL), - decoder_inst32_ptr_(NULL) { - return; -} - -ACMG722_1::~ACMG722_1() { - return; -} - -int16_t ACMG722_1::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMG722_1::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMG722_1::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMG722_1::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMG722_1::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMG722_1::CreateInstance(void) { - return NULL; -} - -int16_t ACMG722_1::InternalCreateEncoder() { - return -1; -} - -void ACMG722_1::DestructEncoderSafe() { - return; -} - -int16_t ACMG722_1::InternalCreateDecoder() { - return -1; -} - -void ACMG722_1::DestructDecoderSafe() { - return; -} - -void ACMG722_1::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -#else //===================== Actual Implementation ======================= -ACMG722_1::ACMG722_1(int16_t codec_id) - : encoder_inst_ptr_(NULL), - encoder_inst_ptr_right_(NULL), - decoder_inst_ptr_(NULL), - encoder_inst16_ptr_(NULL), - encoder_inst16_ptr_right_(NULL), - encoder_inst24_ptr_(NULL), - encoder_inst24_ptr_right_(NULL), - encoder_inst32_ptr_(NULL), - encoder_inst32_ptr_right_(NULL), - decoder_inst16_ptr_(NULL), - decoder_inst24_ptr_(NULL), - decoder_inst32_ptr_(NULL) { - codec_id_ = codec_id; - if (codec_id_ == ACMCodecDB::kG722_1_16) { - operational_rate_ = 16000; - } else if (codec_id_ == ACMCodecDB::kG722_1_24) { - operational_rate_ = 24000; - } else if (codec_id_ == ACMCodecDB::kG722_1_32) { - operational_rate_ = 32000; - } else { - operational_rate_ = -1; - } - return; -} - -ACMG722_1::~ACMG722_1() { - if (encoder_inst_ptr_ != NULL) { - delete encoder_inst_ptr_; - encoder_inst_ptr_ = NULL; - } - if (encoder_inst_ptr_right_ != NULL) { - delete encoder_inst_ptr_right_; - encoder_inst_ptr_right_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - delete decoder_inst_ptr_; - decoder_inst_ptr_ = NULL; - } - - switch (operational_rate_) { - case 16000: { - encoder_inst16_ptr_ = NULL; - encoder_inst16_ptr_right_ = NULL; - decoder_inst16_ptr_ = NULL; - break; - } - case 24000: { - encoder_inst24_ptr_ = NULL; - encoder_inst24_ptr_right_ = NULL; - decoder_inst24_ptr_ = NULL; - break; - } - case 32000: { - encoder_inst32_ptr_ = NULL; - encoder_inst32_ptr_right_ = NULL; - decoder_inst32_ptr_ = NULL; - break; - } - default: { - break; - } - } - return; -} - -int16_t ACMG722_1::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - int16_t left_channel[320]; - int16_t right_channel[320]; - int16_t len_in_bytes; - int16_t out_bits[160]; - - // If stereo, split input signal in left and right channel before encoding - if (num_channels_ == 2) { - for (int i = 0, j = 0; i < frame_len_smpl_ * 2; i += 2, j++) { - left_channel[j] = in_audio_[in_audio_ix_read_ + i]; - right_channel[j] = in_audio_[in_audio_ix_read_ + i + 1]; - } - } else { - memcpy(left_channel, &in_audio_[in_audio_ix_read_], 320); - } - - switch (operational_rate_) { - case 16000: { - len_in_bytes = WebRtcG7221_Encode16(encoder_inst16_ptr_, left_channel, - 320, &out_bits[0]); - if (num_channels_ == 2) { - len_in_bytes += WebRtcG7221_Encode16(encoder_inst16_ptr_right_, - right_channel, 320, - &out_bits[len_in_bytes / 2]); - } - break; - } - case 24000: { - len_in_bytes = WebRtcG7221_Encode24(encoder_inst24_ptr_, left_channel, - 320, &out_bits[0]); - if (num_channels_ == 2) { - len_in_bytes += WebRtcG7221_Encode24(encoder_inst24_ptr_right_, - right_channel, 320, - &out_bits[len_in_bytes / 2]); - } - break; - } - case 32000: { - len_in_bytes = WebRtcG7221_Encode32(encoder_inst32_ptr_, left_channel, - 320, &out_bits[0]); - if (num_channels_ == 2) { - len_in_bytes += WebRtcG7221_Encode32(encoder_inst32_ptr_right_, - right_channel, 320, - &out_bits[len_in_bytes / 2]); - } - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitEncode: Wrong rate for G722_1."); - return -1; - } - } - memcpy(bitstream, out_bits, len_in_bytes); - *bitstream_len_byte = len_in_bytes; - - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += 320 * num_channels_; - return *bitstream_len_byte; -} - -int16_t ACMG722_1::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMG722_1::InternalInitEncoder( - WebRtcACMCodecParams* codec_params) { - int16_t ret; - - switch (operational_rate_) { - case 16000: { - ret = WebRtcG7221_EncoderInit16(encoder_inst16_ptr_right_); - if (ret < 0) { - return ret; - } - return WebRtcG7221_EncoderInit16(encoder_inst16_ptr_); - } - case 24000: { - ret = WebRtcG7221_EncoderInit24(encoder_inst24_ptr_right_); - if (ret < 0) { - return ret; - } - return WebRtcG7221_EncoderInit24(encoder_inst24_ptr_); - } - case 32000: { - ret = WebRtcG7221_EncoderInit32(encoder_inst32_ptr_right_); - if (ret < 0) { - return ret; - } - return WebRtcG7221_EncoderInit32(encoder_inst32_ptr_); - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, - unique_id_, "InternalInitEncoder: Wrong rate for G722_1."); - return -1; - } - } -} - -int16_t ACMG722_1::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - switch (operational_rate_) { - case 16000: { - return WebRtcG7221_DecoderInit16(decoder_inst16_ptr_); - } - case 24000: { - return WebRtcG7221_DecoderInit24(decoder_inst24_ptr_); - } - case 32000: { - return WebRtcG7221_DecoderInit32(decoder_inst32_ptr_); - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitDecoder: Wrong rate for G722_1."); - return -1; - } - } -} - -int32_t ACMG722_1::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - // Todo: - // log error - return -1; - } - // NetEq has an array of pointers to WebRtcNetEQ_CodecDef. - // Get an entry of that array (neteq wrapper will allocate memory) - // by calling "netEq->CodecDef", where "NETEQ_CODEC_G722_1_XX" would - // be the index of the entry. - // Fill up the given structure by calling - // "SET_CODEC_PAR" & "SET_G722_1_XX_FUNCTION." - // Then return the structure back to NetEQ to add the codec to it's - // database. - switch (operational_rate_) { - case 16000: { - SET_CODEC_PAR((codec_def), kDecoderG722_1_16, codec_inst.pltype, - decoder_inst16_ptr_, 16000); - SET_G722_1_16_FUNCTIONS((codec_def)); - break; - } - case 24000: { - SET_CODEC_PAR((codec_def), kDecoderG722_1_24, codec_inst.pltype, - decoder_inst24_ptr_, 16000); - SET_G722_1_24_FUNCTIONS((codec_def)); - break; - } - case 32000: { - SET_CODEC_PAR((codec_def), kDecoderG722_1_32, codec_inst.pltype, - decoder_inst32_ptr_, 16000); - SET_G722_1_32_FUNCTIONS((codec_def)); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodecDef: Wrong rate for G722_1."); - return -1; - } - } - return 0; -} - -ACMGenericCodec* ACMG722_1::CreateInstance(void) { - return NULL; -} - -int16_t ACMG722_1::InternalCreateEncoder() { - if ((encoder_inst_ptr_ == NULL) || (encoder_inst_ptr_right_ == NULL)) { - return -1; - } - switch (operational_rate_) { - case 16000: { - WebRtcG7221_CreateEnc16(&encoder_inst16_ptr_); - WebRtcG7221_CreateEnc16(&encoder_inst16_ptr_right_); - break; - } - case 24000: { - WebRtcG7221_CreateEnc24(&encoder_inst24_ptr_); - WebRtcG7221_CreateEnc24(&encoder_inst24_ptr_right_); - break; - } - case 32000: { - WebRtcG7221_CreateEnc32(&encoder_inst32_ptr_); - WebRtcG7221_CreateEnc32(&encoder_inst32_ptr_right_); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: Wrong rate for G722_1."); - return -1; - } - } - return 0; -} - -void ACMG722_1::DestructEncoderSafe() { - encoder_exist_ = false; - encoder_initialized_ = false; - if (encoder_inst_ptr_ != NULL) { - delete encoder_inst_ptr_; - encoder_inst_ptr_ = NULL; - } - if (encoder_inst_ptr_right_ != NULL) { - delete encoder_inst_ptr_right_; - encoder_inst_ptr_right_ = NULL; - } - encoder_inst16_ptr_ = NULL; - encoder_inst24_ptr_ = NULL; - encoder_inst32_ptr_ = NULL; -} - -int16_t ACMG722_1::InternalCreateDecoder() { - if (decoder_inst_ptr_ == NULL) { - return -1; - } - switch (operational_rate_) { - case 16000: { - WebRtcG7221_CreateDec16(&decoder_inst16_ptr_); - break; - } - case 24000: { - WebRtcG7221_CreateDec24(&decoder_inst24_ptr_); - break; - } - case 32000: { - WebRtcG7221_CreateDec32(&decoder_inst32_ptr_); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateDecoder: Wrong rate for G722_1."); - return -1; - } - } - return 0; -} - -void ACMG722_1::DestructDecoderSafe() { - decoder_exist_ = false; - decoder_initialized_ = false; - if (decoder_inst_ptr_ != NULL) { - delete decoder_inst_ptr_; - decoder_inst_ptr_ = NULL; - } - decoder_inst16_ptr_ = NULL; - decoder_inst24_ptr_ = NULL; - decoder_inst32_ptr_ = NULL; -} - -void ACMG722_1::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - delete ptr_inst; - } - return; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_g7221.h b/webrtc/modules/audio_coding/main/source/acm_g7221.h deleted file mode 100644 index 8ea66742c..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g7221.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7221_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7221_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct G722_1_16_encinst_t_; -struct G722_1_16_decinst_t_; -struct G722_1_24_encinst_t_; -struct G722_1_24_decinst_t_; -struct G722_1_32_encinst_t_; -struct G722_1_32_decinst_t_; -struct G722_1_Inst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMG722_1: public ACMGenericCodec { - public: - explicit ACMG722_1(int16_t codec_id); - ~ACMG722_1(); - - // for FEC - ACMGenericCodec* CreateInstance(void); - - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); - - int16_t InternalInitEncoder(WebRtcACMCodecParams *codec_params); - - int16_t InternalInitDecoder(WebRtcACMCodecParams *codec_params); - - protected: - int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, int16_t* audio_samples, - int8_t* speech_type); - - int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst); - - void DestructEncoderSafe(); - - void DestructDecoderSafe(); - - int16_t InternalCreateEncoder(); - - int16_t InternalCreateDecoder(); - - void InternalDestructEncoderInst(void* ptr_inst); - - int32_t operational_rate_; - - G722_1_Inst_t_* encoder_inst_ptr_; - G722_1_Inst_t_* encoder_inst_ptr_right_; // Used in stereo mode - G722_1_Inst_t_* decoder_inst_ptr_; - - // Only one set of these pointer is valid at any instance - G722_1_16_encinst_t_* encoder_inst16_ptr_; - G722_1_16_encinst_t_* encoder_inst16_ptr_right_; - G722_1_24_encinst_t_* encoder_inst24_ptr_; - G722_1_24_encinst_t_* encoder_inst24_ptr_right_; - G722_1_32_encinst_t_* encoder_inst32_ptr_; - G722_1_32_encinst_t_* encoder_inst32_ptr_right_; - - // Only one of these pointer is valid at any instance - G722_1_16_decinst_t_* decoder_inst16_ptr_; - G722_1_24_decinst_t_* decoder_inst24_ptr_; - G722_1_32_decinst_t_* decoder_inst32_ptr_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7221_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_g7221c.cc b/webrtc/modules/audio_coding/main/source/acm_g7221c.cc deleted file mode 100644 index 96caba0a0..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g7221c.cc +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_g7221c.h" - -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_G722_1C -// NOTE! G.722.1C is not included in the open-source package. The following -// interface file is needed: -// -// /modules/audio_coding/codecs/g7221c/main/interface/g7221c_interface.h -// -// The API in the header file should match the one below. -// - -// int16_t WebRtcG7221C_CreateEnc24(G722_1C_24_encinst_t_** enc_inst); -// int16_t WebRtcG7221C_CreateEnc32(G722_1C_32_encinst_t_** enc_inst); -// int16_t WebRtcG7221C_CreateEnc48(G722_1C_48_encinst_t_** enc_inst); -// int16_t WebRtcG7221C_CreateDec24(G722_1C_24_decinst_t_** dec_inst); -// int16_t WebRtcG7221C_CreateDec32(G722_1C_32_decinst_t_** dec_inst); -// int16_t WebRtcG7221C_CreateDec48(G722_1C_48_decinst_t_** dec_inst); -// -// int16_t WebRtcG7221C_FreeEnc24(G722_1C_24_encinst_t_** enc_inst); -// int16_t WebRtcG7221C_FreeEnc32(G722_1C_32_encinst_t_** enc_inst); -// int16_t WebRtcG7221C_FreeEnc48(G722_1C_48_encinst_t_** enc_inst); -// int16_t WebRtcG7221C_FreeDec24(G722_1C_24_decinst_t_** dec_inst); -// int16_t WebRtcG7221C_FreeDec32(G722_1C_32_decinst_t_** dec_inst); -// int16_t WebRtcG7221C_FreeDec48(G722_1C_48_decinst_t_** dec_inst); -// -// int16_t WebRtcG7221C_EncoderInit24(G722_1C_24_encinst_t_* enc_inst); -// int16_t WebRtcG7221C_EncoderInit32(G722_1C_32_encinst_t_* enc_inst); -// int16_t WebRtcG7221C_EncoderInit48(G722_1C_48_encinst_t_* enc_inst); -// int16_t WebRtcG7221C_DecoderInit24(G722_1C_24_decinst_t_* dec_inst); -// int16_t WebRtcG7221C_DecoderInit32(G722_1C_32_decinst_t_* dec_inst); -// int16_t WebRtcG7221C_DecoderInit48(G722_1C_48_decinst_t_* dec_inst); -// -// int16_t WebRtcG7221C_Encode24(G722_1C_24_encinst_t_* enc_inst, -// int16_t* input, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221C_Encode32(G722_1C_32_encinst_t_* enc_inst, -// int16_t* input, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221C_Encode48(G722_1C_48_encinst_t_* enc_inst, -// int16_t* input, -// int16_t len, -// int16_t* output); -// -// int16_t WebRtcG7221C_Decode24(G722_1C_24_decinst_t_* dec_inst, -// int16_t* bitstream, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221C_Decode32(G722_1C_32_decinst_t_* dec_inst, -// int16_t* bitstream, -// int16_t len, -// int16_t* output); -// int16_t WebRtcG7221C_Decode48(G722_1C_48_decinst_t_* dec_inst, -// int16_t* bitstream, -// int16_t len, -// int16_t* output); -// -// int16_t WebRtcG7221C_DecodePlc24(G722_1C_24_decinst_t_* dec_inst, -// int16_t* output, -// int16_t nr_lost_frames); -// int16_t WebRtcG7221C_DecodePlc32(G722_1C_32_decinst_t_* dec_inst, -// int16_t* output, -// int16_t nr_lost_frames); -// int16_t WebRtcG7221C_DecodePlc48(G722_1C_48_decinst_t_* dec_inst, -// int16_t* output, -// int16_t nr_lost_frames); -#include "g7221c_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_G722_1C - -ACMG722_1C::ACMG722_1C(int16_t /* codec_id */) - : operational_rate_(-1), - encoder_inst_ptr_(NULL), - encoder_inst_ptr_right_(NULL), - decoder_inst_ptr_(NULL), - encoder_inst24_ptr_(NULL), - encoder_inst24_ptr_right_(NULL), - encoder_inst32_ptr_(NULL), - encoder_inst32_ptr_right_(NULL), - encoder_inst48_ptr_(NULL), - encoder_inst48_ptr_right_(NULL), - decoder_inst24_ptr_(NULL), - decoder_inst32_ptr_(NULL), - decoder_inst48_ptr_(NULL) { - return; -} - -ACMG722_1C::~ACMG722_1C() { - return; -} - -int16_t ACMG722_1C::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMG722_1C::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMG722_1C::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMG722_1C::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMG722_1C::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMG722_1C::CreateInstance(void) { - return NULL; -} - -int16_t ACMG722_1C::InternalCreateEncoder() { - return -1; -} - -void ACMG722_1C::DestructEncoderSafe() { - return; -} - -int16_t ACMG722_1C::InternalCreateDecoder() { - return -1; -} - -void ACMG722_1C::DestructDecoderSafe() { - return; -} - -void ACMG722_1C::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -#else //===================== Actual Implementation ======================= -ACMG722_1C::ACMG722_1C(int16_t codec_id) - : encoder_inst_ptr_(NULL), - encoder_inst_ptr_right_(NULL), - decoder_inst_ptr_(NULL), - encoder_inst24_ptr_(NULL), - encoder_inst24_ptr_right_(NULL), - encoder_inst32_ptr_(NULL), - encoder_inst32_ptr_right_(NULL), - encoder_inst48_ptr_(NULL), - encoder_inst48_ptr_right_(NULL), - decoder_inst24_ptr_(NULL), - decoder_inst32_ptr_(NULL), - decoder_inst48_ptr_(NULL) { - codec_id_ = codec_id; - if (codec_id_ == ACMCodecDB::kG722_1C_24) { - operational_rate_ = 24000; - } else if (codec_id_ == ACMCodecDB::kG722_1C_32) { - operational_rate_ = 32000; - } else if (codec_id_ == ACMCodecDB::kG722_1C_48) { - operational_rate_ = 48000; - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Wrong codec id for G722_1c."); - operational_rate_ = -1; - } - return; -} - -ACMG722_1C::~ACMG722_1C() { - if (encoder_inst_ptr_ != NULL) { - delete encoder_inst_ptr_; - encoder_inst_ptr_ = NULL; - } - if (encoder_inst_ptr_right_ != NULL) { - delete encoder_inst_ptr_right_; - encoder_inst_ptr_right_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - delete decoder_inst_ptr_; - decoder_inst_ptr_ = NULL; - } - - switch (operational_rate_) { - case 24000: { - encoder_inst24_ptr_ = NULL; - encoder_inst24_ptr_right_ = NULL; - decoder_inst24_ptr_ = NULL; - break; - } - case 32000: { - encoder_inst32_ptr_ = NULL; - encoder_inst32_ptr_right_ = NULL; - decoder_inst32_ptr_ = NULL; - break; - } - case 48000: { - encoder_inst48_ptr_ = NULL; - encoder_inst48_ptr_right_ = NULL; - decoder_inst48_ptr_ = NULL; - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Wrong rate for G722_1c."); - break; - } - } - return; -} - -int16_t ACMG722_1C::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - int16_t left_channel[640]; - int16_t right_channel[640]; - int16_t len_in_bytes; - int16_t out_bits[240]; - - // If stereo, split input signal in left and right channel before encoding - if (num_channels_ == 2) { - for (int i = 0, j = 0; i < frame_len_smpl_ * 2; i += 2, j++) { - left_channel[j] = in_audio_[in_audio_ix_read_ + i]; - right_channel[j] = in_audio_[in_audio_ix_read_ + i + 1]; - } - } else { - memcpy(left_channel, &in_audio_[in_audio_ix_read_], 640); - } - - switch (operational_rate_) { - case 24000: { - len_in_bytes = WebRtcG7221C_Encode24(encoder_inst24_ptr_, left_channel, - 640, &out_bits[0]); - if (num_channels_ == 2) { - len_in_bytes += WebRtcG7221C_Encode24(encoder_inst24_ptr_right_, - right_channel, 640, - &out_bits[len_in_bytes / 2]); - } - break; - } - case 32000: { - len_in_bytes = WebRtcG7221C_Encode32(encoder_inst32_ptr_, left_channel, - 640, &out_bits[0]); - if (num_channels_ == 2) { - len_in_bytes += WebRtcG7221C_Encode32(encoder_inst32_ptr_right_, - right_channel, 640, - &out_bits[len_in_bytes / 2]); - } - break; - } - case 48000: { - len_in_bytes = WebRtcG7221C_Encode48(encoder_inst48_ptr_, left_channel, - 640, &out_bits[0]); - if (num_channels_ == 2) { - len_in_bytes += WebRtcG7221C_Encode48(encoder_inst48_ptr_right_, - right_channel, 640, - &out_bits[len_in_bytes / 2]); - } - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalEncode: Wrong rate for G722_1c."); - return -1; - } - } - - memcpy(bitstream, out_bits, len_in_bytes); - *bitstream_len_byte = len_in_bytes; - - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += 640 * num_channels_; - - return *bitstream_len_byte; -} - -int16_t ACMG722_1C::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMG722_1C::InternalInitEncoder( - WebRtcACMCodecParams* codec_params) { - int16_t ret; - - switch (operational_rate_) { - case 24000: { - ret = WebRtcG7221C_EncoderInit24(encoder_inst24_ptr_right_); - if (ret < 0) { - return ret; - } - return WebRtcG7221C_EncoderInit24(encoder_inst24_ptr_); - } - case 32000: { - ret = WebRtcG7221C_EncoderInit32(encoder_inst32_ptr_right_); - if (ret < 0) { - return ret; - } - return WebRtcG7221C_EncoderInit32(encoder_inst32_ptr_); - } - case 48000: { - ret = WebRtcG7221C_EncoderInit48(encoder_inst48_ptr_right_); - if (ret < 0) { - return ret; - } - return WebRtcG7221C_EncoderInit48(encoder_inst48_ptr_); - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitEncode: Wrong rate for G722_1c."); - return -1; - } - } -} - -int16_t ACMG722_1C::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - switch (operational_rate_) { - case 24000: { - return WebRtcG7221C_DecoderInit24(decoder_inst24_ptr_); - } - case 32000: { - return WebRtcG7221C_DecoderInit32(decoder_inst32_ptr_); - } - case 48000: { - return WebRtcG7221C_DecoderInit48(decoder_inst48_ptr_); - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitDecoder: Wrong rate for G722_1c."); - return -1; - } - } -} - -int32_t ACMG722_1C::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodeDef: decoder not initialized for G722_1c"); - return -1; - } - // NetEq has an array of pointers to WebRtcNetEQ_CodecDef. - // get an entry of that array (neteq wrapper will allocate memory) - // by calling "netEq->CodecDef", where "NETEQ_CODEC_G722_1_XX" would - // be the index of the entry. - // Fill up the given structure by calling - // "SET_CODEC_PAR" & "SET_G722_1_XX_FUNCTION." - // Then return the structure back to NetEQ to add the codec to it's - // database. - switch (operational_rate_) { - case 24000: { - SET_CODEC_PAR((codec_def), kDecoderG722_1C_24, codec_inst.pltype, - decoder_inst24_ptr_, 32000); - SET_G722_1C_24_FUNCTIONS((codec_def)); - break; - } - case 32000: { - SET_CODEC_PAR((codec_def), kDecoderG722_1C_32, codec_inst.pltype, - decoder_inst32_ptr_, 32000); - SET_G722_1C_32_FUNCTIONS((codec_def)); - break; - } - case 48000: { - SET_CODEC_PAR((codec_def), kDecoderG722_1C_32, codec_inst.pltype, - decoder_inst48_ptr_, 32000); - SET_G722_1C_48_FUNCTIONS((codec_def)); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodeDef: Wrong rate for G722_1c."); - return -1; - } - } - return 0; -} - -ACMGenericCodec* -ACMG722_1C::CreateInstance(void) { - return NULL; -} - -int16_t ACMG722_1C::InternalCreateEncoder() { - if ((encoder_inst_ptr_ == NULL) || (encoder_inst_ptr_right_ == NULL)) { - return -1; - } - switch (operational_rate_) { - case 24000: { - WebRtcG7221C_CreateEnc24(&encoder_inst24_ptr_); - WebRtcG7221C_CreateEnc24(&encoder_inst24_ptr_right_); - break; - } - case 32000: { - WebRtcG7221C_CreateEnc32(&encoder_inst32_ptr_); - WebRtcG7221C_CreateEnc32(&encoder_inst32_ptr_right_); - break; - } - case 48000: { - WebRtcG7221C_CreateEnc48(&encoder_inst48_ptr_); - WebRtcG7221C_CreateEnc48(&encoder_inst48_ptr_right_); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: Wrong rate for G722_1c."); - return -1; - } - } - return 0; -} - -void ACMG722_1C::DestructEncoderSafe() { - encoder_exist_ = false; - encoder_initialized_ = false; - if (encoder_inst_ptr_ != NULL) { - delete encoder_inst_ptr_; - encoder_inst_ptr_ = NULL; - } - if (encoder_inst_ptr_right_ != NULL) { - delete encoder_inst_ptr_right_; - encoder_inst_ptr_right_ = NULL; - } - encoder_inst24_ptr_ = NULL; - encoder_inst32_ptr_ = NULL; - encoder_inst48_ptr_ = NULL; -} - -int16_t ACMG722_1C::InternalCreateDecoder() { - if (decoder_inst_ptr_ == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: cannot create decoder"); - return -1; - } - switch (operational_rate_) { - case 24000: { - WebRtcG7221C_CreateDec24(&decoder_inst24_ptr_); - break; - } - case 32000: { - WebRtcG7221C_CreateDec32(&decoder_inst32_ptr_); - break; - } - case 48000: { - WebRtcG7221C_CreateDec48(&decoder_inst48_ptr_); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: Wrong rate for G722_1c."); - return -1; - } - } - return 0; -} - -void ACMG722_1C::DestructDecoderSafe() { - decoder_exist_ = false; - decoder_initialized_ = false; - if (decoder_inst_ptr_ != NULL) { - delete decoder_inst_ptr_; - decoder_inst_ptr_ = NULL; - } - decoder_inst24_ptr_ = NULL; - decoder_inst32_ptr_ = NULL; - decoder_inst48_ptr_ = NULL; -} - -void ACMG722_1C::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - delete ptr_inst; - } - return; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_g7221c.h b/webrtc/modules/audio_coding/main/source/acm_g7221c.h deleted file mode 100644 index d8875aa2f..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g7221c.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7221C_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7221C_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct G722_1C_24_encinst_t_; -struct G722_1C_24_decinst_t_; -struct G722_1C_32_encinst_t_; -struct G722_1C_32_decinst_t_; -struct G722_1C_48_encinst_t_; -struct G722_1C_48_decinst_t_; -struct G722_1_Inst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMG722_1C : public ACMGenericCodec { - public: - explicit ACMG722_1C(int16_t codec_id); - ~ACMG722_1C(); - - // for FEC - ACMGenericCodec* CreateInstance(void); - - int16_t InternalEncode( - uint8_t* bitstream, - int16_t* bitstream_len_byte); - - int16_t InternalInitEncoder( - WebRtcACMCodecParams *codec_params); - - int16_t InternalInitDecoder( - WebRtcACMCodecParams *codec_params); - - protected: - int16_t DecodeSafe( - uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type); - - int32_t CodecDef( - WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst); - - void DestructEncoderSafe(); - - void DestructDecoderSafe(); - - int16_t InternalCreateEncoder(); - - int16_t InternalCreateDecoder(); - - void InternalDestructEncoderInst( - void* ptr_inst); - - int32_t operational_rate_; - - G722_1_Inst_t_* encoder_inst_ptr_; - G722_1_Inst_t_* encoder_inst_ptr_right_; // Used in stereo mode - G722_1_Inst_t_* decoder_inst_ptr_; - - // Only one set of these pointer is valid at any instance - G722_1C_24_encinst_t_* encoder_inst24_ptr_; - G722_1C_24_encinst_t_* encoder_inst24_ptr_right_; - G722_1C_32_encinst_t_* encoder_inst32_ptr_; - G722_1C_32_encinst_t_* encoder_inst32_ptr_right_; - G722_1C_48_encinst_t_* encoder_inst48_ptr_; - G722_1C_48_encinst_t_* encoder_inst48_ptr_right_; - - // Only one of these pointer is valid at any instance - G722_1C_24_decinst_t_* decoder_inst24_ptr_; - G722_1C_32_decinst_t_* decoder_inst32_ptr_; - G722_1C_48_decinst_t_* decoder_inst48_ptr_; -}; - -} // namespace acm1 - -} // namespace webrtc; - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7221C_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_g729.cc b/webrtc/modules/audio_coding/main/source/acm_g729.cc deleted file mode 100644 index 406bb61e4..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g729.cc +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_g729.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_G729 -// NOTE! G.729 is not included in the open-source package. Modify this file -// or your codec API to match the function calls and names of used G.729 API -// file. -#include "g729_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_G729 - -ACMG729::ACMG729(int16_t /* codec_id */) -: encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL) { - return; -} - -ACMG729::~ACMG729() { - return; -} - -int16_t ACMG729::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMG729::EnableDTX() { - return -1; -} - -int16_t ACMG729::DisableDTX() { - return -1; -} - -int32_t ACMG729::ReplaceInternalDTXSafe( - const bool /*replace_internal_dtx */) { - return -1; -} - -int32_t ACMG729::IsInternalDTXReplacedSafe( - bool* /* internal_dtx_replaced */) { - return -1; -} - -int16_t ACMG729::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMG729::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMG729::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMG729::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMG729::CreateInstance(void) { - return NULL; -} - -int16_t ACMG729::InternalCreateEncoder() { - return -1; -} - -void ACMG729::DestructEncoderSafe() { - return; -} - -int16_t ACMG729::InternalCreateDecoder() { - return -1; -} - -void ACMG729::DestructDecoderSafe() { - return; -} - -void ACMG729::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -#else //===================== Actual Implementation ======================= -ACMG729::ACMG729(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL) { - codec_id_ = codec_id; - has_internal_dtx_ = true; - return; -} - -ACMG729::~ACMG729() { - if (encoder_inst_ptr_ != NULL) { - // Delete encoder memory - WebRtcG729_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - // Delete decoder memory - WebRtcG729_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMG729::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - // Initialize before entering the loop - int16_t num_encoded_samples = 0; - int16_t tmp_len_byte = 0; - int16_t vad_decision = 0; - *bitstream_len_byte = 0; - while (num_encoded_samples < frame_len_smpl_) { - // Call G.729 encoder with pointer to encoder memory, input - // audio, number of samples and bitsream - tmp_len_byte = WebRtcG729_Encode( - encoder_inst_ptr_, &in_audio_[in_audio_ix_read_], 80, - (int16_t*)(&(bitstream[*bitstream_len_byte]))); - - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += 80; - - // sanity check - if (tmp_len_byte < 0) { - // error has happened - *bitstream_len_byte = 0; - return -1; - } - - // increment number of written bytes - *bitstream_len_byte += tmp_len_byte; - switch (tmp_len_byte) { - case 0: { - if (0 == num_encoded_samples) { - // this is the first 10 ms in this packet and there is - // no data generated, perhaps DTX is enabled and the - // codec is not generating any bit-stream for this 10 ms. - // we do not continue encoding this frame. - return 0; - } - break; - } - case 2: { - // check if G.729 internal DTX is enabled - if (has_internal_dtx_ && dtx_enabled_) { - vad_decision = 0; - for (int16_t n = 0; n < MAX_FRAME_SIZE_10MSEC; n++) { - vad_label_[n] = vad_decision; - } - } - // we got a SID and have to send out this packet no matter - // how much audio we have encoded - return *bitstream_len_byte; - } - case 10: { - vad_decision = 1; - // this is a valid length just continue encoding - break; - } - default: { - return -1; - } - } - - // update number of encoded samples - num_encoded_samples += 80; - } - - // update VAD decision vector - if (has_internal_dtx_ && !vad_decision && dtx_enabled_) { - for (int16_t n = 0; n < MAX_FRAME_SIZE_10MSEC; n++) { - vad_label_[n] = vad_decision; - } - } - - // done encoding, return number of encoded bytes - return *bitstream_len_byte; -} - -int16_t ACMG729::EnableDTX() { - if (dtx_enabled_) { - // DTX already enabled, do nothing - return 0; - } else if (encoder_exist_) { - // Re-init the G.729 encoder to turn on DTX - if (WebRtcG729_EncoderInit(encoder_inst_ptr_, 1) < 0) { - return -1; - } - dtx_enabled_ = true; - return 0; - } else { - return -1; - } -} - -int16_t ACMG729::DisableDTX() { - if (!dtx_enabled_) { - // DTX already dissabled, do nothing - return 0; - } else if (encoder_exist_) { - // Re-init the G.729 decoder to turn off DTX - if (WebRtcG729_EncoderInit(encoder_inst_ptr_, 0) < 0) { - return -1; - } - dtx_enabled_ = false; - return 0; - } else { - // encoder doesn't exists, therefore disabling is harmless - return 0; - } -} - -int32_t ACMG729::ReplaceInternalDTXSafe(const bool replace_internal_dtx) { - // This function is used to disable the G.729 built in DTX and use an - // external instead. - - if (replace_internal_dtx == has_internal_dtx_) { - // Make sure we keep the DTX/VAD setting if possible - bool old_enable_dtx = dtx_enabled_; - bool old_enable_vad = vad_enabled_; - ACMVADMode old_mode = vad_mode_; - if (replace_internal_dtx) { - // Disable internal DTX before enabling external DTX - DisableDTX(); - } else { - // Disable external DTX before enabling internal - ACMGenericCodec::DisableDTX(); - } - has_internal_dtx_ = !replace_internal_dtx; - int16_t status = SetVADSafe(old_enable_dtx, old_enable_vad, old_mode); - // Check if VAD status has changed from inactive to active, or if error was - // reported - if (status == 1) { - vad_enabled_ = true; - return status; - } else if (status < 0) { - has_internal_dtx_ = replace_internal_dtx; - return -1; - } - } - return 0; -} - -int32_t ACMG729::IsInternalDTXReplacedSafe(bool* internal_dtx_replaced) { - // Get status of wether DTX is replaced or not - *internal_dtx_replaced = !has_internal_dtx_; - return 0; -} - -int16_t ACMG729::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - // This function is not used. G.729 decoder is called from inside NetEQ - return 0; -} - -int16_t ACMG729::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { - // Init G.729 encoder - return WebRtcG729_EncoderInit(encoder_inst_ptr_, - ((codec_params->enable_dtx) ? 1 : 0)); -} - -int16_t ACMG729::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - // Init G.729 decoder - return WebRtcG729_DecoderInit(decoder_inst_ptr_); -} - -int32_t ACMG729::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - // Todo: - // log error - return -1; - } - - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_G729_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderG729, codec_inst.pltype, decoder_inst_ptr_, - 8000); - SET_G729_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMG729::CreateInstance(void) { - // Function not used - return NULL; -} - -int16_t ACMG729::InternalCreateEncoder() { - // Create encoder memory - return WebRtcG729_CreateEnc(&encoder_inst_ptr_); -} - -void ACMG729::DestructEncoderSafe() { - // Free encoder memory - encoder_exist_ = false; - encoder_initialized_ = false; - if (encoder_inst_ptr_ != NULL) { - WebRtcG729_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } -} - -int16_t ACMG729::InternalCreateDecoder() { - // Create decoder memory - return WebRtcG729_CreateDec(&decoder_inst_ptr_); -} - -void ACMG729::DestructDecoderSafe() { - // Free decoder memory - decoder_exist_ = false; - decoder_initialized_ = false; - if (decoder_inst_ptr_ != NULL) { - WebRtcG729_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } -} - -void ACMG729::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcG729_FreeEnc((G729_encinst_t_*) ptr_inst); - } - return; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_g729.h b/webrtc/modules/audio_coding/main/source/acm_g729.h deleted file mode 100644 index 5cfff63b6..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g729.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G729_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G729_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct G729_encinst_t_; -struct G729_decinst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMG729 : public ACMGenericCodec { - public: - explicit ACMG729(int16_t codec_id); - ~ACMG729(); - - // for FEC - ACMGenericCodec* CreateInstance(void); - - int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte); - - int16_t InternalInitEncoder(WebRtcACMCodecParams *codec_params); - - int16_t InternalInitDecoder(WebRtcACMCodecParams *codec_params); - - protected: - int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type); - - int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst); - - void DestructEncoderSafe(); - - void DestructDecoderSafe(); - - int16_t InternalCreateEncoder(); - - int16_t InternalCreateDecoder(); - - void InternalDestructEncoderInst(void* ptr_inst); - - int16_t EnableDTX(); - - int16_t DisableDTX(); - - int32_t ReplaceInternalDTXSafe(const bool replace_internal_dtx); - - int32_t IsInternalDTXReplacedSafe(bool* internal_dtx_replaced); - - G729_encinst_t_* encoder_inst_ptr_; - G729_decinst_t_* decoder_inst_ptr_; - -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G729_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_g7291.cc b/webrtc/modules/audio_coding/main/source/acm_g7291.cc deleted file mode 100644 index 0da6c99d2..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g7291.cc +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_g7291.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" -#ifdef WEBRTC_CODEC_G729_1 -// NOTE! G.729.1 is not included in the open-source package. Modify this file -// or your codec API to match the function calls and names of used G.729.1 API -// file. -#include "g7291_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_G729_1 - -ACMG729_1::ACMG729_1(int16_t /* codec_id */) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - my_rate_(32000), - flag_8khz_(0), - flag_g729_mode_(0) { - return; -} - -ACMG729_1::~ACMG729_1() { - return; -} - -int16_t ACMG729_1::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMG729_1::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMG729_1::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMG729_1::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMG729_1::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMG729_1::CreateInstance(void) { - return NULL; -} - -int16_t ACMG729_1::InternalCreateEncoder() { - return -1; -} - -void ACMG729_1::DestructEncoderSafe() { - return; -} - -int16_t ACMG729_1::InternalCreateDecoder() { - return -1; -} - -void ACMG729_1::DestructDecoderSafe() { - return; -} - -void ACMG729_1::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -int16_t ACMG729_1::SetBitRateSafe(const int32_t /*rate*/) { - return -1; -} - -#else //===================== Actual Implementation ======================= - -struct G729_1_inst_t_; - -ACMG729_1::ACMG729_1(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - my_rate_(32000), // Default rate. - flag_8khz_(0), - flag_g729_mode_(0) { - // TODO(tlegrand): We should add codec_id as a input variable to the - // constructor of ACMGenericCodec. - codec_id_ = codec_id; - return; -} - -ACMG729_1::~ACMG729_1() { - if (encoder_inst_ptr_ != NULL) { - WebRtcG7291_Free(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcG7291_Free(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMG729_1::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - - // Initialize before entering the loop - int16_t num_encoded_samples = 0; - *bitstream_len_byte = 0; - - int16_t byte_length_frame = 0; - - // Derive number of 20ms frames per encoded packet. - // [1,2,3] <=> [20,40,60]ms <=> [320,640,960] samples - int16_t num_20ms_frames = (frame_len_smpl_ / 320); - // Byte length for the frame. +1 is for rate information. - byte_length_frame = my_rate_ / (8 * 50) * num_20ms_frames + (1 - - flag_g729_mode_); - - // The following might be revised if we have G729.1 Annex C (support for DTX); - do { - *bitstream_len_byte = WebRtcG7291_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], - (int16_t*) bitstream, - my_rate_, num_20ms_frames); - - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += 160; - - // sanity check - if (*bitstream_len_byte < 0) { - // error has happened - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalEncode: Encode error for G729_1"); - *bitstream_len_byte = 0; - return -1; - } - - num_encoded_samples += 160; - } while (*bitstream_len_byte == 0); - - // This criteria will change if we have Annex C. - if (*bitstream_len_byte != byte_length_frame) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalEncode: Encode error for G729_1"); - *bitstream_len_byte = 0; - return -1; - } - - if (num_encoded_samples != frame_len_smpl_) { - *bitstream_len_byte = 0; - return -1; - } - - return *bitstream_len_byte; -} - -int16_t ACMG729_1::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMG729_1::InternalInitEncoder( - WebRtcACMCodecParams* codec_params) { - //set the bit rate and initialize - my_rate_ = codec_params->codec_inst.rate; - return SetBitRateSafe((uint32_t) my_rate_); -} - -int16_t ACMG729_1::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - if (WebRtcG7291_DecoderInit(decoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitDecoder: init decoder failed for G729_1"); - return -1; - } - return 0; -} - -int32_t ACMG729_1::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodeDef: Decoder uninitialized for G729_1"); - return -1; - } - - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_G729_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderG729_1, codec_inst.pltype, - decoder_inst_ptr_, 16000); - SET_G729_1_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMG729_1::CreateInstance(void) { - return NULL; -} - -int16_t ACMG729_1::InternalCreateEncoder() { - if (WebRtcG7291_Create(&encoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: create encoder failed for G729_1"); - return -1; - } - return 0; -} - -void ACMG729_1::DestructEncoderSafe() { - encoder_exist_ = false; - encoder_initialized_ = false; - if (encoder_inst_ptr_ != NULL) { - WebRtcG7291_Free(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } -} - -int16_t ACMG729_1::InternalCreateDecoder() { - if (WebRtcG7291_Create(&decoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateDecoder: create decoder failed for G729_1"); - return -1; - } - return 0; -} - -void ACMG729_1::DestructDecoderSafe() { - decoder_exist_ = false; - decoder_initialized_ = false; - if (decoder_inst_ptr_ != NULL) { - WebRtcG7291_Free(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } -} - -void ACMG729_1::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - // WebRtcG7291_Free((G729_1_inst_t*)ptrInst); - } - return; -} - -int16_t ACMG729_1::SetBitRateSafe(const int32_t rate) { - // allowed rates: { 8000, 12000, 14000, 16000, 18000, 20000, - // 22000, 24000, 26000, 28000, 30000, 32000}; - // TODO(tlegrand): This check exists in one other place two. Should be - // possible to reuse code. - switch (rate) { - case 8000: { - my_rate_ = 8000; - break; - } - case 12000: { - my_rate_ = 12000; - break; - } - case 14000: { - my_rate_ = 14000; - break; - } - case 16000: { - my_rate_ = 16000; - break; - } - case 18000: { - my_rate_ = 18000; - break; - } - case 20000: { - my_rate_ = 20000; - break; - } - case 22000: { - my_rate_ = 22000; - break; - } - case 24000: { - my_rate_ = 24000; - break; - } - case 26000: { - my_rate_ = 26000; - break; - } - case 28000: { - my_rate_ = 28000; - break; - } - case 30000: { - my_rate_ = 30000; - break; - } - case 32000: { - my_rate_ = 32000; - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetBitRateSafe: Invalid rate G729_1"); - return -1; - } - } - - // Re-init with new rate - if (WebRtcG7291_EncoderInit(encoder_inst_ptr_, my_rate_, flag_8khz_, - flag_g729_mode_) >= 0) { - encoder_params_.codec_inst.rate = my_rate_; - return 0; - } else { - return -1; - } -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_g7291.h b/webrtc/modules/audio_coding/main/source/acm_g7291.h deleted file mode 100644 index bac7faf83..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_g7291.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7291_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7291_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct G729_1_inst_t_; -struct G729_1_inst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMG729_1 : public ACMGenericCodec { - public: - explicit ACMG729_1(int16_t codec_id); - ~ACMG729_1(); - - // for FEC - ACMGenericCodec* CreateInstance(void); - - int16_t InternalEncode(uint8_t* bitstream, int16_t* bitstream_len_byte); - - int16_t InternalInitEncoder(WebRtcACMCodecParams *codec_params); - - int16_t InternalInitDecoder(WebRtcACMCodecParams *codec_params); - - protected: - int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type); - - int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst); - - void DestructEncoderSafe(); - - void DestructDecoderSafe(); - - int16_t InternalCreateEncoder(); - - int16_t InternalCreateDecoder(); - - void InternalDestructEncoderInst(void* ptr_inst); - - int16_t SetBitRateSafe(const int32_t rate); - - G729_1_inst_t_* encoder_inst_ptr_; - G729_1_inst_t_* decoder_inst_ptr_; - - uint16_t my_rate_; - int16_t flag_8khz_; - int16_t flag_g729_mode_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_G7291_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_generic_codec.cc b/webrtc/modules/audio_coding/main/source/acm_generic_codec.cc deleted file mode 100644 index 4e53b873a..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_generic_codec.cc +++ /dev/null @@ -1,1263 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -#include -#include - -#include "webrtc/common_audio/vad/include/webrtc_vad.h" -#include "webrtc/modules/audio_coding/codecs/cng/include/webrtc_cng.h" -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/system_wrappers/interface/trace.h" - -namespace webrtc { - -namespace acm1 { - -// Enum for CNG -enum { - kMaxPLCParamsCNG = WEBRTC_CNG_MAX_LPC_ORDER, - kNewCNGNumPLCParams = 8 -}; - -// Interval for sending new CNG parameters (SID frames) is 100 msec. -enum { - kCngSidIntervalMsec = 100 -}; - -// We set some of the variables to invalid values as a check point -// if a proper initialization has happened. Another approach is -// to initialize to a default codec that we are sure is always included. -ACMGenericCodec::ACMGenericCodec() - : in_audio_ix_write_(0), - in_audio_ix_read_(0), - in_timestamp_ix_write_(0), - in_audio_(NULL), - in_timestamp_(NULL), - frame_len_smpl_(-1), // invalid value - num_channels_(1), - codec_id_(-1), // invalid value - num_missed_samples_(0), - encoder_exist_(false), - decoder_exist_(false), - encoder_initialized_(false), - decoder_initialized_(false), - registered_in_neteq_(false), - has_internal_dtx_(false), - ptr_vad_inst_(NULL), - vad_enabled_(false), - vad_mode_(VADNormal), - dtx_enabled_(false), - ptr_dtx_inst_(NULL), - num_lpc_params_(kNewCNGNumPLCParams), - sent_cn_previous_(false), - is_master_(true), - prev_frame_cng_(0), - neteq_decode_lock_(NULL), - codec_wrapper_lock_(*RWLockWrapper::CreateRWLock()), - last_encoded_timestamp_(0), - last_timestamp_(0xD87F3F9F), - is_audio_buff_fresh_(true), - unique_id_(0) { - // Initialize VAD vector. - for (int i = 0; i < MAX_FRAME_SIZE_10MSEC; i++) { - vad_label_[i] = 0; - } - // Nullify memory for encoder and decoder, and set payload type to an - // invalid value. - memset(&encoder_params_, 0, sizeof(WebRtcACMCodecParams)); - encoder_params_.codec_inst.pltype = -1; - memset(&decoder_params_, 0, sizeof(WebRtcACMCodecParams)); - decoder_params_.codec_inst.pltype = -1; -} - -ACMGenericCodec::~ACMGenericCodec() { - // Check all the members which are pointers, and if they are not NULL - // delete/free them. - if (ptr_vad_inst_ != NULL) { - WebRtcVad_Free(ptr_vad_inst_); - ptr_vad_inst_ = NULL; - } - if (in_audio_ != NULL) { - delete[] in_audio_; - in_audio_ = NULL; - } - if (in_timestamp_ != NULL) { - delete[] in_timestamp_; - in_timestamp_ = NULL; - } - if (ptr_dtx_inst_ != NULL) { - WebRtcCng_FreeEnc(ptr_dtx_inst_); - ptr_dtx_inst_ = NULL; - } - delete &codec_wrapper_lock_; -} - -int32_t ACMGenericCodec::Add10MsData(const uint32_t timestamp, - const int16_t* data, - const uint16_t length_smpl, - const uint8_t audio_channel) { - WriteLockScoped wl(codec_wrapper_lock_); - return Add10MsDataSafe(timestamp, data, length_smpl, audio_channel); -} - -int32_t ACMGenericCodec::Add10MsDataSafe(const uint32_t timestamp, - const int16_t* data, - const uint16_t length_smpl, - const uint8_t audio_channel) { - // The codec expects to get data in correct sampling rate. Get the sampling - // frequency of the codec. - uint16_t plfreq_hz; - if (EncoderSampFreq(plfreq_hz) < 0) { - return -1; - } - - // Sanity check to make sure the length of the input corresponds to 10 ms. - if ((plfreq_hz / 100) != length_smpl) { - // This is not 10 ms of audio, given the sampling frequency of the codec. - return -1; - } - - if (last_timestamp_ == timestamp) { - // Same timestamp as the last time, overwrite. - if ((in_audio_ix_write_ >= length_smpl * audio_channel) && - (in_timestamp_ix_write_ > 0)) { - in_audio_ix_write_ -= length_smpl * audio_channel; - in_timestamp_ix_write_--; - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, unique_id_, - "Adding 10ms with previous timestamp, overwriting the " - "previous 10ms"); - } else { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, unique_id_, - "Adding 10ms with previous timestamp, this will sound bad"); - } - } - - last_timestamp_ = timestamp; - - // If the data exceeds the buffer size, we throw away the oldest data and - // add the newly received 10 msec at the end. - if ((in_audio_ix_write_ + length_smpl * audio_channel) > - AUDIO_BUFFER_SIZE_W16) { - // Get the number of samples to be overwritten. - int16_t missed_samples = in_audio_ix_write_ + length_smpl * audio_channel - - AUDIO_BUFFER_SIZE_W16; - - // Move the data (overwrite the old data). - memmove(in_audio_, in_audio_ + missed_samples, - (AUDIO_BUFFER_SIZE_W16 - length_smpl * audio_channel) * - sizeof(int16_t)); - - // Copy the new data. - memcpy(in_audio_ + (AUDIO_BUFFER_SIZE_W16 - length_smpl * audio_channel), - data, length_smpl * audio_channel * sizeof(int16_t)); - - // Get the number of 10 ms blocks which are overwritten. - int16_t missed_10ms_blocks =static_cast( - (missed_samples / audio_channel * 100) / plfreq_hz); - - // Move the timestamps. - memmove(in_timestamp_, in_timestamp_ + missed_10ms_blocks, - (in_timestamp_ix_write_ - missed_10ms_blocks) * sizeof(uint32_t)); - in_timestamp_ix_write_ -= missed_10ms_blocks; - assert(in_timestamp_ix_write_ >= 0); - in_timestamp_[in_timestamp_ix_write_] = timestamp; - in_timestamp_ix_write_++; - - // Buffer is full. - in_audio_ix_write_ = AUDIO_BUFFER_SIZE_W16; - IncreaseNoMissedSamples(missed_samples); - is_audio_buff_fresh_ = false; - return -missed_samples; - } - - // Store the input data in our data buffer. - memcpy(in_audio_ + in_audio_ix_write_, data, - length_smpl * audio_channel * sizeof(int16_t)); - in_audio_ix_write_ += length_smpl * audio_channel; - - assert(in_timestamp_ix_write_ < TIMESTAMP_BUFFER_SIZE_W32); - assert(in_timestamp_ix_write_ >= 0); - - in_timestamp_[in_timestamp_ix_write_] = timestamp; - in_timestamp_ix_write_++; - is_audio_buff_fresh_ = false; - return 0; -} - -bool ACMGenericCodec::HasFrameToEncode() const { - ReadLockScoped lockCodec(codec_wrapper_lock_); - if (in_audio_ix_write_ < frame_len_smpl_ * num_channels_) - return false; - return true; -} - -int16_t ACMGenericCodec::Encode(uint8_t* bitstream, - int16_t* bitstream_len_byte, - uint32_t* timestamp, - WebRtcACMEncodingType* encoding_type) { - if (!HasFrameToEncode()) { - // There is not enough audio - *timestamp = 0; - *bitstream_len_byte = 0; - // Doesn't really matter what this parameter set to - *encoding_type = kNoEncoding; - return 0; - } - WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); - - // Not all codecs accept the whole frame to be pushed into encoder at once. - // Some codecs needs to be feed with a specific number of samples different - // from the frame size. If this is the case, |myBasicCodingBlockSmpl| will - // report a number different from 0, and we will loop over calls to encoder - // further down, until we have encode a complete frame. - const int16_t my_basic_coding_block_smpl = - ACMCodecDB::BasicCodingBlock(codec_id_); - if (my_basic_coding_block_smpl < 0 || !encoder_initialized_ || - !encoder_exist_) { - // This should not happen, but in case it does, report no encoding done. - *timestamp = 0; - *bitstream_len_byte = 0; - *encoding_type = kNoEncoding; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EncodeSafe: error, basic coding sample block is negative"); - return -1; - } - // This makes the internal encoder read from the beginning of the buffer. - in_audio_ix_read_ = 0; - *timestamp = in_timestamp_[0]; - - // Process the audio through VAD. The function will set |_vad_labels|. - // If VAD is disabled all entries in |_vad_labels| are set to ONE (active). - int16_t status = 0; - int16_t dtx_processed_samples = 0; - status = ProcessFrameVADDTX(bitstream, bitstream_len_byte, - &dtx_processed_samples); - if (status < 0) { - *timestamp = 0; - *bitstream_len_byte = 0; - *encoding_type = kNoEncoding; - } else { - if (dtx_processed_samples > 0) { - // Dtx have processed some samples, and even if a bit-stream is generated - // we should not do any encoding (normally there won't be enough data). - - // Setting the following makes sure that the move of audio data and - // timestamps done correctly. - in_audio_ix_read_ = dtx_processed_samples; - // This will let the owner of ACMGenericCodec to know that the - // generated bit-stream is DTX to use correct payload type. - uint16_t samp_freq_hz; - EncoderSampFreq(samp_freq_hz); - if (samp_freq_hz == 8000) { - *encoding_type = kPassiveDTXNB; - } else if (samp_freq_hz == 16000) { - *encoding_type = kPassiveDTXWB; - } else if (samp_freq_hz == 32000) { - *encoding_type = kPassiveDTXSWB; - } else if (samp_freq_hz == 48000) { - *encoding_type = kPassiveDTXFB; - } else { - status = -1; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EncodeSafe: Wrong sampling frequency for DTX."); - } - - // Transport empty frame if we have an empty bitstream. - if ((*bitstream_len_byte == 0) && - (sent_cn_previous_ || - ((in_audio_ix_write_ - in_audio_ix_read_) <= 0))) { - // Makes sure we transmit an empty frame. - *bitstream_len_byte = 1; - *encoding_type = kNoEncoding; - } - sent_cn_previous_ = true; - } else { - // We should encode the audio frame. Either VAD and/or DTX is off, or the - // audio was considered "active". - - sent_cn_previous_ = false; - if (my_basic_coding_block_smpl == 0) { - // This codec can handle all allowed frame sizes as basic coding block. - status = InternalEncode(bitstream, bitstream_len_byte); - if (status < 0) { - // TODO(tlegrand): Maybe reseting the encoder to be fresh for the next - // frame. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, - unique_id_, "EncodeSafe: error in internal_encode"); - *bitstream_len_byte = 0; - *encoding_type = kNoEncoding; - } - } else { - // A basic-coding-block for this codec is defined so we loop over the - // audio with the steps of the basic-coding-block. - int16_t tmp_bitstream_len_byte; - - // Reset the variables which will be incremented in the loop. - *bitstream_len_byte = 0; - do { - status = InternalEncode(&bitstream[*bitstream_len_byte], - &tmp_bitstream_len_byte); - *bitstream_len_byte += tmp_bitstream_len_byte; - - // Guard Against errors and too large payloads. - if ((status < 0) || (*bitstream_len_byte > MAX_PAYLOAD_SIZE_BYTE)) { - // Error has happened, and even if we are in the middle of a full - // frame we have to exit. Before exiting, whatever bits are in the - // buffer are probably corrupted, so we ignore them. - *bitstream_len_byte = 0; - *encoding_type = kNoEncoding; - // We might have come here because of the second condition. - status = -1; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, - unique_id_, "EncodeSafe: error in InternalEncode"); - // break from the loop - break; - } - } while (in_audio_ix_read_ < frame_len_smpl_ * num_channels_); - } - if (status >= 0) { - *encoding_type = (vad_label_[0] == 1) ? kActiveNormalEncoded : - kPassiveNormalEncoded; - // Transport empty frame if we have an empty bitstream. - if ((*bitstream_len_byte == 0) && - ((in_audio_ix_write_ - in_audio_ix_read_) <= 0)) { - // Makes sure we transmit an empty frame. - *bitstream_len_byte = 1; - *encoding_type = kNoEncoding; - } - } - } - } - - // Move the timestamp buffer according to the number of 10 ms blocks - // which are read. - uint16_t samp_freq_hz; - EncoderSampFreq(samp_freq_hz); - int16_t num_10ms_blocks = static_cast( - (in_audio_ix_read_ / num_channels_ * 100) / samp_freq_hz); - if (in_timestamp_ix_write_ > num_10ms_blocks) { - memmove(in_timestamp_, in_timestamp_ + num_10ms_blocks, - (in_timestamp_ix_write_ - num_10ms_blocks) * sizeof(int32_t)); - } - in_timestamp_ix_write_ -= num_10ms_blocks; - assert(in_timestamp_ix_write_ >= 0); - // Remove encoded audio and move next audio to be encoded to the beginning - // of the buffer. Accordingly, adjust the read and write indices. - if (in_audio_ix_read_ < in_audio_ix_write_) { - memmove(in_audio_, &in_audio_[in_audio_ix_read_], - (in_audio_ix_write_ - in_audio_ix_read_) * sizeof(int16_t)); - } - in_audio_ix_write_ -= in_audio_ix_read_; - assert(in_timestamp_ix_write_ >= 0); - in_audio_ix_read_ = 0; - last_encoded_timestamp_ = *timestamp; - return (status < 0) ? (-1) : (*bitstream_len_byte); -} - -int16_t ACMGenericCodec::Decode(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) { - WriteLockScoped wl(codec_wrapper_lock_); - return DecodeSafe(bitstream, bitstream_len_byte, audio, audio_samples, - speech_type); -} - -bool ACMGenericCodec::EncoderInitialized() { - ReadLockScoped rl(codec_wrapper_lock_); - return encoder_initialized_; -} - -bool ACMGenericCodec::DecoderInitialized() { - ReadLockScoped rl(codec_wrapper_lock_); - return decoder_initialized_; -} - -int32_t ACMGenericCodec::RegisterInNetEq(ACMNetEQ* neteq, - const CodecInst& codec_inst) { - WebRtcNetEQ_CodecDef codec_def; - WriteLockScoped wl(codec_wrapper_lock_); - - if (CodecDef(codec_def, codec_inst) < 0) { - // Failed to register the decoder. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "RegisterInNetEq: error, failed to register"); - registered_in_neteq_ = false; - return -1; - } else { - if (neteq->AddCodec(&codec_def, is_master_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "RegisterInNetEq: error, failed to add codec"); - registered_in_neteq_ = false; - return -1; - } - // Succeeded registering the decoder. - registered_in_neteq_ = true; - return 0; - } -} - -int16_t ACMGenericCodec::EncoderParams(WebRtcACMCodecParams* enc_params) { - ReadLockScoped rl(codec_wrapper_lock_); - return EncoderParamsSafe(enc_params); -} - -int16_t ACMGenericCodec::EncoderParamsSafe(WebRtcACMCodecParams* enc_params) { - // Codec parameters are valid only if the encoder is initialized. - if (encoder_initialized_) { - int32_t current_rate; - memcpy(enc_params, &encoder_params_, sizeof(WebRtcACMCodecParams)); - current_rate = enc_params->codec_inst.rate; - CurrentRate(current_rate); - enc_params->codec_inst.rate = current_rate; - return 0; - } else { - enc_params->codec_inst.plname[0] = '\0'; - enc_params->codec_inst.pltype = -1; - enc_params->codec_inst.pacsize = 0; - enc_params->codec_inst.rate = 0; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EncoderParamsSafe: error, encoder not initialized"); - return -1; - } -} - -bool ACMGenericCodec::DecoderParams(WebRtcACMCodecParams* dec_params, - const uint8_t payload_type) { - ReadLockScoped rl(codec_wrapper_lock_); - return DecoderParamsSafe(dec_params, payload_type); -} - -bool ACMGenericCodec::DecoderParamsSafe(WebRtcACMCodecParams* dec_params, - const uint8_t payload_type) { - // Decoder parameters are valid only if decoder is initialized. - if (decoder_initialized_) { - if (payload_type == decoder_params_.codec_inst.pltype) { - memcpy(dec_params, &decoder_params_, sizeof(WebRtcACMCodecParams)); - return true; - } - } - - dec_params->codec_inst.plname[0] = '\0'; - dec_params->codec_inst.pltype = -1; - dec_params->codec_inst.pacsize = 0; - dec_params->codec_inst.rate = 0; - return false; -} - -int16_t ACMGenericCodec::ResetEncoder() { - WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); - return ResetEncoderSafe(); -} - -int16_t ACMGenericCodec::ResetEncoderSafe() { - if (!encoder_exist_ || !encoder_initialized_) { - // We don't reset if encoder doesn't exists or isn't initialized yet. - return 0; - } - - in_audio_ix_write_ = 0; - in_audio_ix_read_ = 0; - in_timestamp_ix_write_ = 0; - num_missed_samples_ = 0; - is_audio_buff_fresh_ = true; - memset(in_audio_, 0, AUDIO_BUFFER_SIZE_W16 * sizeof(int16_t)); - memset(in_timestamp_, 0, TIMESTAMP_BUFFER_SIZE_W32 * sizeof(int32_t)); - - // Store DTX/VAD parameters. - bool enable_vad = vad_enabled_; - bool enable_dtx = dtx_enabled_; - ACMVADMode mode = vad_mode_; - - // Reset the encoder. - if (InternalResetEncoder() < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "ResetEncoderSafe: error in reset encoder"); - return -1; - } - - // Disable DTX & VAD to delete the states and have a fresh start. - DisableDTX(); - DisableVAD(); - - // Set DTX/VAD. - int status = SetVADSafe(&enable_dtx, &enable_vad, &mode); - dtx_enabled_ = enable_dtx; - vad_enabled_ = enable_vad; - vad_mode_ = mode; - return status; -} - -int16_t ACMGenericCodec::InternalResetEncoder() { - // Call the codecs internal encoder initialization/reset function. - return InternalInitEncoder(&encoder_params_); -} - -int16_t ACMGenericCodec::InitEncoder(WebRtcACMCodecParams* codec_params, - bool force_initialization) { - WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); - return InitEncoderSafe(codec_params, force_initialization); -} - -int16_t ACMGenericCodec::InitEncoderSafe(WebRtcACMCodecParams* codec_params, - bool force_initialization) { - // Check if we got a valid set of parameters. - int mirrorID; - int codec_number = ACMCodecDB::CodecNumber(&(codec_params->codec_inst), - &mirrorID); - if (codec_number < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitEncoderSafe: error, codec number negative"); - return -1; - } - // Check if the parameters are for this codec. - if ((codec_id_ >= 0) && (codec_id_ != codec_number) && - (codec_id_ != mirrorID)) { - // The current codec is not the same as the one given by codec_params. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitEncoderSafe: current codec is not the same as the one " - "given by codec_params"); - return -1; - } - - if (!CanChangeEncodingParam(codec_params->codec_inst)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitEncoderSafe: cannot change encoding parameters"); - return -1; - } - - if (encoder_initialized_ && !force_initialization) { - // The encoder is already initialized, and we don't want to force - // initialization. - return 0; - } - int16_t status; - if (!encoder_exist_) { - // New encoder, start with creating. - encoder_initialized_ = false; - status = CreateEncoder(); - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitEncoderSafe: cannot create encoder"); - return -1; - } else { - encoder_exist_ = true; - } - } - frame_len_smpl_ = (codec_params->codec_inst).pacsize; - num_channels_ = codec_params->codec_inst.channels; - status = InternalInitEncoder(codec_params); - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitEncoderSafe: error in init encoder"); - encoder_initialized_ = false; - return -1; - } else { - // Store encoder parameters. - memcpy(&encoder_params_, codec_params, sizeof(WebRtcACMCodecParams)); - encoder_initialized_ = true; - if (in_audio_ == NULL) { - in_audio_ = new int16_t[AUDIO_BUFFER_SIZE_W16]; - if (in_audio_ == NULL) { - return -1; - } - } - if (in_timestamp_ == NULL) { - in_timestamp_ = new uint32_t[TIMESTAMP_BUFFER_SIZE_W32]; - if (in_timestamp_ == NULL) { - return -1; - } - } - // Fresh start for audio buffer. - is_audio_buff_fresh_ = true; - memset(in_audio_, 0, AUDIO_BUFFER_SIZE_W16 * sizeof(int16_t)); - memset(in_timestamp_, 0, sizeof(uint32_t) * TIMESTAMP_BUFFER_SIZE_W32); - in_audio_ix_write_ = 0; - in_audio_ix_read_ = 0; - in_timestamp_ix_write_ = 0; - } - status = SetVADSafe(&codec_params->enable_dtx, &codec_params->enable_vad, - &codec_params->vad_mode); - return status; -} - -// TODO(tlegrand): Remove the function CanChangeEncodingParam. Returns true -// for all codecs. -bool ACMGenericCodec::CanChangeEncodingParam(CodecInst& /*codec_inst*/) { - return true; -} - -void ACMGenericCodec::CurrentRate(int32_t& /* rate_bps */) { - return; -} - -int16_t ACMGenericCodec::InitDecoder(WebRtcACMCodecParams* codec_params, - bool force_initialization) { - WriteLockScoped lockCodc(codec_wrapper_lock_); - WriteLockScoped lockNetEq(*neteq_decode_lock_); - return InitDecoderSafe(codec_params, force_initialization); -} - -int16_t ACMGenericCodec::InitDecoderSafe(WebRtcACMCodecParams* codec_params, - bool force_initialization) { - int mirror_id; - // Check if we got a valid set of parameters. - int codec_number = ACMCodecDB::ReceiverCodecNumber(&codec_params->codec_inst, - &mirror_id); - if (codec_number < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitDecoderSafe: error, invalid codec number"); - return -1; - } - // Check if the parameters are for this codec. - if ((codec_id_ >= 0) && (codec_id_ != codec_number) && - (codec_id_ != mirror_id)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitDecoderSafe: current codec is not the same as the one " - "given by codec_params"); - // The current codec is not the same as the one given by codec_params. - return -1; - } - - if (decoder_initialized_ && !force_initialization) { - // The decoder is already initialized, and we don't want to force - // initialization. - return 0; - } - - int16_t status; - if (!decoder_exist_) { - // New decoder, start with creating. - decoder_initialized_ = false; - status = CreateDecoder(); - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitDecoderSafe: cannot create decoder"); - return -1; - } else { - decoder_exist_ = true; - } - } - - status = InternalInitDecoder(codec_params); - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InitDecoderSafe: cannot init decoder"); - decoder_initialized_ = false; - return -1; - } else { - // Store decoder parameters. - SaveDecoderParamSafe(codec_params); - decoder_initialized_ = true; - } - return 0; -} - -int16_t ACMGenericCodec::ResetDecoder(int16_t payload_type) { - WriteLockScoped lockCodec(codec_wrapper_lock_); - WriteLockScoped lockNetEq(*neteq_decode_lock_); - return ResetDecoderSafe(payload_type); -} - -int16_t ACMGenericCodec::ResetDecoderSafe(int16_t payload_type) { - WebRtcACMCodecParams decoder_params; - if (!decoder_exist_ || !decoder_initialized_) { - return 0; - } - // Initialization of the decoder should work for all the codec. For codecs - // that needs to keep some states an overloading implementation of - // |DecoderParamsSafe| exists. - DecoderParamsSafe(&decoder_params, static_cast(payload_type)); - return InternalInitDecoder(&decoder_params); -} - -void ACMGenericCodec::ResetNoMissedSamples() { - WriteLockScoped cs(codec_wrapper_lock_); - num_missed_samples_ = 0; -} - -void ACMGenericCodec::IncreaseNoMissedSamples(const int16_t num_samples) { - num_missed_samples_ += num_samples; -} - -// Get the number of missed samples, this can be public. -uint32_t ACMGenericCodec::NoMissedSamples() const { - ReadLockScoped cs(codec_wrapper_lock_); - return num_missed_samples_; -} - -void ACMGenericCodec::DestructEncoder() { - WriteLockScoped wl(codec_wrapper_lock_); - - // Disable VAD and delete the instance. - if (ptr_vad_inst_ != NULL) { - WebRtcVad_Free(ptr_vad_inst_); - ptr_vad_inst_ = NULL; - } - vad_enabled_ = false; - vad_mode_ = VADNormal; - - // Disable DTX and delete the instance. - dtx_enabled_ = false; - if (ptr_dtx_inst_ != NULL) { - WebRtcCng_FreeEnc(ptr_dtx_inst_); - ptr_dtx_inst_ = NULL; - } - num_lpc_params_ = kNewCNGNumPLCParams; - - DestructEncoderSafe(); -} - -void ACMGenericCodec::DestructDecoder() { - WriteLockScoped wl(codec_wrapper_lock_); - decoder_params_.codec_inst.pltype = -1; - DestructDecoderSafe(); -} - -int16_t ACMGenericCodec::SetBitRate(const int32_t bitrate_bps) { - WriteLockScoped wl(codec_wrapper_lock_); - return SetBitRateSafe(bitrate_bps); -} - -int16_t ACMGenericCodec::SetBitRateSafe(const int32_t bitrate_bps) { - // If the codec can change the bit-rate this function is overloaded. - // Otherwise the only acceptable value is the one that is in the database. - CodecInst codec_params; - if (ACMCodecDB::Codec(codec_id_, &codec_params) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetBitRateSafe: error in ACMCodecDB::Codec"); - return -1; - } - if (codec_params.rate != bitrate_bps) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetBitRateSafe: rate value is not acceptable"); - return -1; - } else { - return 0; - } -} - -// iSAC specific functions: -int32_t ACMGenericCodec::GetEstimatedBandwidth() { - WriteLockScoped wl(codec_wrapper_lock_); - return GetEstimatedBandwidthSafe(); -} - -int32_t ACMGenericCodec::GetEstimatedBandwidthSafe() { - // All codecs but iSAC will return -1. - return -1; -} - -int32_t ACMGenericCodec::SetEstimatedBandwidth(int32_t estimated_bandwidth) { - WriteLockScoped wl(codec_wrapper_lock_); - return SetEstimatedBandwidthSafe(estimated_bandwidth); -} - -int32_t ACMGenericCodec::SetEstimatedBandwidthSafe( - int32_t /*estimated_bandwidth*/) { - // All codecs but iSAC will return -1. - return -1; -} -// End of iSAC specific functions. - -int32_t ACMGenericCodec::GetRedPayload(uint8_t* red_payload, - int16_t* payload_bytes) { - WriteLockScoped wl(codec_wrapper_lock_); - return GetRedPayloadSafe(red_payload, payload_bytes); -} - -int32_t ACMGenericCodec::GetRedPayloadSafe(uint8_t* /* red_payload */, - int16_t* /* payload_bytes */) { - return -1; // Do nothing by default. -} - -int16_t ACMGenericCodec::CreateEncoder() { - int16_t status = 0; - if (!encoder_exist_) { - status = InternalCreateEncoder(); - // We just created the codec and obviously it is not initialized. - encoder_initialized_ = false; - } - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CreateEncoder: error in internal create encoder"); - encoder_exist_ = false; - } else { - encoder_exist_ = true; - } - return status; -} - -int16_t ACMGenericCodec::CreateDecoder() { - int16_t status = 0; - if (!decoder_exist_) { - status = InternalCreateDecoder(); - // Decoder just created and obviously it is not initialized. - decoder_initialized_ = false; - } - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CreateDecoder: error in internal create decoder"); - decoder_exist_ = false; - } else { - decoder_exist_ = true; - } - return status; -} - -void ACMGenericCodec::DestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WriteLockScoped lockCodec(codec_wrapper_lock_); - ReadLockScoped lockNetEq(*neteq_decode_lock_); - InternalDestructEncoderInst(ptr_inst); - } -} - -// Get the current audio buffer including read and write states, and timestamps. -int16_t ACMGenericCodec::AudioBuffer(WebRtcACMAudioBuff& audio_buff) { - ReadLockScoped cs(codec_wrapper_lock_); - memcpy(audio_buff.in_audio, in_audio_, - AUDIO_BUFFER_SIZE_W16 * sizeof(int16_t)); - audio_buff.in_audio_ix_read = in_audio_ix_read_; - audio_buff.in_audio_ix_write = in_audio_ix_write_; - memcpy(audio_buff.in_timestamp, in_timestamp_, - TIMESTAMP_BUFFER_SIZE_W32 * sizeof(uint32_t)); - audio_buff.in_timestamp_ix_write = in_timestamp_ix_write_; - audio_buff.last_timestamp = last_timestamp_; - return 0; -} - -// Set the audio buffer. -int16_t ACMGenericCodec::SetAudioBuffer(WebRtcACMAudioBuff& audio_buff) { - WriteLockScoped cs(codec_wrapper_lock_); - memcpy(in_audio_, audio_buff.in_audio, - AUDIO_BUFFER_SIZE_W16 * sizeof(int16_t)); - in_audio_ix_read_ = audio_buff.in_audio_ix_read; - in_audio_ix_write_ = audio_buff.in_audio_ix_write; - memcpy(in_timestamp_, audio_buff.in_timestamp, - TIMESTAMP_BUFFER_SIZE_W32 * sizeof(uint32_t)); - in_timestamp_ix_write_ = audio_buff.in_timestamp_ix_write; - last_timestamp_ = audio_buff.last_timestamp; - is_audio_buff_fresh_ = false; - return 0; -} - -uint32_t ACMGenericCodec::LastEncodedTimestamp() const { - ReadLockScoped cs(codec_wrapper_lock_); - return last_encoded_timestamp_; -} - -uint32_t ACMGenericCodec::EarliestTimestamp() const { - ReadLockScoped cs(codec_wrapper_lock_); - return in_timestamp_[0]; -} - -int16_t ACMGenericCodec::SetVAD(bool* enable_dtx, bool* enable_vad, - ACMVADMode* mode) { - WriteLockScoped cs(codec_wrapper_lock_); - return SetVADSafe(enable_dtx, enable_vad, mode); -} - -int16_t ACMGenericCodec::SetVADSafe(bool* enable_dtx, bool* enable_vad, - ACMVADMode* mode) { - if (!STR_CASE_CMP(encoder_params_.codec_inst.plname, "OPUS") || - encoder_params_.codec_inst.channels == 2 ) { - // VAD/DTX is not supported for Opus (even if sending mono), or other - // stereo codecs. - DisableDTX(); - DisableVAD(); - *enable_dtx = false; - *enable_vad = false; - return 0; - } - - if (*enable_dtx) { - // Make G729 AnnexB a special case. - if (!STR_CASE_CMP(encoder_params_.codec_inst.plname, "G729") - && !has_internal_dtx_) { - if (ACMGenericCodec::EnableDTX() < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetVADSafe: error in enable DTX"); - *enable_dtx = false; - *enable_vad = vad_enabled_; - return -1; - } - } else { - if (EnableDTX() < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetVADSafe: error in enable DTX"); - *enable_dtx = false; - *enable_vad = vad_enabled_; - return -1; - } - } - - // If codec does not have internal DTX (normal case) enabling DTX requires - // an active VAD. '*enable_dtx == true' overwrites VAD status. - // If codec has internal DTX, practically we don't need WebRtc VAD, however, - // we let the user to turn it on if they need call-backs on silence. - if (!has_internal_dtx_) { - // DTX is enabled, and VAD will be activated. - *enable_vad = true; - } - } else { - // Make G729 AnnexB a special case. - if (!STR_CASE_CMP(encoder_params_.codec_inst.plname, "G729") - && !has_internal_dtx_) { - ACMGenericCodec::DisableDTX(); - *enable_dtx = false; - } else { - DisableDTX(); - *enable_dtx = false; - } - } - - int16_t status = (*enable_vad) ? EnableVAD(*mode) : DisableVAD(); - if (status < 0) { - // Failed to set VAD, disable DTX. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetVADSafe: error in enable VAD"); - DisableDTX(); - *enable_dtx = false; - *enable_vad = false; - } - return status; -} - -int16_t ACMGenericCodec::EnableDTX() { - if (has_internal_dtx_) { - // We should not be here if we have internal DTX this function should be - // overloaded by the derived class in this case. - return -1; - } - if (!dtx_enabled_) { - if (WebRtcCng_CreateEnc(&ptr_dtx_inst_) < 0) { - ptr_dtx_inst_ = NULL; - return -1; - } - uint16_t freq_hz; - EncoderSampFreq(freq_hz); - if (WebRtcCng_InitEnc(ptr_dtx_inst_, freq_hz, kCngSidIntervalMsec, - num_lpc_params_) < 0) { - // Couldn't initialize, has to return -1, and free the memory. - WebRtcCng_FreeEnc(ptr_dtx_inst_); - ptr_dtx_inst_ = NULL; - return -1; - } - dtx_enabled_ = true; - } - return 0; -} - -int16_t ACMGenericCodec::DisableDTX() { - if (has_internal_dtx_) { - // We should not be here if we have internal DTX this function should be - // overloaded by the derived class in this case. - return -1; - } - if (ptr_dtx_inst_ != NULL) { - WebRtcCng_FreeEnc(ptr_dtx_inst_); - ptr_dtx_inst_ = NULL; - } - dtx_enabled_ = false; - return 0; -} - -int16_t ACMGenericCodec::EnableVAD(ACMVADMode mode) { - if ((mode < VADNormal) || (mode > VADVeryAggr)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EnableVAD: error in VAD mode range"); - return -1; - } - - if (!vad_enabled_) { - if (WebRtcVad_Create(&ptr_vad_inst_) < 0) { - ptr_vad_inst_ = NULL; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EnableVAD: error in create VAD"); - return -1; - } - if (WebRtcVad_Init(ptr_vad_inst_) < 0) { - WebRtcVad_Free(ptr_vad_inst_); - ptr_vad_inst_ = NULL; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EnableVAD: error in init VAD"); - return -1; - } - } - - // Set the VAD mode to the given value. - if (WebRtcVad_set_mode(ptr_vad_inst_, mode) < 0) { - // We failed to set the mode and we have to return -1. If we already have a - // working VAD (vad_enabled_ == true) then we leave it to work. Otherwise, - // the following will be executed. - if (!vad_enabled_) { - // We just created the instance but cannot set the mode we have to free - // the memory. - WebRtcVad_Free(ptr_vad_inst_); - ptr_vad_inst_ = NULL; - } - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, unique_id_, - "EnableVAD: failed to set the VAD mode"); - return -1; - } - vad_mode_ = mode; - vad_enabled_ = true; - return 0; -} - -int16_t ACMGenericCodec::DisableVAD() { - if (ptr_vad_inst_ != NULL) { - WebRtcVad_Free(ptr_vad_inst_); - ptr_vad_inst_ = NULL; - } - vad_enabled_ = false; - return 0; -} - -int32_t ACMGenericCodec::ReplaceInternalDTX(const bool replace_internal_dtx) { - WriteLockScoped cs(codec_wrapper_lock_); - return ReplaceInternalDTXSafe(replace_internal_dtx); -} - -int32_t ACMGenericCodec::ReplaceInternalDTXSafe( - const bool /* replace_internal_dtx */) { - return -1; -} - -int32_t ACMGenericCodec::IsInternalDTXReplaced(bool* internal_dtx_replaced) { - WriteLockScoped cs(codec_wrapper_lock_); - return IsInternalDTXReplacedSafe(internal_dtx_replaced); -} - -int32_t ACMGenericCodec::IsInternalDTXReplacedSafe( - bool* internal_dtx_replaced) { - *internal_dtx_replaced = false; - return 0; -} - -int16_t ACMGenericCodec::ProcessFrameVADDTX(uint8_t* bitstream, - int16_t* bitstream_len_byte, - int16_t* samples_processed) { - if (!vad_enabled_) { - // VAD not enabled, set all |vad_lable_[]| to 1 (speech detected). - for (int n = 0; n < MAX_FRAME_SIZE_10MSEC; n++) { - vad_label_[n] = 1; - } - *samples_processed = 0; - return 0; - } - - uint16_t freq_hz; - EncoderSampFreq(freq_hz); - - // Calculate number of samples in 10 ms blocks, and number ms in one frame. - int16_t samples_in_10ms = static_cast(freq_hz / 100); - int32_t frame_len_ms = static_cast(frame_len_smpl_) * 1000 / freq_hz; - int16_t status; - - // Vector for storing maximum 30 ms of mono audio at 48 kHz. - int16_t audio[1440]; - - // Calculate number of VAD-blocks to process, and number of samples in each - // block. - int num_samples_to_process[2]; - if (frame_len_ms == 40) { - // 20 ms in each VAD block. - num_samples_to_process[0] = num_samples_to_process[1] = 2 * samples_in_10ms; - } else { - // For 10-30 ms framesizes, second VAD block will be size zero ms, - // for 50 and 60 ms first VAD block will be 30 ms. - num_samples_to_process[0] = - (frame_len_ms > 30) ? 3 * samples_in_10ms : frame_len_smpl_; - num_samples_to_process[1] = frame_len_smpl_ - num_samples_to_process[0]; - } - - int offset = 0; - int loops = (num_samples_to_process[1] > 0) ? 2 : 1; - for (int i = 0; i < loops; i++) { - // TODO(turajs): Do we need to care about VAD together with stereo? - // If stereo, calculate mean of the two channels. - if (num_channels_ == 2) { - for (int j = 0; j < num_samples_to_process[i]; j++) { - audio[j] = (in_audio_[(offset + j) * 2] + - in_audio_[(offset + j) * 2 + 1]) / 2; - } - offset = num_samples_to_process[0]; - } else { - // Mono, copy data from in_audio_ to continue work on. - memcpy(audio, in_audio_, sizeof(int16_t) * num_samples_to_process[i]); - } - - // Call VAD. - status = static_cast(WebRtcVad_Process(ptr_vad_inst_, - static_cast(freq_hz), - audio, - num_samples_to_process[i])); - vad_label_[i] = status; - - if (status < 0) { - // This will force that the data be removed from the buffer. - *samples_processed += num_samples_to_process[i]; - return -1; - } - - // If VAD decision non-active, update DTX. NOTE! We only do this if the - // first part of a frame gets the VAD decision "inactive". Otherwise DTX - // might say it is time to transmit SID frame, but we will encode the whole - // frame, because the first part is active. - *samples_processed = 0; - if ((status == 0) && (i == 0) && dtx_enabled_ && !has_internal_dtx_) { - int16_t bitstream_len; - int num_10ms_frames = num_samples_to_process[i] / samples_in_10ms; - *bitstream_len_byte = 0; - for (int n = 0; n < num_10ms_frames; n++) { - // This block is (passive) && (vad enabled). If first CNG after - // speech, force SID by setting last parameter to "1". - status = WebRtcCng_Encode(ptr_dtx_inst_, &audio[n * samples_in_10ms], - samples_in_10ms, bitstream, &bitstream_len, - !prev_frame_cng_); - if (status < 0) { - return -1; - } - - // Update previous frame was CNG. - prev_frame_cng_ = 1; - - *samples_processed += samples_in_10ms * num_channels_; - - // |bitstream_len_byte| will only be > 0 once per 100 ms. - *bitstream_len_byte += bitstream_len; - } - - // Check if all samples got processed by the DTX. - if (*samples_processed != num_samples_to_process[i] * num_channels_) { - // Set to zero since something went wrong. Shouldn't happen. - *samples_processed = 0; - } - } else { - // Update previous frame was not CNG. - prev_frame_cng_ = 0; - } - - if (*samples_processed > 0) { - // The block contains inactive speech, and is processed by DTX. - // Discontinue running VAD. - break; - } - } - - return status; -} - -int16_t ACMGenericCodec::SamplesLeftToEncode() { - ReadLockScoped rl(codec_wrapper_lock_); - return (frame_len_smpl_ <= in_audio_ix_write_) ? 0 : - (frame_len_smpl_ - in_audio_ix_write_); -} - -void ACMGenericCodec::SetUniqueID(const uint32_t id) { - unique_id_ = id; -} - -bool ACMGenericCodec::IsAudioBufferFresh() const { - ReadLockScoped rl(codec_wrapper_lock_); - return is_audio_buff_fresh_; -} - -int16_t ACMGenericCodec::UpdateDecoderSampFreq(int16_t /* codec_id */) { - return 0; -} - -// This function is replaced by codec specific functions for some codecs. -int16_t ACMGenericCodec::EncoderSampFreq(uint16_t& samp_freq_hz) { - int32_t f; - f = ACMCodecDB::CodecFreq(codec_id_); - if (f < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EncoderSampFreq: codec frequency is negative"); - return -1; - } else { - samp_freq_hz = static_cast(f); - return 0; - } -} - -int32_t ACMGenericCodec::ConfigISACBandwidthEstimator( - const uint8_t /* init_frame_size_msec */, - const uint16_t /* init_rate_bit_per_sec */, - const bool /* enforce_frame_size */) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, unique_id_, - "The send-codec is not iSAC, failed to config iSAC bandwidth " - "estimator."); - return -1; -} - -int32_t ACMGenericCodec::SetISACMaxRate( - const uint32_t /* max_rate_bit_per_sec */) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, unique_id_, - "The send-codec is not iSAC, failed to set iSAC max rate."); - return -1; -} - -int32_t ACMGenericCodec::SetISACMaxPayloadSize( - const uint16_t /* max_payload_len_bytes */) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, unique_id_, - "The send-codec is not iSAC, failed to set iSAC max " - "payload-size."); - return -1; -} - -void ACMGenericCodec::SaveDecoderParam( - const WebRtcACMCodecParams* codec_params) { - WriteLockScoped wl(codec_wrapper_lock_); - SaveDecoderParamSafe(codec_params); -} - -void ACMGenericCodec::SaveDecoderParamSafe( - const WebRtcACMCodecParams* codec_params) { - memcpy(&decoder_params_, codec_params, sizeof(WebRtcACMCodecParams)); -} - -int16_t ACMGenericCodec::UpdateEncoderSampFreq( - uint16_t /* samp_freq_hz */) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "It is asked for a change in smapling frequency while the " - "current send-codec supports only one sampling rate."); - return -1; -} - -void ACMGenericCodec::SetIsMaster(bool is_master) { - WriteLockScoped wl(codec_wrapper_lock_); - is_master_ = is_master; -} - -int16_t ACMGenericCodec::REDPayloadISAC(const int32_t /* isac_rate */, - const int16_t /* isac_bw_estimate */, - uint8_t* /* payload */, - int16_t* /* payload_len_bytes */) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Error: REDPayloadISAC is an iSAC specific function"); - return -1; -} - -bool ACMGenericCodec::IsTrueStereoCodec() { return false; } - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_generic_codec.h b/webrtc/modules/audio_coding/main/source/acm_generic_codec.h deleted file mode 100644 index c1f9cdc55..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_generic_codec.h +++ /dev/null @@ -1,1224 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_GENERIC_CODEC_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_GENERIC_CODEC_H_ - -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#define MAX_FRAME_SIZE_10MSEC 6 - -// forward declaration -struct WebRtcVadInst; -struct WebRtcCngEncInst; - -namespace webrtc { - -// forward declaration -struct CodecInst; -struct WebRtcACMCodecParams; - -namespace acm1 { - -class ACMNetEQ; - -class ACMGenericCodec { - public: - /////////////////////////////////////////////////////////////////////////// - // Constructor of the class - // - ACMGenericCodec(); - - /////////////////////////////////////////////////////////////////////////// - // Destructor of the class. - // - virtual ~ACMGenericCodec(); - - /////////////////////////////////////////////////////////////////////////// - // ACMGenericCodec* CreateInstance(); - // The function will be used for FEC. It is not implemented yet. - // - virtual ACMGenericCodec* CreateInstance() = 0; - - /////////////////////////////////////////////////////////////////////////// - // int16_t Encode() - // The function is called to perform an encoding of the audio stored in - // audio buffer. An encoding is performed only if enough audio, i.e. equal - // to the frame-size of the codec, exist. The audio frame will be processed - // by VAD and CN/DTX if required. There are few different cases. - // - // A) Neither VAD nor DTX is active; the frame is encoded by the encoder. - // - // B) VAD is enabled but not DTX; in this case the audio is processed by VAD - // and encoded by the encoder. The "*encoding_type" will be either - // "kActiveNormalEncode" or "kPassiveNormalEncode" if frame is active or - // passive, respectively. - // - // C) DTX is enabled; if the codec has internal VAD/DTX we just encode the - // frame by the encoder. Otherwise, the frame is passed through VAD and - // if identified as passive, then it will be processed by CN/DTX. If the - // frame is active it will be encoded by the encoder. - // - // This function acquires the appropriate locks and calls EncodeSafe() for - // the actual processing. - // - // Outputs: - // -bitstream : a buffer where bit-stream will be written to. - // -bitstream_len_byte : contains the length of the bit-stream in - // bytes. - // -timestamp : contains the RTP timestamp, this is the - // sampling time of the first sample encoded - // (measured in number of samples). - // -encoding_type : contains the type of encoding applied on the - // audio samples. The alternatives are - // (c.f. acm_common_types.h) - // -kNoEncoding: - // there was not enough data to encode. or - // some error has happened that we could - // not do encoding. - // -kActiveNormalEncoded: - // the audio frame is active and encoded by - // the given codec. - // -kPassiveNormalEncoded: - // the audio frame is passive but coded with - // the given codec (NO DTX). - // -kPassiveDTXWB: - // The audio frame is passive and used - // wide-band CN to encode. - // -kPassiveDTXNB: - // The audio frame is passive and used - // narrow-band CN to encode. - // - // Return value: - // -1 if error is occurred, otherwise the length of the bit-stream in - // bytes. - // - int16_t Encode(uint8_t* bitstream, - int16_t* bitstream_len_byte, - uint32_t* timestamp, - WebRtcACMEncodingType* encoding_type); - - /////////////////////////////////////////////////////////////////////////// - // int16_t Decode() - // This function is used to decode a given bit-stream, without engaging - // NetEQ. - // - // This function acquires the appropriate locks and calls DecodeSafe() for - // the actual processing. Please note that this is not functional yet. - // - // Inputs: - // -bitstream : a buffer where bit-stream will be read. - // -bitstream_len_byte : the length of the bit-stream in bytes. - // - // Outputs: - // -audio : pointer to a buffer where the audio will written. - // -audio_samples : number of audio samples out of decoding the given - // bit-stream. - // -speech_type : speech type (for future use). - // - // Return value: - // -1 if failed to decode, - // 0 if succeeded. - // - int16_t Decode(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type); - - /////////////////////////////////////////////////////////////////////////// - // void SplitStereoPacket() - // This function is used to split stereo payloads in left and right channel. - // Codecs which has stereo support has there own implementation of the - // function. - // - // Input/Output: - // -payload : a vector with the received payload data. - // The function will reorder the data so that - // first half holds the left channel data, and the - // second half the right channel data. - // -payload_length : length of payload in bytes. Will be changed to - // twice the input in case of true stereo, where - // we simply copy the data and return it both for - // left channel and right channel decoding. - // - virtual void SplitStereoPacket(uint8_t* /* payload */, - int32_t* /* payload_length */) {} - - /////////////////////////////////////////////////////////////////////////// - // bool EncoderInitialized(); - // - // Return value: - // True if the encoder is successfully initialized, - // false otherwise. - // - bool EncoderInitialized(); - - /////////////////////////////////////////////////////////////////////////// - // bool DecoderInitialized(); - // - // Return value: - // True if the decoder is successfully initialized, - // false otherwise. - // - bool DecoderInitialized(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t EncoderParams() - // It is called to get encoder parameters. It will call - // EncoderParamsSafe() in turn. - // - // Output: - // -enc_params : a buffer where the encoder parameters is - // written to. If the encoder is not - // initialized this buffer is filled with - // invalid values - // Return value: - // -1 if the encoder is not initialized, - // 0 otherwise. - // - int16_t EncoderParams(WebRtcACMCodecParams *enc_params); - - /////////////////////////////////////////////////////////////////////////// - // int16_t DecoderParams(...) - // It is called to get decoder parameters. It will call DecoderParamsSafe() - // in turn. - // - // Output: - // -dec_params : a buffer where the decoder parameters is - // written to. If the decoder is not initialized - // this buffer is filled with invalid values - // - // Return value: - // -1 if the decoder is not initialized, - // 0 otherwise. - // - // - bool DecoderParams(WebRtcACMCodecParams *dec_params, - const uint8_t payload_type); - - /////////////////////////////////////////////////////////////////////////// - // int16_t InitEncoder(...) - // This function is called to initialize the encoder with the given - // parameters. - // - // Input: - // -codec_params : parameters of encoder. - // -force_initialization: if false the initialization is invoked only if - // the encoder is not initialized. If true the - // encoder is forced to (re)initialize. - // - // Return value: - // 0 if could initialize successfully, - // -1 if failed to initialize. - // - // - int16_t InitEncoder(WebRtcACMCodecParams* codec_params, - bool force_initialization); - - /////////////////////////////////////////////////////////////////////////// - // int16_t InitDecoder() - // This function is called to initialize the decoder with the given - // parameters. (c.f. acm_common_defs.h & common_types.h for the - // definition of the structure) - // - // Input: - // -codec_params : parameters of decoder. - // -force_initialization: if false the initialization is invoked only - // if the decoder is not initialized. If true - // the encoder is forced to(re)initialize. - // - // Return value: - // 0 if could initialize successfully, - // -1 if failed to initialize. - // - // - int16_t InitDecoder(WebRtcACMCodecParams* codec_params, - bool force_initialization); - - /////////////////////////////////////////////////////////////////////////// - // int32_t RegisterInNetEq(...) - // This function is called to register the decoder in NetEq, with the given - // payload type. - // - // Inputs: - // -neteq : pointer to NetEq Instance - // -codec_inst : instance with of the codec settings of the codec - // - // Return values - // -1 if failed to register, - // 0 if successfully initialized. - // - int32_t RegisterInNetEq(ACMNetEQ* neteq, const CodecInst& codec_inst); - - /////////////////////////////////////////////////////////////////////////// - // int32_t Add10MsData(...) - // This function is called to add 10 ms of audio to the audio buffer of - // the codec. - // - // Inputs: - // -timestamp : the timestamp of the 10 ms audio. the timestamp - // is the sampling time of the - // first sample measured in number of samples. - // -data : a buffer that contains the audio. The codec - // expects to get the audio in correct sampling - // frequency - // -length : the length of the audio buffer - // -audio_channel : 0 for mono, 1 for stereo (not supported yet) - // - // Return values: - // -1 if failed - // 0 otherwise. - // - int32_t Add10MsData(const uint32_t timestamp, - const int16_t* data, - const uint16_t length, - const uint8_t audio_channel); - - /////////////////////////////////////////////////////////////////////////// - // uint32_t NoMissedSamples() - // This function returns the number of samples which are overwritten in - // the audio buffer. The audio samples are overwritten if the input audio - // buffer is full, but Add10MsData() is called. (We might remove this - // function if it is not used) - // - // Return Value: - // Number of samples which are overwritten. - // - uint32_t NoMissedSamples() const; - - /////////////////////////////////////////////////////////////////////////// - // void ResetNoMissedSamples() - // This function resets the number of overwritten samples to zero. - // (We might remove this function if we remove NoMissedSamples()) - // - void ResetNoMissedSamples(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t SetBitRate() - // The function is called to set the encoding rate. - // - // Input: - // -bitrate_bps : encoding rate in bits per second - // - // Return value: - // -1 if failed to set the rate, due to invalid input or given - // codec is not rate-adjustable. - // 0 if the rate is adjusted successfully - // - int16_t SetBitRate(const int32_t bitrate_bps); - - /////////////////////////////////////////////////////////////////////////// - // DestructEncoderInst() - // This API is used in conferencing. It will free the memory that is pointed - // by |ptr_inst|. |ptr_inst| is a pointer to encoder instance, created and - // filled up by calling EncoderInst(...). - // - // Inputs: - // -ptr_inst : pointer to an encoder instance to be deleted. - // - // - void DestructEncoderInst(void* ptr_inst); - - /////////////////////////////////////////////////////////////////////////// - // int16_t AudioBuffer() - // This is used when synchronization of codecs is required. There are cases - // that the audio buffers of two codecs have to be synched. By calling this - // function on can get the audio buffer and other related parameters, such - // as timestamps... - // - // Output: - // -audio_buff : a pointer to WebRtcACMAudioBuff where the audio - // buffer of this codec will be written to. - // - // Return value: - // -1 if fails to copy the audio buffer, - // 0 if succeeded. - // - int16_t AudioBuffer(WebRtcACMAudioBuff& audio_buff); - - /////////////////////////////////////////////////////////////////////////// - // uint32_t EarliestTimestamp() - // Returns the timestamp of the first 10 ms in audio buffer. This is used - // to identify if a synchronization of two encoders is required. - // - // Return value: - // timestamp of the first 10 ms audio in the audio buffer. - // - uint32_t EarliestTimestamp() const; - - /////////////////////////////////////////////////////////////////////////// - // int16_t SetAudioBuffer() - // This function is called to set the audio buffer and the associated - // parameters to a given value. - // - // Return value: - // -1 if fails to copy the audio buffer, - // 0 if succeeded. - // - int16_t SetAudioBuffer(WebRtcACMAudioBuff& audio_buff); - - /////////////////////////////////////////////////////////////////////////// - // int16_t SetVAD() - // This is called to set VAD & DTX. If the codec has internal DTX, it will - // be used. If DTX is enabled and the codec does not have internal DTX, - // WebRtc-VAD will be used to decide if the frame is active. If DTX is - // disabled but VAD is enabled the audio is passed through VAD to label it - // as active or passive, but the frame is encoded normally. However the - // bit-stream is labeled properly so that ACM::Process() can use this - // information. In case of failure, the previous states of the VAD & DTX - // are kept. - // - // Input/Output: - // -enable_dtx : if true DTX will be enabled otherwise the DTX is - // disabled. If codec has internal DTX that will be - // used, otherwise WebRtc-CNG is used. In the latter - // case VAD is automatically activated. - // -enable_vad : if true WebRtc-VAD is enabled, otherwise VAD is - // disabled, except for the case that DTX is enabled - // but codec doesn't have internal DTX. In this case - // VAD is enabled regardless of the value of - // |enable_vad|. - // -mode : this specifies the aggressiveness of VAD. - // - // Return value - // -1 if failed to set DTX & VAD as specified, - // 0 if succeeded. - // - int16_t SetVAD(bool* enable_dtx, - bool* enable_vad, - ACMVADMode* mode); - - /////////////////////////////////////////////////////////////////////////// - // int32_t ReplaceInternalDTX() - // This is called to replace the codec internal DTX with WebRtc DTX. - // This is only valid for G729 where the user has possibility to replace - // AnnexB with WebRtc DTX. For other codecs this function has no effect. - // - // Input: - // -replace_internal_dtx : if true the internal DTX is replaced with WebRtc. - // - // Return value - // -1 if failed to replace internal DTX, - // 0 if succeeded. - // - int32_t ReplaceInternalDTX(const bool replace_internal_dtx); - - /////////////////////////////////////////////////////////////////////////// - // int32_t IsInternalDTXReplaced() - // This is called to check if the codec internal DTX is replaced by WebRtc - // DTX. This is only valid for G729 where the user has possibility to replace - // AnnexB with WebRtc DTX. For other codecs this function has no effect. - // - // Output: - // -internal_dtx_replaced: if true the internal DTX is replaced with WebRtc. - // - // Return value - // -1 if failed to check - // 0 if succeeded. - // - int32_t IsInternalDTXReplaced(bool* internal_dtx_replaced); - - /////////////////////////////////////////////////////////////////////////// - // void SetNetEqDecodeLock() - // Passes the NetEq lock to the codec. - // - // Input: - // -neteq_decode_lock : pointer to the lock associated with NetEQ of ACM. - // - void SetNetEqDecodeLock(RWLockWrapper* neteq_decode_lock) { - neteq_decode_lock_ = neteq_decode_lock; - } - - /////////////////////////////////////////////////////////////////////////// - // bool HasInternalDTX() - // Used to check if the codec has internal DTX. - // - // Return value: - // true if the codec has an internal DTX, e.g. G729, - // false otherwise. - // - bool HasInternalDTX() const { - return has_internal_dtx_; - } - - /////////////////////////////////////////////////////////////////////////// - // int32_t GetEstimatedBandwidth() - // Used to get decoder estimated bandwidth. Only iSAC will provide a value. - // - // - // Return value: - // -1 if fails to get decoder estimated bandwidth, - // >0 estimated bandwidth in bits/sec. - // - int32_t GetEstimatedBandwidth(); - - /////////////////////////////////////////////////////////////////////////// - // int32_t SetEstimatedBandwidth() - // Used to set estiamted bandwidth sent out of band from other side. Only - // iSAC will have use for the value. - // - // Input: - // -estimated_bandwidth: estimated bandwidth in bits/sec - // - // Return value: - // -1 if fails to set estimated bandwidth, - // 0 on success. - // - int32_t SetEstimatedBandwidth(int32_t estimated_bandwidth); - - /////////////////////////////////////////////////////////////////////////// - // int32_t GetRedPayload() - // Used to get codec specific RED payload (if such is implemented). - // Currently only done in iSAC. - // - // Outputs: - // -red_payload : a pointer to the data for RED payload. - // -payload_bytes : number of bytes in RED payload. - // - // Return value: - // -1 if fails to get codec specific RED, - // 0 if succeeded. - // - int32_t GetRedPayload(uint8_t* red_payload, - int16_t* payload_bytes); - - /////////////////////////////////////////////////////////////////////////// - // int16_t ResetEncoder() - // By calling this function you would re-initialize the encoder with the - // current parameters. All the settings, e.g. VAD/DTX, frame-size... should - // remain unchanged. (In case of iSAC we don't want to lose BWE history.) - // - // Return value - // -1 if failed, - // 0 if succeeded. - // - int16_t ResetEncoder(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t ResetEncoder() - // By calling this function you would re-initialize the decoder with the - // current parameters. - // - // Return value - // -1 if failed, - // 0 if succeeded. - // - int16_t ResetDecoder(int16_t payload_type); - - /////////////////////////////////////////////////////////////////////////// - // void DestructEncoder() - // This function is called to delete the encoder instance, if possible, to - // have a fresh start. For codecs where encoder and decoder share the same - // instance we cannot delete the encoder and instead we will initialize the - // encoder. We also delete VAD and DTX if they have been created. - // - void DestructEncoder(); - - /////////////////////////////////////////////////////////////////////////// - // void DestructDecoder() - // This function is called to delete the decoder instance, if possible, to - // have a fresh start. For codecs where encoder and decoder share the same - // instance we cannot delete the encoder and instead we will initialize the - // decoder. Before deleting decoder instance it has to be removed from the - // NetEq list. - // - void DestructDecoder(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t SamplesLeftToEncode() - // Returns the number of samples required to be able to do encoding. - // - // Return value: - // Number of samples. - // - int16_t SamplesLeftToEncode(); - - /////////////////////////////////////////////////////////////////////////// - // uint32_t LastEncodedTimestamp() - // Returns the timestamp of the last frame it encoded. - // - // Return value: - // Timestamp. - // - uint32_t LastEncodedTimestamp() const; - - /////////////////////////////////////////////////////////////////////////// - // SetUniqueID() - // Set a unique ID for the codec to be used for tracing and debugging - // - // Input - // -id : A number to identify the codec. - // - void SetUniqueID(const uint32_t id); - - /////////////////////////////////////////////////////////////////////////// - // IsAudioBufferFresh() - // Specifies if ever audio is injected to this codec. - // - // Return value - // -true; no audio is feed into this codec - // -false; audio has already been fed to the codec. - // - bool IsAudioBufferFresh() const; - - /////////////////////////////////////////////////////////////////////////// - // UpdateDecoderSampFreq() - // For most of the codecs this function does nothing. It must be - // implemented for those codecs that one codec instance serves as the - // decoder for different flavors of the codec. One example is iSAC. there, - // iSAC 16 kHz and iSAC 32 kHz are treated as two different codecs with - // different payload types, however, there is only one iSAC instance to - // decode. The reason for that is we would like to decode and encode with - // the same codec instance for bandwidth estimator to work. - // - // Each time that we receive a new payload type, we call this function to - // prepare the decoder associated with the new payload. Normally, decoders - // doesn't have to do anything. For iSAC the decoder has to change it's - // sampling rate. The input parameter specifies the current flavor of the - // codec in codec database. For instance, if we just got a SWB payload then - // the input parameter is ACMCodecDB::isacswb. - // - // Input: - // -codec_id : the ID of the codec associated with the - // payload type that we just received. - // - // Return value: - // 0 if succeeded in updating the decoder. - // -1 if failed to update. - // - virtual int16_t UpdateDecoderSampFreq(int16_t /* codec_id */); - - /////////////////////////////////////////////////////////////////////////// - // UpdateEncoderSampFreq() - // Call this function to update the encoder sampling frequency. This - // is for codecs where one payload-name supports several encoder sampling - // frequencies. Otherwise, to change the sampling frequency we need to - // register new codec. ACM will consider that as registration of a new - // codec, not a change in parameter. For iSAC, switching from WB to SWB - // is treated as a change in parameter. Therefore, we need this function. - // - // Input: - // -samp_freq_hz : encoder sampling frequency. - // - // Return value: - // -1 if failed, or if this is meaningless for the given codec. - // 0 if succeeded. - // - virtual int16_t UpdateEncoderSampFreq( - uint16_t samp_freq_hz); - - /////////////////////////////////////////////////////////////////////////// - // EncoderSampFreq() - // Get the sampling frequency that the encoder (WebRtc wrapper) expects. - // - // Output: - // -samp_freq_hz : sampling frequency, in Hertz, which the encoder - // should be fed with. - // - // Return value: - // -1 if failed to output sampling rate. - // 0 if the sample rate is returned successfully. - // - virtual int16_t EncoderSampFreq(uint16_t& samp_freq_hz); - - /////////////////////////////////////////////////////////////////////////// - // int32_t ConfigISACBandwidthEstimator() - // Call this function to configure the bandwidth estimator of ISAC. - // During the adaptation of bit-rate, iSAC automatically adjusts the - // frame-size (either 30 or 60 ms) to save on RTP header. The initial - // frame-size can be specified by the first argument. The configuration also - // regards the initial estimate of bandwidths. The estimator starts from - // this point and converges to the actual bottleneck. This is given by the - // second parameter. Furthermore, it is also possible to control the - // adaptation of frame-size. This is specified by the last parameter. - // - // Input: - // -init_frame_fize_ms : initial frame-size in milliseconds. For iSAC-wb - // 30 ms and 60 ms (default) are acceptable values, - // and for iSAC-swb 30 ms is the only acceptable - // value. Zero indicates default value. - // -init_rate_bps : initial estimate of the bandwidth. Values - // between 10000 and 58000 are acceptable. - // -enforce_frame_size : if true, the frame-size will not be adapted. - // - // Return value: - // -1 if failed to configure the bandwidth estimator, - // 0 if the configuration was successfully applied. - // - virtual int32_t ConfigISACBandwidthEstimator( - const uint8_t init_frame_size_msec, - const uint16_t init_rate_bps, - const bool enforce_frame_size); - - /////////////////////////////////////////////////////////////////////////// - // SetISACMaxPayloadSize() - // Set the maximum payload size of iSAC packets. No iSAC payload, - // regardless of its frame-size, may exceed the given limit. For - // an iSAC payload of size B bits and frame-size T sec we have; - // (B < max_payload_len_bytes * 8) and (B/T < max_rate_bit_per_sec), c.f. - // SetISACMaxRate(). - // - // Input: - // -max_payload_len_bytes : maximum payload size in bytes. - // - // Return value: - // -1 if failed to set the maximum payload-size. - // 0 if the given length is set successfully. - // - virtual int32_t SetISACMaxPayloadSize( - const uint16_t max_payload_len_bytes); - - /////////////////////////////////////////////////////////////////////////// - // SetISACMaxRate() - // Set the maximum instantaneous rate of iSAC. For a payload of B bits - // with a frame-size of T sec the instantaneous rate is B/T bits per - // second. Therefore, (B/T < max_rate_bit_per_sec) and - // (B < max_payload_len_bytes * 8) are always satisfied for iSAC payloads, - // c.f SetISACMaxPayloadSize(). - // - // Input: - // -max_rate_bps : maximum instantaneous bit-rate given in bits/sec. - // - // Return value: - // -1 if failed to set the maximum rate. - // 0 if the maximum rate is set successfully. - // - virtual int32_t SetISACMaxRate(const uint32_t max_rate_bps); - - /////////////////////////////////////////////////////////////////////////// - // SaveDecoderParamS() - // Save the parameters of decoder. - // - // Input: - // -codec_params : pointer to a structure where the parameters of - // decoder is stored in. - // - void SaveDecoderParam(const WebRtcACMCodecParams* codec_params); - - int32_t FrameSize() { - return frame_len_smpl_; - } - - void SetIsMaster(bool is_master); - - /////////////////////////////////////////////////////////////////////////// - // REDPayloadISAC() - // This is an iSAC-specific function. The function is called to get RED - // payload from a default-encoder. - // - // Inputs: - // -isac_rate : the target rate of the main payload. A RED - // payload is generated according to the rate of - // main payload. Note that we are not specifying the - // rate of RED payload, but the main payload. - // -isac_bw_estimate : bandwidth information should be inserted in - // RED payload. - // - // Output: - // -payload : pointer to a buffer where the RED payload will - // written to. - // -payload_len_bytes : a place-holder to write the length of the RED - // payload in Bytes. - // - // Return value: - // -1 if an error occurs, otherwise the length of the payload (in Bytes) - // is returned. - // - virtual int16_t REDPayloadISAC(const int32_t isac_rate, - const int16_t isac_bw_estimate, - uint8_t* payload, - int16_t* payload_len_bytes); - - /////////////////////////////////////////////////////////////////////////// - // IsTrueStereoCodec() - // Call to see if current encoder is a true stereo codec. This function - // should be overwritten for codecs which are true stereo codecs - // Return value: - // -true if stereo codec - // -false if not stereo codec. - // - virtual bool IsTrueStereoCodec(); - - /////////////////////////////////////////////////////////////////////////// - // HasFrameToEncode() - // Returns true if there is enough audio buffered for encoding, such that - // calling Encode() will return a payload. - // - bool HasFrameToEncode() const; - - protected: - /////////////////////////////////////////////////////////////////////////// - // All the functions with FunctionNameSafe(...) contain the actual - // implementation of FunctionName(...). FunctionName() acquires an - // appropriate lock and calls FunctionNameSafe() to do the actual work. - // Therefore, for the description of functionality, input/output arguments - // and return value we refer to FunctionName() - // - - /////////////////////////////////////////////////////////////////////////// - // See Decode() for the description of function, input(s)/output(s) and - // return value. - // - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) = 0; - - /////////////////////////////////////////////////////////////////////////// - // See Add10MsSafe() for the description of function, input(s)/output(s) - // and return value. - // - virtual int32_t Add10MsDataSafe(const uint32_t timestamp, - const int16_t* data, - const uint16_t length, - const uint8_t audio_channel); - - /////////////////////////////////////////////////////////////////////////// - // See RegisterInNetEq() for the description of function, - // input(s)/output(s) and return value. - // - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) = 0; - - /////////////////////////////////////////////////////////////////////////// - // See EncoderParam() for the description of function, input(s)/output(s) - // and return value. - // - int16_t EncoderParamsSafe(WebRtcACMCodecParams *enc_params); - - /////////////////////////////////////////////////////////////////////////// - // See DecoderParam for the description of function, input(s)/output(s) - // and return value. - // - // Note: - // Any Class where a single instance handle several flavors of the - // same codec, therefore, several payload types are associated with - // the same instance have to implement this function. - // - // Currently only iSAC is implementing it. A single iSAC instance is - // used for decoding both WB & SWB stream. At one moment both WB & SWB - // can be registered as receive codec. Hence two payloads are associated - // with a single codec instance. - // - virtual bool DecoderParamsSafe(WebRtcACMCodecParams *dec_params, - const uint8_t payload_type); - - /////////////////////////////////////////////////////////////////////////// - // See ResetEncoder() for the description of function, input(s)/output(s) - // and return value. - // - int16_t ResetEncoderSafe(); - - /////////////////////////////////////////////////////////////////////////// - // See InitEncoder() for the description of function, input(s)/output(s) - // and return value. - // - int16_t InitEncoderSafe(WebRtcACMCodecParams *codec_params, - bool force_initialization); - - /////////////////////////////////////////////////////////////////////////// - // See InitDecoder() for the description of function, input(s)/output(s) - // and return value. - // - int16_t InitDecoderSafe(WebRtcACMCodecParams *codec_params, - bool force_initialization); - - /////////////////////////////////////////////////////////////////////////// - // See ResetDecoder() for the description of function, input(s)/output(s) - // and return value. - // - int16_t ResetDecoderSafe(int16_t payload_type); - - /////////////////////////////////////////////////////////////////////////// - // See DestructEncoder() for the description of function, - // input(s)/output(s) and return value. - // - virtual void DestructEncoderSafe() = 0; - - /////////////////////////////////////////////////////////////////////////// - // See DestructDecoder() for the description of function, - // input(s)/output(s) and return value. - // - virtual void DestructDecoderSafe() = 0; - - /////////////////////////////////////////////////////////////////////////// - // See SetBitRate() for the description of function, input(s)/output(s) - // and return value. - // - // Any codec that can change the bit-rate has to implement this. - // - virtual int16_t SetBitRateSafe(const int32_t bitrate_bps); - - /////////////////////////////////////////////////////////////////////////// - // See GetEstimatedBandwidth() for the description of function, - // input(s)/output(s) and return value. - // - virtual int32_t GetEstimatedBandwidthSafe(); - - /////////////////////////////////////////////////////////////////////////// - // See SetEstimatedBandwidth() for the description of function, - // input(s)/output(s) and return value. - // - virtual int32_t SetEstimatedBandwidthSafe( - int32_t estimated_bandwidth); - - /////////////////////////////////////////////////////////////////////////// - // See GetRedPayload() for the description of function, input(s)/output(s) - // and return value. - // - virtual int32_t GetRedPayloadSafe(uint8_t* red_payload, - int16_t* payload_bytes); - - /////////////////////////////////////////////////////////////////////////// - // See SetVAD() for the description of function, input(s)/output(s) and - // return value. - // - int16_t SetVADSafe(bool* enable_dtx, - bool* enable_vad, - ACMVADMode* mode); - - /////////////////////////////////////////////////////////////////////////// - // See ReplaceInternalDTX() for the description of function, input and - // return value. - // - virtual int32_t ReplaceInternalDTXSafe(const bool replace_internal_dtx); - - /////////////////////////////////////////////////////////////////////////// - // See IsInternalDTXReplaced() for the description of function, input and - // return value. - // - virtual int32_t IsInternalDTXReplacedSafe(bool* internal_dtx_replaced); - - /////////////////////////////////////////////////////////////////////////// - // int16_t CreateEncoder() - // Creates the encoder instance. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - int16_t CreateEncoder(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t CreateDecoder() - // Creates the decoder instance. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - int16_t CreateDecoder(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t EnableVAD(); - // Enables VAD with the given mode. The VAD instance will be created if - // it does not exists. - // - // Input: - // -mode : VAD mode c.f. audio_coding_module_typedefs.h for - // the options. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - int16_t EnableVAD(ACMVADMode mode); - - /////////////////////////////////////////////////////////////////////////// - // int16_t DisableVAD() - // Disables VAD. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - int16_t DisableVAD(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t EnableDTX() - // Enables DTX. This method should be overwritten for codecs which have - // internal DTX. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual int16_t EnableDTX(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t DisableDTX() - // Disables usage of DTX. This method should be overwritten for codecs which - // have internal DTX. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual int16_t DisableDTX(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t InternalEncode() - // This is a codec-specific function called in EncodeSafe() to actually - // encode a frame of audio. - // - // Outputs: - // -bitstream : pointer to a buffer where the bit-stream is - // written to. - // -bitstream_len_byte : the length of the bit-stream in bytes, - // a negative value indicates error. - // - // Return value: - // -1 if failed, - // otherwise the length of the bit-stream is returned. - // - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) = 0; - - /////////////////////////////////////////////////////////////////////////// - // int16_t InternalInitEncoder() - // This is a codec-specific function called in InitEncoderSafe(), it has to - // do all codec-specific operation to initialize the encoder given the - // encoder parameters. - // - // Input: - // -codec_params : pointer to a structure that contains parameters to - // initialize encoder. - // Set codec_params->codec_inst.rate to -1 for - // iSAC to operate in adaptive mode. - // (to do: if frame-length is -1 frame-length will be - // automatically adjusted, otherwise, given - // frame-length is forced) - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams *codec_params) = 0; - - /////////////////////////////////////////////////////////////////////////// - // int16_t InternalInitDecoder() - // This is a codec-specific function called in InitDecoderSafe(), it has to - // do all codec-specific operation to initialize the decoder given the - // decoder parameters. - // - // Input: - // -codec_params : pointer to a structure that contains parameters to - // initialize encoder. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams *codec_params) = 0; - - /////////////////////////////////////////////////////////////////////////// - // void IncreaseNoMissedSamples() - // This method is called to increase the number of samples that are - // overwritten in the audio buffer. - // - // Input: - // -num_samples : the number of overwritten samples is incremented - // by this value. - // - void IncreaseNoMissedSamples(const int16_t num_samples); - - /////////////////////////////////////////////////////////////////////////// - // int16_t InternalCreateEncoder() - // This is a codec-specific method called in CreateEncoderSafe() it is - // supposed to perform all codec-specific operations to create encoder - // instance. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual int16_t InternalCreateEncoder() = 0; - - /////////////////////////////////////////////////////////////////////////// - // int16_t InternalCreateDecoder() - // This is a codec-specific method called in CreateDecoderSafe() it is - // supposed to perform all codec-specific operations to create decoder - // instance. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual int16_t InternalCreateDecoder() = 0; - - /////////////////////////////////////////////////////////////////////////// - // void InternalDestructEncoderInst() - // This is a codec-specific method, used in conferencing, called from - // DestructEncoderInst(). The input argument is pointer to encoder instance - // (codec instance for codecs that encoder and decoder share the same - // instance). This method is called to free the memory that |ptr_inst| is - // pointing to. - // - // Input: - // -ptr_inst : pointer to encoder instance. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual void InternalDestructEncoderInst(void* ptr_inst) = 0; - - /////////////////////////////////////////////////////////////////////////// - // int16_t InternalResetEncoder() - // This method is called to reset the states of encoder. However, the - // current parameters, e.g. frame-length, should remain as they are. For - // most of the codecs a re-initialization of the encoder is what needs to - // be down. But for iSAC we like to keep the BWE history so we cannot - // re-initialize. As soon as such an API is implemented in iSAC this method - // has to be overwritten in ACMISAC class. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - virtual int16_t InternalResetEncoder(); - - /////////////////////////////////////////////////////////////////////////// - // int16_t ProcessFrameVADDTX() - // This function is called when a full frame of audio is available. It will - // break the audio frame into blocks such that each block could be processed - // by VAD & CN/DTX. If a frame is divided into two blocks then there are two - // cases. First, the first block is active, the second block will not be - // processed by CN/DTX but only by VAD and return to caller with - // '*samples_processed' set to zero. There, the audio frame will be encoded - // by the encoder. Second, the first block is inactive and is processed by - // CN/DTX, then we stop processing the next block and return to the caller - // which is EncodeSafe(), with "*samples_processed" equal to the number of - // samples in first block. - // - // Output: - // -bitstream : pointer to a buffer where DTX frame, if - // generated, will be written to. - // -bitstream_len_byte : contains the length of bit-stream in bytes, if - // generated. Zero if no bit-stream is generated. - // -samples_processed : contains no of samples that actually CN has - // processed. Those samples processed by CN will not - // be encoded by the encoder, obviously. If - // contains zero, it means that the frame has been - // identified as active by VAD. Note that - // "*samples_processed" might be non-zero but - // "*bitstream_len_byte" be zero. - // - // Return value: - // -1 if failed, - // 0 if succeeded. - // - int16_t ProcessFrameVADDTX(uint8_t* bitstream, - int16_t* bitstream_len_byte, - int16_t* samples_processed); - - /////////////////////////////////////////////////////////////////////////// - // CanChangeEncodingParam() - // Check if the codec parameters can be changed. In conferencing normally - // codec parameters cannot be changed. The exception is bit-rate of isac. - // - // return value: - // -true if codec parameters are allowed to change. - // -false otherwise. - // - virtual bool CanChangeEncodingParam(CodecInst& codec_inst); - - /////////////////////////////////////////////////////////////////////////// - // CurrentRate() - // Call to get the current encoding rate of the encoder. This function - // should be overwritten for codecs which automatically change their - // target rate. One example is iSAC. The output of the function is the - // current target rate. - // - // Output: - // -rate_bps : the current target rate of the codec. - // - virtual void CurrentRate(int32_t& /* rate_bps */); - - virtual void SaveDecoderParamSafe(const WebRtcACMCodecParams* codec_params); - - // &in_audio_[in_audio_ix_write_] always point to where new audio can be - // written to - int16_t in_audio_ix_write_; - - // &in_audio_[in_audio_ix_read_] points to where audio has to be read from - int16_t in_audio_ix_read_; - - int16_t in_timestamp_ix_write_; - - // Where the audio is stored before encoding, - // To save memory the following buffer can be allocated - // dynamically for 80 ms depending on the sampling frequency - // of the codec. - int16_t* in_audio_; - uint32_t* in_timestamp_; - - int16_t frame_len_smpl_; - uint16_t num_channels_; - - // This will point to a static database of the supported codecs - int16_t codec_id_; - - // This will account for the number of samples were not encoded - // the case is rare, either samples are missed due to overwrite - // at input buffer or due to encoding error - uint32_t num_missed_samples_; - - // True if the encoder instance created - bool encoder_exist_; - bool decoder_exist_; - // True if the encoder instance initialized - bool encoder_initialized_; - bool decoder_initialized_; - - bool registered_in_neteq_; - - // VAD/DTX - bool has_internal_dtx_; - WebRtcVadInst* ptr_vad_inst_; - bool vad_enabled_; - ACMVADMode vad_mode_; - int16_t vad_label_[MAX_FRAME_SIZE_10MSEC]; - bool dtx_enabled_; - WebRtcCngEncInst* ptr_dtx_inst_; - uint8_t num_lpc_params_; - bool sent_cn_previous_; - bool is_master_; - int16_t prev_frame_cng_; - - WebRtcACMCodecParams encoder_params_; - WebRtcACMCodecParams decoder_params_; - - // Used as a global lock for all available decoders - // so that no decoder is used when NetEQ decodes. - RWLockWrapper* neteq_decode_lock_; - // Used to lock wrapper internal data - // such as buffers and state variables. - RWLockWrapper& codec_wrapper_lock_; - - uint32_t last_encoded_timestamp_; - uint32_t last_timestamp_; - bool is_audio_buff_fresh_; - uint32_t unique_id_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_GENERIC_CODEC_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_gsmfr.cc b/webrtc/modules/audio_coding/main/source/acm_gsmfr.cc deleted file mode 100644 index 5ea0c56d9..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_gsmfr.cc +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_gsmfr.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" -#ifdef WEBRTC_CODEC_GSMFR -// NOTE! GSM-FR is not included in the open-source package. Modify this file -// or your codec API to match the function calls and names of used GSM-FR API -// file. -#include "gsmfr_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_GSMFR - -ACMGSMFR::ACMGSMFR(int16_t /* codec_id */) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL) { - return; -} - -ACMGSMFR::~ACMGSMFR() { - return; -} - -int16_t ACMGSMFR::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMGSMFR::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMGSMFR::EnableDTX() { - return -1; -} - -int16_t ACMGSMFR::DisableDTX() { - return -1; -} - -int16_t ACMGSMFR::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMGSMFR::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMGSMFR::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMGSMFR::CreateInstance(void) { - return NULL; -} - -int16_t ACMGSMFR::InternalCreateEncoder() { - return -1; -} - -void ACMGSMFR::DestructEncoderSafe() { - return; -} - -int16_t ACMGSMFR::InternalCreateDecoder() { - return -1; -} - -void ACMGSMFR::DestructDecoderSafe() { - return; -} - -void ACMGSMFR::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -#else //===================== Actual Implementation ======================= - -ACMGSMFR::ACMGSMFR(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL) { - codec_id_ = codec_id; - has_internal_dtx_ = true; - return; -} - -ACMGSMFR::~ACMGSMFR() { - if (encoder_inst_ptr_ != NULL) { - WebRtcGSMFR_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcGSMFR_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMGSMFR::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - *bitstream_len_byte = WebRtcGSMFR_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], - frame_len_smpl_, - (int16_t*)bitstream); - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += frame_len_smpl_; - return *bitstream_len_byte; -} - -int16_t ACMGSMFR::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMGSMFR::EnableDTX() { - if (dtx_enabled_) { - return 0; - } else if (encoder_exist_) { - if (WebRtcGSMFR_EncoderInit(encoder_inst_ptr_, 1) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "EnableDTX: cannot init encoder for GSMFR"); - return -1; - } - dtx_enabled_ = true; - return 0; - } else { - return -1; - } -} - -int16_t ACMGSMFR::DisableDTX() { - if (!dtx_enabled_) { - return 0; - } else if (encoder_exist_) { - if (WebRtcGSMFR_EncoderInit(encoder_inst_ptr_, 0) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "DisableDTX: cannot init encoder for GSMFR"); - return -1; - } - dtx_enabled_ = false; - return 0; - } else { - // encoder doesn't exists, therefore disabling is harmless - return 0; - } -} - -int16_t ACMGSMFR::InternalInitEncoder( - WebRtcACMCodecParams* codec_params) { - if (WebRtcGSMFR_EncoderInit(encoder_inst_ptr_, - ((codec_params->enable_dtx) ? 1 : 0)) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitEncoder: cannot init encoder for GSMFR"); - } - return 0; -} - -int16_t ACMGSMFR::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - if (WebRtcGSMFR_DecoderInit(decoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitDecoder: cannot init decoder for GSMFR"); - return -1; - } - return 0; -} - -int32_t ACMGSMFR::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodecDef: decoder is not initialized for GSMFR"); - return -1; - } - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_GSMFR_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderGSMFR, codec_inst.pltype, - decoder_inst_ptr_, 8000); - SET_GSMFR_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMGSMFR::CreateInstance(void) { - return NULL; -} - -int16_t ACMGSMFR::InternalCreateEncoder() { - if (WebRtcGSMFR_CreateEnc(&encoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: cannot create instance for GSMFR " - "encoder"); - return -1; - } - return 0; -} - -void ACMGSMFR::DestructEncoderSafe() { - if (encoder_inst_ptr_ != NULL) { - WebRtcGSMFR_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - encoder_exist_ = false; - encoder_initialized_ = false; -} - -int16_t ACMGSMFR::InternalCreateDecoder() { - if (WebRtcGSMFR_CreateDec(&decoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateDecoder: cannot create instance for GSMFR " - "decoder"); - return -1; - } - return 0; -} - -void ACMGSMFR::DestructDecoderSafe() { - if (decoder_inst_ptr_ != NULL) { - WebRtcGSMFR_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - decoder_exist_ = false; - decoder_initialized_ = false; -} - -void ACMGSMFR::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcGSMFR_FreeEnc((GSMFR_encinst_t_*) ptr_inst); - } - return; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_gsmfr.h b/webrtc/modules/audio_coding/main/source/acm_gsmfr.h deleted file mode 100644 index aa499734a..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_gsmfr.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_GSMFR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_GSMFR_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct GSMFR_encinst_t_; -struct GSMFR_decinst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMGSMFR : public ACMGenericCodec { - public: - explicit ACMGSMFR(int16_t codec_id); - ~ACMGSMFR(); - - // for FEC - ACMGenericCodec* CreateInstance(void); - - int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte); - - int16_t InternalInitEncoder(WebRtcACMCodecParams *codec_params); - - int16_t InternalInitDecoder(WebRtcACMCodecParams *codec_params); - - protected: - int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type); - - int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst); - - void DestructEncoderSafe(); - - void DestructDecoderSafe(); - - int16_t InternalCreateEncoder(); - - int16_t InternalCreateDecoder(); - - void InternalDestructEncoderInst(void* ptr_inst); - - int16_t EnableDTX(); - - int16_t DisableDTX(); - - GSMFR_encinst_t_* encoder_inst_ptr_; - GSMFR_decinst_t_* decoder_inst_ptr_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_GSMFR_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_ilbc.cc b/webrtc/modules/audio_coding/main/source/acm_ilbc.cc deleted file mode 100644 index 0f8049e80..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_ilbc.cc +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_ilbc.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_ILBC -#include "webrtc/modules/audio_coding/codecs/ilbc/interface/ilbc.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_ILBC - -ACMILBC::ACMILBC(int16_t /* codec_id */) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL) { - return; -} - -ACMILBC::~ACMILBC() { - return; -} - -int16_t ACMILBC::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMILBC::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMILBC::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMILBC::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMILBC::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMILBC::CreateInstance(void) { - return NULL; -} - -int16_t ACMILBC::InternalCreateEncoder() { - return -1; -} - -void ACMILBC::DestructEncoderSafe() { - return; -} - -int16_t ACMILBC::InternalCreateDecoder() { - return -1; -} - -void ACMILBC::DestructDecoderSafe() { - return; -} - -void ACMILBC::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -int16_t ACMILBC::SetBitRateSafe(const int32_t /* rate */) { - return -1; -} - -#else //===================== Actual Implementation ======================= - -ACMILBC::ACMILBC(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL) { - codec_id_ = codec_id; - return; -} - -ACMILBC::~ACMILBC() { - if (encoder_inst_ptr_ != NULL) { - WebRtcIlbcfix_EncoderFree(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcIlbcfix_DecoderFree(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMILBC::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - *bitstream_len_byte = WebRtcIlbcfix_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], - frame_len_smpl_, - (int16_t*)bitstream); - if (*bitstream_len_byte < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalEncode: error in encode for ILBC"); - return -1; - } - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += frame_len_smpl_; - return *bitstream_len_byte; -} - -int16_t ACMILBC::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMILBC::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { - // initialize with a correct processing block length - if ((160 == (codec_params->codec_inst).pacsize) || - (320 == (codec_params->codec_inst).pacsize)) { - // processing block of 20ms - return WebRtcIlbcfix_EncoderInit(encoder_inst_ptr_, 20); - } else if ((240 == (codec_params->codec_inst).pacsize) || - (480 == (codec_params->codec_inst).pacsize)) { - // processing block of 30ms - return WebRtcIlbcfix_EncoderInit(encoder_inst_ptr_, 30); - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitEncoder: invalid processing block"); - return -1; - } -} - -int16_t ACMILBC::InternalInitDecoder(WebRtcACMCodecParams* codec_params) { - // initialize with a correct processing block length - if ((160 == (codec_params->codec_inst).pacsize) || - (320 == (codec_params->codec_inst).pacsize)) { - // processing block of 20ms - return WebRtcIlbcfix_DecoderInit(decoder_inst_ptr_, 20); - } else if ((240 == (codec_params->codec_inst).pacsize) || - (480 == (codec_params->codec_inst).pacsize)) { - // processing block of 30ms - return WebRtcIlbcfix_DecoderInit(decoder_inst_ptr_, 30); - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalInitDecoder: invalid processing block"); - return -1; - } -} - -int32_t ACMILBC::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodeDef: decoder not initialized for ILBC"); - return -1; - } - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_ILBC_FUNCTION." - // Then return the structure back to NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderILBC, codec_inst.pltype, decoder_inst_ptr_, - 8000); - SET_ILBC_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMILBC::CreateInstance(void) { - return NULL; -} - -int16_t ACMILBC::InternalCreateEncoder() { - if (WebRtcIlbcfix_EncoderCreate(&encoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateEncoder: cannot create instance for ILBC " - "encoder"); - return -1; - } - return 0; -} - -void ACMILBC::DestructEncoderSafe() { - encoder_initialized_ = false; - encoder_exist_ = false; - if (encoder_inst_ptr_ != NULL) { - WebRtcIlbcfix_EncoderFree(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } -} - -int16_t ACMILBC::InternalCreateDecoder() { - if (WebRtcIlbcfix_DecoderCreate(&decoder_inst_ptr_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalCreateDecoder: cannot create instance for ILBC " - "decoder"); - return -1; - } - return 0; -} - -void ACMILBC::DestructDecoderSafe() { - decoder_initialized_ = false; - decoder_exist_ = false; - if (decoder_inst_ptr_ != NULL) { - WebRtcIlbcfix_DecoderFree(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } -} - -void ACMILBC::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcIlbcfix_EncoderFree((iLBC_encinst_t_*) ptr_inst); - } - return; -} - -int16_t ACMILBC::SetBitRateSafe(const int32_t rate) { - // Check that rate is valid. No need to store the value - if (rate == 13300) { - WebRtcIlbcfix_EncoderInit(encoder_inst_ptr_, 30); - } else if (rate == 15200) { - WebRtcIlbcfix_EncoderInit(encoder_inst_ptr_, 20); - } else { - return -1; - } - encoder_params_.codec_inst.rate = rate; - - return 0; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_ilbc.h b/webrtc/modules/audio_coding/main/source/acm_ilbc.h deleted file mode 100644 index bd2495fe3..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_ilbc.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ILBC_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ILBC_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct iLBC_encinst_t_; -struct iLBC_decinst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMILBC : public ACMGenericCodec { - public: - explicit ACMILBC(int16_t codec_id); - virtual ~ACMILBC(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual int16_t SetBitRateSafe(const int32_t rate) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - iLBC_encinst_t_* encoder_inst_ptr_; - iLBC_decinst_t_* decoder_inst_ptr_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ILBC_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_isac.cc b/webrtc/modules/audio_coding/main/source/acm_isac.cc deleted file mode 100644 index 61fa32f6d..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_isac.cc +++ /dev/null @@ -1,903 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_isac.h" - -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_ISAC -#include "webrtc/modules/audio_coding/codecs/isac/main/interface/isac.h" -#include "webrtc/modules/audio_coding/main/source/acm_isac_macros.h" -#endif - -#ifdef WEBRTC_CODEC_ISACFX -#include "webrtc/modules/audio_coding/codecs/isac/fix/interface/isacfix.h" -#include "webrtc/modules/audio_coding/main/source/acm_isac_macros.h" -#endif - -namespace webrtc { - -namespace acm1 { - -// we need this otherwise we cannot use forward declaration -// in the header file -#if (defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) -struct ACMISACInst { - ACM_ISAC_STRUCT *inst; -}; -#endif - -#define ISAC_MIN_RATE 10000 -#define ISAC_MAX_RATE 56000 - -// Tables for bandwidth estimates -#define NR_ISAC_BANDWIDTHS 24 -static const int32_t kIsacRatesWb[NR_ISAC_BANDWIDTHS] = { - 10000, 11100, 12300, 13700, 15200, 16900, - 18800, 20900, 23300, 25900, 28700, 31900, - 10100, 11200, 12400, 13800, 15300, 17000, - 18900, 21000, 23400, 26000, 28800, 32000 -}; - -static const int32_t kIsacRatesSwb[NR_ISAC_BANDWIDTHS] = { - 10000, 11000, 12400, 13800, 15300, 17000, - 18900, 21000, 23200, 25400, 27600, 29800, - 32000, 34100, 36300, 38500, 40700, 42900, - 45100, 47300, 49500, 51700, 53900, 56000, -}; - -#if (!defined(WEBRTC_CODEC_ISAC) && !defined(WEBRTC_CODEC_ISACFX)) - -ACMISAC::ACMISAC(int16_t /* codec_id */) - : codec_inst_ptr_(NULL), - is_enc_initialized_(false), - isac_coding_mode_(CHANNEL_INDEPENDENT), - enforce_frame_size_(false), - isac_currentBN_(32000), - samples_in10MsAudio_(160) { // Initiates to 16 kHz mode. - // Initiate decoder parameters for the 32 kHz mode. - memset(&decoder_params32kHz_, 0, sizeof(WebRtcACMCodecParams)); - decoder_params32kHz_.codec_inst.pltype = -1; - - return; -} - -ACMISAC::~ACMISAC() { - return; -} - -ACMGenericCodec* ACMISAC::CreateInstance(void) { - return NULL; -} - -int16_t ACMISAC::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMISAC::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMISAC::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMISAC::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMISAC::InternalCreateDecoder() { - return -1; -} - -void ACMISAC::DestructDecoderSafe() { - return; -} - -int16_t ACMISAC::InternalCreateEncoder() { - return -1; -} - -void ACMISAC::DestructEncoderSafe() { - return; -} - -int32_t ACMISAC::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -void ACMISAC::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -int16_t ACMISAC::DeliverCachedIsacData( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */, - uint32_t* /* timestamp */, - WebRtcACMEncodingType* /* encoding_type */, - const uint16_t /* isac_rate */, - const uint8_t /* isac_bw_estimate */) { - return -1; -} - -int16_t ACMISAC::Transcode(uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */, - int16_t /* q_bwe */, - int32_t /* scale */, - bool /* is_red */) { - return -1; -} - -int16_t ACMISAC::SetBitRateSafe(int32_t /* bit_rate */) { - return -1; -} - -int32_t ACMISAC::GetEstimatedBandwidthSafe() { - return -1; -} - -int32_t ACMISAC::SetEstimatedBandwidthSafe( - int32_t /* estimated_bandwidth */) { - return -1; -} - -int32_t ACMISAC::GetRedPayloadSafe(uint8_t* /* red_payload */, - int16_t* /* payload_bytes */) { - return -1; -} - -int16_t ACMISAC::UpdateDecoderSampFreq(int16_t /* codec_id */) { - return -1; -} - -int16_t ACMISAC::UpdateEncoderSampFreq( - uint16_t /* encoder_samp_freq_hz */) { - return -1; -} - -int16_t ACMISAC::EncoderSampFreq(uint16_t& /* samp_freq_hz */) { - return -1; -} - -int32_t ACMISAC::ConfigISACBandwidthEstimator( - const uint8_t /* init_frame_size_msec */, - const uint16_t /* init_rate_bit_per_sec */, - const bool /* enforce_frame_size */) { - return -1; -} - -int32_t ACMISAC::SetISACMaxPayloadSize( - const uint16_t /* max_payload_len_bytes */) { - return -1; -} - -int32_t ACMISAC::SetISACMaxRate( - const uint32_t /* max_rate_bit_per_sec */) { - return -1; -} - -void ACMISAC::UpdateFrameLen() { - return; -} - -void ACMISAC::CurrentRate(int32_t& /*rate_bit_per_sec */) { - return; -} - -bool -ACMISAC::DecoderParamsSafe( - WebRtcACMCodecParams* /* dec_params */, - const uint8_t /* payload_type */) { - return false; -} - -void -ACMISAC::SaveDecoderParamSafe( - const WebRtcACMCodecParams* /* codec_params */) { - return; -} - -int16_t ACMISAC::REDPayloadISAC( - const int32_t /* isac_rate */, - const int16_t /* isac_bw_estimate */, - uint8_t* /* payload */, - int16_t* /* payload_len_bytes */) { - return -1; -} - -#else //===================== Actual Implementation ======================= - -#ifdef WEBRTC_CODEC_ISACFX - -// How the scaling is computed. iSAC computes a gain based on the -// bottleneck. It follows the following expression for that -// -// G(BN_kbps) = pow(10, (a + b * BN_kbps + c * BN_kbps * BN_kbps) / 20.0) -// / 3.4641; -// -// Where for 30 ms framelength we have, -// -// a = -23; b = 0.48; c = 0; -// -// As the default encoder is operating at 32kbps we have the scale as -// -// S(BN_kbps) = G(BN_kbps) / G(32); - -#define ISAC_NUM_SUPPORTED_RATES 9 - -static const uint16_t kIsacSuportedRates[ISAC_NUM_SUPPORTED_RATES] = { - 32000, 30000, 26000, 23000, 21000, - 19000, 17000, 15000, 12000 -}; - -static const float kIsacScale[ISAC_NUM_SUPPORTED_RATES] = { - 1.0f, 0.8954f, 0.7178f, 0.6081f, 0.5445f, - 0.4875f, 0.4365f, 0.3908f, 0.3311f -}; - -enum IsacSamplingRate { - kIsacWideband = 16, - kIsacSuperWideband = 32 -}; - -static float ACMISACFixTranscodingScale(uint16_t rate) { - // find the scale for transcoding, the scale is rounded - // downward - float scale = -1; - for (int16_t n = 0; n < ISAC_NUM_SUPPORTED_RATES; n++) { - if (rate >= kIsacSuportedRates[n]) { - scale = kIsacScale[n]; - break; - } - } - return scale; -} - -static void ACMISACFixGetSendBitrate(ACM_ISAC_STRUCT* inst, - int32_t* bottleneck) { - *bottleneck = WebRtcIsacfix_GetUplinkBw(inst); -} - -static int16_t ACMISACFixGetNewBitstream(ACM_ISAC_STRUCT* inst, - int16_t bwe_index, - int16_t /* jitter_index */, - int32_t rate, - int16_t* bitstream, - bool is_red) { - if (is_red) { - // RED not supported with iSACFIX - return -1; - } - float scale = ACMISACFixTranscodingScale((uint16_t) rate); - return WebRtcIsacfix_GetNewBitStream(inst, bwe_index, scale, bitstream); -} - -static int16_t ACMISACFixGetSendBWE(ACM_ISAC_STRUCT* inst, - int16_t* rate_index, - int16_t* /* dummy */) { - int16_t local_rate_index; - int16_t status = WebRtcIsacfix_GetDownLinkBwIndex(inst, - &local_rate_index); - if (status < 0) { - return -1; - } else { - *rate_index = local_rate_index; - return 0; - } -} - -static int16_t ACMISACFixControlBWE(ACM_ISAC_STRUCT* inst, - int32_t rate_bps, - int16_t frame_size_ms, - int16_t enforce_frame_size) { - return WebRtcIsacfix_ControlBwe(inst, (int16_t) rate_bps, frame_size_ms, - enforce_frame_size); -} - -static int16_t ACMISACFixControl(ACM_ISAC_STRUCT* inst, - int32_t rate_bps, - int16_t frame_size_ms) { - return WebRtcIsacfix_Control(inst, (int16_t) rate_bps, frame_size_ms); -} - -// The following two function should have the same signature as their counter -// part in iSAC floating-point, i.e. WebRtcIsac_EncSampRate & -// WebRtcIsac_DecSampRate. -static uint16_t ACMISACFixGetEncSampRate(ACM_ISAC_STRUCT* /* inst */) { - return 16000; -} - -static uint16_t ACMISACFixGetDecSampRate(ACM_ISAC_STRUCT* /* inst */) { - return 16000; -} - -#endif - -ACMISAC::ACMISAC(int16_t codec_id) - : is_enc_initialized_(false), - isac_coding_mode_(CHANNEL_INDEPENDENT), - enforce_frame_size_(false), - isac_current_bn_(32000), - samples_in_10ms_audio_(160) { // Initiates to 16 kHz mode. - codec_id_ = codec_id; - - // Create codec instance. - codec_inst_ptr_ = new ACMISACInst; - if (codec_inst_ptr_ == NULL) { - return; - } - codec_inst_ptr_->inst = NULL; - - // Initiate decoder parameters for the 32 kHz mode. - memset(&decoder_params_32khz_, 0, sizeof(WebRtcACMCodecParams)); - decoder_params_32khz_.codec_inst.pltype = -1; - - // TODO(tlegrand): Check if the following is really needed, now that - // ACMGenericCodec has been updated to initialize this value. - // Initialize values that can be used uninitialized otherwise - decoder_params_.codec_inst.pltype = -1; -} - -ACMISAC::~ACMISAC() { - if (codec_inst_ptr_ != NULL) { - if (codec_inst_ptr_->inst != NULL) { - ACM_ISAC_FREE(codec_inst_ptr_->inst); - codec_inst_ptr_->inst = NULL; - } - delete codec_inst_ptr_; - codec_inst_ptr_ = NULL; - } - return; -} - -ACMGenericCodec* ACMISAC::CreateInstance(void) { - return NULL; -} - -int16_t ACMISAC::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - // ISAC takes 10ms audio everytime we call encoder, therefor, - // it should be treated like codecs with 'basic coding block' - // non-zero, and the following 'while-loop' should not be necessary. - // However, due to a mistake in the codec the frame-size might change - // at the first 10ms pushed in to iSAC if the bit-rate is low, this is - // sort of a bug in iSAC. to address this we treat iSAC as the - // following. - if (codec_inst_ptr_ == NULL) { - return -1; - } - *bitstream_len_byte = 0; - while ((*bitstream_len_byte == 0) && (in_audio_ix_read_ < frame_len_smpl_)) { - if (in_audio_ix_read_ > in_audio_ix_write_) { - // something is wrong. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "The actual fram-size of iSAC appears to be larger that " - "expected. All audio pushed in but no bit-stream is " - "generated."); - return -1; - } - *bitstream_len_byte = ACM_ISAC_ENCODE(codec_inst_ptr_->inst, - &in_audio_[in_audio_ix_read_], - (int16_t*)bitstream); - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += samples_in_10ms_audio_; - } - if (*bitstream_len_byte == 0) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, unique_id_, - "ISAC Has encoded the whole frame but no bit-stream is " - "generated."); - } - - // a packet is generated iSAC, is set in adaptive mode may change - // the frame length and we like to update the bottleneck value as - // well, although updating bottleneck is not crucial - if ((*bitstream_len_byte > 0) && (isac_coding_mode_ == ADAPTIVE)) { - ACM_ISAC_GETSENDBITRATE(codec_inst_ptr_->inst, &isac_current_bn_); - } - UpdateFrameLen(); - return *bitstream_len_byte; -} - -int16_t ACMISAC::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_sample */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMISAC::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { - // if rate is set to -1 then iSAC has to be in adaptive mode - if (codec_params->codec_inst.rate == -1) { - isac_coding_mode_ = ADAPTIVE; - } else if ((codec_params->codec_inst.rate >= ISAC_MIN_RATE) && - (codec_params->codec_inst.rate <= ISAC_MAX_RATE)) { - // sanity check that rate is in acceptable range - isac_coding_mode_ = CHANNEL_INDEPENDENT; - isac_current_bn_ = codec_params->codec_inst.rate; - } else { - return -1; - } - - // we need to set the encoder sampling frequency. - if (UpdateEncoderSampFreq((uint16_t) codec_params->codec_inst.plfreq) - < 0) { - return -1; - } - if (ACM_ISAC_ENCODERINIT(codec_inst_ptr_->inst, isac_coding_mode_) < 0) { - return -1; - } - - // apply the frame-size and rate if operating in - // channel-independent mode - if (isac_coding_mode_ == CHANNEL_INDEPENDENT) { - if (ACM_ISAC_CONTROL(codec_inst_ptr_->inst, - codec_params->codec_inst.rate, - codec_params->codec_inst.pacsize / - (codec_params->codec_inst.plfreq / 1000)) < 0) { - return -1; - } - } else { - // We need this for adaptive case and has to be called - // after initialization - ACM_ISAC_GETSENDBITRATE(codec_inst_ptr_->inst, &isac_current_bn_); - } - frame_len_smpl_ = ACM_ISAC_GETNEWFRAMELEN(codec_inst_ptr_->inst); - return 0; -} - -int16_t ACMISAC::InternalInitDecoder(WebRtcACMCodecParams* codec_params) { - if (codec_inst_ptr_ == NULL) { - return -1; - } - - // set decoder sampling frequency. - if (codec_params->codec_inst.plfreq == 32000 || - codec_params->codec_inst.plfreq == 48000) { - UpdateDecoderSampFreq(ACMCodecDB::kISACSWB); - } else { - UpdateDecoderSampFreq(ACMCodecDB::kISAC); - } - - // in a one-way communication we may never register send-codec. - // However we like that the BWE to work properly so it has to - // be initialized. The BWE is initialized when iSAC encoder is initialized. - // Therefore, we need this. - if (!encoder_initialized_) { - // Since we don't require a valid rate or a valid packet size when - // initializing the decoder, we set valid values before initializing encoder - codec_params->codec_inst.rate = kIsacWbDefaultRate; - codec_params->codec_inst.pacsize = kIsacPacSize960; - if (InternalInitEncoder(codec_params) < 0) { - return -1; - } - encoder_initialized_ = true; - } - - return ACM_ISAC_DECODERINIT(codec_inst_ptr_->inst); -} - -int16_t ACMISAC::InternalCreateDecoder() { - if (codec_inst_ptr_ == NULL) { - return -1; - } - int16_t status = ACM_ISAC_CREATE(&(codec_inst_ptr_->inst)); - - // specific to codecs with one instance for encoding and decoding - encoder_initialized_ = false; - if (status < 0) { - encoder_exist_ = false; - } else { - encoder_exist_ = true; - } - return status; -} - -void ACMISAC::DestructDecoderSafe() { - // codec with shared instance cannot delete. - decoder_initialized_ = false; - return; -} - -int16_t ACMISAC::InternalCreateEncoder() { - if (codec_inst_ptr_ == NULL) { - return -1; - } - int16_t status = ACM_ISAC_CREATE(&(codec_inst_ptr_->inst)); - - // specific to codecs with one instance for encoding and decoding - decoder_initialized_ = false; - if (status < 0) { - decoder_exist_ = false; - } else { - decoder_exist_ = true; - } - return status; -} - -void ACMISAC::DestructEncoderSafe() { - // codec with shared instance cannot delete. - encoder_initialized_ = false; - return; -} - -int32_t ACMISAC::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - // Sanity checks - if (codec_inst_ptr_ == NULL) { - return -1; - } - if (!decoder_initialized_ || !decoder_exist_) { - return -1; - } - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_ISAC_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - if (codec_inst.plfreq == 16000) { - SET_CODEC_PAR((codec_def), kDecoderISAC, codec_inst.pltype, - codec_inst_ptr_->inst, 16000); -#ifdef WEBRTC_CODEC_ISAC - SET_ISAC_FUNCTIONS((codec_def)); -#else - SET_ISACfix_FUNCTIONS((codec_def)); -#endif - } else { -#ifdef WEBRTC_CODEC_ISAC - // Decoder is either @ 16 kHz or 32 kHz. Even if encoder is set @ 48 kHz - // decoding is @ 32 kHz. - if (codec_inst.plfreq == 32000) { - SET_CODEC_PAR((codec_def), kDecoderISACswb, codec_inst.pltype, - codec_inst_ptr_->inst, 32000); - SET_ISACSWB_FUNCTIONS((codec_def)); - } else { - SET_CODEC_PAR((codec_def), kDecoderISACfb, codec_inst.pltype, - codec_inst_ptr_->inst, 32000); - SET_ISACFB_FUNCTIONS((codec_def)); - } -#else - return -1; -#endif - } - return 0; -} - -void ACMISAC::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - ACM_ISAC_FREE((ACM_ISAC_STRUCT *) ptr_inst); - } - return; -} - -int16_t ACMISAC::Transcode(uint8_t* bitstream, - int16_t* bitstream_len_byte, - int16_t q_bwe, - int32_t rate, - bool is_red) { - int16_t jitter_info = 0; - // transcode from a higher rate to lower rate sanity check - if (codec_inst_ptr_ == NULL) { - return -1; - } - - *bitstream_len_byte = ACM_ISAC_GETNEWBITSTREAM(codec_inst_ptr_->inst, q_bwe, - jitter_info, rate, - (int16_t*)bitstream, - (is_red) ? 1 : 0); - - if (*bitstream_len_byte < 0) { - // error happened - *bitstream_len_byte = 0; - return -1; - } else { - return *bitstream_len_byte; - } -} - -int16_t ACMISAC::SetBitRateSafe(int32_t bit_rate) { - if (codec_inst_ptr_ == NULL) { - return -1; - } - uint16_t encoder_samp_freq; - EncoderSampFreq(encoder_samp_freq); - bool reinit = false; - // change the BN of iSAC - if (bit_rate == -1) { - // ADAPTIVE MODE - // Check if it was already in adaptive mode - if (isac_coding_mode_ != ADAPTIVE) { - // was not in adaptive, then set the mode to adaptive - // and flag for re-initialization - isac_coding_mode_ = ADAPTIVE; - reinit = true; - } - } else if ((bit_rate >= ISAC_MIN_RATE) && (bit_rate <= ISAC_MAX_RATE)) { - // Sanity check if the rate valid - // check if it was in channel-independent mode before - if (isac_coding_mode_ != CHANNEL_INDEPENDENT) { - // was not in channel independent, set the mode to - // channel-independent and flag for re-initialization - isac_coding_mode_ = CHANNEL_INDEPENDENT; - reinit = true; - } - // store the bottleneck - isac_current_bn_ = (uint16_t) bit_rate; - } else { - // invlaid rate - return -1; - } - - int16_t status = 0; - if (reinit) { - // initialize and check if it is successful - if (ACM_ISAC_ENCODERINIT(codec_inst_ptr_->inst, isac_coding_mode_) < 0) { - // failed initialization - return -1; - } - } - if (isac_coding_mode_ == CHANNEL_INDEPENDENT) { - status = ACM_ISAC_CONTROL( - codec_inst_ptr_->inst, isac_current_bn_, - (encoder_samp_freq == 32000 || encoder_samp_freq == 48000) ? 30 : - (frame_len_smpl_ / 16)); - if (status < 0) { - status = -1; - } - } - - // Update encoder parameters - encoder_params_.codec_inst.rate = bit_rate; - - UpdateFrameLen(); - return status; -} - -int32_t ACMISAC::GetEstimatedBandwidthSafe() { - int16_t bandwidth_index = 0; - int16_t delay_index = 0; - int samp_rate; - - // Get bandwidth information - ACM_ISAC_GETSENDBWE(codec_inst_ptr_->inst, &bandwidth_index, &delay_index); - - // Validy check of index - if ((bandwidth_index < 0) || (bandwidth_index >= NR_ISAC_BANDWIDTHS)) { - return -1; - } - - // Check sample frequency - samp_rate = ACM_ISAC_GETDECSAMPRATE(codec_inst_ptr_->inst); - if (samp_rate == 16000) { - return kIsacRatesWb[bandwidth_index]; - } else { - return kIsacRatesSwb[bandwidth_index]; - } -} - -int32_t ACMISAC::SetEstimatedBandwidthSafe( - int32_t estimated_bandwidth) { - int samp_rate; - int16_t bandwidth_index; - - // Check sample frequency and choose appropriate table - samp_rate = ACM_ISAC_GETENCSAMPRATE(codec_inst_ptr_->inst); - - if (samp_rate == 16000) { - // Search through the WB rate table to find the index - bandwidth_index = NR_ISAC_BANDWIDTHS / 2 - 1; - for (int i = 0; i < (NR_ISAC_BANDWIDTHS / 2); i++) { - if (estimated_bandwidth == kIsacRatesWb[i]) { - bandwidth_index = i; - break; - } else if (estimated_bandwidth - == kIsacRatesWb[i + NR_ISAC_BANDWIDTHS / 2]) { - bandwidth_index = i + NR_ISAC_BANDWIDTHS / 2; - break; - } else if (estimated_bandwidth < kIsacRatesWb[i]) { - bandwidth_index = i; - break; - } - } - } else { - // Search through the SWB rate table to find the index - bandwidth_index = NR_ISAC_BANDWIDTHS - 1; - for (int i = 0; i < NR_ISAC_BANDWIDTHS; i++) { - if (estimated_bandwidth <= kIsacRatesSwb[i]) { - bandwidth_index = i; - break; - } - } - } - - // Set iSAC Bandwidth Estimate - ACM_ISAC_SETBWE(codec_inst_ptr_->inst, bandwidth_index); - - return 0; -} - -int32_t ACMISAC::GetRedPayloadSafe( -#if (!defined(WEBRTC_CODEC_ISAC)) - uint8_t* /* red_payload */, int16_t* /* payload_bytes */) { - return -1; -#else - uint8_t* red_payload, int16_t* payload_bytes) { - int16_t bytes = WebRtcIsac_GetRedPayload(codec_inst_ptr_->inst, - (int16_t*)red_payload); - if (bytes < 0) { - return -1; - } - *payload_bytes = bytes; - return 0; -#endif -} - -int16_t ACMISAC::UpdateDecoderSampFreq( -#ifdef WEBRTC_CODEC_ISAC - int16_t codec_id) { - // The decoder supports only wideband and super-wideband. - if (ACMCodecDB::kISAC == codec_id) { - return WebRtcIsac_SetDecSampRate(codec_inst_ptr_->inst, 16000); - } else if (ACMCodecDB::kISACSWB == codec_id || - ACMCodecDB::kISACFB == codec_id) { - return WebRtcIsac_SetDecSampRate(codec_inst_ptr_->inst, 32000); - } else { - return -1; - } -#else - int16_t /* codec_id */) { - return 0; -#endif -} - -int16_t ACMISAC::UpdateEncoderSampFreq( -#ifdef WEBRTC_CODEC_ISAC - uint16_t encoder_samp_freq_hz) { - uint16_t current_samp_rate_hz; - EncoderSampFreq(current_samp_rate_hz); - - if (current_samp_rate_hz != encoder_samp_freq_hz) { - if ((encoder_samp_freq_hz != 16000) && - (encoder_samp_freq_hz != 32000) && - (encoder_samp_freq_hz != 48000)) { - return -1; - } else { - in_audio_ix_read_ = 0; - in_audio_ix_write_ = 0; - in_timestamp_ix_write_ = 0; - if (WebRtcIsac_SetEncSampRate(codec_inst_ptr_->inst, - encoder_samp_freq_hz) < 0) { - return -1; - } - samples_in_10ms_audio_ = encoder_samp_freq_hz / 100; - frame_len_smpl_ = ACM_ISAC_GETNEWFRAMELEN(codec_inst_ptr_->inst); - encoder_params_.codec_inst.pacsize = frame_len_smpl_; - encoder_params_.codec_inst.plfreq = encoder_samp_freq_hz; - return 0; - } - } -#else - uint16_t /* codec_id */) { -#endif - return 0; -} - -int16_t ACMISAC::EncoderSampFreq(uint16_t& samp_freq_hz) { - samp_freq_hz = ACM_ISAC_GETENCSAMPRATE(codec_inst_ptr_->inst); - return 0; -} - -int32_t ACMISAC::ConfigISACBandwidthEstimator( - const uint8_t init_frame_size_msec, - const uint16_t init_rate_bit_per_sec, - const bool enforce_frame_size) { - int16_t status; - { - uint16_t samp_freq_hz; - EncoderSampFreq(samp_freq_hz); - // TODO(turajs): at 32kHz we hardcode calling with 30ms and enforce - // the frame-size otherwise we might get error. Revise if - // control-bwe is changed. - if (samp_freq_hz == 32000 || samp_freq_hz == 48000) { - status = ACM_ISAC_CONTROL_BWE(codec_inst_ptr_->inst, - init_rate_bit_per_sec, 30, 1); - } else { - status = ACM_ISAC_CONTROL_BWE(codec_inst_ptr_->inst, - init_rate_bit_per_sec, - init_frame_size_msec, - enforce_frame_size ? 1 : 0); - } - } - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Couldn't config iSAC BWE."); - return -1; - } - UpdateFrameLen(); - ACM_ISAC_GETSENDBITRATE(codec_inst_ptr_->inst, &isac_current_bn_); - return 0; -} - -int32_t ACMISAC::SetISACMaxPayloadSize( - const uint16_t max_payload_len_bytes) { - return ACM_ISAC_SETMAXPAYLOADSIZE(codec_inst_ptr_->inst, - max_payload_len_bytes); -} - -int32_t ACMISAC::SetISACMaxRate( - const uint32_t max_rate_bit_per_sec) { - return ACM_ISAC_SETMAXRATE(codec_inst_ptr_->inst, max_rate_bit_per_sec); -} - -void ACMISAC::UpdateFrameLen() { - frame_len_smpl_ = ACM_ISAC_GETNEWFRAMELEN(codec_inst_ptr_->inst); - encoder_params_.codec_inst.pacsize = frame_len_smpl_; -} - -void ACMISAC::CurrentRate(int32_t& rate_bit_per_sec) { - if (isac_coding_mode_ == ADAPTIVE) { - ACM_ISAC_GETSENDBITRATE(codec_inst_ptr_->inst, &rate_bit_per_sec); - } -} - -bool ACMISAC::DecoderParamsSafe(WebRtcACMCodecParams* dec_params, - const uint8_t payload_type) { - if (decoder_initialized_) { - if (payload_type == decoder_params_.codec_inst.pltype) { - memcpy(dec_params, &decoder_params_, sizeof(WebRtcACMCodecParams)); - return true; - } - if (payload_type == decoder_params_32khz_.codec_inst.pltype) { - memcpy(dec_params, &decoder_params_32khz_, sizeof(WebRtcACMCodecParams)); - return true; - } - } - return false; -} - -void ACMISAC::SaveDecoderParamSafe(const WebRtcACMCodecParams* codec_params) { - // set decoder sampling frequency. - if (codec_params->codec_inst.plfreq == 32000 || - codec_params->codec_inst.plfreq == 48000) { - memcpy(&decoder_params_32khz_, codec_params, sizeof(WebRtcACMCodecParams)); - } else { - memcpy(&decoder_params_, codec_params, sizeof(WebRtcACMCodecParams)); - } -} - -int16_t ACMISAC::REDPayloadISAC(const int32_t isac_rate, - const int16_t isac_bw_estimate, - uint8_t* payload, - int16_t* payload_len_bytes) { - int16_t status; - ReadLockScoped rl(codec_wrapper_lock_); - status = Transcode(payload, payload_len_bytes, isac_bw_estimate, isac_rate, - true); - return status; -} - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_isac.h b/webrtc/modules/audio_coding/main/source/acm_isac.h deleted file mode 100644 index 20b6c5391..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_isac.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ISAC_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ISAC_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -namespace webrtc { - -namespace acm1 { - -struct ACMISACInst; - -enum IsacCodingMode { - ADAPTIVE, - CHANNEL_INDEPENDENT -}; - -class ACMISAC : public ACMGenericCodec { - public: - explicit ACMISAC(int16_t codec_id); - virtual ~ACMISAC(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - int16_t DeliverCachedIsacData(uint8_t* bitstream, - int16_t* bitstream_len_byte, - uint32_t* timestamp, - WebRtcACMEncodingType* encoding_type, - const uint16_t isac_rate, - const uint8_t isac_bwestimate); - - int16_t DeliverCachedData(uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */, - uint32_t* /* timestamp */, - WebRtcACMEncodingType* /* encoding_type */) { - return -1; - } - - virtual int16_t UpdateDecoderSampFreq(int16_t codec_id) OVERRIDE; - - virtual int16_t UpdateEncoderSampFreq(uint16_t samp_freq_hz) OVERRIDE; - - virtual int16_t EncoderSampFreq(uint16_t& samp_freq_hz) OVERRIDE; - - virtual int32_t ConfigISACBandwidthEstimator( - const uint8_t init_frame_size_msec, - const uint16_t init_rate_bit_per_sec, - const bool enforce_frame_size) OVERRIDE; - - virtual int32_t SetISACMaxPayloadSize( - const uint16_t max_payload_len_bytes) OVERRIDE; - - virtual int32_t SetISACMaxRate(const uint32_t max_rate_bit_per_sec) OVERRIDE; - - virtual int16_t REDPayloadISAC(const int32_t isac_rate, - const int16_t isac_bw_estimate, - uint8_t* payload, - int16_t* payload_len_bytes) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t SetBitRateSafe(const int32_t bit_rate) OVERRIDE; - - virtual int32_t GetEstimatedBandwidthSafe() OVERRIDE; - - virtual int32_t SetEstimatedBandwidthSafe( - int32_t estimated_bandwidth) OVERRIDE; - - virtual int32_t GetRedPayloadSafe(uint8_t* red_payload, - int16_t* payload_bytes) OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - int16_t Transcode(uint8_t* bitstream, - int16_t* bitstream_len_byte, - int16_t q_bwe, - int32_t rate, - bool is_red); - - virtual void CurrentRate(int32_t& rate_bit_per_sec) OVERRIDE; - - void UpdateFrameLen(); - - virtual bool DecoderParamsSafe(WebRtcACMCodecParams* dec_params, - const uint8_t payload_type) OVERRIDE; - - virtual void SaveDecoderParamSafe( - const WebRtcACMCodecParams* codec_params) OVERRIDE; - - ACMISACInst* codec_inst_ptr_; - bool is_enc_initialized_; - IsacCodingMode isac_coding_mode_; - bool enforce_frame_size_; - int32_t isac_current_bn_; - uint16_t samples_in_10ms_audio_; - WebRtcACMCodecParams decoder_params_32khz_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ISAC_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_isac_macros.h b/webrtc/modules/audio_coding/main/source/acm_isac_macros.h deleted file mode 100644 index 01e1e44b3..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_isac_macros.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ISAC_MACROS_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ISAC_MACROS_H_ - -#include "webrtc/engine_configurations.h" - -namespace webrtc { - -namespace acm1 { - -#ifdef WEBRTC_CODEC_ISAC -#define ACM_ISAC_CREATE WebRtcIsac_Create -#define ACM_ISAC_FREE WebRtcIsac_Free -#define ACM_ISAC_ENCODERINIT WebRtcIsac_EncoderInit -#define ACM_ISAC_ENCODE WebRtcIsac_Encode -#define ACM_ISAC_DECODERINIT WebRtcIsac_DecoderInit -#define ACM_ISAC_DECODE_BWE WebRtcIsac_UpdateBwEstimate -#define ACM_ISAC_DECODE_B WebRtcIsac_Decode -#define ACM_ISAC_DECODEPLC WebRtcIsac_DecodePlc -#define ACM_ISAC_CONTROL WebRtcIsac_Control -#define ACM_ISAC_CONTROL_BWE WebRtcIsac_ControlBwe -#define ACM_ISAC_GETFRAMELEN WebRtcIsac_ReadFrameLen -#define ACM_ISAC_GETERRORCODE WebRtcIsac_GetErrorCode -#define ACM_ISAC_GETSENDBITRATE WebRtcIsac_GetUplinkBw -#define ACM_ISAC_SETMAXPAYLOADSIZE WebRtcIsac_SetMaxPayloadSize -#define ACM_ISAC_SETMAXRATE WebRtcIsac_SetMaxRate -#define ACM_ISAC_GETNEWBITSTREAM WebRtcIsac_GetNewBitStream -#define ACM_ISAC_GETSENDBWE WebRtcIsac_GetDownLinkBwIndex -#define ACM_ISAC_SETBWE WebRtcIsac_UpdateUplinkBw -#define ACM_ISAC_GETBWE WebRtcIsac_ReadBwIndex -#define ACM_ISAC_GETNEWFRAMELEN WebRtcIsac_GetNewFrameLen -#define ACM_ISAC_STRUCT ISACStruct -#define ACM_ISAC_GETENCSAMPRATE WebRtcIsac_EncSampRate -#define ACM_ISAC_GETDECSAMPRATE WebRtcIsac_DecSampRate -#endif - -#ifdef WEBRTC_CODEC_ISACFX -#define ACM_ISAC_CREATE WebRtcIsacfix_Create -#define ACM_ISAC_FREE WebRtcIsacfix_Free -#define ACM_ISAC_ENCODERINIT WebRtcIsacfix_EncoderInit -#define ACM_ISAC_ENCODE WebRtcIsacfix_Encode -#define ACM_ISAC_DECODERINIT WebRtcIsacfix_DecoderInit -#define ACM_ISAC_DECODE_BWE WebRtcIsacfix_UpdateBwEstimate -#define ACM_ISAC_DECODE_B WebRtcIsacfix_Decode -#define ACM_ISAC_DECODEPLC WebRtcIsacfix_DecodePlc -#define ACM_ISAC_CONTROL ACMISACFixControl // local Impl -#define ACM_ISAC_CONTROL_BWE ACMISACFixControlBWE // local Impl -#define ACM_ISAC_GETFRAMELEN WebRtcIsacfix_ReadFrameLen -#define ACM_ISAC_GETERRORCODE WebRtcIsacfix_GetErrorCode -#define ACM_ISAC_GETSENDBITRATE ACMISACFixGetSendBitrate // local Impl -#define ACM_ISAC_SETMAXPAYLOADSIZE WebRtcIsacfix_SetMaxPayloadSize -#define ACM_ISAC_SETMAXRATE WebRtcIsacfix_SetMaxRate -#define ACM_ISAC_GETNEWBITSTREAM ACMISACFixGetNewBitstream // local Impl -#define ACM_ISAC_GETSENDBWE ACMISACFixGetSendBWE // local Impl -#define ACM_ISAC_SETBWE WebRtcIsacfix_UpdateUplinkBw -#define ACM_ISAC_GETBWE WebRtcIsacfix_ReadBwIndex -#define ACM_ISAC_GETNEWFRAMELEN WebRtcIsacfix_GetNewFrameLen -#define ACM_ISAC_STRUCT ISACFIX_MainStruct -#define ACM_ISAC_GETENCSAMPRATE ACMISACFixGetEncSampRate // local Impl -#define ACM_ISAC_GETDECSAMPRATE ACMISACFixGetDecSampRate // local Impl -#endif - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_ISAC_MACROS_H_ - diff --git a/webrtc/modules/audio_coding/main/source/acm_neteq.cc b/webrtc/modules/audio_coding/main/source/acm_neteq.cc deleted file mode 100644 index 154cc54d0..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_neteq.cc +++ /dev/null @@ -1,1151 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_neteq.h" - -#include // malloc - -#include // sort -#include - -#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" -#include "webrtc/common_types.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h" -#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" -#include "webrtc/system_wrappers/interface/trace_event.h" - -namespace webrtc { - -namespace acm1 { - -#define RTP_HEADER_SIZE 12 -#define NETEQ_INIT_FREQ 8000 -#define NETEQ_INIT_FREQ_KHZ (NETEQ_INIT_FREQ/1000) -#define NETEQ_ERR_MSG_LEN_BYTE (WEBRTC_NETEQ_MAX_ERROR_NAME + 1) - -ACMNetEQ::ACMNetEQ() - : id_(0), - current_samp_freq_khz_(NETEQ_INIT_FREQ_KHZ), - avt_playout_(false), - playout_mode_(voice), - neteq_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - vad_status_(false), - vad_mode_(VADNormal), - decode_lock_(RWLockWrapper::CreateRWLock()), - num_slaves_(0), - received_stereo_(false), - master_slave_info_(NULL), - previous_audio_activity_(AudioFrame::kVadUnknown), - callback_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - min_of_max_num_packets_(0), - min_of_buffer_size_bytes_(0), - per_packet_overhead_bytes_(0), - av_sync_(false), - minimum_delay_ms_(0), - maximum_delay_ms_(0) { - for (int n = 0; n < MAX_NUM_SLAVE_NETEQ + 1; n++) { - is_initialized_[n] = false; - ptr_vadinst_[n] = NULL; - inst_[n] = NULL; - inst_mem_[n] = NULL; - neteq_packet_buffer_[n] = NULL; - } -} - -ACMNetEQ::~ACMNetEQ() { - { - CriticalSectionScoped lock(neteq_crit_sect_); - RemoveNetEQSafe(0); // Master. - RemoveSlavesSafe(); - } - if (neteq_crit_sect_ != NULL) { - delete neteq_crit_sect_; - } - - if (decode_lock_ != NULL) { - delete decode_lock_; - } - - if (callback_crit_sect_ != NULL) { - delete callback_crit_sect_; - } -} - -int32_t ACMNetEQ::Init() { - CriticalSectionScoped lock(neteq_crit_sect_); - - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (InitByIdxSafe(idx) < 0) { - return -1; - } - // delete VAD instance and start fresh if required. - if (ptr_vadinst_[idx] != NULL) { - WebRtcVad_Free(ptr_vadinst_[idx]); - ptr_vadinst_[idx] = NULL; - } - if (vad_status_) { - // Has to enable VAD - if (EnableVADByIdxSafe(idx) < 0) { - // Failed to enable VAD. - // Delete VAD instance, if it is created - if (ptr_vadinst_[idx] != NULL) { - WebRtcVad_Free(ptr_vadinst_[idx]); - ptr_vadinst_[idx] = NULL; - } - // We are at initialization of NetEq, if failed to - // enable VAD, we delete the NetEq instance. - if (inst_mem_[idx] != NULL) { - free(inst_mem_[idx]); - inst_mem_[idx] = NULL; - inst_[idx] = NULL; - } - is_initialized_[idx] = false; - return -1; - } - } - is_initialized_[idx] = true; - } - if (EnableVAD() == -1) { - return -1; - } - return 0; -} - -int16_t ACMNetEQ::InitByIdxSafe(const int16_t idx) { - int memory_size_bytes; - if (WebRtcNetEQ_AssignSize(&memory_size_bytes) != 0) { - LogError("AssignSize", idx); - return -1; - } - - if (inst_mem_[idx] != NULL) { - free(inst_mem_[idx]); - inst_mem_[idx] = NULL; - inst_[idx] = NULL; - } - inst_mem_[idx] = malloc(memory_size_bytes); - if (inst_mem_[idx] == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "InitByIdxSafe: NetEq Initialization error: could not " - "allocate memory for NetEq"); - is_initialized_[idx] = false; - return -1; - } - if (WebRtcNetEQ_Assign(&inst_[idx], inst_mem_[idx]) != 0) { - if (inst_mem_[idx] != NULL) { - free(inst_mem_[idx]); - inst_mem_[idx] = NULL; - inst_[idx] = NULL; - } - LogError("Assign", idx); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "InitByIdxSafe: NetEq Initialization error: could not Assign"); - is_initialized_[idx] = false; - return -1; - } - if (WebRtcNetEQ_Init(inst_[idx], NETEQ_INIT_FREQ) != 0) { - if (inst_mem_[idx] != NULL) { - free(inst_mem_[idx]); - inst_mem_[idx] = NULL; - inst_[idx] = NULL; - } - LogError("Init", idx); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "InitByIdxSafe: NetEq Initialization error: could not " - "initialize NetEq"); - is_initialized_[idx] = false; - return -1; - } - is_initialized_[idx] = true; - return 0; -} - -int16_t ACMNetEQ::EnableVADByIdxSafe(const int16_t idx) { - if (ptr_vadinst_[idx] == NULL) { - if (WebRtcVad_Create(&ptr_vadinst_[idx]) < 0) { - ptr_vadinst_[idx] = NULL; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "EnableVADByIdxSafe: NetEq Initialization error: could not " - "create VAD"); - return -1; - } - } - - if (WebRtcNetEQ_SetVADInstance( - inst_[idx], ptr_vadinst_[idx], - (WebRtcNetEQ_VADInitFunction) WebRtcVad_Init, - (WebRtcNetEQ_VADSetmodeFunction) WebRtcVad_set_mode, - (WebRtcNetEQ_VADFunction) WebRtcVad_Process) < 0) { - LogError("setVADinstance", idx); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "EnableVADByIdxSafe: NetEq Initialization error: could not " - "set VAD instance"); - return -1; - } - - if (WebRtcNetEQ_SetVADMode(inst_[idx], vad_mode_) < 0) { - LogError("setVADmode", idx); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "EnableVADByIdxSafe: NetEq Initialization error: could not " - "set VAD mode"); - return -1; - } - return 0; -} - -int32_t ACMNetEQ::AllocatePacketBuffer( - const WebRtcNetEQDecoder* used_codecs, - int16_t num_codecs) { - // Due to WebRtcNetEQ_GetRecommendedBufferSize - // the following has to be int otherwise we will have compiler error - // if not casted - - CriticalSectionScoped lock(neteq_crit_sect_); - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (AllocatePacketBufferByIdxSafe(used_codecs, num_codecs, idx) < 0) { - return -1; - } - } - return 0; -} - -int16_t ACMNetEQ::AllocatePacketBufferByIdxSafe( - const WebRtcNetEQDecoder* used_codecs, - int16_t num_codecs, - const int16_t idx) { - int max_num_packets; - int buffer_size_in_bytes; - int per_packet_overhead_bytes; - - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AllocatePacketBufferByIdxSafe: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_GetRecommendedBufferSize(inst_[idx], used_codecs, - num_codecs, - kTCPXLargeJitter, - &max_num_packets, - &buffer_size_in_bytes, - &per_packet_overhead_bytes) != 0) { - LogError("GetRecommendedBufferSize", idx); - return -1; - } - if (idx == 0) { - min_of_buffer_size_bytes_ = buffer_size_in_bytes; - min_of_max_num_packets_ = max_num_packets; - per_packet_overhead_bytes_ = per_packet_overhead_bytes; - } else { - min_of_buffer_size_bytes_ = std::min(min_of_buffer_size_bytes_, - buffer_size_in_bytes); - min_of_max_num_packets_ = std::min(min_of_max_num_packets_, - max_num_packets); - } - if (neteq_packet_buffer_[idx] != NULL) { - free(neteq_packet_buffer_[idx]); - neteq_packet_buffer_[idx] = NULL; - } - - neteq_packet_buffer_[idx] = (int16_t *) malloc(buffer_size_in_bytes); - if (neteq_packet_buffer_[idx] == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AllocatePacketBufferByIdxSafe: NetEq Initialization error: " - "could not allocate memory for NetEq Packet Buffer"); - return -1; - } - if (WebRtcNetEQ_AssignBuffer(inst_[idx], max_num_packets, - neteq_packet_buffer_[idx], - buffer_size_in_bytes) != 0) { - if (neteq_packet_buffer_[idx] != NULL) { - free(neteq_packet_buffer_[idx]); - neteq_packet_buffer_[idx] = NULL; - } - LogError("AssignBuffer", idx); - return -1; - } - return 0; -} - -int32_t ACMNetEQ::SetAVTPlayout(const bool enable) { - CriticalSectionScoped lock(neteq_crit_sect_); - if (avt_playout_ != enable) { - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "SetAVTPlayout: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_SetAVTPlayout(inst_[idx], (enable) ? 1 : 0) < 0) { - LogError("SetAVTPlayout", idx); - return -1; - } - } - } - avt_playout_ = enable; - return 0; -} - -bool ACMNetEQ::avt_playout() const { - CriticalSectionScoped lock(neteq_crit_sect_); - return avt_playout_; -} - -int32_t ACMNetEQ::CurrentSampFreqHz() const { - CriticalSectionScoped lock(neteq_crit_sect_); - if (!is_initialized_[0]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "CurrentSampFreqHz: NetEq is not initialized."); - return -1; - } - return (int32_t)(1000 * current_samp_freq_khz_); -} - -int32_t ACMNetEQ::SetPlayoutMode(const AudioPlayoutMode mode) { - CriticalSectionScoped lock(neteq_crit_sect_); - if (playout_mode_ == mode) - return 0; - - enum WebRtcNetEQPlayoutMode playout_mode = kPlayoutOff; - enum WebRtcNetEQBGNMode background_noise_mode = kBGNOn; - switch (mode) { - case voice: - playout_mode = kPlayoutOn; - background_noise_mode = kBGNOn; - break; - case fax: - playout_mode = kPlayoutFax; - WebRtcNetEQ_GetBGNMode(inst_[0], &background_noise_mode); // No change. - break; - case streaming: - playout_mode = kPlayoutStreaming; - background_noise_mode = kBGNOff; - break; - case off: - playout_mode = kPlayoutOff; - background_noise_mode = kBGNOff; - break; - } - - int err = 0; - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "SetPlayoutMode: NetEq is not initialized."); - return -1; - } - - if (WebRtcNetEQ_SetPlayoutMode(inst_[idx], playout_mode) < 0) { - LogError("SetPlayoutMode", idx); - err = -1; - } - - if (WebRtcNetEQ_SetBGNMode(inst_[idx], kBGNOff) < 0) { - LogError("SetPlayoutMode::SetBGNMode", idx); - err = -1; - } - } - if (err == 0) - playout_mode_ = mode; - return err; -} - -AudioPlayoutMode ACMNetEQ::playout_mode() const { - CriticalSectionScoped lock(neteq_crit_sect_); - return playout_mode_; -} - -int32_t ACMNetEQ::NetworkStatistics( - ACMNetworkStatistics* statistics) const { - WebRtcNetEQ_NetworkStatistics stats; - CriticalSectionScoped lock(neteq_crit_sect_); - if (!is_initialized_[0]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "NetworkStatistics: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_GetNetworkStatistics(inst_[0], &stats) == 0) { - statistics->currentAccelerateRate = stats.currentAccelerateRate; - statistics->currentBufferSize = stats.currentBufferSize; - statistics->jitterPeaksFound = (stats.jitterPeaksFound > 0); - statistics->currentDiscardRate = stats.currentDiscardRate; - statistics->currentExpandRate = stats.currentExpandRate; - statistics->currentPacketLossRate = stats.currentPacketLossRate; - statistics->currentPreemptiveRate = stats.currentPreemptiveRate; - statistics->preferredBufferSize = stats.preferredBufferSize; - statistics->clockDriftPPM = stats.clockDriftPPM; - statistics->addedSamples = stats.addedSamples; - } else { - LogError("getNetworkStatistics", 0); - return -1; - } - const int kArrayLen = 100; - int waiting_times[kArrayLen]; - int waiting_times_len = WebRtcNetEQ_GetRawFrameWaitingTimes(inst_[0], - kArrayLen, - waiting_times); - if (waiting_times_len > 0) { - std::vector waiting_times_vec(waiting_times, - waiting_times + waiting_times_len); - std::sort(waiting_times_vec.begin(), waiting_times_vec.end()); - size_t size = waiting_times_vec.size(); - assert(size == static_cast(waiting_times_len)); - if (size % 2 == 0) { - statistics->medianWaitingTimeMs = (waiting_times_vec[size / 2 - 1] + - waiting_times_vec[size / 2]) / 2; - } else { - statistics->medianWaitingTimeMs = waiting_times_vec[size / 2]; - } - statistics->minWaitingTimeMs = waiting_times_vec.front(); - statistics->maxWaitingTimeMs = waiting_times_vec.back(); - double sum = 0; - for (size_t i = 0; i < size; ++i) { - sum += waiting_times_vec[i]; - } - statistics->meanWaitingTimeMs = static_cast(sum / size); - } else if (waiting_times_len == 0) { - statistics->meanWaitingTimeMs = -1; - statistics->medianWaitingTimeMs = -1; - statistics->minWaitingTimeMs = -1; - statistics->maxWaitingTimeMs = -1; - } else { - LogError("getRawFrameWaitingTimes", 0); - return -1; - } - return 0; -} - -// Should only be called in AV-sync mode. -int ACMNetEQ::RecIn(const WebRtcRTPHeader& rtp_info, - uint32_t receive_timestamp) { - assert(av_sync_); - - // Translate to NetEq structure. - WebRtcNetEQ_RTPInfo neteq_rtpinfo; - neteq_rtpinfo.payloadType = rtp_info.header.payloadType; - neteq_rtpinfo.sequenceNumber = rtp_info.header.sequenceNumber; - neteq_rtpinfo.timeStamp = rtp_info.header.timestamp; - neteq_rtpinfo.SSRC = rtp_info.header.ssrc; - neteq_rtpinfo.markerBit = rtp_info.header.markerBit; - - CriticalSectionScoped lock(neteq_crit_sect_); - - // Master should be initialized. - assert(is_initialized_[0]); - - // Push into Master. - int status = WebRtcNetEQ_RecInSyncRTP(inst_[0], &neteq_rtpinfo, - receive_timestamp); - if (status < 0) { - LogError("RecInSyncRTP", 0); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecIn (sync): NetEq, error in pushing in Master"); - return -1; - } - - // If the received stream is stereo, insert a sync payload into slave. - if (rtp_info.type.Audio.channel == 2) { - // Slave should be initialized. - assert(is_initialized_[1]); - - // PUSH into Slave - status = WebRtcNetEQ_RecInSyncRTP(inst_[1], &neteq_rtpinfo, - receive_timestamp); - if (status < 0) { - LogError("RecInRTPStruct", 1); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecIn (sync): NetEq, error in pushing in Slave"); - return -1; - } - } - return status; -} - -int32_t ACMNetEQ::RecIn(const uint8_t* incoming_payload, - const int32_t length_payload, - const WebRtcRTPHeader& rtp_info, - uint32_t receive_timestamp) { - int16_t payload_length = static_cast(length_payload); - - // Translate to NetEq structure. - WebRtcNetEQ_RTPInfo neteq_rtpinfo; - neteq_rtpinfo.payloadType = rtp_info.header.payloadType; - neteq_rtpinfo.sequenceNumber = rtp_info.header.sequenceNumber; - neteq_rtpinfo.timeStamp = rtp_info.header.timestamp; - neteq_rtpinfo.SSRC = rtp_info.header.ssrc; - neteq_rtpinfo.markerBit = rtp_info.header.markerBit; - - CriticalSectionScoped lock(neteq_crit_sect_); - - int status; - // In case of stereo payload, first half of the data should be pushed into - // master, and the second half into slave. - if (rtp_info.type.Audio.channel == 2) { - payload_length = payload_length / 2; - } - - // Check that master is initialized. - if (!is_initialized_[0]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecIn: NetEq is not initialized."); - return -1; - } - // Push into Master. - status = WebRtcNetEQ_RecInRTPStruct(inst_[0], &neteq_rtpinfo, - incoming_payload, payload_length, - receive_timestamp); - if (status < 0) { - LogError("RecInRTPStruct", 0); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecIn: NetEq, error in pushing in Master"); - return -1; - } - - // If the received stream is stereo, insert second half of paket into slave. - if (rtp_info.type.Audio.channel == 2) { - if (!is_initialized_[1]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecIn: NetEq is not initialized."); - return -1; - } - // Push into Slave. - status = WebRtcNetEQ_RecInRTPStruct(inst_[1], &neteq_rtpinfo, - &incoming_payload[payload_length], - payload_length, receive_timestamp); - if (status < 0) { - LogError("RecInRTPStruct", 1); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecIn: NetEq, error in pushing in Slave"); - return -1; - } - } - - return 0; -} - -int32_t ACMNetEQ::RecOut(AudioFrame& audio_frame) { - enum WebRtcNetEQOutputType type; - int16_t payload_len_sample; - enum WebRtcNetEQOutputType type_master; - enum WebRtcNetEQOutputType type_slave; - - int16_t payload_len_sample_slave; - - CriticalSectionScoped lockNetEq(neteq_crit_sect_); - - if (!received_stereo_) { - if (!is_initialized_[0]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecOut: NetEq is not initialized."); - return -1; - } - { - WriteLockScoped lockCodec(*decode_lock_); - if (WebRtcNetEQ_RecOut(inst_[0], &(audio_frame.data_[0]), - &payload_len_sample) != 0) { - LogError("RecOut", 0); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecOut: NetEq, error in pulling out for mono case"); - // Check for errors that can be recovered from: - // RECOUT_ERROR_SAMPLEUNDERRUN = 2003 - int error_code = WebRtcNetEQ_GetErrorCode(inst_[0]); - if (error_code != 2003) { - // Cannot recover; return an error - return -1; - } - } - } - WebRtcNetEQ_GetSpeechOutputType(inst_[0], &type); - audio_frame.num_channels_ = 1; - } else { - if (!is_initialized_[0] || !is_initialized_[1]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecOut: NetEq is not initialized."); - return -1; - } - int16_t payload_master[480]; - int16_t payload_slave[480]; - { - WriteLockScoped lockCodec(*decode_lock_); - if (WebRtcNetEQ_RecOutMasterSlave(inst_[0], payload_master, - &payload_len_sample, master_slave_info_, - 1) != 0) { - LogError("RecOutMasterSlave", 0); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecOut: NetEq, error in pulling out for master"); - - // Check for errors that can be recovered from: - // RECOUT_ERROR_SAMPLEUNDERRUN = 2003 - int error_code = WebRtcNetEQ_GetErrorCode(inst_[0]); - if (error_code != 2003) { - // Cannot recover; return an error - return -1; - } - } - if (WebRtcNetEQ_RecOutMasterSlave(inst_[1], payload_slave, - &payload_len_sample_slave, - master_slave_info_, 0) != 0) { - LogError("RecOutMasterSlave", 1); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RecOut: NetEq, error in pulling out for slave"); - - // Check for errors that can be recovered from: - // RECOUT_ERROR_SAMPLEUNDERRUN = 2003 - int error_code = WebRtcNetEQ_GetErrorCode(inst_[1]); - if (error_code != 2003) { - // Cannot recover; return an error - return -1; - } - } - } - - if (payload_len_sample != payload_len_sample_slave) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - "RecOut: mismatch between the lenght of the decoded audio " - "by Master (%d samples) and Slave (%d samples).", - payload_len_sample, payload_len_sample_slave); - if (payload_len_sample > payload_len_sample_slave) { - memset(&payload_slave[payload_len_sample_slave], 0, - (payload_len_sample - payload_len_sample_slave) * - sizeof(int16_t)); - } - } - - for (int16_t n = 0; n < payload_len_sample; n++) { - audio_frame.data_[n << 1] = payload_master[n]; - audio_frame.data_[(n << 1) + 1] = payload_slave[n]; - } - audio_frame.num_channels_ = 2; - - WebRtcNetEQ_GetSpeechOutputType(inst_[0], &type_master); - WebRtcNetEQ_GetSpeechOutputType(inst_[1], &type_slave); - if ((type_master == kOutputNormal) || (type_slave == kOutputNormal)) { - type = kOutputNormal; - } else { - type = type_master; - } - } - - audio_frame.samples_per_channel_ = - static_cast(payload_len_sample); - // NetEq always returns 10 ms of audio. - current_samp_freq_khz_ = - static_cast(audio_frame.samples_per_channel_) / 10.0f; - audio_frame.sample_rate_hz_ = audio_frame.samples_per_channel_ * 100; - if (vad_status_) { - if (type == kOutputVADPassive) { - audio_frame.vad_activity_ = AudioFrame::kVadPassive; - audio_frame.speech_type_ = AudioFrame::kNormalSpeech; - } else if (type == kOutputNormal) { - audio_frame.vad_activity_ = AudioFrame::kVadActive; - audio_frame.speech_type_ = AudioFrame::kNormalSpeech; - } else if (type == kOutputPLC) { - audio_frame.vad_activity_ = previous_audio_activity_; - audio_frame.speech_type_ = AudioFrame::kPLC; - } else if (type == kOutputCNG) { - audio_frame.vad_activity_ = AudioFrame::kVadPassive; - audio_frame.speech_type_ = AudioFrame::kCNG; - } else { - audio_frame.vad_activity_ = AudioFrame::kVadPassive; - audio_frame.speech_type_ = AudioFrame::kPLCCNG; - } - } else { - // Always return kVadUnknown when receive VAD is inactive - audio_frame.vad_activity_ = AudioFrame::kVadUnknown; - if (type == kOutputNormal) { - audio_frame.speech_type_ = AudioFrame::kNormalSpeech; - } else if (type == kOutputPLC) { - audio_frame.speech_type_ = AudioFrame::kPLC; - } else if (type == kOutputPLCtoCNG) { - audio_frame.speech_type_ = AudioFrame::kPLCCNG; - } else if (type == kOutputCNG) { - audio_frame.speech_type_ = AudioFrame::kCNG; - } else { - // type is kOutputVADPassive which - // we don't expect to get if vad_status_ is false - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - "RecOut: NetEq returned kVadPassive while vad_status_ is " - "false."); - audio_frame.vad_activity_ = AudioFrame::kVadUnknown; - audio_frame.speech_type_ = AudioFrame::kNormalSpeech; - } - } - previous_audio_activity_ = audio_frame.vad_activity_; - - WebRtcNetEQ_ProcessingActivity processing_stats; - WebRtcNetEQ_GetProcessingActivity(inst_[0], &processing_stats); - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_, - "ACM::RecOut accelerate_bgn=%d accelerate_normal=%d" - " expand_bgn=%d expand_normal=%d" - " preemptive_bgn=%d preemptive_normal=%d" - " merge_bgn=%d merge_normal=%d", - processing_stats.accelerate_bgn_samples, - processing_stats.accelerate_normal_samples, - processing_stats.expand_bgn_sampels, - processing_stats.expand_normal_samples, - processing_stats.preemptive_expand_bgn_samples, - processing_stats.preemptive_expand_normal_samples, - processing_stats.merge_expand_bgn_samples, - processing_stats.merge_expand_normal_samples); - return 0; -} - -// When ACMGenericCodec has set the codec specific parameters in codec_def -// it calls AddCodec() to add the new codec to the NetEQ database. -int32_t ACMNetEQ::AddCodec(WebRtcNetEQ_CodecDef* codec_def, - bool to_master) { - if (codec_def == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ACMNetEQ::AddCodec: error, codec_def is NULL"); - return -1; - } - CriticalSectionScoped lock(neteq_crit_sect_); - - int16_t idx; - if (to_master) { - idx = 0; - } else { - idx = 1; - } - - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ACMNetEQ::AddCodec: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_CodecDbAdd(inst_[idx], codec_def) < 0) { - LogError("CodecDB_Add", idx); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ACMNetEQ::AddCodec: NetEq, error in adding codec"); - return -1; - } else { - return 0; - } -} - -// Creates a Word16 RTP packet out of a Word8 payload and an rtp info struct. -// Must be byte order safe. -void ACMNetEQ::RTPPack(int16_t* rtp_packet, const int8_t* payload, - const int32_t payload_length_bytes, - const WebRtcRTPHeader& rtp_info) { - int32_t idx = 0; - WEBRTC_SPL_SET_BYTE(rtp_packet, (int8_t) 0x80, idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, rtp_info.header.payloadType, idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, - WEBRTC_SPL_GET_BYTE(&(rtp_info.header.sequenceNumber), 1), - idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, - WEBRTC_SPL_GET_BYTE(&(rtp_info.header.sequenceNumber), 0), - idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, - WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 3), - idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, - WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 2), - idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, - WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 1), - idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, - WEBRTC_SPL_GET_BYTE(&(rtp_info.header.timestamp), 0), - idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, - WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc), 3), idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc), - 2), idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc), - 1), idx); - idx++; - WEBRTC_SPL_SET_BYTE(rtp_packet, WEBRTC_SPL_GET_BYTE(&(rtp_info.header.ssrc), - 0), idx); - idx++; - for (int16_t i = 0; i < payload_length_bytes; i++) { - WEBRTC_SPL_SET_BYTE(rtp_packet, payload[i], idx); - idx++; - } - if (payload_length_bytes & 1) { - // Our 16 bits buffer is one byte too large, set that - // last byte to zero. - WEBRTC_SPL_SET_BYTE(rtp_packet, 0x0, idx); - } -} - -int16_t ACMNetEQ::EnableVAD() { - CriticalSectionScoped lock(neteq_crit_sect_); - if (vad_status_) { - return 0; - } - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "SetVADStatus: NetEq is not initialized."); - return -1; - } - // VAD was off and we have to turn it on - if (EnableVADByIdxSafe(idx) < 0) { - return -1; - } - - // Set previous VAD status to PASSIVE - previous_audio_activity_ = AudioFrame::kVadPassive; - } - vad_status_ = true; - return 0; -} - -ACMVADMode ACMNetEQ::vad_mode() const { - CriticalSectionScoped lock(neteq_crit_sect_); - return vad_mode_; -} - -int16_t ACMNetEQ::SetVADMode(const ACMVADMode mode) { - CriticalSectionScoped lock(neteq_crit_sect_); - if ((mode < VADNormal) || (mode > VADVeryAggr)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "SetVADMode: NetEq error: could not set VAD mode, mode is not " - "supported"); - return -1; - } else { - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "SetVADMode: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_SetVADMode(inst_[idx], mode) < 0) { - LogError("SetVADmode", idx); - return -1; - } - } - vad_mode_ = mode; - return 0; - } -} - -int32_t ACMNetEQ::FlushBuffers() { - CriticalSectionScoped lock(neteq_crit_sect_); - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "FlushBuffers: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_FlushBuffers(inst_[idx]) < 0) { - LogError("FlushBuffers", idx); - return -1; - } - } - return 0; -} - -int16_t ACMNetEQ::RemoveCodec(WebRtcNetEQDecoder codec_idx, - bool is_stereo) { - // sanity check - if ((codec_idx <= kDecoderReservedStart) || - (codec_idx >= kDecoderReservedEnd)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RemoveCodec: NetEq error: could not Remove Codec, codec " - "index out of range"); - return -1; - } - CriticalSectionScoped lock(neteq_crit_sect_); - if (!is_initialized_[0]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RemoveCodec: NetEq is not initialized."); - return -1; - } - - if (WebRtcNetEQ_CodecDbRemove(inst_[0], codec_idx) < 0) { - LogError("CodecDB_Remove", 0); - return -1; - } - - if (is_stereo) { - if (WebRtcNetEQ_CodecDbRemove(inst_[1], codec_idx) < 0) { - LogError("CodecDB_Remove", 1); - return -1; - } - } - - return 0; -} - -int16_t ACMNetEQ::SetBackgroundNoiseMode( - const ACMBackgroundNoiseMode mode) { - CriticalSectionScoped lock(neteq_crit_sect_); - for (int16_t idx = 0; idx < num_slaves_ + 1; idx++) { - if (!is_initialized_[idx]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "SetBackgroundNoiseMode: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_SetBGNMode(inst_[idx], (WebRtcNetEQBGNMode) mode) < 0) { - LogError("SetBGNMode", idx); - return -1; - } - } - return 0; -} - -int16_t ACMNetEQ::BackgroundNoiseMode(ACMBackgroundNoiseMode& mode) { - WebRtcNetEQBGNMode my_mode; - CriticalSectionScoped lock(neteq_crit_sect_); - if (!is_initialized_[0]) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "BackgroundNoiseMode: NetEq is not initialized."); - return -1; - } - if (WebRtcNetEQ_GetBGNMode(inst_[0], &my_mode) < 0) { - LogError("WebRtcNetEQ_GetBGNMode", 0); - return -1; - } else { - mode = (ACMBackgroundNoiseMode) my_mode; - } - return 0; -} - -void ACMNetEQ::set_id(int32_t id) { - CriticalSectionScoped lock(neteq_crit_sect_); - id_ = id; -} - -void ACMNetEQ::LogError(const char* neteq_func_name, - const int16_t idx) const { - char error_name[NETEQ_ERR_MSG_LEN_BYTE]; - char my_func_name[50]; - int neteq_error_code = WebRtcNetEQ_GetErrorCode(inst_[idx]); - WebRtcNetEQ_GetErrorName(neteq_error_code, error_name, - NETEQ_ERR_MSG_LEN_BYTE - 1); - strncpy(my_func_name, neteq_func_name, 49); - error_name[NETEQ_ERR_MSG_LEN_BYTE - 1] = '\0'; - my_func_name[49] = '\0'; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "NetEq-%d Error in function %s, error-code: %d, error-string: " - " %s", idx, my_func_name, neteq_error_code, error_name); -} - -int32_t ACMNetEQ::PlayoutTimestamp(uint32_t& timestamp) { - CriticalSectionScoped lock(neteq_crit_sect_); - if (WebRtcNetEQ_GetSpeechTimeStamp(inst_[0], ×tamp) < 0) { - LogError("GetSpeechTimeStamp", 0); - return -1; - } else { - return 0; - } -} - -void ACMNetEQ::RemoveSlaves() { - CriticalSectionScoped lock(neteq_crit_sect_); - RemoveSlavesSafe(); -} - -void ACMNetEQ::RemoveSlavesSafe() { - for (int i = 1; i < num_slaves_ + 1; i++) { - RemoveNetEQSafe(i); - } - - if (master_slave_info_ != NULL) { - free(master_slave_info_); - master_slave_info_ = NULL; - } - num_slaves_ = 0; -} - -void ACMNetEQ::RemoveNetEQSafe(int index) { - if (inst_mem_[index] != NULL) { - free(inst_mem_[index]); - inst_mem_[index] = NULL; - inst_[index] = NULL; - } - if (neteq_packet_buffer_[index] != NULL) { - free(neteq_packet_buffer_[index]); - neteq_packet_buffer_[index] = NULL; - } - if (ptr_vadinst_[index] != NULL) { - WebRtcVad_Free(ptr_vadinst_[index]); - ptr_vadinst_[index] = NULL; - } -} - -int16_t ACMNetEQ::AddSlave(const WebRtcNetEQDecoder* used_codecs, - int16_t num_codecs) { - CriticalSectionScoped lock(neteq_crit_sect_); - const int16_t slave_idx = 1; - if (num_slaves_ < 1) { - // initialize the receiver, this also sets up VAD. - if (InitByIdxSafe(slave_idx) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AddSlave: AddSlave Failed, Could not Initialize"); - return -1; - } - - // Allocate buffer. - if (AllocatePacketBufferByIdxSafe(used_codecs, num_codecs, - slave_idx) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AddSlave: AddSlave Failed, Could not Allocate Packet " - "Buffer"); - return -1; - } - - if (master_slave_info_ != NULL) { - free(master_slave_info_); - master_slave_info_ = NULL; - } - int ms_info_size = WebRtcNetEQ_GetMasterSlaveInfoSize(); - master_slave_info_ = malloc(ms_info_size); - - if (master_slave_info_ == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AddSlave: AddSlave Failed, Could not Allocate memory for " - "Master-Slave Info"); - return -1; - } - - // We accept this as initialized NetEQ, the rest is to synchronize - // Slave with Master. - num_slaves_ = 1; - is_initialized_[slave_idx] = true; - - // Set AVT - if (WebRtcNetEQ_SetAVTPlayout(inst_[slave_idx], - (avt_playout_) ? 1 : 0) < 0) { - LogError("SetAVTPlayout", slave_idx); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AddSlave: AddSlave Failed, Could not set AVT playout."); - return -1; - } - - // Set Background Noise - WebRtcNetEQBGNMode current_mode; - if (WebRtcNetEQ_GetBGNMode(inst_[0], ¤t_mode) < 0) { - LogError("GetBGNMode", 0); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AAddSlave: AddSlave Failed, Could not Get BGN form " - "Master."); - return -1; - } - - if (WebRtcNetEQ_SetBGNMode(inst_[slave_idx], - (WebRtcNetEQBGNMode) current_mode) < 0) { - LogError("SetBGNMode", slave_idx); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AddSlave: AddSlave Failed, Could not set BGN mode."); - return -1; - } - - enum WebRtcNetEQPlayoutMode playout_mode = kPlayoutOff; - switch (playout_mode_) { - case voice: - playout_mode = kPlayoutOn; - break; - case fax: - playout_mode = kPlayoutFax; - break; - case streaming: - playout_mode = kPlayoutStreaming; - break; - case off: - playout_mode = kPlayoutOff; - break; - } - if (WebRtcNetEQ_SetPlayoutMode(inst_[slave_idx], playout_mode) < 0) { - LogError("SetPlayoutMode", 1); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "AddSlave: AddSlave Failed, Could not Set Playout Mode."); - return -1; - } - - // Set AV-sync for the slave. - WebRtcNetEQ_EnableAVSync(inst_[slave_idx], av_sync_ ? 1 : 0); - - // Set minimum delay. - if (minimum_delay_ms_ > 0) - WebRtcNetEQ_SetMinimumDelay(inst_[slave_idx], minimum_delay_ms_); - - // Set maximum delay. - if (maximum_delay_ms_ > 0) - WebRtcNetEQ_SetMaximumDelay(inst_[slave_idx], maximum_delay_ms_); - } - - return 0; -} - -void ACMNetEQ::set_received_stereo(bool received_stereo) { - CriticalSectionScoped lock(neteq_crit_sect_); - received_stereo_ = received_stereo; -} - -uint8_t ACMNetEQ::num_slaves() { - CriticalSectionScoped lock(neteq_crit_sect_); - return num_slaves_; -} - -void ACMNetEQ::EnableAVSync(bool enable) { - CriticalSectionScoped lock(neteq_crit_sect_); - av_sync_ = enable; - for (int i = 0; i < num_slaves_ + 1; ++i) { - assert(is_initialized_[i]); - WebRtcNetEQ_EnableAVSync(inst_[i], enable ? 1 : 0); - } -} - -int ACMNetEQ::SetMinimumDelay(int minimum_delay_ms) { - CriticalSectionScoped lock(neteq_crit_sect_); - for (int i = 0; i < num_slaves_ + 1; ++i) { - assert(is_initialized_[i]); - if (WebRtcNetEQ_SetMinimumDelay(inst_[i], minimum_delay_ms) < 0) - return -1; - } - minimum_delay_ms_ = minimum_delay_ms; - return 0; -} - -int ACMNetEQ::SetMaximumDelay(int maximum_delay_ms) { - CriticalSectionScoped lock(neteq_crit_sect_); - for (int i = 0; i < num_slaves_ + 1; ++i) { - assert(is_initialized_[i]); - if (WebRtcNetEQ_SetMaximumDelay(inst_[i], maximum_delay_ms) < 0) - return -1; - } - maximum_delay_ms_ = maximum_delay_ms; - return 0; -} - -int ACMNetEQ::LeastRequiredDelayMs() const { - CriticalSectionScoped lock(neteq_crit_sect_); - assert(is_initialized_[0]); - - // Sufficient to query the master. - return WebRtcNetEQ_GetRequiredDelayMs(inst_[0]); -} - -bool ACMNetEQ::DecodedRtpInfo(int* sequence_number, uint32_t* timestamp) const { - CriticalSectionScoped lock(neteq_crit_sect_); - if (WebRtcNetEQ_DecodedRtpInfo(inst_[0], sequence_number, timestamp) < 0) - return false; - return true; -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_neteq.h b/webrtc/modules/audio_coding/main/source/acm_neteq.h deleted file mode 100644 index e52ddc795..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_neteq.h +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_NETEQ_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_NETEQ_H_ - -#include "webrtc/common_audio/vad/include/webrtc_vad.h" -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/interface/module_common_types.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -class CriticalSectionWrapper; -class RWLockWrapper; -struct CodecInst; - -namespace acm1 { - -#define MAX_NUM_SLAVE_NETEQ 1 - -class ACMNetEQ { - public: - enum JitterBuffer { - kMasterJb = 0, - kSlaveJb = 1 - }; - - // Constructor of the class - ACMNetEQ(); - - // Destructor of the class. - ~ACMNetEQ(); - - // - // Init() - // Allocates memory for NetEQ and VAD and initializes them. - // - // Return value : 0 if ok. - // -1 if NetEQ or VAD returned an error or - // if out of memory. - // - int32_t Init(); - - // - // RecIn() - // Gives the payload to NetEQ. - // - // Input: - // - incoming_payload : Incoming audio payload. - // - length_payload : Length of incoming audio payload. - // - rtp_info : RTP header for the incoming payload containing - // information about payload type, sequence number, - // timestamp, SSRC and marker bit. - // - receive_timestamp : received timestamp. - // - // Return value : 0 if ok. - // <0 if NetEQ returned an error. - // - int32_t RecIn(const uint8_t* incoming_payload, - const int32_t length_payload, - const WebRtcRTPHeader& rtp_info, - uint32_t receive_timestamp); - - // - // RecIn() - // Insert a sync payload to NetEq. Should only be called if |av_sync_| is - // enabled; - // - // Input: - // - rtp_info : RTP header for the incoming payload containing - // information about payload type, sequence number, - // timestamp, SSRC and marker bit. - // - receive_timestamp : received timestamp. - // - // Return value : 0 if ok. - // <0 if NetEQ returned an error. - // - int RecIn(const WebRtcRTPHeader& rtp_info, uint32_t receive_timestamp); - - // - // RecOut() - // Asks NetEQ for 10 ms of decoded audio. - // - // Input: - // -audio_frame : an audio frame were output data and - // associated parameters are written to. - // - // Return value : 0 if ok. - // -1 if NetEQ returned an error. - // - int32_t RecOut(AudioFrame& audio_frame); - - // - // AddCodec() - // Adds a new codec to the NetEQ codec database. - // - // Input: - // - codec_def : The codec to be added. - // - to_master : true if the codec has to be added to Master - // NetEq, otherwise will be added to the Slave - // NetEQ. - // - // Return value : 0 if ok. - // <0 if NetEQ returned an error. - // - int32_t AddCodec(WebRtcNetEQ_CodecDef *codec_def, - bool to_master = true); - - // - // AllocatePacketBuffer() - // Allocates the NetEQ packet buffer. - // - // Input: - // - used_codecs : An array of the codecs to be used by NetEQ. - // - num_codecs : Number of codecs in used_codecs. - // - // Return value : 0 if ok. - // <0 if NetEQ returned an error. - // - int32_t AllocatePacketBuffer(const WebRtcNetEQDecoder* used_codecs, - int16_t num_codecs); - - // - // SetAVTPlayout() - // Enable/disable playout of AVT payloads. - // - // Input: - // - enable : Enable if true, disable if false. - // - // Return value : 0 if ok. - // <0 if NetEQ returned an error. - // - int32_t SetAVTPlayout(const bool enable); - - // - // AVTPlayout() - // Get the current AVT playout state. - // - // Return value : True if AVT playout is enabled. - // False if AVT playout is disabled. - // - bool avt_playout() const; - - // - // CurrentSampFreqHz() - // Get the current sampling frequency in Hz. - // - // Return value : Sampling frequency in Hz. - // - int32_t CurrentSampFreqHz() const; - - // - // SetPlayoutMode() - // Sets the playout mode to voice or fax. - // - // Input: - // - mode : The playout mode to be used, voice, - // fax, or streaming. - // - // Return value : 0 if ok. - // <0 if NetEQ returned an error. - // - int32_t SetPlayoutMode(const AudioPlayoutMode mode); - - // - // PlayoutMode() - // Get the current playout mode. - // - // Return value : The current playout mode. - // - AudioPlayoutMode playout_mode() const; - - // - // NetworkStatistics() - // Get the current network statistics from NetEQ. - // - // Output: - // - statistics : The current network statistics. - // - // Return value : 0 if ok. - // <0 if NetEQ returned an error. - // - int32_t NetworkStatistics(ACMNetworkStatistics* statistics) const; - - // - // VADMode() - // Get the current VAD Mode. - // - // Return value : The current VAD mode. - // - ACMVADMode vad_mode() const; - - // - // SetVADMode() - // Set the VAD mode. - // - // Input: - // - mode : The new VAD mode. - // - // Return value : 0 if ok. - // -1 if an error occurred. - // - int16_t SetVADMode(const ACMVADMode mode); - - // - // DecodeLock() - // Get the decode lock used to protect decoder instances while decoding. - // - // Return value : Pointer to the decode lock. - // - RWLockWrapper* DecodeLock() const { - return decode_lock_; - } - - // - // FlushBuffers() - // Flushes the NetEQ packet and speech buffers. - // - // Return value : 0 if ok. - // -1 if NetEQ returned an error. - // - int32_t FlushBuffers(); - - // - // RemoveCodec() - // Removes a codec from the NetEQ codec database. - // - // Input: - // - codec_idx : Codec to be removed. - // - // Return value : 0 if ok. - // -1 if an error occurred. - // - int16_t RemoveCodec(WebRtcNetEQDecoder codec_idx, - bool is_stereo = false); - - // - // SetBackgroundNoiseMode() - // Set the mode of the background noise. - // - // Input: - // - mode : an enumerator specifying the mode of the - // background noise. - // - // Return value : 0 if succeeded, - // -1 if failed to set the mode. - // - int16_t SetBackgroundNoiseMode(const ACMBackgroundNoiseMode mode); - - // - // BackgroundNoiseMode() - // return the mode of the background noise. - // - // Return value : The mode of background noise. - // - int16_t BackgroundNoiseMode(ACMBackgroundNoiseMode& mode); - - void set_id(int32_t id); - - int32_t PlayoutTimestamp(uint32_t& timestamp); - - void set_received_stereo(bool received_stereo); - - uint8_t num_slaves(); - - // Delete all slaves. - void RemoveSlaves(); - - int16_t AddSlave(const WebRtcNetEQDecoder* used_codecs, - int16_t num_codecs); - - void BufferSpec(int& num_packets, int& size_bytes, int& overhead_bytes) { - num_packets = min_of_max_num_packets_; - size_bytes = min_of_buffer_size_bytes_; - overhead_bytes = per_packet_overhead_bytes_; - } - - // - // Set AV-sync mode. - // - void EnableAVSync(bool enable); - - // - // Get sequence number and timestamp of the last decoded RTP. - // - bool DecodedRtpInfo(int* sequence_number, uint32_t* timestamp) const; - - // - // Set a minimum delay in NetEq. Unless channel condition dictates a longer - // delay, the given delay is maintained by NetEq. - // - int SetMinimumDelay(int minimum_delay_ms); - - // - // Set a maximum delay in NetEq. - // - int SetMaximumDelay(int maximum_delay_ms); - - // - // The shortest latency, in milliseconds, required by jitter buffer. This - // is computed based on inter-arrival times and playout mode of NetEq. The - // actual delay is the maximum of least-required-delay and the minimum-delay - // specified by SetMinumumPlayoutDelay() API. - // - int LeastRequiredDelayMs() const ; - - private: - // - // RTPPack() - // Creates a Word16 RTP packet out of the payload data in Word16 and - // a WebRtcRTPHeader. - // - // Input: - // - payload : Payload to be packetized. - // - payload_length_bytes : Length of the payload in bytes. - // - rtp_info : RTP header structure. - // - // Output: - // - rtp_packet : The RTP packet. - // - static void RTPPack(int16_t* rtp_packet, const int8_t* payload, - const int32_t payload_length_bytes, - const WebRtcRTPHeader& rtp_info); - - void LogError(const char* neteq_func_name, const int16_t idx) const; - - int16_t InitByIdxSafe(const int16_t idx); - - // - // EnableVAD() - // Enable VAD. - // - // Return value : 0 if ok. - // -1 if an error occurred. - // - int16_t EnableVAD(); - - int16_t EnableVADByIdxSafe(const int16_t idx); - - int16_t AllocatePacketBufferByIdxSafe( - const WebRtcNetEQDecoder* used_codecs, - int16_t num_codecs, - const int16_t idx); - - // Delete the NetEQ corresponding to |index|. - void RemoveNetEQSafe(int index); - - void RemoveSlavesSafe(); - - void* inst_[MAX_NUM_SLAVE_NETEQ + 1]; - void* inst_mem_[MAX_NUM_SLAVE_NETEQ + 1]; - - int16_t* neteq_packet_buffer_[MAX_NUM_SLAVE_NETEQ + 1]; - - int32_t id_; - float current_samp_freq_khz_; - bool avt_playout_; - AudioPlayoutMode playout_mode_; - CriticalSectionWrapper* neteq_crit_sect_; - - WebRtcVadInst* ptr_vadinst_[MAX_NUM_SLAVE_NETEQ + 1]; - - bool vad_status_; - ACMVADMode vad_mode_; - RWLockWrapper* decode_lock_; - bool is_initialized_[MAX_NUM_SLAVE_NETEQ + 1]; - uint8_t num_slaves_; - bool received_stereo_; - void* master_slave_info_; - AudioFrame::VADActivity previous_audio_activity_; - - CriticalSectionWrapper* callback_crit_sect_; - // Minimum of "max number of packets," among all NetEq instances. - int min_of_max_num_packets_; - // Minimum of buffer-size among all NetEq instances. - int min_of_buffer_size_bytes_; - int per_packet_overhead_bytes_; - - // Keep track of AV-sync. Just used to set the slave when a slave is added. - bool av_sync_; - - int minimum_delay_ms_; - int maximum_delay_ms_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_NETEQ_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_neteq_unittest.cc b/webrtc/modules/audio_coding/main/source/acm_neteq_unittest.cc deleted file mode 100644 index 8b973ba23..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_neteq_unittest.cc +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2012 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 file contains unit tests for ACM's NetEQ wrapper (class ACMNetEQ). - -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" - -#include - -#include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/modules/interface/module_common_types.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -namespace acm1 { - -class AcmNetEqTest : public ::testing::Test { - protected: - static const size_t kMaxPayloadLen = 5760; // 60 ms, 48 kHz, 16 bit samples. - static const int kPcm16WbPayloadType = 94; - AcmNetEqTest() {} - virtual void SetUp(); - virtual void TearDown() {} - - void InsertZeroPacket(uint16_t sequence_number, - uint32_t timestamp, - uint8_t payload_type, - uint32_t ssrc, - bool marker_bit, - size_t len_payload_bytes); - void PullData(int expected_num_samples); - - ACMNetEQ neteq_; -}; - -void AcmNetEqTest::SetUp() { - ASSERT_EQ(0, neteq_.Init()); - ASSERT_EQ(0, neteq_.AllocatePacketBuffer(ACMCodecDB::NetEQDecoders(), - ACMCodecDB::kNumCodecs)); - WebRtcNetEQ_CodecDef codec_def; - SET_CODEC_PAR(codec_def, kDecoderPCM16Bwb, kPcm16WbPayloadType, NULL, 16000); - SET_PCM16B_WB_FUNCTIONS(codec_def); - ASSERT_EQ(0, neteq_.AddCodec(&codec_def, true)); -} - -void AcmNetEqTest::InsertZeroPacket(uint16_t sequence_number, - uint32_t timestamp, - uint8_t payload_type, - uint32_t ssrc, - bool marker_bit, - size_t len_payload_bytes) { - ASSERT_TRUE(len_payload_bytes <= kMaxPayloadLen); - uint16_t payload[kMaxPayloadLen] = {0}; - WebRtcRTPHeader rtp_header; - rtp_header.header.sequenceNumber = sequence_number; - rtp_header.header.timestamp = timestamp; - rtp_header.header.ssrc = ssrc; - rtp_header.header.payloadType = payload_type; - rtp_header.header.markerBit = marker_bit; - rtp_header.type.Audio.channel = 1; - // Receive timestamp can be set to send timestamp in this test. - ASSERT_EQ(0, neteq_.RecIn(reinterpret_cast(payload), - len_payload_bytes, rtp_header, timestamp)); -} - -void AcmNetEqTest::PullData(int expected_num_samples) { - AudioFrame out_frame; - ASSERT_EQ(0, neteq_.RecOut(out_frame)); - ASSERT_EQ(expected_num_samples, out_frame.samples_per_channel_); -} - -TEST_F(AcmNetEqTest, NetworkStatistics) { - // Use fax mode to avoid time-scaling. This is to simplify the testing of - // packet waiting times in the packet buffer. - neteq_.SetPlayoutMode(fax); - // Insert 31 dummy packets at once. Each packet contains 10 ms 16 kHz audio. - int num_frames = 30; - const int kSamples = 10 * 16; - const int kPayloadBytes = kSamples * 2; - int i, j; - for (i = 0; i < num_frames; ++i) { - InsertZeroPacket(i, i * kSamples, kPcm16WbPayloadType, 0x1234, false, - kPayloadBytes); - } - // Pull out data once. - PullData(kSamples); - // Insert one more packet (to produce different mean and median). - i = num_frames; - InsertZeroPacket(i, i * kSamples, kPcm16WbPayloadType, 0x1234, false, - kPayloadBytes); - // Pull out all data. - for (j = 1; j < num_frames + 1; ++j) { - PullData(kSamples); - } - - ACMNetworkStatistics stats; - ASSERT_EQ(0, neteq_.NetworkStatistics(&stats)); - EXPECT_EQ(0, stats.currentBufferSize); - EXPECT_EQ(0, stats.preferredBufferSize); - EXPECT_FALSE(stats.jitterPeaksFound); - EXPECT_EQ(0, stats.currentPacketLossRate); - EXPECT_EQ(0, stats.currentDiscardRate); - EXPECT_EQ(0, stats.currentExpandRate); - EXPECT_EQ(0, stats.currentPreemptiveRate); - EXPECT_EQ(0, stats.currentAccelerateRate); - EXPECT_EQ(-916, stats.clockDriftPPM); // Initial value is slightly off. - EXPECT_EQ(300, stats.maxWaitingTimeMs); - EXPECT_EQ(10, stats.minWaitingTimeMs); - EXPECT_EQ(159, stats.meanWaitingTimeMs); - EXPECT_EQ(160, stats.medianWaitingTimeMs); -} - -TEST_F(AcmNetEqTest, TestZeroLengthWaitingTimesVector) { - // Insert one packet. - const int kSamples = 10 * 16; - const int kPayloadBytes = kSamples * 2; - int i = 0; - InsertZeroPacket(i, i * kSamples, kPcm16WbPayloadType, 0x1234, false, - kPayloadBytes); - // Do not pull out any data. - - ACMNetworkStatistics stats; - ASSERT_EQ(0, neteq_.NetworkStatistics(&stats)); - EXPECT_EQ(0, stats.currentBufferSize); - EXPECT_EQ(0, stats.preferredBufferSize); - EXPECT_FALSE(stats.jitterPeaksFound); - EXPECT_EQ(0, stats.currentPacketLossRate); - EXPECT_EQ(0, stats.currentDiscardRate); - EXPECT_EQ(0, stats.currentExpandRate); - EXPECT_EQ(0, stats.currentPreemptiveRate); - EXPECT_EQ(0, stats.currentAccelerateRate); - EXPECT_EQ(-916, stats.clockDriftPPM); // Initial value is slightly off. - EXPECT_EQ(-1, stats.minWaitingTimeMs); - EXPECT_EQ(-1, stats.maxWaitingTimeMs); - EXPECT_EQ(-1, stats.meanWaitingTimeMs); - EXPECT_EQ(-1, stats.medianWaitingTimeMs); -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_opus.cc b/webrtc/modules/audio_coding/main/source/acm_opus.cc deleted file mode 100644 index 413f3715f..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_opus.cc +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_opus.h" - -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_OPUS -#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_OPUS - -ACMOpus::ACMOpus(int16_t /* codec_id */) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - sample_freq_(0), - bitrate_(0), - channels_(1) { - return; -} - -ACMOpus::~ACMOpus() { - return; -} - -int16_t ACMOpus::InternalEncode(uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMOpus::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMOpus::InternalInitEncoder(WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMOpus::InternalInitDecoder(WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMOpus::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMOpus::CreateInstance(void) { - return NULL; -} - -int16_t ACMOpus::InternalCreateEncoder() { - return -1; -} - -void ACMOpus::DestructEncoderSafe() { - return; -} - -int16_t ACMOpus::InternalCreateDecoder() { - return -1; -} - -void ACMOpus::DestructDecoderSafe() { - return; -} - -void ACMOpus::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -int16_t ACMOpus::SetBitRateSafe(const int32_t /*rate*/) { - return -1; -} - -bool ACMOpus::IsTrueStereoCodec() { - return true; -} - -void ACMOpus::SplitStereoPacket(uint8_t* /*payload*/, - int32_t* /*payload_length*/) {} - -#else //===================== Actual Implementation ======================= - -ACMOpus::ACMOpus(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - sample_freq_(32000), // Default sampling frequency. - bitrate_(20000), // Default bit-rate. - channels_(1) { // Default mono - codec_id_ = codec_id; - - // Opus has internal DTX, but we don't use it for now. - has_internal_dtx_ = false; - - if (codec_id_ != ACMCodecDB::kOpus) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Wrong codec id for Opus."); - sample_freq_ = -1; - bitrate_ = -1; - } - return; -} - -ACMOpus::~ACMOpus() { - if (encoder_inst_ptr_ != NULL) { - WebRtcOpus_EncoderFree(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcOpus_DecoderFree(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMOpus::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - // Call Encoder. - *bitstream_len_byte = WebRtcOpus_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], - frame_len_smpl_, - MAX_PAYLOAD_SIZE_BYTE, bitstream); - // Check for error reported from encoder. - if (*bitstream_len_byte < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "InternalEncode: Encode error for Opus"); - *bitstream_len_byte = 0; - return -1; - } - - // Increment the read index. This tells the caller how far - // we have gone forward in reading the audio buffer. - in_audio_ix_read_ += frame_len_smpl_ * channels_; - - return *bitstream_len_byte; -} - -int16_t ACMOpus::DecodeSafe(uint8_t* bitstream, int16_t bitstream_len_byte, - int16_t* audio, int16_t* audio_samples, - int8_t* speech_type) { - return 0; -} - -int16_t ACMOpus::InternalInitEncoder(WebRtcACMCodecParams* codec_params) { - int16_t ret; - if (encoder_inst_ptr_ != NULL) { - WebRtcOpus_EncoderFree(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - ret = WebRtcOpus_EncoderCreate(&encoder_inst_ptr_, - codec_params->codec_inst.channels); - // Store number of channels. - channels_ = codec_params->codec_inst.channels; - - if (ret < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Encoder creation failed for Opus"); - return ret; - } - ret = WebRtcOpus_SetBitRate(encoder_inst_ptr_, - codec_params->codec_inst.rate); - if (ret < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Setting initial bitrate failed for Opus"); - return ret; - } - - // Store bitrate. - bitrate_ = codec_params->codec_inst.rate; - - return 0; -} - -int16_t ACMOpus::InternalInitDecoder(WebRtcACMCodecParams* codec_params) { - if (decoder_inst_ptr_ == NULL) { - if (WebRtcOpus_DecoderCreate(&decoder_inst_ptr_, - codec_params->codec_inst.channels) < 0) { - return -1; - } - } - - // Number of channels in decoder should match the number in |codec_params|. - assert(codec_params->codec_inst.channels == - WebRtcOpus_DecoderChannels(decoder_inst_ptr_)); - - if (WebRtcOpus_DecoderInit(decoder_inst_ptr_) < 0) { - return -1; - } - if (WebRtcOpus_DecoderInitSlave(decoder_inst_ptr_) < 0) { - return -1; - } - return 0; -} - -int32_t ACMOpus::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "CodeDef: Decoder uninitialized for Opus"); - return -1; - } - - // Fill up the structure by calling "SET_CODEC_PAR" & "SET_OPUS_FUNCTION." - // Then call NetEQ to add the codec to its database. - // TODO(tlegrand): Decoder is registered in NetEQ as a 32 kHz decoder, which - // is true until we have a full 48 kHz system, and remove the downsampling - // in the Opus decoder wrapper. - SET_CODEC_PAR(codec_def, kDecoderOpus, codec_inst.pltype, - decoder_inst_ptr_, 32000); - - // If this is the master of NetEQ, regular decoder will be added, otherwise - // the slave decoder will be used. - if (is_master_) { - SET_OPUS_FUNCTIONS(codec_def); - } else { - SET_OPUSSLAVE_FUNCTIONS(codec_def); - } - - return 0; -} - -ACMGenericCodec* ACMOpus::CreateInstance(void) { - return NULL; -} - -int16_t ACMOpus::InternalCreateEncoder() { - // Real encoder will be created in InternalInitEncoder. - return 0; -} - -void ACMOpus::DestructEncoderSafe() { - if (encoder_inst_ptr_) { - WebRtcOpus_EncoderFree(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } -} - -int16_t ACMOpus::InternalCreateDecoder() { - // Real decoder will be created in InternalInitDecoder - return 0; -} - -void ACMOpus::DestructDecoderSafe() { - decoder_initialized_ = false; - if (decoder_inst_ptr_) { - WebRtcOpus_DecoderFree(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } -} - -void ACMOpus::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcOpus_EncoderFree(reinterpret_cast(ptr_inst)); - } - return; -} - -int16_t ACMOpus::SetBitRateSafe(const int32_t rate) { - if (rate < 6000 || rate > 510000) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "SetBitRateSafe: Invalid rate Opus"); - return -1; - } - - bitrate_ = rate; - - // Ask the encoder for the new rate. - if (WebRtcOpus_SetBitRate(encoder_inst_ptr_, bitrate_) >= 0) { - encoder_params_.codec_inst.rate = bitrate_; - return 0; - } - - return -1; -} - -bool ACMOpus::IsTrueStereoCodec() { - return true; -} - -// Copy the stereo packet so that NetEq will insert into both master and slave. -void ACMOpus::SplitStereoPacket(uint8_t* payload, int32_t* payload_length) { - // Check for valid inputs. - assert(payload != NULL); - assert(*payload_length > 0); - - // Duplicate the payload. - memcpy(&payload[*payload_length], &payload[0], - sizeof(uint8_t) * (*payload_length)); - // Double the size of the packet. - *payload_length *= 2; -} - -#endif // WEBRTC_CODEC_OPUS - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_opus.h b/webrtc/modules/audio_coding/main/source/acm_opus.h deleted file mode 100644 index 1e586ff41..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_opus.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_OPUS_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_OPUS_H_ - -#include "webrtc/common_audio/resampler/include/resampler.h" -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -struct WebRtcOpusEncInst; -struct WebRtcOpusDecInst; - -namespace webrtc { - -namespace acm1 { - -class ACMOpus : public ACMGenericCodec { - public: - explicit ACMOpus(int16_t codec_id); - virtual ~ACMOpus(); - - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual int16_t SetBitRateSafe(const int32_t rate) OVERRIDE; - - virtual bool IsTrueStereoCodec() OVERRIDE; - - virtual void SplitStereoPacket(uint8_t* payload, - int32_t* payload_length) OVERRIDE; - - WebRtcOpusEncInst* encoder_inst_ptr_; - WebRtcOpusDecInst* decoder_inst_ptr_; - uint16_t sample_freq_; - uint32_t bitrate_; - int channels_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_OPUS_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_pcm16b.cc b/webrtc/modules/audio_coding/main/source/acm_pcm16b.cc deleted file mode 100644 index 6fe12f757..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_pcm16b.cc +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_pcm16b.h" - -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_PCM16 -#include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_PCM16 - -ACMPCM16B::ACMPCM16B(int16_t /* codec_id */) { - return; -} - -ACMPCM16B::~ACMPCM16B() { - return; -} - -int16_t ACMPCM16B::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMPCM16B::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMPCM16B::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMPCM16B::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMPCM16B::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMPCM16B::CreateInstance(void) { - return NULL; -} - -int16_t ACMPCM16B::InternalCreateEncoder() { - return -1; -} - -int16_t ACMPCM16B::InternalCreateDecoder() { - return -1; -} - -void ACMPCM16B::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -void ACMPCM16B::DestructEncoderSafe() { - return; -} - -void ACMPCM16B::DestructDecoderSafe() { - return; -} - -void ACMPCM16B::SplitStereoPacket(uint8_t* /*payload*/, - int32_t* /*payload_length*/) { -} - -#else //===================== Actual Implementation ======================= -ACMPCM16B::ACMPCM16B(int16_t codec_id) { - codec_id_ = codec_id; - sampling_freq_hz_ = ACMCodecDB::CodecFreq(codec_id_); -} - -ACMPCM16B::~ACMPCM16B() { - return; -} - -int16_t ACMPCM16B::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - *bitstream_len_byte = WebRtcPcm16b_Encode(&in_audio_[in_audio_ix_read_], - frame_len_smpl_ * num_channels_, - bitstream); - // Increment the read index to tell the caller that how far - // we have gone forward in reading the audio buffer. - in_audio_ix_read_ += frame_len_smpl_ * num_channels_; - return *bitstream_len_byte; -} - -int16_t ACMPCM16B::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMPCM16B::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, PCM has no instance. - return 0; -} - -int16_t ACMPCM16B::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, PCM has no instance. - return 0; -} - -int32_t ACMPCM16B::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - // Fill up the structure by calling "SET_CODEC_PAR" & "SET_PCMU_FUNCTION". - // Then call NetEQ to add the codec to it's database. - if (codec_inst.channels == 1) { - switch (sampling_freq_hz_) { - case 8000: { - SET_CODEC_PAR(codec_def, kDecoderPCM16B, codec_inst.pltype, NULL, 8000); - SET_PCM16B_FUNCTIONS(codec_def); - break; - } - case 16000: { - SET_CODEC_PAR(codec_def, kDecoderPCM16Bwb, codec_inst.pltype, NULL, - 16000); - SET_PCM16B_WB_FUNCTIONS(codec_def); - break; - } - case 32000: { - SET_CODEC_PAR(codec_def, kDecoderPCM16Bswb32kHz, codec_inst.pltype, - NULL, 32000); - SET_PCM16B_SWB32_FUNCTIONS(codec_def); - break; - } - default: { - return -1; - } - } - } else { - switch (sampling_freq_hz_) { - case 8000: { - SET_CODEC_PAR(codec_def, kDecoderPCM16B_2ch, codec_inst.pltype, NULL, - 8000); - SET_PCM16B_FUNCTIONS(codec_def); - break; - } - case 16000: { - SET_CODEC_PAR(codec_def, kDecoderPCM16Bwb_2ch, codec_inst.pltype, - NULL, 16000); - SET_PCM16B_WB_FUNCTIONS(codec_def); - break; - } - case 32000: { - SET_CODEC_PAR(codec_def, kDecoderPCM16Bswb32kHz_2ch, codec_inst.pltype, - NULL, 32000); - SET_PCM16B_SWB32_FUNCTIONS(codec_def); - break; - } - default: { - return -1; - } - } - } - return 0; -} - -ACMGenericCodec* ACMPCM16B::CreateInstance(void) { - return NULL; -} - -int16_t ACMPCM16B::InternalCreateEncoder() { - // PCM has no instance. - return 0; -} - -int16_t ACMPCM16B::InternalCreateDecoder() { - // PCM has no instance. - return 0; -} - -void ACMPCM16B::InternalDestructEncoderInst(void* /* ptr_inst */) { - // PCM has no instance. - return; -} - -void ACMPCM16B::DestructEncoderSafe() { - // PCM has no instance. - encoder_exist_ = false; - encoder_initialized_ = false; - return; -} - -void ACMPCM16B::DestructDecoderSafe() { - // PCM has no instance. - decoder_exist_ = false; - decoder_initialized_ = false; - return; -} - -// Split the stereo packet and place left and right channel after each other -// in the payload vector. -void ACMPCM16B::SplitStereoPacket(uint8_t* payload, int32_t* payload_length) { - uint8_t right_byte_msb; - uint8_t right_byte_lsb; - - // Check for valid inputs. - assert(payload != NULL); - assert(*payload_length > 0); - - // Move two bytes representing right channel each loop, and place it at the - // end of the bytestream vector. After looping the data is reordered to: - // l1 l2 l3 l4 ... l(N-1) lN r1 r2 r3 r4 ... r(N-1) r(N), - // where N is the total number of samples. - - for (int i = 0; i < *payload_length / 2; i += 2) { - right_byte_msb = payload[i + 2]; - right_byte_lsb = payload[i + 3]; - memmove(&payload[i + 2], &payload[i + 4], *payload_length - i - 4); - payload[*payload_length - 2] = right_byte_msb; - payload[*payload_length - 1] = right_byte_lsb; - } -} -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_pcm16b.h b/webrtc/modules/audio_coding/main/source/acm_pcm16b.h deleted file mode 100644 index a97589b57..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_pcm16b.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCM16B_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCM16B_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -namespace webrtc { - -namespace acm1 { - -class ACMPCM16B : public ACMGenericCodec { - public: - explicit ACMPCM16B(int16_t codec_id); - virtual ~ACMPCM16B(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual void SplitStereoPacket(uint8_t* payload, - int32_t* payload_length) OVERRIDE; - - int32_t sampling_freq_hz_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCM16B_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_pcma.cc b/webrtc/modules/audio_coding/main/source/acm_pcma.cc deleted file mode 100644 index 9e5514a9e..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_pcma.cc +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_pcma.h" - -#include "webrtc/modules/audio_coding/codecs/g711/include/g711_interface.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -// Codec interface - -namespace webrtc { - -namespace acm1 { - -ACMPCMA::ACMPCMA(int16_t codec_id) { - codec_id_ = codec_id; -} - -ACMPCMA::~ACMPCMA() { - return; -} - -int16_t ACMPCMA::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - *bitstream_len_byte = WebRtcG711_EncodeA(NULL, &in_audio_[in_audio_ix_read_], - frame_len_smpl_ * num_channels_, - (int16_t*) bitstream); - // Increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer. - in_audio_ix_read_ += frame_len_smpl_ * num_channels_; - return *bitstream_len_byte; -} - -int16_t ACMPCMA::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMPCMA::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, PCM has no instance. - return 0; -} - -int16_t ACMPCMA::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, PCM has no instance. - return 0; -} - -int32_t ACMPCMA::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_PCMA_FUNCTION." - // Then call NetEQ to add the codec to it's database. - if (codec_inst.channels == 1) { - // Mono mode. - SET_CODEC_PAR(codec_def, kDecoderPCMa, codec_inst.pltype, NULL, 8000); - } else { - // Stereo mode. - SET_CODEC_PAR(codec_def, kDecoderPCMa_2ch, codec_inst.pltype, NULL, 8000); - } - SET_PCMA_FUNCTIONS(codec_def); - return 0; -} - -ACMGenericCodec* ACMPCMA::CreateInstance(void) { - return NULL; -} - -int16_t ACMPCMA::InternalCreateEncoder() { - // PCM has no instance. - return 0; -} - -int16_t ACMPCMA::InternalCreateDecoder() { - // PCM has no instance. - return 0; -} - -void ACMPCMA::InternalDestructEncoderInst(void* /* ptr_inst */) { - // PCM has no instance. - return; -} - -void ACMPCMA::DestructEncoderSafe() { - // PCM has no instance. - return; -} - -void ACMPCMA::DestructDecoderSafe() { - // PCM has no instance. - decoder_initialized_ = false; - decoder_exist_ = false; - return; -} - -// Split the stereo packet and place left and right channel after each other -// in the payload vector. -void ACMPCMA::SplitStereoPacket(uint8_t* payload, int32_t* payload_length) { - uint8_t right_byte; - - // Check for valid inputs. - assert(payload != NULL); - assert(*payload_length > 0); - - // Move one bytes representing right channel each loop, and place it at the - // end of the bytestream vector. After looping the data is reordered to: - // l1 l2 l3 l4 ... l(N-1) lN r1 r2 r3 r4 ... r(N-1) r(N), - // where N is the total number of samples. - for (int i = 0; i < *payload_length / 2; i++) { - right_byte = payload[i + 1]; - memmove(&payload[i + 1], &payload[i + 2], *payload_length - i - 2); - payload[*payload_length - 1] = right_byte; - } -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_pcma.h b/webrtc/modules/audio_coding/main/source/acm_pcma.h deleted file mode 100644 index cb506eaa6..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_pcma.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCMA_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCMA_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -namespace webrtc { - -namespace acm1 { - -class ACMPCMA : public ACMGenericCodec { - public: - explicit ACMPCMA(int16_t codec_id); - virtual ~ACMPCMA(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual void SplitStereoPacket(uint8_t* payload, - int32_t* payload_length) OVERRIDE; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCMA_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_pcmu.cc b/webrtc/modules/audio_coding/main/source/acm_pcmu.cc deleted file mode 100644 index 6f4eb27aa..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_pcmu.cc +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_pcmu.h" - -#include "webrtc/modules/audio_coding/codecs/g711/include/g711_interface.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -// Codec interface - -namespace webrtc { - -namespace acm1 { - -ACMPCMU::ACMPCMU(int16_t codec_id) { - codec_id_ = codec_id; -} - -ACMPCMU::~ACMPCMU() { - return; -} - -int16_t ACMPCMU::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - *bitstream_len_byte = WebRtcG711_EncodeU(NULL, &in_audio_[in_audio_ix_read_], - frame_len_smpl_ * num_channels_, - (int16_t*)bitstream); - // Increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer. - in_audio_ix_read_ += frame_len_smpl_ * num_channels_; - return *bitstream_len_byte; -} - -int16_t ACMPCMU::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMPCMU::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, PCM has no instance. - return 0; -} - -int16_t ACMPCMU::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, PCM has no instance. - return 0; -} - -int32_t ACMPCMU::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_PCMU_FUNCTION." - // Then call NetEQ to add the codec to it's database. - if (codec_inst.channels == 1) { - // Mono mode. - SET_CODEC_PAR(codec_def, kDecoderPCMu, codec_inst.pltype, NULL, 8000); - } else { - // Stereo mode. - SET_CODEC_PAR(codec_def, kDecoderPCMu_2ch, codec_inst.pltype, NULL, 8000); - } - SET_PCMU_FUNCTIONS(codec_def); - return 0; -} - -ACMGenericCodec* ACMPCMU::CreateInstance(void) { - return NULL; -} - -int16_t ACMPCMU::InternalCreateEncoder() { - // PCM has no instance. - return 0; -} - -int16_t ACMPCMU::InternalCreateDecoder() { - // PCM has no instance. - return 0; -} - -void ACMPCMU::InternalDestructEncoderInst(void* /* ptr_inst */) { - // PCM has no instance. - return; -} - -void ACMPCMU::DestructEncoderSafe() { - // PCM has no instance. - encoder_exist_ = false; - encoder_initialized_ = false; - return; -} - -void ACMPCMU::DestructDecoderSafe() { - // PCM has no instance. - decoder_initialized_ = false; - decoder_exist_ = false; - return; -} - -// Split the stereo packet and place left and right channel after each other -// in the payload vector. -void ACMPCMU::SplitStereoPacket(uint8_t* payload, int32_t* payload_length) { - uint8_t right_byte; - - // Check for valid inputs. - assert(payload != NULL); - assert(*payload_length > 0); - - // Move one bytes representing right channel each loop, and place it at the - // end of the bytestream vector. After looping the data is reordered to: - // l1 l2 l3 l4 ... l(N-1) lN r1 r2 r3 r4 ... r(N-1) r(N), - // where N is the total number of samples. - for (int i = 0; i < *payload_length / 2; i++) { - right_byte = payload[i + 1]; - memmove(&payload[i + 1], &payload[i + 2], *payload_length - i - 2); - payload[*payload_length - 1] = right_byte; - } -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_pcmu.h b/webrtc/modules/audio_coding/main/source/acm_pcmu.h deleted file mode 100644 index ea401d59c..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_pcmu.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCMU_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCMU_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -namespace webrtc { - -namespace acm1 { - -class ACMPCMU : public ACMGenericCodec { - public: - explicit ACMPCMU(int16_t codec_id); - virtual ~ACMPCMU(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; - - virtual void SplitStereoPacket(uint8_t* payload, - int32_t* payload_length) OVERRIDE; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_PCMU_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_red.cc b/webrtc/modules/audio_coding/main/source/acm_red.cc deleted file mode 100644 index 0d8134c17..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_red.cc +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_red.h" - -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -namespace webrtc { - -namespace acm1 { - -ACMRED::ACMRED(int16_t codec_id) { - codec_id_ = codec_id; -} - -ACMRED::~ACMRED() { - return; -} - -int16_t ACMRED::InternalEncode(uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - // RED is never used as an encoder - // RED has no instance - return 0; -} - -int16_t ACMRED::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMRED::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, - // RED has no instance - return 0; -} - -int16_t ACMRED::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - // This codec does not need initialization, - // RED has no instance - return 0; -} - -int32_t ACMRED::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - // Todo: - // log error - return -1; - } - - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_PCMU_FUNCTION." - // Then call NetEQ to add the codec to it's - // database. - SET_CODEC_PAR((codec_def), kDecoderRED, codec_inst.pltype, NULL, 8000); - SET_RED_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMRED::CreateInstance(void) { - return NULL; -} - -int16_t ACMRED::InternalCreateEncoder() { - // RED has no instance - return 0; -} - -int16_t ACMRED::InternalCreateDecoder() { - // RED has no instance - return 0; -} - -void ACMRED::InternalDestructEncoderInst(void* /* ptr_inst */) { - // RED has no instance - return; -} - -void ACMRED::DestructEncoderSafe() { - // RED has no instance - return; -} - -void ACMRED::DestructDecoderSafe() { - // RED has no instance - return; -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_red.h b/webrtc/modules/audio_coding/main/source/acm_red.h deleted file mode 100644 index ede18b521..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_red.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_RED_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_RED_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -namespace webrtc { - -namespace acm1 { - -class ACMRED : public ACMGenericCodec { - public: - explicit ACMRED(int16_t codec_id); - virtual ~ACMRED(); - - // for FEC - virtual ACMGenericCodec* CreateInstance(void) OVERRIDE; - - virtual int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) OVERRIDE; - - virtual int16_t InternalInitEncoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - virtual int16_t InternalInitDecoder( - WebRtcACMCodecParams* codec_params) OVERRIDE; - - protected: - virtual int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type) OVERRIDE; - - virtual int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) OVERRIDE; - - virtual void DestructEncoderSafe() OVERRIDE; - - virtual void DestructDecoderSafe() OVERRIDE; - - virtual int16_t InternalCreateEncoder() OVERRIDE; - - virtual int16_t InternalCreateDecoder() OVERRIDE; - - virtual void InternalDestructEncoderInst(void* ptr_inst) OVERRIDE; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_RED_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_resampler.cc b/webrtc/modules/audio_coding/main/source/acm_resampler.cc deleted file mode 100644 index 50ddab1d8..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_resampler.cc +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_resampler.h" - -#include - -#include "webrtc/common_audio/resampler/include/push_resampler.h" -#include "webrtc/system_wrappers/interface/logging.h" - -namespace webrtc { - -namespace acm1 { - -ACMResampler::ACMResampler() { -} - -ACMResampler::~ACMResampler() { -} - -int16_t ACMResampler::Resample10Msec(const int16_t* in_audio, - int32_t in_freq_hz, - int16_t* out_audio, - int32_t out_freq_hz, - uint8_t num_audio_channels) { - if (in_freq_hz == out_freq_hz) { - size_t length = static_cast(in_freq_hz * num_audio_channels / 100); - memcpy(out_audio, in_audio, length * sizeof(int16_t)); - return static_cast(in_freq_hz / 100); - } - - // |max_length| is the maximum number of samples for 10ms at 48kHz. - // TODO(turajs): is this actually the capacity of the |out_audio| buffer? - int max_length = 480 * num_audio_channels; - int in_length = in_freq_hz / 100 * num_audio_channels; - - if (resampler_.InitializeIfNeeded(in_freq_hz, out_freq_hz, - num_audio_channels) != 0) { - LOG_FERR3(LS_ERROR, InitializeIfNeeded, in_freq_hz, out_freq_hz, - num_audio_channels); - return -1; - } - - int out_length = resampler_.Resample(in_audio, in_length, out_audio, - max_length); - if (out_length == -1) { - LOG_FERR4(LS_ERROR, Resample, in_audio, in_length, out_audio, max_length); - return -1; - } - - return out_length / num_audio_channels; -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_resampler.h b/webrtc/modules/audio_coding/main/source/acm_resampler.h deleted file mode 100644 index b50e722c4..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_resampler.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_RESAMPLER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_RESAMPLER_H_ - -#include "webrtc/common_audio/resampler/include/push_resampler.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -namespace acm1 { - -class ACMResampler { - public: - ACMResampler(); - ~ACMResampler(); - - int16_t Resample10Msec(const int16_t* in_audio, - const int32_t in_freq_hz, - int16_t* out_audio, - const int32_t out_freq_hz, - uint8_t num_audio_channels); - - private: - PushResampler resampler_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_RESAMPLER_H_ diff --git a/webrtc/modules/audio_coding/main/source/acm_speex.cc b/webrtc/modules/audio_coding/main/source/acm_speex.cc deleted file mode 100644 index 1567929d8..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_speex.cc +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/acm_speex.h" - -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/system_wrappers/interface/trace.h" - -#ifdef WEBRTC_CODEC_SPEEX -// NOTE! Speex is not included in the open-source package. Modify this file or -// your codec API to match the function calls and names of used Speex API file. -#include "speex_interface.h" -#endif - -namespace webrtc { - -namespace acm1 { - -#ifndef WEBRTC_CODEC_SPEEX -ACMSPEEX::ACMSPEEX(int16_t /* codec_id */) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL), - compl_mode_(0), - vbr_enabled_(false), - encoding_rate_(-1), - sampling_frequency_(-1), - samples_in_20ms_audio_(-1) { - return; -} - -ACMSPEEX::~ACMSPEEX() { - return; -} - -int16_t ACMSPEEX::InternalEncode( - uint8_t* /* bitstream */, - int16_t* /* bitstream_len_byte */) { - return -1; -} - -int16_t ACMSPEEX::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return -1; -} - -int16_t ACMSPEEX::EnableDTX() { - return -1; -} - -int16_t ACMSPEEX::DisableDTX() { - return -1; -} - -int16_t ACMSPEEX::InternalInitEncoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int16_t ACMSPEEX::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - return -1; -} - -int32_t ACMSPEEX::CodecDef(WebRtcNetEQ_CodecDef& /* codec_def */, - const CodecInst& /* codec_inst */) { - return -1; -} - -ACMGenericCodec* ACMSPEEX::CreateInstance(void) { - return NULL; -} - -int16_t ACMSPEEX::InternalCreateEncoder() { - return -1; -} - -void ACMSPEEX::DestructEncoderSafe() { - return; -} - -int16_t ACMSPEEX::InternalCreateDecoder() { - return -1; -} - -void ACMSPEEX::DestructDecoderSafe() { - return; -} - -int16_t ACMSPEEX::SetBitRateSafe(const int32_t /* rate */) { - return -1; -} - -void ACMSPEEX::InternalDestructEncoderInst(void* /* ptr_inst */) { - return; -} - -#ifdef UNUSEDSPEEX -int16_t ACMSPEEX::EnableVBR() { - return -1; -} - -int16_t ACMSPEEX::DisableVBR() { - return -1; -} - -int16_t ACMSPEEX::SetComplMode(int16_t mode) { - return -1; -} -#endif - -#else //===================== Actual Implementation ======================= - -ACMSPEEX::ACMSPEEX(int16_t codec_id) - : encoder_inst_ptr_(NULL), - decoder_inst_ptr_(NULL) { - codec_id_ = codec_id; - - // Set sampling frequency, frame size and rate Speex - if (codec_id_ == ACMCodecDB::kSPEEX8) { - sampling_frequency_ = 8000; - samples_in_20ms_audio_ = 160; - encoding_rate_ = 11000; - } else if (codec_id_ == ACMCodecDB::kSPEEX16) { - sampling_frequency_ = 16000; - samples_in_20ms_audio_ = 320; - encoding_rate_ = 22000; - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Wrong codec id for Speex."); - - sampling_frequency_ = -1; - samples_in_20ms_audio_ = -1; - encoding_rate_ = -1; - } - - has_internal_dtx_ = true; - dtx_enabled_ = false; - vbr_enabled_ = false; - compl_mode_ = 3; // default complexity value - - return; -} - -ACMSPEEX::~ACMSPEEX() { - if (encoder_inst_ptr_ != NULL) { - WebRtcSpeex_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - if (decoder_inst_ptr_ != NULL) { - WebRtcSpeex_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - return; -} - -int16_t ACMSPEEX::InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte) { - int16_t status; - int16_t num_encoded_samples = 0; - int16_t n = 0; - - while (num_encoded_samples < frame_len_smpl_) { - status = WebRtcSpeex_Encode(encoder_inst_ptr_, - &in_audio_[in_audio_ix_read_], encoding_rate_); - - // increment the read index this tell the caller that how far - // we have gone forward in reading the audio buffer - in_audio_ix_read_ += samples_in_20ms_audio_; - num_encoded_samples += samples_in_20ms_audio_; - - if (status < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Error in Speex encoder"); - return status; - } - - // Update VAD, if internal DTX is used - if (has_internal_dtx_ && dtx_enabled_) { - vad_label_[n++] = status; - vad_label_[n++] = status; - } - - if (status == 0) { - // This frame is detected as inactive. We need send whatever - // encoded so far. - *bitstream_len_byte = WebRtcSpeex_GetBitstream(encoder_inst_ptr_, - (int16_t*)bitstream); - return *bitstream_len_byte; - } - } - - *bitstream_len_byte = WebRtcSpeex_GetBitstream(encoder_inst_ptr_, - (int16_t*)bitstream); - return *bitstream_len_byte; -} - -int16_t ACMSPEEX::DecodeSafe(uint8_t* /* bitstream */, - int16_t /* bitstream_len_byte */, - int16_t* /* audio */, - int16_t* /* audio_samples */, - int8_t* /* speech_type */) { - return 0; -} - -int16_t ACMSPEEX::EnableDTX() { - if (dtx_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // enable DTX - if (WebRtcSpeex_EncoderInit(encoder_inst_ptr_, (vbr_enabled_ ? 1 : 0), - compl_mode_, 1) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Cannot enable DTX for Speex"); - return -1; - } - dtx_enabled_ = true; - return 0; - } else { - return -1; - } - - return 0; -} - -int16_t ACMSPEEX::DisableDTX() { - if (!dtx_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // disable DTX - if (WebRtcSpeex_EncoderInit(encoder_inst_ptr_, (vbr_enabled_ ? 1 : 0), - compl_mode_, 0) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Cannot disable DTX for Speex"); - return -1; - } - dtx_enabled_ = false; - return 0; - } else { - // encoder doesn't exists, therefore disabling is harmless - return 0; - } - - return 0; -} - -int16_t ACMSPEEX::InternalInitEncoder( - WebRtcACMCodecParams* codec_params) { - // sanity check - if (encoder_inst_ptr_ == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Cannot initialize Speex encoder, instance does not exist"); - return -1; - } - - int16_t status = SetBitRateSafe((codec_params->codecInstant).rate); - status += - (WebRtcSpeex_EncoderInit(encoder_inst_ptr_, vbr_enabled_, compl_mode_, - ((codec_params->enable_dtx) ? 1 : 0)) < 0) ? - -1 : 0; - - if (status >= 0) { - return 0; - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Error in initialization of Speex encoder"); - return -1; - } -} - -int16_t ACMSPEEX::InternalInitDecoder( - WebRtcACMCodecParams* /* codec_params */) { - int16_t status; - - // sanity check - if (decoder_inst_ptr_ == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Cannot initialize Speex decoder, instance does not exist"); - return -1; - } - status = ((WebRtcSpeex_DecoderInit(decoder_inst_ptr_) < 0) ? -1 : 0); - - if (status >= 0) { - return 0; - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Error in initialization of Speex decoder"); - return -1; - } -} - -int32_t ACMSPEEX::CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst) { - if (!decoder_initialized_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Error, Speex decoder is not initialized"); - return -1; - } - - // Fill up the structure by calling - // "SET_CODEC_PAR" & "SET_SPEEX_FUNCTION." - // Then call NetEQ to add the codec to its - // database. - - switch (sampling_frequency_) { - case 8000: { - SET_CODEC_PAR((codec_def), kDecoderSPEEX_8, codec_inst.pltype, - decoder_inst_ptr_, 8000); - break; - } - case 16000: { - SET_CODEC_PAR((codec_def), kDecoderSPEEX_16, codec_inst.pltype, - decoder_inst_ptr_, 16000); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Unsupported sampling frequency for Speex"); - - return -1; - } - } - - SET_SPEEX_FUNCTIONS((codec_def)); - return 0; -} - -ACMGenericCodec* ACMSPEEX::CreateInstance(void) { - return NULL; -} - -int16_t ACMSPEEX::InternalCreateEncoder() { - return WebRtcSpeex_CreateEnc(&encoder_inst_ptr_, sampling_frequency_); -} - -void ACMSPEEX::DestructEncoderSafe() { - if (encoder_inst_ptr_ != NULL) { - WebRtcSpeex_FreeEnc(encoder_inst_ptr_); - encoder_inst_ptr_ = NULL; - } - // there is no encoder set the following - encoder_exist_ = false; - encoder_initialized_ = false; - encoding_rate_ = 0; -} - -int16_t ACMSPEEX::InternalCreateDecoder() { - return WebRtcSpeex_CreateDec(&decoder_inst_ptr_, sampling_frequency_, 1); -} - -void ACMSPEEX::DestructDecoderSafe() { - if (decoder_inst_ptr_ != NULL) { - WebRtcSpeex_FreeDec(decoder_inst_ptr_); - decoder_inst_ptr_ = NULL; - } - // there is no encoder instance set the followings - decoder_exist_ = false; - decoder_initialized_ = false; -} - -int16_t ACMSPEEX::SetBitRateSafe(const int32_t rate) { - // Check if changed rate - if (rate == encoding_rate_) { - return 0; - } else if (rate > 2000) { - encoding_rate_ = rate; - encoder_params_.codecInstant.rate = rate; - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Unsupported encoding rate for Speex"); - - return -1; - } - - return 0; -} - -void ACMSPEEX::InternalDestructEncoderInst(void* ptr_inst) { - if (ptr_inst != NULL) { - WebRtcSpeex_FreeEnc((SPEEX_encinst_t_*) ptr_inst); - } - return; -} - -#ifdef UNUSEDSPEEX - -// This API is currently not in use. If requested to be able to enable/disable -// VBR an ACM API need to be added. -int16_t ACMSPEEX::EnableVBR() { - if (vbr_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // enable Variable Bit Rate (VBR) - if (WebRtcSpeex_EncoderInit(encoder_inst_ptr_, 1, compl_mode_, - (dtx_enabled_ ? 1 : 0)) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Cannot enable VBR mode for Speex"); - - return -1; - } - vbr_enabled_ = true; - return 0; - } else { - return -1; - } -} - -// This API is currently not in use. If requested to be able to enable/disable -// VBR an ACM API need to be added. -int16_t ACMSPEEX::DisableVBR() { - if (!vbr_enabled_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // disable DTX - if (WebRtcSpeex_EncoderInit(encoder_inst_ptr_, 0, compl_mode_, - (dtx_enabled_ ? 1 : 0)) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Cannot disable DTX for Speex"); - - return -1; - } - vbr_enabled_ = false; - return 0; - } else { - // encoder doesn't exists, therefore disabling is harmless - return 0; - } -} - -// This API is currently not in use. If requested to be able to set complexity -// an ACM API need to be added. -int16_t ACMSPEEX::SetComplMode(int16_t mode) { - // Check if new mode - if (mode == compl_mode_) { - return 0; - } else if (encoder_exist_) { // check if encoder exist - // Set new mode - if (WebRtcSpeex_EncoderInit(encoder_inst_ptr_, 0, mode, - (dtx_enabled_ ? 1 : 0)) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, unique_id_, - "Error in complexity mode for Speex"); - return -1; - } - compl_mode_ = mode; - return 0; - } else { - // encoder doesn't exists, therefore disabling is harmless - return 0; - } -} - -#endif - -#endif - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/acm_speex.h b/webrtc/modules/audio_coding/main/source/acm_speex.h deleted file mode 100644 index 762aea8d9..000000000 --- a/webrtc/modules/audio_coding/main/source/acm_speex.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_SPEEX_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_SPEEX_H_ - -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" - -// forward declaration -struct SPEEX_encinst_t_; -struct SPEEX_decinst_t_; - -namespace webrtc { - -namespace acm1 { - -class ACMSPEEX : public ACMGenericCodec { - public: - explicit ACMSPEEX(int16_t codec_id); - ~ACMSPEEX(); - - // for FEC - ACMGenericCodec* CreateInstance(void); - - int16_t InternalEncode(uint8_t* bitstream, - int16_t* bitstream_len_byte); - - int16_t InternalInitEncoder(WebRtcACMCodecParams *codec_params); - - int16_t InternalInitDecoder(WebRtcACMCodecParams *codec_params); - - protected: - int16_t DecodeSafe(uint8_t* bitstream, - int16_t bitstream_len_byte, - int16_t* audio, - int16_t* audio_samples, - int8_t* speech_type); - - int32_t CodecDef(WebRtcNetEQ_CodecDef& codec_def, - const CodecInst& codec_inst); - - void DestructEncoderSafe(); - - void DestructDecoderSafe(); - - int16_t InternalCreateEncoder(); - - int16_t InternalCreateDecoder(); - - void InternalDestructEncoderInst(void* ptr_inst); - - int16_t SetBitRateSafe(const int32_t rate); - - int16_t EnableDTX(); - - int16_t DisableDTX(); - -#ifdef UNUSEDSPEEX - int16_t EnableVBR(); - - int16_t DisableVBR(); - - int16_t SetComplMode(int16_t mode); -#endif - - SPEEX_encinst_t_* encoder_inst_ptr_; - SPEEX_decinst_t_* decoder_inst_ptr_; - int16_t compl_mode_; - bool vbr_enabled_; - int32_t encoding_rate_; - int16_t sampling_frequency_; - uint16_t samples_in_20ms_audio_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_ACM_SPEEX_H_ diff --git a/webrtc/modules/audio_coding/main/source/audio_coding_module.gypi b/webrtc/modules/audio_coding/main/source/audio_coding_module.gypi deleted file mode 100644 index a0389b03e..000000000 --- a/webrtc/modules/audio_coding/main/source/audio_coding_module.gypi +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright (c) 2012 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. - -{ - 'variables': { - 'audio_coding_dependencies': [ - 'CNG', - 'G711', - 'G722', - 'iLBC', - 'iSAC', - 'iSACFix', - 'PCM16B', - 'NetEq', - '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', - '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', - ], - 'audio_coding_defines': [], - 'conditions': [ - ['include_opus==1', { - 'audio_coding_dependencies': ['webrtc_opus',], - 'audio_coding_defines': ['WEBRTC_CODEC_OPUS',], - }], - ], - }, - 'targets': [ - { - 'target_name': 'audio_coding_module', - 'type': 'static_library', - 'defines': [ - '<@(audio_coding_defines)', - ], - 'dependencies': [ - '<@(audio_coding_dependencies)', - 'acm2', - ], - 'include_dirs': [ - '../interface', - '../../../interface', - '<(webrtc_root)', - ], - 'direct_dependent_settings': { - 'include_dirs': [ - '../interface', - '../../../interface', - '<(webrtc_root)', - ], - }, - 'sources': [ - '../interface/audio_coding_module.h', - '../interface/audio_coding_module_typedefs.h', - 'acm_amr.cc', - 'acm_amr.h', - 'acm_amrwb.cc', - 'acm_amrwb.h', - 'acm_celt.cc', - 'acm_celt.h', - 'acm_cng.cc', - 'acm_cng.h', - 'acm_codec_database.cc', - 'acm_codec_database.h', - 'acm_dtmf_detection.cc', - 'acm_dtmf_detection.h', - 'acm_dtmf_playout.cc', - 'acm_dtmf_playout.h', - 'acm_g722.cc', - 'acm_g722.h', - 'acm_g7221.cc', - 'acm_g7221.h', - 'acm_g7221c.cc', - 'acm_g7221c.h', - 'acm_g729.cc', - 'acm_g729.h', - 'acm_g7291.cc', - 'acm_g7291.h', - 'acm_generic_codec.cc', - 'acm_generic_codec.h', - 'acm_gsmfr.cc', - 'acm_gsmfr.h', - 'acm_ilbc.cc', - 'acm_ilbc.h', - 'acm_isac.cc', - 'acm_isac.h', - 'acm_isac_macros.h', - 'acm_neteq.cc', - 'acm_neteq.h', - 'acm_opus.cc', - 'acm_opus.h', - 'acm_speex.cc', - 'acm_speex.h', - 'acm_pcm16b.cc', - 'acm_pcm16b.h', - 'acm_pcma.cc', - 'acm_pcma.h', - 'acm_pcmu.cc', - 'acm_pcmu.h', - 'acm_red.cc', - 'acm_red.h', - 'acm_resampler.cc', - 'acm_resampler.h', - 'audio_coding_module_impl.cc', - 'audio_coding_module_impl.h', - ], - }, - ], - 'conditions': [ - ['include_tests==1', { - 'targets': [ - { - 'target_name': 'delay_test', - 'type': 'executable', - 'dependencies': [ - 'audio_coding_module', - '<(DEPTH)/testing/gtest.gyp:gtest', - '<(webrtc_root)/test/test.gyp:test_support', - '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', - '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', - ], - 'sources': [ - '../test/delay_test.cc', - '../test/Channel.cc', - '../test/PCMFile.cc', - '../test/utility.cc', - ], - }, # delay_test - { - 'target_name': 'insert_packet_with_timing', - 'type': 'executable', - 'dependencies': [ - 'audio_coding_module', - '<(DEPTH)/testing/gtest.gyp:gtest', - '<(webrtc_root)/test/test.gyp:test_support', - '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', - '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', - ], - 'sources': [ - '../test/insert_packet_with_timing.cc', - '../test/Channel.cc', - '../test/PCMFile.cc', - ], - }, # delay_test - ], - }], - ], - 'includes': [ - '../acm2/audio_coding_module.gypi', - ], -} diff --git a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.cc b/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.cc deleted file mode 100644 index b8060ce43..000000000 --- a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.cc +++ /dev/null @@ -1,3044 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h" - -#include -#include - -#include // For std::max. - -#include "webrtc/engine_configurations.h" -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" -#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" -#include "webrtc/modules/audio_coding/main/source/acm_dtmf_detection.h" -#include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" -#include "webrtc/modules/audio_coding/main/source/acm_resampler.h" -#include "webrtc/modules/audio_coding/main/acm2/nack.h" -#include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/logging.h" -#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" -#include "webrtc/system_wrappers/interface/tick_util.h" -#include "webrtc/system_wrappers/interface/trace.h" -#include "webrtc/system_wrappers/interface/trace_event.h" - -namespace webrtc { - -namespace acm1 { - -enum { - kACMToneEnd = 999 -}; - -// Maximum number of bytes in one packet (PCM16B, 20 ms packets, stereo). -enum { - kMaxPacketSize = 2560 -}; - -// Maximum number of payloads that can be packed in one RED payload. For -// regular FEC, we only pack two payloads. In case of dual-streaming, in worst -// case we might pack 3 payloads in one RED payload. -enum { - kNumFecFragmentationVectors = 2, - kMaxNumFragmentationVectors = 3 -}; - -static const uint32_t kMaskTimestamp = 0x03ffffff; -static const int kDefaultTimestampDiff = 960; // 20 ms @ 48 kHz. - -// If packet N is arrived all packets prior to N - |kNackThresholdPackets| which -// are not received are considered as lost, and appear in NACK list. -static const int kNackThresholdPackets = 2; - -namespace { - -bool IsCodecRED(const CodecInst* codec) { - return (STR_CASE_CMP(codec->plname, "RED") == 0); -} - -bool IsCodecRED(int index) { - return (IsCodecRED(&ACMCodecDB::database_[index])); -} - -bool IsCodecCN(const CodecInst* codec) { - return (STR_CASE_CMP(codec->plname, "CN") == 0); -} - -bool IsCodecCN(int index) { - return (IsCodecCN(&ACMCodecDB::database_[index])); -} - -// Stereo-to-mono can be used as in-place. -int DownMix(const AudioFrame& frame, int length_out_buff, int16_t* out_buff) { - if (length_out_buff < frame.samples_per_channel_) { - return -1; - } - for (int n = 0; n < frame.samples_per_channel_; ++n) - out_buff[n] = (frame.data_[2 * n] + frame.data_[2 * n + 1]) >> 1; - return 0; -} - -// Mono-to-stereo can be used as in-place. -int UpMix(const AudioFrame& frame, int length_out_buff, int16_t* out_buff) { - if (length_out_buff < frame.samples_per_channel_) { - return -1; - } - for (int n = frame.samples_per_channel_ - 1; n >= 0; --n) { - out_buff[2 * n + 1] = frame.data_[n]; - out_buff[2 * n] = frame.data_[n]; - } - return 0; -} - -// Return 1 if timestamp t1 is less than timestamp t2, while compensating for -// wrap-around. -int TimestampLessThan(uint32_t t1, uint32_t t2) { - uint32_t kHalfFullRange = static_cast(0xFFFFFFFF) / 2; - if (t1 == t2) { - return 0; - } else if (t1 < t2) { - if (t2 - t1 < kHalfFullRange) - return 1; - return 0; - } else { - if (t1 - t2 < kHalfFullRange) - return 0; - return 1; - } -} - -} // namespace - -AudioCodingModuleImpl::AudioCodingModuleImpl(const int32_t id, Clock* clock) - : packetization_callback_(NULL), - id_(id), - last_timestamp_(0xD87F3F9F), - last_in_timestamp_(0xD87F3F9F), - send_codec_inst_(), - cng_nb_pltype_(255), - cng_wb_pltype_(255), - cng_swb_pltype_(255), - cng_fb_pltype_(255), - red_pltype_(255), - vad_enabled_(false), - dtx_enabled_(false), - vad_mode_(VADNormal), - stereo_receive_registered_(false), - stereo_send_(false), - prev_received_channel_(0), - expected_channels_(1), - current_send_codec_idx_(-1), - current_receive_codec_idx_(-1), - send_codec_registered_(false), - acm_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - vad_callback_(NULL), - last_recv_audio_codec_pltype_(255), - is_first_red_(true), - fec_enabled_(false), - last_fec_timestamp_(0), - receive_red_pltype_(255), - previous_pltype_(255), - dummy_rtp_header_(NULL), - recv_pl_frame_size_smpls_(0), - receiver_initialized_(false), - dtmf_detector_(NULL), - dtmf_callback_(NULL), - last_detected_tone_(kACMToneEnd), - callback_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - secondary_send_codec_inst_(), - initial_delay_ms_(0), - num_packets_accumulated_(0), - num_bytes_accumulated_(0), - accumulated_audio_ms_(0), - first_payload_received_(false), - last_incoming_send_timestamp_(0), - track_neteq_buffer_(false), - playout_ts_(0), - av_sync_(false), - last_timestamp_diff_(kDefaultTimestampDiff), - last_sequence_number_(0), - last_ssrc_(0), - last_packet_was_sync_(false), - clock_(clock), - nack_(), - nack_enabled_(false) { - - // Nullify send codec memory, set payload type and set codec name to - // invalid values. - const char no_name[] = "noCodecRegistered"; - strncpy(send_codec_inst_.plname, no_name, RTP_PAYLOAD_NAME_SIZE - 1); - send_codec_inst_.pltype = -1; - - strncpy(secondary_send_codec_inst_.plname, no_name, - RTP_PAYLOAD_NAME_SIZE - 1); - secondary_send_codec_inst_.pltype = -1; - - for (int i = 0; i < ACMCodecDB::kMaxNumCodecs; i++) { - codecs_[i] = NULL; - registered_pltypes_[i] = -1; - stereo_receive_[i] = false; - slave_codecs_[i] = NULL; - mirror_codec_idx_[i] = -1; - } - - neteq_.set_id(id_); - - // Allocate memory for RED. - red_buffer_ = new uint8_t[MAX_PAYLOAD_SIZE_BYTE]; - - // TODO(turajs): This might not be exactly how this class is supposed to work. - // The external usage might be that |fragmentationVectorSize| has to match - // the allocated space for the member-arrays, while here, we allocate - // according to the maximum number of fragmentations and change - // |fragmentationVectorSize| on-the-fly based on actual number of - // fragmentations. However, due to copying to local variable before calling - // SendData, the RTP module receives a "valid" fragmentation, where allocated - // space matches |fragmentationVectorSize|, therefore, this should not cause - // any problem. A better approach is not using RTPFragmentationHeader as - // member variable, instead, use an ACM-specific structure to hold RED-related - // data. See module_common_type.h for the definition of - // RTPFragmentationHeader. - fragmentation_.VerifyAndAllocateFragmentationHeader( - kMaxNumFragmentationVectors); - - // Register the default payload type for RED and for CNG at sampling rates of - // 8, 16, 32 and 48 kHz. - for (int i = (ACMCodecDB::kNumCodecs - 1); i >= 0; i--) { - if (IsCodecRED(i)) { - red_pltype_ = static_cast(ACMCodecDB::database_[i].pltype); - } else if (IsCodecCN(i)) { - if (ACMCodecDB::database_[i].plfreq == 8000) { - cng_nb_pltype_ = static_cast(ACMCodecDB::database_[i].pltype); - } else if (ACMCodecDB::database_[i].plfreq == 16000) { - cng_wb_pltype_ = static_cast(ACMCodecDB::database_[i].pltype); - } else if (ACMCodecDB::database_[i].plfreq == 32000) { - cng_swb_pltype_ = static_cast(ACMCodecDB::database_[i].pltype); - } else if (ACMCodecDB::database_[i].plfreq == 48000) { - cng_fb_pltype_ = static_cast(ACMCodecDB::database_[i].pltype); - } - } - } - - if (InitializeReceiverSafe() < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot initialize receiver"); - } - WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceAudioCoding, id, "Created"); -} - -AudioCodingModuleImpl::~AudioCodingModuleImpl() { - { - CriticalSectionScoped lock(acm_crit_sect_); - current_send_codec_idx_ = -1; - - for (int i = 0; i < ACMCodecDB::kMaxNumCodecs; i++) { - if (codecs_[i] != NULL) { - // True stereo codecs share the same memory for master and - // slave, so slave codec need to be nullified here, since the - // memory will be deleted. - if (slave_codecs_[i] == codecs_[i]) { - slave_codecs_[i] = NULL; - } - - // Mirror index holds the address of the codec memory. - assert(mirror_codec_idx_[i] > -1); - if (codecs_[mirror_codec_idx_[i]] != NULL) { - delete codecs_[mirror_codec_idx_[i]]; - codecs_[mirror_codec_idx_[i]] = NULL; - } - - codecs_[i] = NULL; - } - - if (slave_codecs_[i] != NULL) { - // Delete memory for stereo usage of mono codecs. - assert(mirror_codec_idx_[i] > -1); - if (slave_codecs_[mirror_codec_idx_[i]] != NULL) { - delete slave_codecs_[mirror_codec_idx_[i]]; - slave_codecs_[mirror_codec_idx_[i]] = NULL; - } - slave_codecs_[i] = NULL; - } - } - - if (dtmf_detector_ != NULL) { - delete dtmf_detector_; - dtmf_detector_ = NULL; - } - if (dummy_rtp_header_ != NULL) { - delete dummy_rtp_header_; - dummy_rtp_header_ = NULL; - } - if (red_buffer_ != NULL) { - delete[] red_buffer_; - red_buffer_ = NULL; - } - } - - delete callback_crit_sect_; - callback_crit_sect_ = NULL; - - delete acm_crit_sect_; - acm_crit_sect_ = NULL; - WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceAudioCoding, id_, - "Destroyed"); -} - -int32_t AudioCodingModuleImpl::ChangeUniqueId(const int32_t id) { - { - CriticalSectionScoped lock(acm_crit_sect_); - id_ = id; - - for (int i = 0; i < ACMCodecDB::kMaxNumCodecs; i++) { - if (codecs_[i] != NULL) { - codecs_[i]->SetUniqueID(id); - } - } - } - - neteq_.set_id(id_); - return 0; -} - -// Returns the number of milliseconds until the module want a -// worker thread to call Process. -int32_t AudioCodingModuleImpl::TimeUntilNextProcess() { - CriticalSectionScoped lock(acm_crit_sect_); - - if (!HaveValidEncoder("TimeUntilNextProcess")) { - return -1; - } - return codecs_[current_send_codec_idx_]->SamplesLeftToEncode() / - (send_codec_inst_.plfreq / 1000); -} - -int32_t AudioCodingModuleImpl::Process() { - bool dual_stream; - { - CriticalSectionScoped lock(acm_crit_sect_); - dual_stream = (secondary_encoder_.get() != NULL); - } - if (dual_stream) { - return ProcessDualStream(); - } - return ProcessSingleStream(); -} - -int AudioCodingModuleImpl::EncodeFragmentation(int fragmentation_index, - int payload_type, - uint32_t current_timestamp, - ACMGenericCodec* encoder, - uint8_t* stream) { - int16_t len_bytes = MAX_PAYLOAD_SIZE_BYTE; - uint32_t rtp_timestamp; - WebRtcACMEncodingType encoding_type; - if (encoder->Encode(stream, &len_bytes, &rtp_timestamp, &encoding_type) < 0) { - return -1; - } - assert(encoding_type == kActiveNormalEncoded); - assert(len_bytes > 0); - - fragmentation_.fragmentationLength[fragmentation_index] = len_bytes; - fragmentation_.fragmentationPlType[fragmentation_index] = payload_type; - fragmentation_.fragmentationTimeDiff[fragmentation_index] = - static_cast(current_timestamp - rtp_timestamp); - fragmentation_.fragmentationVectorSize++; - return len_bytes; -} - -// Primary payloads are sent immediately, whereas a single secondary payload is -// buffered to be combined with "the next payload." -// Normally "the next payload" would be a primary payload. In case two -// consecutive secondary payloads are generated with no primary payload in -// between, then two secondary payloads are packed in one RED. -int AudioCodingModuleImpl::ProcessDualStream() { - uint8_t stream[kMaxNumFragmentationVectors * MAX_PAYLOAD_SIZE_BYTE]; - uint32_t current_timestamp; - int16_t length_bytes = 0; - RTPFragmentationHeader my_fragmentation; - - uint8_t my_red_payload_type; - - { - CriticalSectionScoped lock(acm_crit_sect_); - // Check if there is an encoder before. - if (!HaveValidEncoder("ProcessDualStream") || - secondary_encoder_.get() == NULL) { - return -1; - } - ACMGenericCodec* primary_encoder = codecs_[current_send_codec_idx_]; - // If primary encoder has a full frame of audio to generate payload. - bool primary_ready_to_encode = primary_encoder->HasFrameToEncode(); - // If the secondary encoder has a frame of audio to generate a payload. - bool secondary_ready_to_encode = secondary_encoder_->HasFrameToEncode(); - - if (!primary_ready_to_encode && !secondary_ready_to_encode) { - // Nothing to send. - return 0; - } - int len_bytes_previous_secondary = static_cast( - fragmentation_.fragmentationLength[2]); - assert(len_bytes_previous_secondary <= MAX_PAYLOAD_SIZE_BYTE); - bool has_previous_payload = len_bytes_previous_secondary > 0; - - uint32_t primary_timestamp = primary_encoder->EarliestTimestamp(); - uint32_t secondary_timestamp = secondary_encoder_->EarliestTimestamp(); - - if (!has_previous_payload && !primary_ready_to_encode && - secondary_ready_to_encode) { - // Secondary payload will be the ONLY bit-stream. Encode by secondary - // encoder, store the payload, and return. No packet is sent. - int16_t len_bytes = MAX_PAYLOAD_SIZE_BYTE; - WebRtcACMEncodingType encoding_type; - if (secondary_encoder_->Encode(red_buffer_, &len_bytes, - &last_fec_timestamp_, - &encoding_type) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ProcessDual(): Encoding of secondary encoder Failed"); - return -1; - } - assert(len_bytes > 0); - assert(encoding_type == kActiveNormalEncoded); - assert(len_bytes <= MAX_PAYLOAD_SIZE_BYTE); - fragmentation_.fragmentationLength[2] = len_bytes; - return 0; - } - - // Initialize with invalid but different values, so later can have sanity - // check if they are different. - int index_primary = -1; - int index_secondary = -2; - int index_previous_secondary = -3; - - if (primary_ready_to_encode) { - index_primary = secondary_ready_to_encode ? - TimestampLessThan(primary_timestamp, secondary_timestamp) : 0; - index_primary += has_previous_payload ? - TimestampLessThan(primary_timestamp, last_fec_timestamp_) : 0; - } - - if (secondary_ready_to_encode) { - // Timestamp of secondary payload can only be less than primary payload, - // but is always larger than the timestamp of previous secondary payload. - index_secondary = primary_ready_to_encode ? - (1 - TimestampLessThan(primary_timestamp, secondary_timestamp)) : 0; - } - - if (has_previous_payload) { - index_previous_secondary = primary_ready_to_encode ? - (1 - TimestampLessThan(primary_timestamp, last_fec_timestamp_)) : 0; - // If secondary is ready it always have a timestamp larger than previous - // secondary. So the index is either 0 or 1. - index_previous_secondary += secondary_ready_to_encode ? 1 : 0; - } - - // Indices must not be equal. - assert(index_primary != index_secondary); - assert(index_primary != index_previous_secondary); - assert(index_secondary != index_previous_secondary); - - // One of the payloads has to be at position zero. - assert(index_primary == 0 || index_secondary == 0 || - index_previous_secondary == 0); - - // Timestamp of the RED payload. - if (index_primary == 0) { - current_timestamp = primary_timestamp; - } else if (index_secondary == 0) { - current_timestamp = secondary_timestamp; - } else { - current_timestamp = last_fec_timestamp_; - } - - fragmentation_.fragmentationVectorSize = 0; - if (has_previous_payload) { - assert(index_previous_secondary >= 0 && - index_previous_secondary < kMaxNumFragmentationVectors); - assert(len_bytes_previous_secondary <= MAX_PAYLOAD_SIZE_BYTE); - memcpy(&stream[index_previous_secondary * MAX_PAYLOAD_SIZE_BYTE], - red_buffer_, sizeof(stream[0]) * len_bytes_previous_secondary); - fragmentation_.fragmentationLength[index_previous_secondary] = - len_bytes_previous_secondary; - fragmentation_.fragmentationPlType[index_previous_secondary] = - secondary_send_codec_inst_.pltype; - fragmentation_.fragmentationTimeDiff[index_previous_secondary] = - static_cast(current_timestamp - last_fec_timestamp_); - fragmentation_.fragmentationVectorSize++; - } - - if (primary_ready_to_encode) { - assert(index_primary >= 0 && index_primary < kMaxNumFragmentationVectors); - int i = index_primary * MAX_PAYLOAD_SIZE_BYTE; - if (EncodeFragmentation(index_primary, send_codec_inst_.pltype, - current_timestamp, primary_encoder, - &stream[i]) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ProcessDualStream(): Encoding of primary encoder Failed"); - return -1; - } - } - - if (secondary_ready_to_encode) { - assert(index_secondary >= 0 && - index_secondary < kMaxNumFragmentationVectors - 1); - int i = index_secondary * MAX_PAYLOAD_SIZE_BYTE; - if (EncodeFragmentation(index_secondary, - secondary_send_codec_inst_.pltype, - current_timestamp, secondary_encoder_.get(), - &stream[i]) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ProcessDualStream(): Encoding of secondary encoder " - "Failed"); - return -1; - } - } - // Copy to local variable, as it will be used outside the ACM lock. - my_fragmentation.CopyFrom(fragmentation_); - my_red_payload_type = red_pltype_; - length_bytes = 0; - for (int n = 0; n < fragmentation_.fragmentationVectorSize; n++) { - length_bytes += fragmentation_.fragmentationLength[n]; - } - } - - { - CriticalSectionScoped lock(callback_crit_sect_); - if (packetization_callback_ != NULL) { - // Callback with payload data, including redundant data (FEC/RED). - if (packetization_callback_->SendData(kAudioFrameSpeech, - my_red_payload_type, - current_timestamp, stream, - length_bytes, - &my_fragmentation) < 0) { - return -1; - } - } - } - - { - CriticalSectionScoped lock(acm_crit_sect_); - // Now that data is sent, clean up fragmentation. - ResetFragmentation(0); - } - return 0; -} - -// Process any pending tasks such as timeouts. -int AudioCodingModuleImpl::ProcessSingleStream() { - // Make room for 1 RED payload. - uint8_t stream[2 * MAX_PAYLOAD_SIZE_BYTE]; - int16_t length_bytes = 2 * MAX_PAYLOAD_SIZE_BYTE; - int16_t red_length_bytes = length_bytes; - uint32_t rtp_timestamp; - int16_t status; - WebRtcACMEncodingType encoding_type; - FrameType frame_type = kAudioFrameSpeech; - uint8_t current_payload_type = 0; - bool has_data_to_send = false; - bool fec_active = false; - RTPFragmentationHeader my_fragmentation; - - // Keep the scope of the ACM critical section limited. - { - CriticalSectionScoped lock(acm_crit_sect_); - // Check if there is an encoder before. - if (!HaveValidEncoder("ProcessSingleStream")) { - return -1; - } - status = codecs_[current_send_codec_idx_]->Encode(stream, &length_bytes, - &rtp_timestamp, - &encoding_type); - if (status < 0) { - // Encode failed. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ProcessSingleStream(): Encoding Failed"); - length_bytes = 0; - return -1; - } else if (status == 0) { - // Not enough data. - return 0; - } else { - switch (encoding_type) { - case kNoEncoding: { - current_payload_type = previous_pltype_; - frame_type = kFrameEmpty; - length_bytes = 0; - break; - } - case kActiveNormalEncoded: - case kPassiveNormalEncoded: { - current_payload_type = static_cast(send_codec_inst_.pltype); - frame_type = kAudioFrameSpeech; - break; - } - case kPassiveDTXNB: { - current_payload_type = cng_nb_pltype_; - frame_type = kAudioFrameCN; - is_first_red_ = true; - break; - } - case kPassiveDTXWB: { - current_payload_type = cng_wb_pltype_; - frame_type = kAudioFrameCN; - is_first_red_ = true; - break; - } - case kPassiveDTXSWB: { - current_payload_type = cng_swb_pltype_; - frame_type = kAudioFrameCN; - is_first_red_ = true; - break; - } - case kPassiveDTXFB: { - current_payload_type = cng_fb_pltype_; - frame_type = kAudioFrameCN; - is_first_red_ = true; - break; - } - } - has_data_to_send = true; - previous_pltype_ = current_payload_type; - - // Redundancy encode is done here. The two bitstreams packetized into - // one RTP packet and the fragmentation points are set. - // Only apply RED on speech data. - if ((fec_enabled_) && - ((encoding_type == kActiveNormalEncoded) || - (encoding_type == kPassiveNormalEncoded))) { - // FEC is enabled within this scope. - // - // Note that, a special solution exists for iSAC since it is the only - // codec for which GetRedPayload has a non-empty implementation. - // - // Summary of the FEC scheme below (use iSAC as example): - // - // 1st (is_first_red_ is true) encoded iSAC frame (primary #1) => - // - call GetRedPayload() and store redundancy for packet #1 in - // second fragment of RED buffer (old data) - // - drop the primary iSAC frame - // - don't call SendData - // 2nd (is_first_red_ is false) encoded iSAC frame (primary #2) => - // - store primary #2 in 1st fragment of RED buffer and send the - // combined packet - // - the transmitted packet contains primary #2 (new) and - // reduncancy for packet #1 (old) - // - call GetRed_Payload() and store redundancy for packet #2 in - // second fragment of RED buffer - // - // ... - // - // Nth encoded iSAC frame (primary #N) => - // - store primary #N in 1st fragment of RED buffer and send the - // combined packet - // - the transmitted packet contains primary #N (new) and - // reduncancy for packet #(N-1) (old) - // - call GetRedPayload() and store redundancy for packet #N in - // second fragment of RED buffer - // - // For all other codecs, GetRedPayload does nothing and returns -1 => - // redundant data is only a copy. - // - // First combined packet contains : #2 (new) and #1 (old) - // Second combined packet contains: #3 (new) and #2 (old) - // Third combined packet contains : #4 (new) and #3 (old) - // - // Hence, even if every second packet is dropped, perfect - // reconstruction is possible. - fec_active = true; - - has_data_to_send = false; - // Skip the following part for the first packet in a RED session. - if (!is_first_red_) { - // Rearrange stream such that FEC packets are included. - // Replace stream now that we have stored current stream. - memcpy(stream + fragmentation_.fragmentationOffset[1], red_buffer_, - fragmentation_.fragmentationLength[1]); - // Update the fragmentation time difference vector, in number of - // timestamps. - uint16_t time_since_last = static_cast(rtp_timestamp - - last_fec_timestamp_); - - // Update fragmentation vectors. - fragmentation_.fragmentationPlType[1] = - fragmentation_.fragmentationPlType[0]; - fragmentation_.fragmentationTimeDiff[1] = time_since_last; - has_data_to_send = true; - } - - // Insert new packet length. - fragmentation_.fragmentationLength[0] = length_bytes; - - // Insert new packet payload type. - fragmentation_.fragmentationPlType[0] = current_payload_type; - last_fec_timestamp_ = rtp_timestamp; - - // Can be modified by the GetRedPayload() call if iSAC is utilized. - red_length_bytes = length_bytes; - - // A fragmentation header is provided => packetization according to - // RFC 2198 (RTP Payload for Redundant Audio Data) will be used. - // First fragment is the current data (new). - // Second fragment is the previous data (old). - length_bytes = static_cast( - fragmentation_.fragmentationLength[0] + - fragmentation_.fragmentationLength[1]); - - // Get, and store, redundant data from the encoder based on the recently - // encoded frame. - // NOTE - only iSAC contains an implementation; all other codecs does - // nothing and returns -1. - if (codecs_[current_send_codec_idx_]->GetRedPayload( - red_buffer_, - &red_length_bytes) == -1) { - // The codec was not iSAC => use current encoder output as redundant - // data instead (trivial FEC scheme). - memcpy(red_buffer_, stream, red_length_bytes); - } - - is_first_red_ = false; - // Update payload type with RED payload type. - current_payload_type = red_pltype_; - // We have packed 2 payloads. - fragmentation_.fragmentationVectorSize = kNumFecFragmentationVectors; - - // Copy to local variable, as it will be used outside ACM lock. - my_fragmentation.CopyFrom(fragmentation_); - // Store RED length. - fragmentation_.fragmentationLength[1] = red_length_bytes; - } - } - } - - if (has_data_to_send) { - CriticalSectionScoped lock(callback_crit_sect_); - - if (packetization_callback_ != NULL) { - if (fec_active) { - // Callback with payload data, including redundant data (FEC/RED). - packetization_callback_->SendData(frame_type, current_payload_type, - rtp_timestamp, stream, - length_bytes, - &my_fragmentation); - } else { - // Callback with payload data. - packetization_callback_->SendData(frame_type, current_payload_type, - rtp_timestamp, stream, - length_bytes, NULL); - } - } - - if (vad_callback_ != NULL) { - // Callback with VAD decision. - vad_callback_->InFrameType(static_cast(encoding_type)); - } - } - return length_bytes; -} - -///////////////////////////////////////// -// Sender -// - -// Initialize send codec. -int32_t AudioCodingModuleImpl::InitializeSender() { - CriticalSectionScoped lock(acm_crit_sect_); - - // Start with invalid values. - send_codec_registered_ = false; - current_send_codec_idx_ = -1; - send_codec_inst_.plname[0] = '\0'; - - // Delete all encoders to start fresh. - for (int id = 0; id < ACMCodecDB::kMaxNumCodecs; id++) { - if (codecs_[id] != NULL) { - codecs_[id]->DestructEncoder(); - } - } - - // Initialize FEC/RED. - is_first_red_ = true; - if (fec_enabled_ || secondary_encoder_.get() != NULL) { - if (red_buffer_ != NULL) { - memset(red_buffer_, 0, MAX_PAYLOAD_SIZE_BYTE); - } - if (fec_enabled_) { - ResetFragmentation(kNumFecFragmentationVectors); - } else { - ResetFragmentation(0); - } - } - - return 0; -} - -int32_t AudioCodingModuleImpl::ResetEncoder() { - CriticalSectionScoped lock(acm_crit_sect_); - if (!HaveValidEncoder("ResetEncoder")) { - return -1; - } - return codecs_[current_send_codec_idx_]->ResetEncoder(); -} - -void AudioCodingModuleImpl::UnregisterSendCodec() { - CriticalSectionScoped lock(acm_crit_sect_); - send_codec_registered_ = false; - current_send_codec_idx_ = -1; - // If send Codec is unregistered then remove the secondary codec as well. - if (secondary_encoder_.get() != NULL) - secondary_encoder_.reset(); - return; -} - -ACMGenericCodec* AudioCodingModuleImpl::CreateCodec(const CodecInst& codec) { - ACMGenericCodec* my_codec = NULL; - - my_codec = ACMCodecDB::CreateCodecInstance(&codec); - if (my_codec == NULL) { - // Error, could not create the codec. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ACMCodecDB::CreateCodecInstance() failed in CreateCodec()"); - return my_codec; - } - my_codec->SetUniqueID(id_); - my_codec->SetNetEqDecodeLock(neteq_.DecodeLock()); - - return my_codec; -} - -// Check if the given codec is a valid to be registered as send codec. -static int IsValidSendCodec(const CodecInst& send_codec, - bool is_primary_encoder, - int acm_id, - int* mirror_id) { - if ((send_codec.channels != 1) && (send_codec.channels != 2)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, acm_id, - "Wrong number of channels (%d, only mono and stereo are " - "supported) for %s encoder", send_codec.channels, - is_primary_encoder ? "primary" : "secondary"); - return -1; - } - - int codec_id = ACMCodecDB::CodecNumber(&send_codec, mirror_id); - if (codec_id < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, acm_id, - "Invalid settings for the send codec."); - return -1; - } - - // TODO(tlegrand): Remove this check. Already taken care of in - // ACMCodecDB::CodecNumber(). - // Check if the payload-type is valid - if (!ACMCodecDB::ValidPayloadType(send_codec.pltype)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, acm_id, - "Invalid payload-type %d for %s.", send_codec.pltype, - send_codec.plname); - return -1; - } - - // Telephone-event cannot be a send codec. - if (!STR_CASE_CMP(send_codec.plname, "telephone-event")) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, acm_id, - "telephone-event cannot be a send codec"); - *mirror_id = -1; - return -1; - } - - if (ACMCodecDB::codec_settings_[codec_id].channel_support - < send_codec.channels) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, acm_id, - "%d number of channels not supportedn for %s.", - send_codec.channels, send_codec.plname); - *mirror_id = -1; - return -1; - } - - if (!is_primary_encoder) { - // If registering the secondary encoder, then RED and CN are not valid - // choices as encoder. - if (IsCodecRED(&send_codec)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, acm_id, - "RED cannot be secondary codec"); - *mirror_id = -1; - return -1; - } - - if (IsCodecCN(&send_codec)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, acm_id, - "DTX cannot be secondary codec"); - *mirror_id = -1; - return -1; - } - } - return codec_id; -} - -int AudioCodingModuleImpl::RegisterSecondarySendCodec( - const CodecInst& send_codec) { - CriticalSectionScoped lock(acm_crit_sect_); - if (!send_codec_registered_) { - return -1; - } - // Primary and Secondary codecs should have the same sampling rates. - if (send_codec.plfreq != send_codec_inst_.plfreq) { - return -1; - } - int mirror_id; - int codec_id = IsValidSendCodec(send_codec, false, id_, &mirror_id); - if (codec_id < 0) { - return -1; - } - ACMGenericCodec* encoder = CreateCodec(send_codec); - WebRtcACMCodecParams codec_params; - // Initialize the codec before registering. For secondary codec VAD & DTX are - // disabled. - memcpy(&(codec_params.codec_inst), &send_codec, sizeof(CodecInst)); - codec_params.enable_vad = false; - codec_params.enable_dtx = false; - codec_params.vad_mode = VADNormal; - // Force initialization. - if (encoder->InitEncoder(&codec_params, true) < 0) { - // Could not initialize, therefore cannot be registered. - delete encoder; - return -1; - } - secondary_encoder_.reset(encoder); - memcpy(&secondary_send_codec_inst_, &send_codec, sizeof(send_codec)); - - // Disable VAD & DTX. - SetVADSafe(false, false, VADNormal); - - // Cleaning. - if (red_buffer_) { - memset(red_buffer_, 0, MAX_PAYLOAD_SIZE_BYTE); - } - ResetFragmentation(0); - return 0; -} - -void AudioCodingModuleImpl::UnregisterSecondarySendCodec() { - CriticalSectionScoped lock(acm_crit_sect_); - if (secondary_encoder_.get() == NULL) { - return; - } - secondary_encoder_.reset(); - ResetFragmentation(0); -} - -int AudioCodingModuleImpl::SecondarySendCodec( - CodecInst* secondary_codec) const { - CriticalSectionScoped lock(acm_crit_sect_); - if (secondary_encoder_.get() == NULL) { - return -1; - } - memcpy(secondary_codec, &secondary_send_codec_inst_, - sizeof(secondary_send_codec_inst_)); - return 0; -} - -// Can be called multiple times for Codec, CNG, RED. -int32_t AudioCodingModuleImpl::RegisterSendCodec( - const CodecInst& send_codec) { - int mirror_id; - int codec_id = IsValidSendCodec(send_codec, true, id_, &mirror_id); - - CriticalSectionScoped lock(acm_crit_sect_); - - // Check for reported errors from function IsValidSendCodec(). - if (codec_id < 0) { - if (!send_codec_registered_) { - // This values has to be NULL if there is no codec registered. - current_send_codec_idx_ = -1; - } - return -1; - } - - // RED can be registered with other payload type. If not registered a default - // payload type is used. - if (IsCodecRED(&send_codec)) { - // TODO(tlegrand): Remove this check. Already taken care of in - // ACMCodecDB::CodecNumber(). - // Check if the payload-type is valid - if (!ACMCodecDB::ValidPayloadType(send_codec.pltype)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Invalid payload-type %d for %s.", send_codec.pltype, - send_codec.plname); - return -1; - } - // Set RED payload type. - red_pltype_ = static_cast(send_codec.pltype); - return 0; - } - - // CNG can be registered with other payload type. If not registered the - // default payload types from codec database will be used. - if (IsCodecCN(&send_codec)) { - // CNG is registered. - switch (send_codec.plfreq) { - case 8000: { - cng_nb_pltype_ = static_cast(send_codec.pltype); - break; - } - case 16000: { - cng_wb_pltype_ = static_cast(send_codec.pltype); - break; - } - case 32000: { - cng_swb_pltype_ = static_cast(send_codec.pltype); - break; - } - case 48000: { - cng_fb_pltype_ = static_cast(send_codec.pltype); - break; - } - default: { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RegisterSendCodec() failed, invalid frequency for CNG " - "registration"); - return -1; - } - } - return 0; - } - - // Set Stereo, and make sure VAD and DTX is turned off. - if (send_codec.channels == 2) { - stereo_send_ = true; - if (vad_enabled_ || dtx_enabled_) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - "VAD/DTX is turned off, not supported when sending stereo."); - } - vad_enabled_ = false; - dtx_enabled_ = false; - } else { - stereo_send_ = false; - } - - // Check if the codec is already registered as send codec. - bool is_send_codec; - if (send_codec_registered_) { - int send_codec_mirror_id; - int send_codec_id = ACMCodecDB::CodecNumber(&send_codec_inst_, - &send_codec_mirror_id); - assert(send_codec_id >= 0); - is_send_codec = (send_codec_id == codec_id) || - (mirror_id == send_codec_mirror_id); - } else { - is_send_codec = false; - } - - // If there is secondary codec registered and the new send codec has a - // sampling rate different than that of secondary codec, then unregister the - // secondary codec. - if (secondary_encoder_.get() != NULL && - secondary_send_codec_inst_.plfreq != send_codec.plfreq) { - secondary_encoder_.reset(); - ResetFragmentation(0); - } - - // If new codec, or new settings, register. - if (!is_send_codec) { - if (codecs_[mirror_id] == NULL) { - codecs_[mirror_id] = CreateCodec(send_codec); - if (codecs_[mirror_id] == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot Create the codec"); - return -1; - } - mirror_codec_idx_[mirror_id] = mirror_id; - } - - if (mirror_id != codec_id) { - codecs_[codec_id] = codecs_[mirror_id]; - mirror_codec_idx_[codec_id] = mirror_id; - } - - ACMGenericCodec* codec_ptr = codecs_[codec_id]; - WebRtcACMCodecParams codec_params; - - memcpy(&(codec_params.codec_inst), &send_codec, sizeof(CodecInst)); - codec_params.enable_vad = vad_enabled_; - codec_params.enable_dtx = dtx_enabled_; - codec_params.vad_mode = vad_mode_; - // Force initialization. - if (codec_ptr->InitEncoder(&codec_params, true) < 0) { - // Could not initialize the encoder. - - // Check if already have a registered codec. - // Depending on that different messages are logged. - if (!send_codec_registered_) { - current_send_codec_idx_ = -1; - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot Initialize the encoder No Encoder is registered"); - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot Initialize the encoder, continue encoding with " - "the previously registered codec"); - } - return -1; - } - - // Update states. - dtx_enabled_ = codec_params.enable_dtx; - vad_enabled_ = codec_params.enable_vad; - vad_mode_ = codec_params.vad_mode; - - // Everything is fine so we can replace the previous codec with this one. - if (send_codec_registered_) { - // If we change codec we start fresh with FEC. - // This is not strictly required by the standard. - is_first_red_ = true; - - codec_ptr->SetVAD(&dtx_enabled_, &vad_enabled_, &vad_mode_); - } - - current_send_codec_idx_ = codec_id; - send_codec_registered_ = true; - memcpy(&send_codec_inst_, &send_codec, sizeof(CodecInst)); - previous_pltype_ = send_codec_inst_.pltype; - return 0; - } else { - // If codec is the same as already registered check if any parameters - // has changed compared to the current values. - // If any parameter is valid then apply it and record. - bool force_init = false; - - if (mirror_id != codec_id) { - codecs_[codec_id] = codecs_[mirror_id]; - mirror_codec_idx_[codec_id] = mirror_id; - } - - // Check the payload type. - if (send_codec.pltype != send_codec_inst_.pltype) { - // At this point check if the given payload type is valid. - // Record it later when the sampling frequency is changed - // successfully. - if (!ACMCodecDB::ValidPayloadType(send_codec.pltype)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Out of range payload type"); - return -1; - } - } - - // If there is a codec that ONE instance of codec supports multiple - // sampling frequencies, then we need to take care of it here. - // one such a codec is iSAC. Both WB and SWB are encoded and decoded - // with one iSAC instance. Therefore, we need to update the encoder - // frequency if required. - if (send_codec_inst_.plfreq != send_codec.plfreq) { - force_init = true; - - // If sampling frequency is changed we have to start fresh with RED. - is_first_red_ = true; - } - - // If packet size or number of channels has changed, we need to - // re-initialize the encoder. - if (send_codec_inst_.pacsize != send_codec.pacsize) { - force_init = true; - } - if (send_codec_inst_.channels != send_codec.channels) { - force_init = true; - } - - if (force_init) { - WebRtcACMCodecParams codec_params; - - memcpy(&(codec_params.codec_inst), &send_codec, sizeof(CodecInst)); - codec_params.enable_vad = vad_enabled_; - codec_params.enable_dtx = dtx_enabled_; - codec_params.vad_mode = vad_mode_; - - // Force initialization. - if (codecs_[current_send_codec_idx_]->InitEncoder(&codec_params, - true) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Could not change the codec packet-size."); - return -1; - } - - send_codec_inst_.plfreq = send_codec.plfreq; - send_codec_inst_.pacsize = send_codec.pacsize; - send_codec_inst_.channels = send_codec.channels; - } - - // If the change of sampling frequency has been successful then - // we store the payload-type. - send_codec_inst_.pltype = send_codec.pltype; - - // Check if a change in Rate is required. - if (send_codec.rate != send_codec_inst_.rate) { - if (codecs_[codec_id]->SetBitRate(send_codec.rate) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Could not change the codec rate."); - return -1; - } - send_codec_inst_.rate = send_codec.rate; - } - previous_pltype_ = send_codec_inst_.pltype; - - return 0; - } -} - -// Get current send codec. -int32_t AudioCodingModuleImpl::SendCodec( - CodecInst* current_codec) const { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "SendCodec()"); - CriticalSectionScoped lock(acm_crit_sect_); - - assert(current_codec); - if (!send_codec_registered_) { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "SendCodec Failed, no codec is registered"); - - return -1; - } - WebRtcACMCodecParams encoder_param; - codecs_[current_send_codec_idx_]->EncoderParams(&encoder_param); - encoder_param.codec_inst.pltype = send_codec_inst_.pltype; - memcpy(current_codec, &(encoder_param.codec_inst), sizeof(CodecInst)); - - return 0; -} - -// Get current send frequency. -int32_t AudioCodingModuleImpl::SendFrequency() const { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "SendFrequency()"); - CriticalSectionScoped lock(acm_crit_sect_); - - if (!send_codec_registered_) { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "SendFrequency Failed, no codec is registered"); - - return -1; - } - - return send_codec_inst_.plfreq; -} - -// Get encode bitrate. -// Adaptive rate codecs return their current encode target rate, while other -// codecs return there longterm avarage or their fixed rate. -int32_t AudioCodingModuleImpl::SendBitrate() const { - CriticalSectionScoped lock(acm_crit_sect_); - - if (!send_codec_registered_) { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "SendBitrate Failed, no codec is registered"); - - return -1; - } - - WebRtcACMCodecParams encoder_param; - codecs_[current_send_codec_idx_]->EncoderParams(&encoder_param); - - return encoder_param.codec_inst.rate; -} - -// Set available bandwidth, inform the encoder about the estimated bandwidth -// received from the remote party. -int32_t AudioCodingModuleImpl::SetReceivedEstimatedBandwidth( - const int32_t bw) { - return codecs_[current_send_codec_idx_]->SetEstimatedBandwidth(bw); -} - -// Register a transport callback which will be called to deliver -// the encoded buffers. -int32_t AudioCodingModuleImpl::RegisterTransportCallback( - AudioPacketizationCallback* transport) { - CriticalSectionScoped lock(callback_crit_sect_); - packetization_callback_ = transport; - return 0; -} - -// Add 10MS of raw (PCM) audio data to the encoder. -int32_t AudioCodingModuleImpl::Add10MsData( - const AudioFrame& audio_frame) { - if (audio_frame.samples_per_channel_ <= 0) { - assert(false); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot Add 10 ms audio, payload length is negative or " - "zero"); - return -1; - } - - if (audio_frame.sample_rate_hz_ > 48000) { - assert(false); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot Add 10 ms audio, input frequency not valid"); - return -1; - } - - // If the length and frequency matches. We currently just support raw PCM. - if ((audio_frame.sample_rate_hz_ / 100) - != audio_frame.samples_per_channel_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot Add 10 ms audio, input frequency and length doesn't" - " match"); - return -1; - } - - if (audio_frame.num_channels_ != 1 && audio_frame.num_channels_ != 2) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot Add 10 ms audio, invalid number of channels."); - return -1; - } - - CriticalSectionScoped lock(acm_crit_sect_); - // Do we have a codec registered? - if (!HaveValidEncoder("Add10MsData")) { - return -1; - } - - const AudioFrame* ptr_frame; - // Perform a resampling, also down-mix if it is required and can be - // performed before resampling (a down mix prior to resampling will take - // place if both primary and secondary encoders are mono and input is in - // stereo). - if (PreprocessToAddData(audio_frame, &ptr_frame) < 0) { - return -1; - } - TRACE_EVENT_ASYNC_BEGIN1("webrtc", "Audio", ptr_frame->timestamp_, - "now", clock_->TimeInMilliseconds()); - - // Check whether we need an up-mix or down-mix? - bool remix = ptr_frame->num_channels_ != send_codec_inst_.channels; - if (secondary_encoder_.get() != NULL) { - remix = remix || - (ptr_frame->num_channels_ != secondary_send_codec_inst_.channels); - } - - // If a re-mix is required (up or down), this buffer will store re-mixed - // version of the input. - int16_t buffer[WEBRTC_10MS_PCM_AUDIO]; - if (remix) { - if (ptr_frame->num_channels_ == 1) { - if (UpMix(*ptr_frame, WEBRTC_10MS_PCM_AUDIO, buffer) < 0) - return -1; - } else { - if (DownMix(*ptr_frame, WEBRTC_10MS_PCM_AUDIO, buffer) < 0) - return -1; - } - } - - // When adding data to encoders this pointer is pointing to an audio buffer - // with correct number of channels. - const int16_t* ptr_audio = ptr_frame->data_; - - // For pushing data to primary, point the |ptr_audio| to correct buffer. - if (send_codec_inst_.channels != ptr_frame->num_channels_) - ptr_audio = buffer; - - if (codecs_[current_send_codec_idx_]->Add10MsData( - ptr_frame->timestamp_, ptr_audio, ptr_frame->samples_per_channel_, - send_codec_inst_.channels) < 0) - return -1; - - if (secondary_encoder_.get() != NULL) { - // For pushing data to secondary, point the |ptr_audio| to correct buffer. - ptr_audio = ptr_frame->data_; - if (secondary_send_codec_inst_.channels != ptr_frame->num_channels_) - ptr_audio = buffer; - - if (secondary_encoder_->Add10MsData( - ptr_frame->timestamp_, ptr_audio, ptr_frame->samples_per_channel_, - secondary_send_codec_inst_.channels) < 0) - return -1; - } - - return 0; -} - -// Perform a resampling and down-mix if required. We down-mix only if -// encoder is mono and input is stereo. In case of dual-streaming, both -// encoders has to be mono for down-mix to take place. -// |*ptr_out| will point to the pre-processed audio-frame. If no pre-processing -// is required, |*ptr_out| points to |in_frame|. -int AudioCodingModuleImpl::PreprocessToAddData(const AudioFrame& in_frame, - const AudioFrame** ptr_out) { - // Primary and secondary (if exists) should have the same sampling rate. - assert((secondary_encoder_.get() != NULL) ? - secondary_send_codec_inst_.plfreq == send_codec_inst_.plfreq : true); - - bool resample = static_cast(in_frame.sample_rate_hz_) != - send_codec_inst_.plfreq; - - // This variable is true if primary codec and secondary codec (if exists) - // are both mono and input is stereo. - bool down_mix; - if (secondary_encoder_.get() != NULL) { - down_mix = (in_frame.num_channels_ == 2) && - (send_codec_inst_.channels == 1) && - (secondary_send_codec_inst_.channels == 1); - } else { - down_mix = (in_frame.num_channels_ == 2) && - (send_codec_inst_.channels == 1); - } - - if (!down_mix && !resample) { - // No pre-processing is required. - last_in_timestamp_ = in_frame.timestamp_; - last_timestamp_ = in_frame.timestamp_; - *ptr_out = &in_frame; - return 0; - } - - *ptr_out = &preprocess_frame_; - preprocess_frame_.num_channels_ = in_frame.num_channels_; - int16_t audio[WEBRTC_10MS_PCM_AUDIO]; - const int16_t* src_ptr_audio = in_frame.data_; - int16_t* dest_ptr_audio = preprocess_frame_.data_; - if (down_mix) { - // If a resampling is required the output of a down-mix is written into a - // local buffer, otherwise, it will be written to the output frame. - if (resample) - dest_ptr_audio = audio; - if (DownMix(in_frame, WEBRTC_10MS_PCM_AUDIO, dest_ptr_audio) < 0) - return -1; - preprocess_frame_.num_channels_ = 1; - // Set the input of the resampler is the down-mixed signal. - src_ptr_audio = audio; - } - - preprocess_frame_.timestamp_ = in_frame.timestamp_; - preprocess_frame_.samples_per_channel_ = in_frame.samples_per_channel_; - preprocess_frame_.sample_rate_hz_ = in_frame.sample_rate_hz_; - // If it is required, we have to do a resampling. - if (resample) { - // The result of the resampler is written to output frame. - dest_ptr_audio = preprocess_frame_.data_; - - uint32_t timestamp_diff; - - // Calculate the timestamp of this frame. - if (last_in_timestamp_ > in_frame.timestamp_) { - // A wrap around has happened. - timestamp_diff = (static_cast(0xFFFFFFFF) - last_in_timestamp_) - + in_frame.timestamp_; - } else { - timestamp_diff = in_frame.timestamp_ - last_in_timestamp_; - } - preprocess_frame_.timestamp_ = last_timestamp_ + - static_cast(timestamp_diff * - (static_cast(send_codec_inst_.plfreq) / - static_cast(in_frame.sample_rate_hz_))); - - preprocess_frame_.samples_per_channel_ = input_resampler_.Resample10Msec( - src_ptr_audio, in_frame.sample_rate_hz_, dest_ptr_audio, - send_codec_inst_.plfreq, preprocess_frame_.num_channels_); - - if (preprocess_frame_.samples_per_channel_ < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot add 10 ms audio, resampling failed"); - return -1; - } - preprocess_frame_.sample_rate_hz_ = send_codec_inst_.plfreq; - } - last_in_timestamp_ = in_frame.timestamp_; - last_timestamp_ = preprocess_frame_.timestamp_; - - return 0; -} - -///////////////////////////////////////// -// (FEC) Forward Error Correction -// - -bool AudioCodingModuleImpl::FECStatus() const { - CriticalSectionScoped lock(acm_crit_sect_); - return fec_enabled_; -} - -// Configure FEC status i.e on/off. -int32_t -AudioCodingModuleImpl::SetFECStatus( -#ifdef WEBRTC_CODEC_RED - const bool enable_fec) { - CriticalSectionScoped lock(acm_crit_sect_); - - if (fec_enabled_ != enable_fec) { - // Reset the RED buffer. - memset(red_buffer_, 0, MAX_PAYLOAD_SIZE_BYTE); - - // Reset fragmentation buffers. - ResetFragmentation(kNumFecFragmentationVectors); - // Set fec_enabled_. - fec_enabled_ = enable_fec; - } - is_first_red_ = true; // Make sure we restart FEC. - return 0; -#else - const bool /* enable_fec */) { - fec_enabled_ = false; - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - " WEBRTC_CODEC_RED is undefined => fec_enabled_ = %d", - fec_enabled_); - return -1; -#endif -} - -///////////////////////////////////////// -// (VAD) Voice Activity Detection -// -int32_t AudioCodingModuleImpl::SetVAD(bool enable_dtx, bool enable_vad, - ACMVADMode mode) { - CriticalSectionScoped lock(acm_crit_sect_); - return SetVADSafe(enable_dtx, enable_vad, mode); -} - -int AudioCodingModuleImpl::SetVADSafe(bool enable_dtx, bool enable_vad, - ACMVADMode mode) { - // Sanity check of the mode. - if ((mode != VADNormal) && (mode != VADLowBitrate) - && (mode != VADAggr) && (mode != VADVeryAggr)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Invalid VAD Mode %d, no change is made to VAD/DTX status", - static_cast(mode)); - return -1; - } - - // Check that the send codec is mono. We don't support VAD/DTX for stereo - // sending. - if ((enable_dtx || enable_vad) && stereo_send_) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "VAD/DTX not supported for stereo sending."); - dtx_enabled_ = false; - vad_enabled_ = false; - vad_mode_ = mode; - return -1; - } - - // We don't support VAD/DTX when dual-streaming is enabled, i.e. - // secondary-encoder is registered. - if ((enable_dtx || enable_vad) && secondary_encoder_.get() != NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "VAD/DTX not supported when dual-streaming is enabled."); - dtx_enabled_ = false; - vad_enabled_ = false; - vad_mode_ = mode; - return -1; - } - - // Store VAD/DTX settings. Values can be changed in the call to "SetVAD" - // below. - dtx_enabled_ = enable_dtx; - vad_enabled_ = enable_vad; - vad_mode_ = mode; - - // If a send codec is registered, set VAD/DTX for the codec. - if (HaveValidEncoder("SetVAD")) { - if (codecs_[current_send_codec_idx_]->SetVAD(&dtx_enabled_, &vad_enabled_, - &vad_mode_) < 0) { - // SetVAD failed. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "SetVAD failed"); - dtx_enabled_ = false; - vad_enabled_ = false; - return -1; - } - } - - return 0; -} - -// Get VAD/DTX settings. -// TODO(tlegrand): Change this method to void. -int32_t AudioCodingModuleImpl::VAD(bool* dtx_enabled, bool* vad_enabled, - ACMVADMode* mode) const { - CriticalSectionScoped lock(acm_crit_sect_); - - *dtx_enabled = dtx_enabled_; - *vad_enabled = vad_enabled_; - *mode = vad_mode_; - - return 0; -} - -///////////////////////////////////////// -// Receiver -// - -int32_t AudioCodingModuleImpl::InitializeReceiver() { - CriticalSectionScoped lock(acm_crit_sect_); - return InitializeReceiverSafe(); -} - -// Initialize receiver, resets codec database etc. -int32_t AudioCodingModuleImpl::InitializeReceiverSafe() { - initial_delay_ms_ = 0; - num_packets_accumulated_ = 0; - num_bytes_accumulated_ = 0; - accumulated_audio_ms_ = 0; - first_payload_received_ = 0; - last_incoming_send_timestamp_ = 0; - track_neteq_buffer_ = false; - playout_ts_ = 0; - // If the receiver is already initialized then we want to destroy any - // existing decoders. After a call to this function, we should have a clean - // start-up. - if (receiver_initialized_) { - for (int i = 0; i < ACMCodecDB::kNumCodecs; i++) { - if (UnregisterReceiveCodecSafe(i) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "InitializeReceiver() failed, Could not unregister codec"); - return -1; - } - } - } - if (neteq_.Init() != 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "InitializeReceiver() failed, Could not initialize NetEQ"); - return -1; - } - neteq_.set_id(id_); - if (neteq_.AllocatePacketBuffer(ACMCodecDB::NetEQDecoders(), - ACMCodecDB::kNumCodecs) != 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "NetEQ cannot allocate_packet Buffer"); - return -1; - } - - // Register RED and CN. - for (int i = 0; i < ACMCodecDB::kNumCodecs; i++) { - if (IsCodecRED(i) || IsCodecCN(i)) { - if (RegisterRecCodecMSSafe(ACMCodecDB::database_[i], i, i, - ACMNetEQ::kMasterJb) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot register master codec."); - return -1; - } - registered_pltypes_[i] = ACMCodecDB::database_[i].pltype; - } - } - - receiver_initialized_ = true; - return 0; -} - -// Reset the decoder state. -int32_t AudioCodingModuleImpl::ResetDecoder() { - CriticalSectionScoped lock(acm_crit_sect_); - - for (int id = 0; id < ACMCodecDB::kMaxNumCodecs; id++) { - if ((codecs_[id] != NULL) && (registered_pltypes_[id] != -1)) { - if (codecs_[id]->ResetDecoder(registered_pltypes_[id]) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "ResetDecoder failed:"); - return -1; - } - } - } - return neteq_.FlushBuffers(); -} - -// Get current receive frequency. -int32_t AudioCodingModuleImpl::ReceiveFrequency() const { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "ReceiveFrequency()"); - WebRtcACMCodecParams codec_params; - - CriticalSectionScoped lock(acm_crit_sect_); - if (DecoderParamByPlType(last_recv_audio_codec_pltype_, codec_params) < 0) { - return neteq_.CurrentSampFreqHz(); - } else if (codec_params.codec_inst.plfreq == 48000) { - // TODO(tlegrand): Remove this option when we have full 48 kHz support. - return 32000; - } else { - return codec_params.codec_inst.plfreq; - } -} - -// Get current playout frequency. -int32_t AudioCodingModuleImpl::PlayoutFrequency() const { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "PlayoutFrequency()"); - - CriticalSectionScoped lock(acm_crit_sect_); - - return neteq_.CurrentSampFreqHz(); -} - -// Register possible receive codecs, can be called multiple times, -// for codecs, CNG (NB, WB and SWB), DTMF, RED. -int32_t AudioCodingModuleImpl::RegisterReceiveCodec( - const CodecInst& receive_codec) { - CriticalSectionScoped lock(acm_crit_sect_); - - if (receive_codec.channels > 2) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "More than 2 audio channel is not supported."); - return -1; - } - - int mirror_id; - int codec_id = ACMCodecDB::ReceiverCodecNumber(&receive_codec, &mirror_id); - - if (codec_id < 0 || codec_id >= ACMCodecDB::kNumCodecs) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Wrong codec params to be registered as receive codec"); - return -1; - } - // Check if the payload-type is valid. - if (!ACMCodecDB::ValidPayloadType(receive_codec.pltype)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Invalid payload-type %d for %s.", receive_codec.pltype, - receive_codec.plname); - return -1; - } - - if (!receiver_initialized_) { - if (InitializeReceiverSafe() < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot initialize reciver, so failed registering a codec."); - return -1; - } - } - - // If codec already registered, unregister. Except for CN where we only - // unregister if payload type is changing. - if ((registered_pltypes_[codec_id] == receive_codec.pltype) - && IsCodecCN(&receive_codec)) { - // Codec already registered as receiver with this payload type. Nothing - // to be done. - return 0; - } else if (registered_pltypes_[codec_id] != -1) { - if (UnregisterReceiveCodecSafe(codec_id) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot register master codec."); - return -1; - } - } - - if (RegisterRecCodecMSSafe(receive_codec, codec_id, mirror_id, - ACMNetEQ::kMasterJb) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot register master codec."); - return -1; - } - - // TODO(andrew): Refactor how the slave is initialized. Can we instead - // always start up a slave and pre-register CN and RED? We should be able - // to get rid of stereo_receive_registered_. - // http://code.google.com/p/webrtc/issues/detail?id=453 - - // Register stereo codecs with the slave, or, if we've had already seen a - // stereo codec, register CN or RED as a special case. - if (receive_codec.channels == 2 || - (stereo_receive_registered_ && (IsCodecCN(&receive_codec) || - IsCodecRED(&receive_codec)))) { - // TODO(andrew): refactor this block to combine with InitStereoSlave(). - - if (!stereo_receive_registered_) { - // This is the first time a stereo codec has been registered. Make - // some stereo preparations. - - // Add a stereo slave. - assert(neteq_.num_slaves() == 0); - if (neteq_.AddSlave(ACMCodecDB::NetEQDecoders(), - ACMCodecDB::kNumCodecs) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot add slave jitter buffer to NetEQ."); - return -1; - } - - // Register any existing CN or RED codecs with the slave and as stereo. - for (int i = 0; i < ACMCodecDB::kNumCodecs; i++) { - if (registered_pltypes_[i] != -1 && (IsCodecRED(i) || IsCodecCN(i))) { - stereo_receive_[i] = true; - - CodecInst codec; - memcpy(&codec, &ACMCodecDB::database_[i], sizeof(CodecInst)); - codec.pltype = registered_pltypes_[i]; - if (RegisterRecCodecMSSafe(codec, i, i, ACMNetEQ::kSlaveJb) < 0) { - WEBRTC_TRACE(kTraceError, kTraceAudioCoding, id_, - "Cannot register slave codec."); - return -1; - } - } - } - } - - if (RegisterRecCodecMSSafe(receive_codec, codec_id, mirror_id, - ACMNetEQ::kSlaveJb) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot register slave codec."); - return -1; - } - - if (!stereo_receive_[codec_id] && - (last_recv_audio_codec_pltype_ == receive_codec.pltype)) { - // The last received payload type is the same as the one we are - // registering. Expected number of channels to receive is one (mono), - // but we are now registering the receiving codec as stereo (number of - // channels is 2). - // Set |last_recv_audio_coded_pltype_| to invalid value to trigger a - // flush in NetEq, and a reset of expected number of channels next time a - // packet is received in AudioCodingModuleImpl::IncomingPacket(). - last_recv_audio_codec_pltype_ = -1; - } - - stereo_receive_[codec_id] = true; - stereo_receive_registered_ = true; - } else { - if (last_recv_audio_codec_pltype_ == receive_codec.pltype && - expected_channels_ == 2) { - // The last received payload type is the same as the one we are - // registering. Expected number of channels to receive is two (stereo), - // but we are now registering the receiving codec as mono (number of - // channels is 1). - // Set |last_recv_audio_coded_pl_type_| to invalid value to trigger a - // flush in NetEq, and a reset of expected number of channels next time a - // packet is received in AudioCodingModuleImpl::IncomingPacket(). - last_recv_audio_codec_pltype_ = -1; - } - stereo_receive_[codec_id] = false; - } - - registered_pltypes_[codec_id] = receive_codec.pltype; - - if (IsCodecRED(&receive_codec)) { - receive_red_pltype_ = receive_codec.pltype; - } - return 0; -} - -int32_t AudioCodingModuleImpl::RegisterRecCodecMSSafe( - const CodecInst& receive_codec, int16_t codec_id, - int16_t mirror_id, ACMNetEQ::JitterBuffer jitter_buffer) { - ACMGenericCodec** codecs; - if (jitter_buffer == ACMNetEQ::kMasterJb) { - codecs = &codecs_[0]; - } else if (jitter_buffer == ACMNetEQ::kSlaveJb) { - codecs = &slave_codecs_[0]; - if (codecs_[codec_id]->IsTrueStereoCodec()) { - // True stereo codecs need to use the same codec memory - // for both master and slave. - slave_codecs_[mirror_id] = codecs_[mirror_id]; - mirror_codec_idx_[mirror_id] = mirror_id; - } - } else { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "RegisterReceiveCodecMSSafe failed, jitter_buffer is neither " - "master or slave "); - return -1; - } - - if (codecs[mirror_id] == NULL) { - codecs[mirror_id] = CreateCodec(receive_codec); - if (codecs[mirror_id] == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot create codec to register as receive codec"); - return -1; - } - mirror_codec_idx_[mirror_id] = mirror_id; - } - if (mirror_id != codec_id) { - codecs[codec_id] = codecs[mirror_id]; - mirror_codec_idx_[codec_id] = mirror_id; - } - - codecs[codec_id]->SetIsMaster(jitter_buffer == ACMNetEQ::kMasterJb); - - int16_t status = 0; - WebRtcACMCodecParams codec_params; - memcpy(&(codec_params.codec_inst), &receive_codec, sizeof(CodecInst)); - codec_params.enable_vad = false; - codec_params.enable_dtx = false; - codec_params.vad_mode = VADNormal; - if (!codecs[codec_id]->DecoderInitialized()) { - // Force initialization. - status = codecs[codec_id]->InitDecoder(&codec_params, true); - if (status < 0) { - // Could not initialize the decoder, we don't want to - // continue if we could not initialize properly. - WEBRTC_TRACE( - webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "could not initialize the receive codec, codec not registered"); - - return -1; - } - } else if (mirror_id != codec_id) { - // Currently this only happens for iSAC. - // We have to store the decoder parameters. - codecs[codec_id]->SaveDecoderParam(&codec_params); - } - - if (codecs[codec_id]->RegisterInNetEq(&neteq_, receive_codec) != 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Receive codec could not be registered in NetEQ"); - return -1; - } - // Guarantee that the same payload-type that is - // registered in NetEQ is stored in the codec. - codecs[codec_id]->SaveDecoderParam(&codec_params); - - return status; -} - -// Get current received codec. -int32_t AudioCodingModuleImpl::ReceiveCodec( - CodecInst* current_codec) const { - WebRtcACMCodecParams decoder_param; - CriticalSectionScoped lock(acm_crit_sect_); - - for (int id = 0; id < ACMCodecDB::kMaxNumCodecs; id++) { - if (codecs_[id] != NULL) { - if (codecs_[id]->DecoderInitialized()) { - if (codecs_[id]->DecoderParams(&decoder_param, - last_recv_audio_codec_pltype_)) { - memcpy(current_codec, &decoder_param.codec_inst, - sizeof(CodecInst)); - return 0; - } - } - } - } - - // If we are here then we haven't found any codec. Set codec pltype to -1 to - // indicate that the structure is invalid and return -1. - current_codec->pltype = -1; - return -1; -} - -// Incoming packet from network parsed and ready for decode. -int32_t AudioCodingModuleImpl::IncomingPacket( - const uint8_t* incoming_payload, - const int32_t payload_length, - const WebRtcRTPHeader& rtp_info) { - WebRtcRTPHeader rtp_header; - - memcpy(&rtp_header, &rtp_info, sizeof(WebRtcRTPHeader)); - - if (payload_length < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "IncomingPacket() Error, payload-length cannot be negative"); - return -1; - } - - { - // Store the payload Type. This will be used to retrieve "received codec" - // and "received frequency." - CriticalSectionScoped lock(acm_crit_sect_); - - // Check there are packets missed between the last injected packet, and the - // latest received packet. If so and we are in AV-sync mode then we would - // like to fill the gap. Shouldn't be the first payload. - if (av_sync_ && first_payload_received_ && - rtp_info.header.sequenceNumber > last_sequence_number_ + 1) { - // If the last packet pushed was sync-packet account for all missing - // packets. Otherwise leave some room for PLC. - if (last_packet_was_sync_) { - while (rtp_info.header.sequenceNumber > last_sequence_number_ + 2) { - PushSyncPacketSafe(); - } - } else { - // Leave two packet room for NetEq perform PLC. - if (rtp_info.header.sequenceNumber > last_sequence_number_ + 3) { - last_sequence_number_ += 2; - last_incoming_send_timestamp_ += last_timestamp_diff_ * 2; - last_receive_timestamp_ += 2 * last_timestamp_diff_; - while (rtp_info.header.sequenceNumber > last_sequence_number_ + 1) - PushSyncPacketSafe(); - } - } - } - - uint8_t my_payload_type; - - // Check if this is an RED payload. - if (rtp_info.header.payloadType == receive_red_pltype_) { - // Get the primary payload-type. - my_payload_type = incoming_payload[0] & 0x7F; - } else { - my_payload_type = rtp_info.header.payloadType; - } - - // If payload is audio, check if received payload is different from - // previous. - if (!rtp_info.type.Audio.isCNG) { - // This is Audio not CNG. - - if (my_payload_type != last_recv_audio_codec_pltype_) { - // We detect a change in payload type. It is necessary for iSAC - // we are going to use ONE iSAC instance for decoding both WB and - // SWB payloads. If payload is changed there might be a need to reset - // sampling rate of decoder. depending what we have received "now". - for (int i = 0; i < ACMCodecDB::kMaxNumCodecs; i++) { - if (registered_pltypes_[i] == my_payload_type) { - if (UpdateUponReceivingCodec(i) != 0) - return -1; - break; - } - } - // Codec is changed, there might be a jump in timestamp, therefore, - // we have to reset some variables that track NetEq buffer. - if (track_neteq_buffer_ || av_sync_) { - last_incoming_send_timestamp_ = rtp_info.header.timestamp; - } - - if (nack_enabled_) { - assert(nack_.get()); - // Codec is changed, reset NACK and update sampling rate. - nack_->Reset(); - nack_->UpdateSampleRate( - ACMCodecDB::database_[current_receive_codec_idx_].plfreq); - } - } - last_recv_audio_codec_pltype_ = my_payload_type; - } - - // Current timestamp based on the receiver sampling frequency. - last_receive_timestamp_ = NowTimestamp(current_receive_codec_idx_); - - if (nack_enabled_) { - assert(nack_.get()); - nack_->UpdateLastReceivedPacket(rtp_header.header.sequenceNumber, - rtp_header.header.timestamp); - } - } - - int per_neteq_payload_length = payload_length; - // Split the payload for stereo packets, so that first half of payload - // vector holds left channel, and second half holds right channel. - if (expected_channels_ == 2) { - if (!rtp_info.type.Audio.isCNG) { - // Create a new vector for the payload, maximum payload size. - int32_t length = payload_length; - uint8_t payload[kMaxPacketSize]; - assert(payload_length <= kMaxPacketSize); - memcpy(payload, incoming_payload, payload_length); - codecs_[current_receive_codec_idx_]->SplitStereoPacket(payload, &length); - rtp_header.type.Audio.channel = 2; - per_neteq_payload_length = length / 2; - // Insert packet into NetEQ. - if (neteq_.RecIn(payload, length, rtp_header, - last_receive_timestamp_) < 0) - return -1; - } else { - // If we receive a CNG packet while expecting stereo, we ignore the - // packet and continue. CNG is not supported for stereo. - return 0; - } - } else { - if (neteq_.RecIn(incoming_payload, payload_length, rtp_header, - last_receive_timestamp_) < 0) - return -1; - } - - { - CriticalSectionScoped lock(acm_crit_sect_); - - // Update buffering uses |last_incoming_send_timestamp_| so it should be - // before the next block. - if (track_neteq_buffer_) - UpdateBufferingSafe(rtp_header, per_neteq_payload_length); - - if (av_sync_) { - if (rtp_info.header.sequenceNumber == last_sequence_number_ + 1) { - last_timestamp_diff_ = rtp_info.header.timestamp - - last_incoming_send_timestamp_; - } - last_sequence_number_ = rtp_info.header.sequenceNumber; - last_ssrc_ = rtp_info.header.ssrc; - last_packet_was_sync_ = false; - } - - if (av_sync_ || track_neteq_buffer_) { - last_incoming_send_timestamp_ = rtp_info.header.timestamp; - } - - // Set the following regardless of tracking NetEq buffer or being in - // AV-sync mode. Only if the received packet is not CNG. - if (!rtp_info.type.Audio.isCNG) - first_payload_received_ = true; - } - return 0; -} - -int AudioCodingModuleImpl::UpdateUponReceivingCodec(int index) { - if (codecs_[index] == NULL) { - WEBRTC_TRACE(kTraceError, kTraceAudioCoding, id_, - "IncomingPacket() error: payload type found but " - "corresponding codec is NULL"); - return -1; - } - codecs_[index]->UpdateDecoderSampFreq(index); - neteq_.set_received_stereo(stereo_receive_[index]); - current_receive_codec_idx_ = index; - - // If we have a change in the expected number of channels, flush packet - // buffers in NetEQ. - if ((stereo_receive_[index] && (expected_channels_ == 1)) || - (!stereo_receive_[index] && (expected_channels_ == 2))) { - neteq_.FlushBuffers(); - codecs_[index]->ResetDecoder(registered_pltypes_[index]); - } - - if (stereo_receive_[index] && (expected_channels_ == 1)) { - // When switching from a mono to stereo codec reset the slave. - if (InitStereoSlave() != 0) - return -1; - } - - // Store number of channels we expect to receive for the current payload type. - if (stereo_receive_[index]) { - expected_channels_ = 2; - } else { - expected_channels_ = 1; - } - - // Reset previous received channel. - prev_received_channel_ = 0; - return 0; -} - -bool AudioCodingModuleImpl::IsCodecForSlave(int index) const { - return (registered_pltypes_[index] != -1 && stereo_receive_[index]); -} - -int AudioCodingModuleImpl::InitStereoSlave() { - neteq_.RemoveSlaves(); - - if (neteq_.AddSlave(ACMCodecDB::NetEQDecoders(), - ACMCodecDB::kNumCodecs) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot add slave jitter buffer to NetEQ."); - return -1; - } - - // Register all needed codecs with slave. - for (int i = 0; i < ACMCodecDB::kNumCodecs; i++) { - if (codecs_[i] != NULL && IsCodecForSlave(i)) { - WebRtcACMCodecParams decoder_params; - if (codecs_[i]->DecoderParams(&decoder_params, registered_pltypes_[i])) { - if (RegisterRecCodecMSSafe(decoder_params.codec_inst, - i, ACMCodecDB::MirrorID(i), - ACMNetEQ::kSlaveJb) < 0) { - WEBRTC_TRACE(kTraceError, kTraceAudioCoding, id_, - "Cannot register slave codec."); - return -1; - } - } - } - } - return 0; -} - -int AudioCodingModuleImpl::SetMinimumPlayoutDelay(int time_ms) { - { - CriticalSectionScoped lock(acm_crit_sect_); - // Don't let the extra delay modified while accumulating buffers in NetEq. - if (track_neteq_buffer_ && first_payload_received_) - return 0; - } - return neteq_.SetMinimumDelay(time_ms); -} - -int AudioCodingModuleImpl::SetMaximumPlayoutDelay(int time_ms) { - return neteq_.SetMaximumDelay(time_ms); -} - -// Get Dtmf playout status. -bool AudioCodingModuleImpl::DtmfPlayoutStatus() const { -#ifndef WEBRTC_CODEC_AVT - return false; -#else - return neteq_.avt_playout(); -#endif -} - -// Configure Dtmf playout status i.e on/off playout the incoming outband -// Dtmf tone. -int32_t AudioCodingModuleImpl::SetDtmfPlayoutStatus( -#ifndef WEBRTC_CODEC_AVT - const bool /* enable */) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - "SetDtmfPlayoutStatus() failed: AVT is not supported."); - return -1; -#else - const bool enable) { - return neteq_.SetAVTPlayout(enable); -#endif -} - -// Estimate the Bandwidth based on the incoming stream, needed for one way -// audio where the RTCP send the BW estimate. -// This is also done in the RTP module. -int32_t AudioCodingModuleImpl::DecoderEstimatedBandwidth() const { - CodecInst codec; - int16_t codec_id = -1; - int pltype_wb; - int pltype_swb; - - // Get iSAC settings. - for (int id = 0; id < ACMCodecDB::kNumCodecs; id++) { - // Store codec settings for codec number "codeCntr" in the output struct. - ACMCodecDB::Codec(id, &codec); - - if (!STR_CASE_CMP(codec.plname, "isac")) { - codec_id = 1; - pltype_wb = codec.pltype; - - ACMCodecDB::Codec(id + 1, &codec); - pltype_swb = codec.pltype; - - break; - } - } - - if (codec_id < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "DecoderEstimatedBandwidth failed"); - return -1; - } - - if ((last_recv_audio_codec_pltype_ == pltype_wb) || - (last_recv_audio_codec_pltype_ == pltype_swb)) { - return codecs_[codec_id]->GetEstimatedBandwidth(); - } else { - return -1; - } -} - -// Set playout mode for: voice, fax, or streaming. -int32_t AudioCodingModuleImpl::SetPlayoutMode( - const AudioPlayoutMode mode) { - if ((mode != voice) && (mode != fax) && (mode != streaming) && - (mode != off)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Invalid playout mode."); - return -1; - } - return neteq_.SetPlayoutMode(mode); -} - -// Get playout mode voice, fax. -AudioPlayoutMode AudioCodingModuleImpl::PlayoutMode() const { - return neteq_.playout_mode(); -} - -// Get 10 milliseconds of raw audio data to play out. -// Automatic resample to the requested frequency. -int32_t AudioCodingModuleImpl::PlayoutData10Ms( - int32_t desired_freq_hz, AudioFrame* audio_frame) { - TRACE_EVENT_ASYNC_BEGIN0("webrtc", "ACM::PlayoutData10Ms", this); - bool stereo_mode; - - if (GetSilence(desired_freq_hz, audio_frame)) { - TRACE_EVENT_ASYNC_END1("webrtc", "ACM::PlayoutData10Ms", this, - "silence", true); - return 0; // Silence is generated, return. - } - - // RecOut always returns 10 ms. - if (neteq_.RecOut(audio_frame_) != 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "PlayoutData failed, RecOut Failed"); - return -1; - } - int decoded_seq_num; - uint32_t decoded_timestamp; - bool update_nack = - neteq_.DecodedRtpInfo(&decoded_seq_num, &decoded_timestamp) && - nack_enabled_; // Update NACK only if it is enabled. - audio_frame->num_channels_ = audio_frame_.num_channels_; - audio_frame->vad_activity_ = audio_frame_.vad_activity_; - audio_frame->speech_type_ = audio_frame_.speech_type_; - - stereo_mode = (audio_frame_.num_channels_ > 1); - - // For stereo playout: - // Master and Slave samples are interleaved starting with Master. - const uint16_t receive_freq = - static_cast(audio_frame_.sample_rate_hz_); - bool tone_detected = false; - int16_t last_detected_tone; - int16_t tone; - - // Limit the scope of ACM Critical section. - { - CriticalSectionScoped lock(acm_crit_sect_); - - // Update call statistics. - call_stats_.DecodedByNetEq(audio_frame->speech_type_); - - if (update_nack) { - assert(nack_.get()); - nack_->UpdateLastDecodedPacket(decoded_seq_num, decoded_timestamp); - } - - // If we are in AV-sync and have already received an audio packet, but the - // latest packet is too late, then insert sync packet. - if (av_sync_ && first_payload_received_ && - NowTimestamp(current_receive_codec_idx_) > 5 * last_timestamp_diff_ + - last_receive_timestamp_) { - if (!last_packet_was_sync_) { - // If the last packet inserted has been a regular packet Skip two - // packets to give room for PLC. - last_incoming_send_timestamp_ += 2 * last_timestamp_diff_; - last_sequence_number_ += 2; - last_receive_timestamp_ += 2 * last_timestamp_diff_; - } - - // One sync packet. - if (PushSyncPacketSafe() < 0) - return -1; - } - - if ((receive_freq != desired_freq_hz) && (desired_freq_hz != -1)) { - TRACE_EVENT_ASYNC_END2("webrtc", "ACM::PlayoutData10Ms", this, - "seqnum", decoded_seq_num, - "now", clock_->TimeInMilliseconds()); - // Resample payload_data. - int16_t temp_len = output_resampler_.Resample10Msec( - audio_frame_.data_, receive_freq, audio_frame->data_, - desired_freq_hz, audio_frame_.num_channels_); - - if (temp_len < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "PlayoutData failed, resampler failed"); - return -1; - } - - // Set the payload data length from the resampler. - audio_frame->samples_per_channel_ = static_cast(temp_len); - // Set the sampling frequency. - audio_frame->sample_rate_hz_ = desired_freq_hz; - } else { - TRACE_EVENT_ASYNC_END2("webrtc", "ACM::PlayoutData10Ms", this, - "seqnum", decoded_seq_num, - "now", clock_->TimeInMilliseconds()); - memcpy(audio_frame->data_, audio_frame_.data_, - audio_frame_.samples_per_channel_ * audio_frame->num_channels_ - * sizeof(int16_t)); - // Set the payload length. - audio_frame->samples_per_channel_ = - audio_frame_.samples_per_channel_; - // Set the sampling frequency. - audio_frame->sample_rate_hz_ = receive_freq; - } - - // Tone detection done for master channel. - if (dtmf_detector_ != NULL) { - // Dtmf Detection. - if (audio_frame->sample_rate_hz_ == 8000) { - // Use audio_frame->data_ then Dtmf detector doesn't - // need resampling. - if (!stereo_mode) { - dtmf_detector_->Detect(audio_frame->data_, - audio_frame->samples_per_channel_, - audio_frame->sample_rate_hz_, tone_detected, - tone); - } else { - // We are in 8 kHz so the master channel needs only 80 samples. - int16_t master_channel[80]; - for (int n = 0; n < 80; n++) { - master_channel[n] = audio_frame->data_[n << 1]; - } - dtmf_detector_->Detect(master_channel, - audio_frame->samples_per_channel_, - audio_frame->sample_rate_hz_, tone_detected, - tone); - } - } else { - // Do the detection on the audio that we got from NetEQ (audio_frame_). - if (!stereo_mode) { - dtmf_detector_->Detect(audio_frame_.data_, - audio_frame_.samples_per_channel_, - receive_freq, tone_detected, tone); - } else { - int16_t master_channel[WEBRTC_10MS_PCM_AUDIO]; - for (int n = 0; n < audio_frame_.samples_per_channel_; n++) { - master_channel[n] = audio_frame_.data_[n << 1]; - } - dtmf_detector_->Detect(master_channel, - audio_frame_.samples_per_channel_, - receive_freq, tone_detected, tone); - } - } - } - - // We want to do this while we are in acm_crit_sect_. - // (Doesn't really need to initialize the following - // variable but Linux complains if we don't.) - last_detected_tone = kACMToneEnd; - if (tone_detected) { - last_detected_tone = last_detected_tone_; - last_detected_tone_ = tone; - } - } - - if (tone_detected) { - // We will deal with callback here, so enter callback critical section. - CriticalSectionScoped lock(callback_crit_sect_); - - if (dtmf_callback_ != NULL) { - if (tone != kACMToneEnd) { - // just a tone - dtmf_callback_->IncomingDtmf(static_cast(tone), false); - } else if ((tone == kACMToneEnd) && (last_detected_tone != kACMToneEnd)) { - // The tone is "END" and the previously detected tone is - // not "END," so call fir an end. - dtmf_callback_->IncomingDtmf(static_cast(last_detected_tone), - true); - } - } - } - - audio_frame->id_ = id_; - audio_frame->energy_ = -1; - audio_frame->timestamp_ = 0; - - return 0; -} - -///////////////////////////////////////// -// Statistics -// - -int32_t AudioCodingModuleImpl::NetworkStatistics( - ACMNetworkStatistics* statistics) { - int32_t status; - status = neteq_.NetworkStatistics(statistics); - return status; -} - -void AudioCodingModuleImpl::DestructEncoderInst(void* inst) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_, - "DestructEncoderInst()"); - if (!HaveValidEncoder("DestructEncoderInst")) { - return; - } - - codecs_[current_send_codec_idx_]->DestructEncoderInst(inst); -} - -int16_t AudioCodingModuleImpl::AudioBuffer( - WebRtcACMAudioBuff& buffer) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_, - "AudioBuffer()"); - if (!HaveValidEncoder("AudioBuffer")) { - return -1; - } - buffer.last_in_timestamp = last_in_timestamp_; - return codecs_[current_send_codec_idx_]->AudioBuffer(buffer); -} - -int16_t AudioCodingModuleImpl::SetAudioBuffer( - WebRtcACMAudioBuff& buffer) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_, - "SetAudioBuffer()"); - if (!HaveValidEncoder("SetAudioBuffer")) { - return -1; - } - return codecs_[current_send_codec_idx_]->SetAudioBuffer(buffer); -} - -uint32_t AudioCodingModuleImpl::EarliestTimestamp() const { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_, - "EarliestTimestamp()"); - if (!HaveValidEncoder("EarliestTimestamp")) { - return -1; - } - return codecs_[current_send_codec_idx_]->EarliestTimestamp(); -} - -int32_t AudioCodingModuleImpl::RegisterVADCallback( - ACMVADCallback* vad_callback) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceAudioCoding, id_, - "RegisterVADCallback()"); - CriticalSectionScoped lock(callback_crit_sect_); - vad_callback_ = vad_callback; - return 0; -} - -// TODO(turajs): Remove this API if it is not used. -// TODO(tlegrand): Modify this function to work for stereo, and add tests. -// TODO(turajs): Receive timestamp in this method is incremented by frame-size -// and does not reflect the true receive frame-size. Therefore, subsequent -// jitter computations are not accurate. -int32_t AudioCodingModuleImpl::IncomingPayload( - const uint8_t* incoming_payload, const int32_t payload_length, - const uint8_t payload_type, const uint32_t timestamp) { - if (payload_length < 0) { - // Log error in trace file. - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "IncomingPacket() Error, payload-length cannot be negative"); - return -1; - } - - if (dummy_rtp_header_ == NULL) { - // This is the first time that we are using |dummy_rtp_header_| - // so we have to create it. - WebRtcACMCodecParams codec_params; - dummy_rtp_header_ = new WebRtcRTPHeader; - if (dummy_rtp_header_ == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "IncomingPayload() Error, out of memory"); - return -1; - } - dummy_rtp_header_->header.payloadType = payload_type; - // Don't matter in this case. - dummy_rtp_header_->header.ssrc = 0; - dummy_rtp_header_->header.markerBit = false; - // Start with random numbers. - dummy_rtp_header_->header.sequenceNumber = rand(); - dummy_rtp_header_->header.timestamp = - (static_cast(rand()) << 16) + - static_cast(rand()); - dummy_rtp_header_->type.Audio.channel = 1; - - if (DecoderParamByPlType(payload_type, codec_params) < 0) { - // We didn't find a codec with the given payload. - // Something is wrong we exit, but we delete |dummy_rtp_header_| - // and set it to NULL to start clean next time. - delete dummy_rtp_header_; - dummy_rtp_header_ = NULL; - return -1; - } - recv_pl_frame_size_smpls_ = codec_params.codec_inst.pacsize; - } - - if (payload_type != dummy_rtp_header_->header.payloadType) { - // Payload type has changed since the last time we might need to - // update the frame-size. - WebRtcACMCodecParams codec_params; - if (DecoderParamByPlType(payload_type, codec_params) < 0) { - // We didn't find a codec with the given payload. - return -1; - } - recv_pl_frame_size_smpls_ = codec_params.codec_inst.pacsize; - dummy_rtp_header_->header.payloadType = payload_type; - } - - if (timestamp > 0) { - dummy_rtp_header_->header.timestamp = timestamp; - } - - // Store the payload Type. this will be used to retrieve "received codec" - // and "received frequency." - last_recv_audio_codec_pltype_ = payload_type; - - last_receive_timestamp_ += recv_pl_frame_size_smpls_; - // Insert in NetEQ. - if (neteq_.RecIn(incoming_payload, payload_length, *dummy_rtp_header_, - last_receive_timestamp_) < 0) { - return -1; - } - - // Get ready for the next payload. - dummy_rtp_header_->header.sequenceNumber++; - dummy_rtp_header_->header.timestamp += recv_pl_frame_size_smpls_; - return 0; -} - -int16_t AudioCodingModuleImpl::DecoderParamByPlType( - const uint8_t payload_type, - WebRtcACMCodecParams& codec_params) const { - CriticalSectionScoped lock(acm_crit_sect_); - for (int16_t id = 0; id < ACMCodecDB::kMaxNumCodecs; - id++) { - if (codecs_[id] != NULL) { - if (codecs_[id]->DecoderInitialized()) { - if (codecs_[id]->DecoderParams(&codec_params, payload_type)) { - return 0; - } - } - } - } - // If we are here it means that we could not find a - // codec with that payload type. reset the values to - // not acceptable values and return -1. - codec_params.codec_inst.plname[0] = '\0'; - codec_params.codec_inst.pacsize = 0; - codec_params.codec_inst.rate = 0; - codec_params.codec_inst.pltype = -1; - return -1; -} - -int16_t AudioCodingModuleImpl::DecoderListIDByPlName( - const char* name, const uint16_t frequency) const { - WebRtcACMCodecParams codec_params; - CriticalSectionScoped lock(acm_crit_sect_); - for (int16_t id = 0; id < ACMCodecDB::kMaxNumCodecs; id++) { - if ((codecs_[id] != NULL)) { - if (codecs_[id]->DecoderInitialized()) { - assert(registered_pltypes_[id] >= 0); - assert(registered_pltypes_[id] <= 255); - codecs_[id]->DecoderParams( - &codec_params, static_cast(registered_pltypes_[id])); - if (!STR_CASE_CMP(codec_params.codec_inst.plname, name)) { - // Check if the given sampling frequency matches. - // A zero sampling frequency means we matching the names - // is sufficient and we don't need to check for the - // frequencies. - // Currently it is only iSAC which has one name but two - // sampling frequencies. - if ((frequency == 0)|| - (codec_params.codec_inst.plfreq == frequency)) { - return id; - } - } - } - } - } - // If we are here it means that we could not find a - // codec with that payload type. return -1. - return -1; -} - -int32_t AudioCodingModuleImpl::LastEncodedTimestamp( - uint32_t& timestamp) const { - CriticalSectionScoped lock(acm_crit_sect_); - if (!HaveValidEncoder("LastEncodedTimestamp")) { - return -1; - } - timestamp = codecs_[current_send_codec_idx_]->LastEncodedTimestamp(); - return 0; -} - -int32_t AudioCodingModuleImpl::ReplaceInternalDTXWithWebRtc( - bool use_webrtc_dtx) { - CriticalSectionScoped lock(acm_crit_sect_); - - if (!HaveValidEncoder("ReplaceInternalDTXWithWebRtc")) { - WEBRTC_TRACE( - webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Cannot replace codec internal DTX when no send codec is registered."); - return -1; - } - - int32_t res = codecs_[current_send_codec_idx_]->ReplaceInternalDTX( - use_webrtc_dtx); - // Check if VAD is turned on, or if there is any error. - if (res == 1) { - vad_enabled_ = true; - } else if (res < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Failed to set ReplaceInternalDTXWithWebRtc(%d)", - use_webrtc_dtx); - return res; - } - - return 0; -} - -int32_t AudioCodingModuleImpl::IsInternalDTXReplacedWithWebRtc( - bool* uses_webrtc_dtx) { - CriticalSectionScoped lock(acm_crit_sect_); - - if (!HaveValidEncoder("IsInternalDTXReplacedWithWebRtc")) { - return -1; - } - if (codecs_[current_send_codec_idx_]->IsInternalDTXReplaced(uses_webrtc_dtx) - < 0) { - return -1; - } - return 0; -} - -int AudioCodingModuleImpl::SetISACMaxRate(int max_bit_per_sec) { - CriticalSectionScoped lock(acm_crit_sect_); - - if (!HaveValidEncoder("SetISACMaxRate")) { - return -1; - } - - return codecs_[current_send_codec_idx_]->SetISACMaxRate(max_bit_per_sec); -} - -int AudioCodingModuleImpl::SetISACMaxPayloadSize(int max_size_bytes) { - CriticalSectionScoped lock(acm_crit_sect_); - - if (!HaveValidEncoder("SetISACMaxPayloadSize")) { - return -1; - } - - return codecs_[current_send_codec_idx_]->SetISACMaxPayloadSize( - max_size_bytes); -} - -int32_t AudioCodingModuleImpl::ConfigISACBandwidthEstimator( - int frame_size_ms, - int rate_bit_per_sec, - bool enforce_frame_size) { - CriticalSectionScoped lock(acm_crit_sect_); - - if (!HaveValidEncoder("ConfigISACBandwidthEstimator")) { - return -1; - } - - return codecs_[current_send_codec_idx_]->ConfigISACBandwidthEstimator( - frame_size_ms, rate_bit_per_sec, enforce_frame_size); -} - -int32_t AudioCodingModuleImpl::PlayoutTimestamp( - uint32_t* timestamp) { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, id_, - "PlayoutTimestamp()"); - { - CriticalSectionScoped lock(acm_crit_sect_); - if (track_neteq_buffer_) { - *timestamp = playout_ts_; - return 0; - } - } - return neteq_.PlayoutTimestamp(*timestamp); -} - -bool AudioCodingModuleImpl::HaveValidEncoder(const char* caller_name) const { - if ((!send_codec_registered_) || (current_send_codec_idx_ < 0) || - (current_send_codec_idx_ >= ACMCodecDB::kNumCodecs)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "%s failed: No send codec is registered.", caller_name); - return false; - } - if ((current_send_codec_idx_ < 0) || - (current_send_codec_idx_ >= ACMCodecDB::kNumCodecs)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "%s failed: Send codec index out of range.", caller_name); - return false; - } - if (codecs_[current_send_codec_idx_] == NULL) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "%s failed: Send codec is NULL pointer.", caller_name); - return false; - } - return true; -} - -int AudioCodingModuleImpl::UnregisterReceiveCodec(uint8_t payload_type) { - CriticalSectionScoped lock(acm_crit_sect_); - int id; - - // Search through the list of registered payload types. - for (id = 0; id < ACMCodecDB::kMaxNumCodecs; id++) { - if (registered_pltypes_[id] == payload_type) { - // We have found the id registered with the payload type. - break; - } - } - - if (id >= ACMCodecDB::kNumCodecs) { - // Payload type was not registered. No need to unregister. - return 0; - } - - // Unregister the codec with the given payload type. - return UnregisterReceiveCodecSafe(id); -} - -int32_t AudioCodingModuleImpl::UnregisterReceiveCodecSafe( - const int16_t codec_id) { - const WebRtcNetEQDecoder *neteq_decoder = ACMCodecDB::NetEQDecoders(); - int16_t mirror_id = ACMCodecDB::MirrorID(codec_id); - bool stereo_receiver = false; - - if (codecs_[codec_id] != NULL) { - if (registered_pltypes_[codec_id] != -1) { - // Store stereo information for future use. - stereo_receiver = stereo_receive_[codec_id]; - - // Before deleting the decoder instance unregister from NetEQ. - if (neteq_.RemoveCodec(neteq_decoder[codec_id], - stereo_receive_[codec_id]) < 0) { - CodecInst codec; - ACMCodecDB::Codec(codec_id, &codec); - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, id_, - "Unregistering %s-%d from NetEQ failed.", codec.plname, - codec.plfreq); - return -1; - } - - // CN is a special case for NetEQ, all three sampling frequencies - // are unregistered if one is deleted. - if (IsCodecCN(codec_id)) { - for (int i = 0; i < ACMCodecDB::kNumCodecs; i++) { - if (IsCodecCN(i)) { - stereo_receive_[i] = false; - registered_pltypes_[i] = -1; - } - } - } else { - if (codec_id == mirror_id) { - codecs_[codec_id]->DestructDecoder(); - if (stereo_receive_[codec_id]) { - slave_codecs_[codec_id]->DestructDecoder(); - stereo_receive_[codec_id] = false; - } - } - } - - // Check if this is the last registered stereo receive codec. - if (stereo_receiver) { - bool no_stereo = true; - - for (int i = 0; i < ACMCodecDB::kNumCodecs; i++) { - if (stereo_receive_[i]) { - // We still have stereo codecs registered. - no_stereo = false; - break; - } - } - - // If we don't have any stereo codecs left, change status. - if (no_stereo) { - neteq_.RemoveSlaves(); // No longer need the slave. - stereo_receive_registered_ = false; - } - } - } - } - - if (registered_pltypes_[codec_id] == receive_red_pltype_) { - // RED is going to be unregistered, set to an invalid value. - receive_red_pltype_ = 255; - } - registered_pltypes_[codec_id] = -1; - - return 0; -} - -int32_t AudioCodingModuleImpl::REDPayloadISAC( - const int32_t isac_rate, const int16_t isac_bw_estimate, - uint8_t* payload, int16_t* length_bytes) { - if (!HaveValidEncoder("EncodeData")) { - return -1; - } - int16_t status; - status = codecs_[current_send_codec_idx_]->REDPayloadISAC(isac_rate, - isac_bw_estimate, - payload, - length_bytes); - return status; -} - -void AudioCodingModuleImpl::ResetFragmentation(int vector_size) { - for (int n = 0; n < kMaxNumFragmentationVectors; n++) { - fragmentation_.fragmentationOffset[n] = n * MAX_PAYLOAD_SIZE_BYTE; - } - memset(fragmentation_.fragmentationLength, 0, kMaxNumFragmentationVectors * - sizeof(fragmentation_.fragmentationLength[0])); - memset(fragmentation_.fragmentationTimeDiff, 0, kMaxNumFragmentationVectors * - sizeof(fragmentation_.fragmentationTimeDiff[0])); - memset(fragmentation_.fragmentationPlType, 0, kMaxNumFragmentationVectors * - sizeof(fragmentation_.fragmentationPlType[0])); - fragmentation_.fragmentationVectorSize = - static_cast(vector_size); -} - -// TODO(turajs): Add second parameter to enable/disable AV-sync. -int AudioCodingModuleImpl::SetInitialPlayoutDelay(int delay_ms) { - if (delay_ms < 0 || delay_ms > 10000) { - return -1; - } - - CriticalSectionScoped lock(acm_crit_sect_); - - // Receiver should be initialized before this call processed. - if (!receiver_initialized_) { - InitializeReceiverSafe(); - } - - if (first_payload_received_) { - // Too late for this API. Only works before a call is started. - return -1; - } - initial_delay_ms_ = delay_ms; - - // If initial delay is zero, NetEq buffer should not be tracked, also we - // don't want to be in AV-sync mode. - track_neteq_buffer_ = delay_ms > 0; - av_sync_ = delay_ms > 0; - - neteq_.EnableAVSync(av_sync_); - return neteq_.SetMinimumDelay(delay_ms); -} - -bool AudioCodingModuleImpl::GetSilence(int desired_sample_rate_hz, - AudioFrame* frame) { - CriticalSectionScoped lock(acm_crit_sect_); - if (initial_delay_ms_ == 0 || !track_neteq_buffer_) { - return false; - } - - if (accumulated_audio_ms_ >= initial_delay_ms_) { - // We have enough data stored that match our initial delay target. - track_neteq_buffer_ = false; - return false; - } - - // Record call to silence generator. - call_stats_.DecodedBySilenceGenerator(); - - // We stop accumulating packets, if the number of packets or the total size - // exceeds a threshold. - int max_num_packets; - int buffer_size_bytes; - int per_payload_overhead_bytes; - neteq_.BufferSpec(max_num_packets, buffer_size_bytes, - per_payload_overhead_bytes); - int total_bytes_accumulated = num_bytes_accumulated_ + - num_packets_accumulated_ * per_payload_overhead_bytes; - if (num_packets_accumulated_ > max_num_packets * 0.9 || - total_bytes_accumulated > buffer_size_bytes * 0.9) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - "GetSilence: Initial delay couldn't be achieved." - " num_packets_accumulated=%d, total_bytes_accumulated=%d", - num_packets_accumulated_, num_bytes_accumulated_); - track_neteq_buffer_ = false; - return false; - } - - if (desired_sample_rate_hz > 0) { - frame->sample_rate_hz_ = desired_sample_rate_hz; - } else { - frame->sample_rate_hz_ = 0; - if (current_receive_codec_idx_ >= 0) { - frame->sample_rate_hz_ = - ACMCodecDB::database_[current_receive_codec_idx_].plfreq; - } else { - // No payload received yet, use the default sampling rate of NetEq. - frame->sample_rate_hz_ = neteq_.CurrentSampFreqHz(); - } - } - frame->num_channels_ = expected_channels_; - frame->samples_per_channel_ = frame->sample_rate_hz_ / 100; // Always 10 ms. - frame->speech_type_ = AudioFrame::kCNG; - frame->vad_activity_ = AudioFrame::kVadPassive; - frame->energy_ = 0; - int samples = frame->samples_per_channel_ * frame->num_channels_; - memset(frame->data_, 0, samples * sizeof(int16_t)); - return true; -} - -// Must be called within the scope of ACM critical section. -int AudioCodingModuleImpl::PushSyncPacketSafe() { - assert(av_sync_); - last_sequence_number_++; - last_incoming_send_timestamp_ += last_timestamp_diff_; - last_receive_timestamp_ += last_timestamp_diff_; - - WebRtcRTPHeader rtp_info; - rtp_info.header.payloadType = last_recv_audio_codec_pltype_; - rtp_info.header.ssrc = last_ssrc_; - rtp_info.header.markerBit = false; - rtp_info.header.sequenceNumber = last_sequence_number_; - rtp_info.header.timestamp = last_incoming_send_timestamp_; - rtp_info.type.Audio.channel = stereo_receive_[current_receive_codec_idx_] ? - 2 : 1; - last_packet_was_sync_ = true; - int payload_len_bytes = neteq_.RecIn(rtp_info, last_receive_timestamp_); - - if (payload_len_bytes < 0) - return -1; - - // This is to account for sync packets inserted during the buffering phase. - if (track_neteq_buffer_) - UpdateBufferingSafe(rtp_info, payload_len_bytes); - - return 0; -} - -// Must be called within the scope of ACM critical section. -void AudioCodingModuleImpl::UpdateBufferingSafe(const WebRtcRTPHeader& rtp_info, - int payload_len_bytes) { - const int in_sample_rate_khz = - (ACMCodecDB::database_[current_receive_codec_idx_].plfreq / 1000); - if (first_payload_received_ && - rtp_info.header.timestamp > last_incoming_send_timestamp_ && - in_sample_rate_khz > 0) { - accumulated_audio_ms_ += (rtp_info.header.timestamp - - last_incoming_send_timestamp_) / in_sample_rate_khz; - } - - num_packets_accumulated_++; - num_bytes_accumulated_ += payload_len_bytes; - - playout_ts_ = static_cast( - rtp_info.header.timestamp - static_cast( - initial_delay_ms_ * in_sample_rate_khz)); -} - -uint32_t AudioCodingModuleImpl::NowTimestamp(int codec_id) { - // Down-cast the time to (32-6)-bit since we only care about - // the least significant bits. (32-6) bits cover 2^(32-6) = 67108864 ms. - // we masked 6 most significant bits of 32-bit so we don't lose resolution - // when do the following multiplication. - int sample_rate_khz = ACMCodecDB::database_[codec_id].plfreq / 1000; - const uint32_t now_in_ms = static_cast( - clock_->TimeInMilliseconds() & kMaskTimestamp); - return static_cast(sample_rate_khz * now_in_ms); -} - -std::vector AudioCodingModuleImpl::GetNackList( - int round_trip_time_ms) const { - CriticalSectionScoped lock(acm_crit_sect_); - if (round_trip_time_ms < 0) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, id_, - "GetNackList: round trip time cannot be negative." - " round_trip_time_ms=%d", round_trip_time_ms); - } - if (nack_enabled_ && round_trip_time_ms >= 0) { - assert(nack_.get()); - return nack_->GetNackList(round_trip_time_ms); - } - std::vector empty_list; - return empty_list; -} - -int AudioCodingModuleImpl::LeastRequiredDelayMs() const { - return std::max(neteq_.LeastRequiredDelayMs(), initial_delay_ms_); -} - -int AudioCodingModuleImpl::EnableNack(size_t max_nack_list_size) { - // Don't do anything if |max_nack_list_size| is out of range. - if (max_nack_list_size == 0 || - max_nack_list_size > acm2::Nack::kNackListSizeLimit) - return -1; - - CriticalSectionScoped lock(acm_crit_sect_); - if (!nack_enabled_) { - nack_.reset(acm2::Nack::Create(kNackThresholdPackets)); - nack_enabled_ = true; - - // Sampling rate might need to be updated if we change from disable to - // enable. Do it if the receive codec is valid. - if (current_receive_codec_idx_ >= 0) { - nack_->UpdateSampleRate( - ACMCodecDB::database_[current_receive_codec_idx_].plfreq); - } - } - return nack_->SetMaxNackListSize(max_nack_list_size); -} - -void AudioCodingModuleImpl::DisableNack() { - CriticalSectionScoped lock(acm_crit_sect_); - nack_.reset(); // Memory is released. - nack_enabled_ = false; -} - -const char* AudioCodingModuleImpl::Version() const { - return kLegacyAcmVersion; -} - -void AudioCodingModuleImpl::GetDecodingCallStatistics( - AudioDecodingCallStats* call_stats) const { - CriticalSectionScoped lock(acm_crit_sect_); - *call_stats = call_stats_.GetDecodingStatistics(); -} - -} // namespace acm1 - -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h b/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h deleted file mode 100644 index f0b22f114..000000000 --- a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h +++ /dev/null @@ -1,455 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_MAIN_SOURCE_AUDIO_CODING_MODULE_IMPL_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_AUDIO_CODING_MODULE_IMPL_H_ - -#include - -#include "webrtc/common_types.h" -#include "webrtc/engine_configurations.h" -#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/source/acm_neteq.h" -#include "webrtc/modules/audio_coding/main/source/acm_resampler.h" -#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" -#include "webrtc/system_wrappers/interface/scoped_ptr.h" - -namespace webrtc { - -struct WebRtcACMAudioBuff; -struct WebRtcACMCodecParams; -class CriticalSectionWrapper; -class RWLockWrapper; -class Clock; - -namespace acm2 { -class Nack; -} - -namespace acm1 { - -class ACMDTMFDetection; -class ACMGenericCodec; - -class AudioCodingModuleImpl : public AudioCodingModule { - public: - AudioCodingModuleImpl(const int32_t id, Clock* clock); - ~AudioCodingModuleImpl(); - - virtual const char* Version() const; - - // Change the unique identifier of this object. - virtual int32_t ChangeUniqueId(const int32_t id); - - // Returns the number of milliseconds until the module want a worker thread - // to call Process. - int32_t TimeUntilNextProcess(); - - // Process any pending tasks such as timeouts. - int32_t Process(); - - ///////////////////////////////////////// - // Sender - // - - // Initialize send codec. - int32_t InitializeSender(); - - // Reset send codec. - int32_t ResetEncoder(); - - // Can be called multiple times for Codec, CNG, RED. - int32_t RegisterSendCodec(const CodecInst& send_codec); - - // Register Secondary codec for dual-streaming. Dual-streaming is activated - // right after the secondary codec is registered. - int RegisterSecondarySendCodec(const CodecInst& send_codec); - - // Unregister the secondary codec. Dual-streaming is deactivated right after - // deregistering secondary codec. - void UnregisterSecondarySendCodec(); - - // Get the secondary codec. - int SecondarySendCodec(CodecInst* secondary_codec) const; - - // Get current send codec. - int32_t SendCodec(CodecInst* current_codec) const; - - // Get current send frequency. - int32_t SendFrequency() const; - - // Get encode bit-rate. - // Adaptive rate codecs return their current encode target rate, while other - // codecs return there long-term average or their fixed rate. - int32_t SendBitrate() const; - - // Set available bandwidth, inform the encoder about the - // estimated bandwidth received from the remote party. - virtual int32_t SetReceivedEstimatedBandwidth(const int32_t bw); - - // Register a transport callback which will be - // called to deliver the encoded buffers. - int32_t RegisterTransportCallback(AudioPacketizationCallback* transport); - - // Add 10 ms of raw (PCM) audio data to the encoder. - int32_t Add10MsData(const AudioFrame& audio_frame); - - ///////////////////////////////////////// - // (FEC) Forward Error Correction - // - - // Configure FEC status i.e on/off. - int32_t SetFECStatus(const bool enable_fec); - - // Get FEC status. - bool FECStatus() const; - - ///////////////////////////////////////// - // (VAD) Voice Activity Detection - // and - // (CNG) Comfort Noise Generation - // - - int32_t SetVAD(bool enable_dtx = true, - bool enable_vad = false, - ACMVADMode mode = VADNormal); - - int32_t VAD(bool* dtx_enabled, bool* vad_enabled, ACMVADMode* mode) const; - - int32_t RegisterVADCallback(ACMVADCallback* vad_callback); - - ///////////////////////////////////////// - // Receiver - // - - // Initialize receiver, resets codec database etc. - int32_t InitializeReceiver(); - - // Reset the decoder state. - int32_t ResetDecoder(); - - // Get current receive frequency. - int32_t ReceiveFrequency() const; - - // Get current playout frequency. - int32_t PlayoutFrequency() const; - - // Register possible receive codecs, can be called multiple times, - // for codecs, CNG, DTMF, RED. - int32_t RegisterReceiveCodec(const CodecInst& receive_codec); - - // Get current received codec. - int32_t ReceiveCodec(CodecInst* current_codec) const; - - // Incoming packet from network parsed and ready for decode. - int32_t IncomingPacket(const uint8_t* incoming_payload, - const int32_t payload_length, - const WebRtcRTPHeader& rtp_info); - - // Incoming payloads, without rtp-info, the rtp-info will be created in ACM. - // One usage for this API is when pre-encoded files are pushed in ACM. - int32_t IncomingPayload(const uint8_t* incoming_payload, - const int32_t payload_length, - const uint8_t payload_type, - const uint32_t timestamp = 0); - - // NetEq minimum playout delay (used for lip-sync). The actual target delay - // is the max of |time_ms| and the required delay dictated by the channel. - int SetMinimumPlayoutDelay(int time_ms); - - // NetEq maximum playout delay. The actual target delay is the min of - // |time_ms| and the required delay dictated by the channel. - int SetMaximumPlayoutDelay(int time_ms); - - // The shortest latency, in milliseconds, required by jitter buffer. This - // is computed based on inter-arrival times and playout mode of NetEq. The - // actual delay is the maximum of least-required-delay and the minimum-delay - // specified by SetMinumumPlayoutDelay() API. - // - int LeastRequiredDelayMs() const ; - - // Configure Dtmf playout status i.e on/off playout the incoming outband Dtmf - // tone. - int32_t SetDtmfPlayoutStatus(const bool enable); - - // Get Dtmf playout status. - bool DtmfPlayoutStatus() const; - - // Estimate the Bandwidth based on the incoming stream, needed - // for one way audio where the RTCP send the BW estimate. - // This is also done in the RTP module . - int32_t DecoderEstimatedBandwidth() const; - - // Set playout mode voice, fax. - int32_t SetPlayoutMode(const AudioPlayoutMode mode); - - // Get playout mode voice, fax. - AudioPlayoutMode PlayoutMode() const; - - // Get playout timestamp. - int32_t PlayoutTimestamp(uint32_t* timestamp); - - // Get 10 milliseconds of raw audio data to play out, and - // automatic resample to the requested frequency if > 0. - int32_t PlayoutData10Ms(int32_t desired_freq_hz, - AudioFrame* audio_frame); - - ///////////////////////////////////////// - // Statistics - // - - int32_t NetworkStatistics(ACMNetworkStatistics* statistics); - - void DestructEncoderInst(void* inst); - - int16_t AudioBuffer(WebRtcACMAudioBuff& buffer); - - // GET RED payload for iSAC. The method id called when 'this' ACM is - // the default ACM. - int32_t REDPayloadISAC(const int32_t isac_rate, - const int16_t isac_bw_estimate, - uint8_t* payload, - int16_t* length_bytes); - - int16_t SetAudioBuffer(WebRtcACMAudioBuff& buffer); - - uint32_t EarliestTimestamp() const; - - int32_t LastEncodedTimestamp(uint32_t& timestamp) const; - - int32_t ReplaceInternalDTXWithWebRtc(const bool use_webrtc_dtx); - - int32_t IsInternalDTXReplacedWithWebRtc(bool* uses_webrtc_dtx); - - int SetISACMaxRate(int max_bit_per_sec); - - int SetISACMaxPayloadSize(int max_size_bytes); - - int32_t ConfigISACBandwidthEstimator( - int frame_size_ms, - int rate_bit_per_sec, - bool enforce_frame_size = false); - - int UnregisterReceiveCodec(uint8_t payload_type); - - std::vector GetNackList(int round_trip_time_ms) const; - - protected: - void UnregisterSendCodec(); - - int32_t UnregisterReceiveCodecSafe(const int16_t id); - - ACMGenericCodec* CreateCodec(const CodecInst& codec); - - int16_t DecoderParamByPlType(const uint8_t payload_type, - WebRtcACMCodecParams& codec_params) const; - - int16_t DecoderListIDByPlName( - const char* name, const uint16_t frequency = 0) const; - - int32_t InitializeReceiverSafe(); - - bool HaveValidEncoder(const char* caller_name) const; - - int32_t RegisterRecCodecMSSafe(const CodecInst& receive_codec, - int16_t codec_id, - int16_t mirror_id, - ACMNetEQ::JitterBuffer jitter_buffer); - - // Set VAD/DTX status. This function does not acquire a lock, and it is - // created to be called only from inside a critical section. - int SetVADSafe(bool enable_dtx, bool enable_vad, ACMVADMode mode); - - // Process buffered audio when dual-streaming is not enabled (When RED is - // enabled still this function is used.) - int ProcessSingleStream(); - - // Process buffered audio when dual-streaming is enabled, i.e. secondary send - // codec is registered. - int ProcessDualStream(); - - // Preprocessing of input audio, including resampling and down-mixing if - // required, before pushing audio into encoder's buffer. - // - // in_frame: input audio-frame - // ptr_out: pointer to output audio_frame. If no preprocessing is required - // |ptr_out| will be pointing to |in_frame|, otherwise pointing to - // |preprocess_frame_|. - // - // Return value: - // -1: if encountering an error. - // 0: otherwise. - int PreprocessToAddData(const AudioFrame& in_frame, - const AudioFrame** ptr_out); - - // Set initial playout delay. - // -delay_ms: delay in millisecond. - // - // Return value: - // -1: if cannot set the delay. - // 0: if delay set successfully. - int SetInitialPlayoutDelay(int delay_ms); - - // Enable NACK and set the maximum size of the NACK list. - int EnableNack(size_t max_nack_list_size); - - // Disable NACK. - void DisableNack(); - - void GetDecodingCallStatistics(AudioDecodingCallStats* call_stats) const; - - private: - // Change required states after starting to receive the codec corresponding - // to |index|. - int UpdateUponReceivingCodec(int index); - - // Remove all slaves and initialize a stereo slave with required codecs - // from the master. - int InitStereoSlave(); - - // Returns true if the codec's |index| is registered with the master and - // is a stereo codec, RED or CN. - bool IsCodecForSlave(int index) const; - - int EncodeFragmentation(int fragmentation_index, int payload_type, - uint32_t current_timestamp, - ACMGenericCodec* encoder, - uint8_t* stream); - - void ResetFragmentation(int vector_size); - - bool GetSilence(int desired_sample_rate_hz, AudioFrame* frame); - - // Push a synchronization packet into NetEq. Such packets result in a frame - // of zeros (not decoded by the corresponding decoder). The size of the frame - // is the same as last decoding. NetEq has a special payload for this. - // Call within the scope of ACM critical section. - int PushSyncPacketSafe(); - - // Update the parameters required in initial phase of buffering, when - // initial playout delay is requested. Call within the scope of ACM critical - // section. - void UpdateBufferingSafe(const WebRtcRTPHeader& rtp_info, - int payload_len_bytes); - - // - // Return the timestamp of current time, computed according to sampling rate - // of the codec identified by |codec_id|. - // - uint32_t NowTimestamp(int codec_id); - - AudioPacketizationCallback* packetization_callback_; - int32_t id_; - uint32_t last_timestamp_; - uint32_t last_in_timestamp_; - CodecInst send_codec_inst_; - uint8_t cng_nb_pltype_; - uint8_t cng_wb_pltype_; - uint8_t cng_swb_pltype_; - uint8_t cng_fb_pltype_; - uint8_t red_pltype_; - bool vad_enabled_; - bool dtx_enabled_; - ACMVADMode vad_mode_; - ACMGenericCodec* codecs_[ACMCodecDB::kMaxNumCodecs]; - ACMGenericCodec* slave_codecs_[ACMCodecDB::kMaxNumCodecs]; - int16_t mirror_codec_idx_[ACMCodecDB::kMaxNumCodecs]; - bool stereo_receive_[ACMCodecDB::kMaxNumCodecs]; - bool stereo_receive_registered_; - bool stereo_send_; - int prev_received_channel_; - int expected_channels_; - int32_t current_send_codec_idx_; - int current_receive_codec_idx_; - bool send_codec_registered_; - ACMResampler input_resampler_; - ACMResampler output_resampler_; - ACMNetEQ neteq_; - CriticalSectionWrapper* acm_crit_sect_; - ACMVADCallback* vad_callback_; - uint8_t last_recv_audio_codec_pltype_; - - // RED/FEC. - bool is_first_red_; - bool fec_enabled_; - // TODO(turajs): |red_buffer_| is allocated in constructor, why having them - // as pointers and not an array. If concerned about the memory, then make a - // set-up function to allocate them only when they are going to be used, i.e. - // FEC or Dual-streaming is enabled. - uint8_t* red_buffer_; - // TODO(turajs): we actually don't need |fragmentation_| as a member variable. - // It is sufficient to keep the length & payload type of previous payload in - // member variables. - RTPFragmentationHeader fragmentation_; - uint32_t last_fec_timestamp_; - // If no RED is registered as receive codec this - // will have an invalid value. - uint8_t receive_red_pltype_; - - // This is to keep track of CN instances where we can send DTMFs. - uint8_t previous_pltype_; - - // This keeps track of payload types associated with codecs_[]. - // We define it as signed variable and initialize with -1 to indicate - // unused elements. - int16_t registered_pltypes_[ACMCodecDB::kMaxNumCodecs]; - - // Used when payloads are pushed into ACM without any RTP info - // One example is when pre-encoded bit-stream is pushed from - // a file. - WebRtcRTPHeader* dummy_rtp_header_; - uint16_t recv_pl_frame_size_smpls_; - - bool receiver_initialized_; - ACMDTMFDetection* dtmf_detector_; - - AudioCodingFeedback* dtmf_callback_; - int16_t last_detected_tone_; - CriticalSectionWrapper* callback_crit_sect_; - - AudioFrame audio_frame_; - AudioFrame preprocess_frame_; - CodecInst secondary_send_codec_inst_; - scoped_ptr secondary_encoder_; - - // Initial delay. - int initial_delay_ms_; - int num_packets_accumulated_; - int num_bytes_accumulated_; - int accumulated_audio_ms_; - int first_payload_received_; - uint32_t last_incoming_send_timestamp_; - bool track_neteq_buffer_; - uint32_t playout_ts_; - - // AV-sync is enabled. In AV-sync mode, sync packet pushed during long packet - // losses. - bool av_sync_; - - // Latest send timestamp difference of two consecutive packets. - uint32_t last_timestamp_diff_; - uint16_t last_sequence_number_; - uint32_t last_ssrc_; - bool last_packet_was_sync_; - int64_t last_receive_timestamp_; - - Clock* clock_; - scoped_ptr nack_; - bool nack_enabled_; - - acm2::CallStatistics call_stats_; -}; - -} // namespace acm1 - -} // namespace webrtc - -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_SOURCE_AUDIO_CODING_MODULE_IMPL_H_ diff --git a/webrtc/modules/audio_coding/main/test/APITest.cc b/webrtc/modules/audio_coding/main/test/APITest.cc index 15bac6adc..82940fa3e 100644 --- a/webrtc/modules/audio_coding/main/test/APITest.cc +++ b/webrtc/modules/audio_coding/main/test/APITest.cc @@ -56,8 +56,8 @@ void APITest::Wait(uint32_t waitLengthMs) { } APITest::APITest(const Config& config) - : _acmA(config.Get().Create(1)), - _acmB(config.Get().Create(2)), + : _acmA(AudioCodingModule::Create(1)), + _acmB(AudioCodingModule::Create(2)), _channel_A2B(NULL), _channel_B2A(NULL), _writeToFile(true), diff --git a/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.cc b/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.cc index cdf9fdcae..d06cc0709 100644 --- a/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.cc +++ b/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.cc @@ -10,16 +10,12 @@ #include "webrtc/modules/audio_coding/main/test/EncodeDecodeTest.h" +#include #include #include -#include - -#include -#include #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/common_types.h" -#include "webrtc/common.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" #include "webrtc/modules/audio_coding/main/test/utility.h" @@ -55,16 +51,19 @@ Sender::Sender() _packetization(NULL) { } -void Sender::Setup(AudioCodingModule *acm, RTPStream *rtpStream) { +void Sender::Setup(AudioCodingModule *acm, RTPStream *rtpStream, + std::string in_file_name, int sample_rate, int channels) { acm->InitializeSender(); struct CodecInst sendCodec; int noOfCodecs = acm->NumberOfCodecs(); int codecNo; // Open input file - const std::string file_name = webrtc::test::ResourcePath( - "audio_coding/testfile32kHz", "pcm"); - _pcmFile.Open(file_name, 32000, "rb"); + const std::string file_name = webrtc::test::ResourcePath(in_file_name, "pcm"); + _pcmFile.Open(file_name, sample_rate, "rb"); + if (channels == 2) { + _pcmFile.ReadStereo(true); + } // Set the codec for the current test. if ((testMode == 0) || (testMode == 1)) { @@ -82,10 +81,8 @@ void Sender::Setup(AudioCodingModule *acm, RTPStream *rtpStream) { } EXPECT_EQ(0, acm->Codec(codecNo, &sendCodec)); - // Default number of channels is 2 for CELT, so we change to 1 in this test. - if (!strcmp(sendCodec.plname, "CELT")) { - sendCodec.channels = 1; - } + + sendCodec.channels = channels; EXPECT_EQ(0, acm->RegisterSendCodec(sendCodec)); _packetization = new TestPacketization(rtpStream, sendCodec.plfreq); @@ -126,21 +123,28 @@ Receiver::Receiver() _payloadSizeBytes(MAX_INCOMING_PAYLOAD) { } -void Receiver::Setup(AudioCodingModule *acm, RTPStream *rtpStream) { +void Receiver::Setup(AudioCodingModule *acm, RTPStream *rtpStream, + std::string out_file_name, int channels) { struct CodecInst recvCodec; int noOfCodecs; EXPECT_EQ(0, acm->InitializeReceiver()); noOfCodecs = acm->NumberOfCodecs(); for (int i = 0; i < noOfCodecs; i++) { - EXPECT_EQ(0, acm->Codec(static_cast(i), &recvCodec)); - EXPECT_EQ(0, acm->RegisterReceiveCodec(recvCodec)); + EXPECT_EQ(0, acm->Codec(i, &recvCodec)); + if (recvCodec.channels == channels) + EXPECT_EQ(0, acm->RegisterReceiveCodec(recvCodec)); + // Forces mono/stereo for Opus. + if (!strcmp(recvCodec.plname, "opus")) { + recvCodec.channels = channels; + EXPECT_EQ(0, acm->RegisterReceiveCodec(recvCodec)); + } } int playSampFreq; std::string file_name; std::stringstream file_stream; - file_stream << webrtc::test::OutputPath() << "encodeDecode_out" + file_stream << webrtc::test::OutputPath() << out_file_name << static_cast(codeId) << ".pcm"; file_name = file_stream.str(); _rtpStream = rtpStream; @@ -157,7 +161,7 @@ void Receiver::Setup(AudioCodingModule *acm, RTPStream *rtpStream) { printf("which means output frequency equal to received signal frequency"); printf("\n\nChoose output sampling frequency: "); ASSERT_GT(scanf("%d", &playSampFreq), 0); - file_name = webrtc::test::OutputPath() + "encodeDecode_out.pcm"; + file_name = webrtc::test::OutputPath() + out_file_name + ".pcm"; _pcmFile.Open(file_name, playSampFreq, "wb+"); } @@ -214,7 +218,8 @@ bool Receiver::PlayoutData() { if (_playoutLengthSmpls == 0) { return false; } - _pcmFile.Write10MsData(audioFrame.data_, audioFrame.samples_per_channel_); + _pcmFile.Write10MsData(audioFrame.data_, + audioFrame.samples_per_channel_ * audioFrame.num_channels_); return true; } @@ -242,16 +247,14 @@ void Receiver::Run() { } } -EncodeDecodeTest::EncodeDecodeTest(const Config& config) - : config_(config) { +EncodeDecodeTest::EncodeDecodeTest() { _testMode = 2; Trace::CreateTrace(); Trace::SetTraceFile( (webrtc::test::OutputPath() + "acm_encdec_trace.txt").c_str()); } -EncodeDecodeTest::EncodeDecodeTest(int testMode, const Config& config) - : config_(config) { +EncodeDecodeTest::EncodeDecodeTest(int testMode) { //testMode == 0 for autotest //testMode == 1 for testing all codecs/parameters //testMode > 1 for specific user-input test (as it was used before) @@ -273,8 +276,7 @@ void EncodeDecodeTest::Perform() { codePars[1] = 0; codePars[2] = 0; - scoped_ptr acm( - config_.Get().Create(0)); + scoped_ptr acm(AudioCodingModule::Create(0)); struct CodecInst sendCodecTmp; numCodecs = acm->NumberOfCodecs(); @@ -314,7 +316,7 @@ void EncodeDecodeTest::Perform() { _receiver.codeId = codeId; rtpFile.ReadHeader(); - _receiver.Setup(acm.get(), &rtpFile); + _receiver.Setup(acm.get(), &rtpFile, "encodeDecode_out", 1); _receiver.Run(); _receiver.Teardown(); rtpFile.Close(); @@ -329,8 +331,7 @@ void EncodeDecodeTest::Perform() { void EncodeDecodeTest::EncodeToFile(int fileType, int codeId, int* codePars, int testMode) { - scoped_ptr acm( - config_.Get().Create(1)); + scoped_ptr acm(AudioCodingModule::Create(1)); RTPFile rtpFile; std::string fileName = webrtc::test::OutputPath() + "outFile.rtp"; rtpFile.Open(fileName.c_str(), "wb+"); @@ -340,7 +341,7 @@ void EncodeDecodeTest::EncodeToFile(int fileType, int codeId, int* codePars, _sender.testMode = testMode; _sender.codeId = codeId; - _sender.Setup(acm.get(), &rtpFile); + _sender.Setup(acm.get(), &rtpFile, "audio_coding/testfile32kHz", 32000, 1); struct CodecInst sendCodecInst; if (acm->SendCodec(&sendCodecInst) >= 0) { _sender.Run(); diff --git a/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.h b/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.h index 5aa359636..dbe3f0cb3 100644 --- a/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.h +++ b/webrtc/modules/audio_coding/main/test/EncodeDecodeTest.h @@ -12,6 +12,7 @@ #define WEBRTC_MODULES_AUDIO_CODING_MAIN_TEST_ENCODEDECODETEST_H_ #include +#include #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #include "webrtc/modules/audio_coding/main/test/ACMTest.h" @@ -23,8 +24,6 @@ namespace webrtc { #define MAX_INCOMING_PAYLOAD 8096 -class Config; - // TestPacketization callback which writes the encoded payloads to file class TestPacketization : public AudioPacketizationCallback { public: @@ -46,7 +45,8 @@ class TestPacketization : public AudioPacketizationCallback { class Sender { public: Sender(); - void Setup(AudioCodingModule *acm, RTPStream *rtpStream); + void Setup(AudioCodingModule *acm, RTPStream *rtpStream, + std::string in_file_name, int sample_rate, int channels); void Teardown(); void Run(); bool Add10MsData(); @@ -55,8 +55,10 @@ class Sender { uint8_t testMode; uint8_t codeId; - private: + protected: AudioCodingModule* _acm; + + private: PCMFile _pcmFile; AudioFrame _audioFrame; TestPacketization* _packetization; @@ -65,10 +67,12 @@ class Sender { class Receiver { public: Receiver(); - void Setup(AudioCodingModule *acm, RTPStream *rtpStream); + virtual ~Receiver() {}; + void Setup(AudioCodingModule *acm, RTPStream *rtpStream, + std::string out_file_name, int channels); void Teardown(); void Run(); - bool IncomingPacket(); + virtual bool IncomingPacket(); bool PlayoutData(); //for auto_test and logging @@ -76,24 +80,26 @@ class Receiver { uint8_t testMode; private: - AudioCodingModule* _acm; - RTPStream* _rtpStream; PCMFile _pcmFile; int16_t* _playoutBuffer; uint16_t _playoutLengthSmpls; - uint8_t _incomingPayload[MAX_INCOMING_PAYLOAD]; - uint16_t _payloadSizeBytes; - uint16_t _realPayloadSizeBytes; int32_t _frequency; bool _firstTime; + + protected: + AudioCodingModule* _acm; + uint8_t _incomingPayload[MAX_INCOMING_PAYLOAD]; + RTPStream* _rtpStream; WebRtcRTPHeader _rtpInfo; + uint16_t _realPayloadSizeBytes; + uint16_t _payloadSizeBytes; uint32_t _nextTime; }; class EncodeDecodeTest : public ACMTest { public: - explicit EncodeDecodeTest(const Config& config); - EncodeDecodeTest(int testMode, const Config& config); + EncodeDecodeTest(); + explicit EncodeDecodeTest(int testMode); virtual void Perform(); uint16_t _playoutFreq; @@ -102,8 +108,6 @@ class EncodeDecodeTest : public ACMTest { private: void EncodeToFile(int fileType, int codeId, int* codePars, int testMode); - const Config& config_; - protected: Sender _sender; Receiver _receiver; diff --git a/webrtc/modules/audio_coding/main/test/PacketLossTest.cc b/webrtc/modules/audio_coding/main/test/PacketLossTest.cc new file mode 100644 index 000000000..e6ef39277 --- /dev/null +++ b/webrtc/modules/audio_coding/main/test/PacketLossTest.cc @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/main/test/PacketLossTest.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/common.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { + +ReceiverWithPacketLoss::ReceiverWithPacketLoss() + : loss_rate_(0), + burst_length_(1), + packet_counter_(0), + lost_packet_counter_(0), + burst_lost_counter_(burst_length_) { +} + +void ReceiverWithPacketLoss::Setup(AudioCodingModule *acm, + RTPStream *rtpStream, + std::string out_file_name, + int channels, + int loss_rate, + int burst_length) { + loss_rate_ = loss_rate; + burst_length_ = burst_length; + burst_lost_counter_ = burst_length_; // To prevent first packet gets lost. + std::stringstream ss; + ss << out_file_name << "_" << loss_rate_ << "_" << burst_length_ << "_"; + Receiver::Setup(acm, rtpStream, ss.str(), channels); +} + +bool ReceiverWithPacketLoss::IncomingPacket() { + if (!_rtpStream->EndOfFile()) { + if (packet_counter_ == 0) { + _realPayloadSizeBytes = _rtpStream->Read(&_rtpInfo, _incomingPayload, + _payloadSizeBytes, &_nextTime); + if (_realPayloadSizeBytes == 0) { + if (_rtpStream->EndOfFile()) { + packet_counter_ = 0; + return true; + } else { + return false; + } + } + } + + if (!PacketLost()) { + _acm->IncomingPacket(_incomingPayload, _realPayloadSizeBytes, _rtpInfo); + } + packet_counter_++; + _realPayloadSizeBytes = _rtpStream->Read(&_rtpInfo, _incomingPayload, + _payloadSizeBytes, &_nextTime); + if (_realPayloadSizeBytes == 0 && _rtpStream->EndOfFile()) { + packet_counter_ = 0; + lost_packet_counter_ = 0; + } + } + return true; +} + +bool ReceiverWithPacketLoss::PacketLost() { + if (burst_lost_counter_ < burst_length_) { + lost_packet_counter_++; + burst_lost_counter_++; + return true; + } + + if (lost_packet_counter_ * 100 < loss_rate_ * packet_counter_) { + lost_packet_counter_++; + burst_lost_counter_ = 1; + return true; + } + return false; +} + +SenderWithFEC::SenderWithFEC() + : expected_loss_rate_(0) { +} + +void SenderWithFEC::Setup(AudioCodingModule *acm, RTPStream *rtpStream, + std::string in_file_name, int sample_rate, + int channels, int expected_loss_rate) { + Sender::Setup(acm, rtpStream, in_file_name, sample_rate, channels); + EXPECT_TRUE(SetFEC(true)); + EXPECT_TRUE(SetPacketLossRate(expected_loss_rate)); +} + +bool SenderWithFEC::SetFEC(bool enable_fec) { + if (_acm->SetCodecFEC(enable_fec) == 0) { + return true; + } + return false; +} + +bool SenderWithFEC::SetPacketLossRate(int expected_loss_rate) { + if (_acm->SetPacketLossRate(expected_loss_rate) == 0) { + expected_loss_rate_ = expected_loss_rate; + return true; + } + return false; +} + +PacketLossTest::PacketLossTest(int channels, int expected_loss_rate, + int actual_loss_rate, int burst_length) + : channels_(channels), + in_file_name_(channels_ == 1 ? "audio_coding/testfile32kHz" : + "audio_coding/teststereo32kHz"), + sample_rate_hz_(32000), + sender_(new SenderWithFEC), + receiver_(new ReceiverWithPacketLoss), + expected_loss_rate_(expected_loss_rate), + actual_loss_rate_(actual_loss_rate), + burst_length_(burst_length) { +} + +void PacketLossTest::Perform() { +#ifndef WEBRTC_CODEC_OPUS + return; +#else + scoped_ptr acm(AudioCodingModule::Create(0)); + + int codec_id = acm->Codec("opus", 48000, channels_); + + RTPFile rtpFile; + std::string fileName = webrtc::test::OutputPath() + "outFile.rtp"; + + // Encode to file + rtpFile.Open(fileName.c_str(), "wb+"); + rtpFile.WriteHeader(); + + sender_->testMode = 0; + sender_->codeId = codec_id; + + sender_->Setup(acm.get(), &rtpFile, in_file_name_, sample_rate_hz_, channels_, + expected_loss_rate_); + struct CodecInst sendCodecInst; + if (acm->SendCodec(&sendCodecInst) >= 0) { + sender_->Run(); + } + sender_->Teardown(); + rtpFile.Close(); + + // Decode to file + rtpFile.Open(fileName.c_str(), "rb"); + rtpFile.ReadHeader(); + + receiver_->testMode = 0; + receiver_->codeId = codec_id; + + receiver_->Setup(acm.get(), &rtpFile, "packetLoss_out", channels_, + actual_loss_rate_, burst_length_); + receiver_->Run(); + receiver_->Teardown(); + rtpFile.Close(); +#endif +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/test/PacketLossTest.h b/webrtc/modules/audio_coding/main/test/PacketLossTest.h new file mode 100644 index 000000000..e34da8c7a --- /dev/null +++ b/webrtc/modules/audio_coding/main/test/PacketLossTest.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_CODING_MAIN_TEST_PACKETLOSSTEST_H_ +#define WEBRTC_MODULES_AUDIO_CODING_MAIN_TEST_PACKETLOSSTEST_H_ + +#include +#include "webrtc/modules/audio_coding/main/test/EncodeDecodeTest.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class ReceiverWithPacketLoss : public Receiver { + public: + ReceiverWithPacketLoss(); + void Setup(AudioCodingModule *acm, RTPStream *rtpStream, + std::string out_file_name, int channels, int loss_rate, + int burst_length); + bool IncomingPacket() OVERRIDE; + protected: + bool PacketLost(); + int loss_rate_; + int burst_length_; + int packet_counter_; + int lost_packet_counter_; + int burst_lost_counter_; +}; + +class SenderWithFEC : public Sender { + public: + SenderWithFEC(); + void Setup(AudioCodingModule *acm, RTPStream *rtpStream, + std::string in_file_name, int sample_rate, int channels, + int expected_loss_rate); + bool SetPacketLossRate(int expected_loss_rate); + bool SetFEC(bool enable_fec); + protected: + int expected_loss_rate_; +}; + +class PacketLossTest : public ACMTest { + public: + PacketLossTest(int channels, int expected_loss_rate_, int actual_loss_rate, + int burst_length); + void Perform(); + protected: + int channels_; + std::string in_file_name_; + int sample_rate_hz_; + scoped_ptr sender_; + scoped_ptr receiver_; + int expected_loss_rate_; + int actual_loss_rate_; + int burst_length_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_TEST_PACKETLOSSTEST_H_ diff --git a/webrtc/modules/audio_coding/main/test/TestAllCodecs.cc b/webrtc/modules/audio_coding/main/test/TestAllCodecs.cc index fba7f0329..d6c6dc4e6 100644 --- a/webrtc/modules/audio_coding/main/test/TestAllCodecs.cc +++ b/webrtc/modules/audio_coding/main/test/TestAllCodecs.cc @@ -99,9 +99,9 @@ void TestPack::reset_payload_size() { payload_size_ = 0; } -TestAllCodecs::TestAllCodecs(int test_mode, const Config& config) - : acm_a_(config.Get().Create(0)), - acm_b_(config.Get().Create(1)), +TestAllCodecs::TestAllCodecs(int test_mode) + : acm_a_(AudioCodingModule::Create(0)), + acm_b_(AudioCodingModule::Create(1)), channel_a_to_b_(NULL), test_count_(0), packet_size_samples_(0), diff --git a/webrtc/modules/audio_coding/main/test/TestAllCodecs.h b/webrtc/modules/audio_coding/main/test/TestAllCodecs.h index 0231d84c6..10d82ae1c 100644 --- a/webrtc/modules/audio_coding/main/test/TestAllCodecs.h +++ b/webrtc/modules/audio_coding/main/test/TestAllCodecs.h @@ -11,7 +11,6 @@ #ifndef WEBRTC_MODULES_AUDIO_CODING_MAIN_TEST_TESTALLCODECS_H_ #define WEBRTC_MODULES_AUDIO_CODING_MAIN_TEST_TESTALLCODECS_H_ -#include "webrtc/common.h" #include "webrtc/modules/audio_coding/main/test/ACMTest.h" #include "webrtc/modules/audio_coding/main/test/Channel.h" #include "webrtc/modules/audio_coding/main/test/PCMFile.h" @@ -50,7 +49,7 @@ class TestPack : public AudioPacketizationCallback { class TestAllCodecs : public ACMTest { public: - TestAllCodecs(int test_mode, const Config& config); + explicit TestAllCodecs(int test_mode); ~TestAllCodecs(); void Perform(); diff --git a/webrtc/modules/audio_coding/main/test/TestFEC.cc b/webrtc/modules/audio_coding/main/test/TestRedFec.cc similarity index 59% rename from webrtc/modules/audio_coding/main/test/TestFEC.cc rename to webrtc/modules/audio_coding/main/test/TestRedFec.cc index 032579cf0..10498d8c5 100644 --- a/webrtc/modules/audio_coding/main/test/TestFEC.cc +++ b/webrtc/modules/audio_coding/main/test/TestRedFec.cc @@ -8,10 +8,9 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/main/test/TestFEC.h" +#include "webrtc/modules/audio_coding/main/test/TestRedFec.h" #include -#include #include "webrtc/common.h" #include "webrtc/common_types.h" @@ -23,21 +22,21 @@ namespace webrtc { -TestFEC::TestFEC(const Config& config) - : _acmA(config.Get().Create(0)), - _acmB(config.Get().Create(1)), +TestRedFec::TestRedFec() + : _acmA(AudioCodingModule::Create(0)), + _acmB(AudioCodingModule::Create(1)), _channelA2B(NULL), _testCntr(0) { } -TestFEC::~TestFEC() { +TestRedFec::~TestRedFec() { if (_channelA2B != NULL) { delete _channelA2B; _channelA2B = NULL; } } -void TestFEC::Perform() { +void TestRedFec::Perform() { const std::string file_name = webrtc::test::ResourcePath( "audio_coding/testfile32kHz", "pcm"); _inFileA.Open(file_name, 32000, "rb"); @@ -49,6 +48,10 @@ void TestFEC::Perform() { CodecInst myCodecParam; for (uint8_t n = 0; n < numEncoders; n++) { EXPECT_EQ(0, _acmB->Codec(n, &myCodecParam)); + // Default number of channels is 2 for opus, so we change to 1 in this test. + if (!strcmp(myCodecParam.plname, "opus")) { + myCodecParam.channels = 1; + } EXPECT_EQ(0, _acmB->RegisterReceiveCodec(myCodecParam)); } @@ -70,13 +73,13 @@ void TestFEC::Perform() { EXPECT_EQ(0, RegisterSendCodec('A', nameRED)); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(true, true, VADAggr)); - EXPECT_EQ(0, _acmA->SetFECStatus(false)); - EXPECT_FALSE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + EXPECT_FALSE(_acmA->REDStatus()); Run(); _outFileB.Close(); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); OpenOutFile(_testCntr); Run(); _outFileB.Close(); @@ -85,13 +88,13 @@ void TestFEC::Perform() { RegisterSendCodec('A', nameISAC, 16000); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(true, true, VADVeryAggr)); - EXPECT_EQ(0, _acmA->SetFECStatus(false)); - EXPECT_FALSE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + EXPECT_FALSE(_acmA->REDStatus()); Run(); _outFileB.Close(); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); OpenOutFile(_testCntr); Run(); _outFileB.Close(); @@ -99,13 +102,13 @@ void TestFEC::Perform() { RegisterSendCodec('A', nameISAC, 32000); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(true, true, VADVeryAggr)); - EXPECT_EQ(0, _acmA->SetFECStatus(false)); - EXPECT_FALSE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + EXPECT_FALSE(_acmA->REDStatus()); Run(); _outFileB.Close(); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); OpenOutFile(_testCntr); Run(); _outFileB.Close(); @@ -113,20 +116,20 @@ void TestFEC::Perform() { RegisterSendCodec('A', nameISAC, 32000); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(false, false, VADNormal)); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); Run(); RegisterSendCodec('A', nameISAC, 16000); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_TRUE(_acmA->REDStatus()); Run(); RegisterSendCodec('A', nameISAC, 32000); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_TRUE(_acmA->REDStatus()); Run(); RegisterSendCodec('A', nameISAC, 16000); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_TRUE(_acmA->REDStatus()); Run(); _outFileB.Close(); @@ -136,13 +139,13 @@ void TestFEC::Perform() { EXPECT_EQ(0, RegisterSendCodec('A', nameCN, 16000)); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(true, true, VADAggr)); - EXPECT_EQ(0, _acmA->SetFECStatus(false)); - EXPECT_FALSE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + EXPECT_FALSE(_acmA->REDStatus()); Run(); _outFileB.Close(); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); OpenOutFile(_testCntr); Run(); _outFileB.Close(); @@ -150,13 +153,13 @@ void TestFEC::Perform() { RegisterSendCodec('A', nameISAC, 16000); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(true, true, VADVeryAggr)); - EXPECT_EQ(0, _acmA->SetFECStatus(false)); - EXPECT_FALSE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + EXPECT_FALSE(_acmA->REDStatus()); Run(); _outFileB.Close(); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); OpenOutFile(_testCntr); Run(); _outFileB.Close(); @@ -164,13 +167,13 @@ void TestFEC::Perform() { RegisterSendCodec('A', nameISAC, 32000); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(true, true, VADVeryAggr)); - EXPECT_EQ(0, _acmA->SetFECStatus(false)); - EXPECT_FALSE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + EXPECT_FALSE(_acmA->REDStatus()); Run(); _outFileB.Close(); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); OpenOutFile(_testCntr); Run(); _outFileB.Close(); @@ -178,30 +181,88 @@ void TestFEC::Perform() { RegisterSendCodec('A', nameISAC, 32000); OpenOutFile(_testCntr); EXPECT_EQ(0, SetVAD(false, false, VADNormal)); - EXPECT_EQ(0, _acmA->SetFECStatus(true)); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); Run(); RegisterSendCodec('A', nameISAC, 16000); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_TRUE(_acmA->REDStatus()); Run(); RegisterSendCodec('A', nameISAC, 32000); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_TRUE(_acmA->REDStatus()); Run(); RegisterSendCodec('A', nameISAC, 16000); - EXPECT_TRUE(_acmA->FECStatus()); + EXPECT_TRUE(_acmA->REDStatus()); Run(); _outFileB.Close(); + +#ifndef WEBRTC_CODEC_OPUS + EXPECT_TRUE(false); + printf("Opus needs to be activated to run this test\n"); + return; +#endif + + char nameOpus[] = "opus"; + RegisterSendCodec('A', nameOpus, 48000); + + EXPECT_TRUE(_acmA->REDStatus()); + + // _channelA2B imposes 25% packet loss rate. + EXPECT_EQ(0, _acmA->SetPacketLossRate(25)); + + // Codec FEC and RED are mutually exclusive. + EXPECT_EQ(-1, _acmA->SetCodecFEC(true)); + + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + EXPECT_EQ(0, _acmA->SetCodecFEC(true)); + + // Codec FEC and RED are mutually exclusive. + EXPECT_EQ(-1, _acmA->SetREDStatus(true)); + + EXPECT_TRUE(_acmA->CodecFEC()); + OpenOutFile(_testCntr); + Run(); + + // Switch to ISAC with RED. + RegisterSendCodec('A', nameISAC, 32000); + EXPECT_EQ(0, SetVAD(false, false, VADNormal)); + + // ISAC does not support FEC, so FEC should be turned off automatically. + EXPECT_FALSE(_acmA->CodecFEC()); + + EXPECT_EQ(0, _acmA->SetREDStatus(true)); + EXPECT_TRUE(_acmA->REDStatus()); + Run(); + + // Switch to Opus again. + RegisterSendCodec('A', nameOpus, 48000); + EXPECT_EQ(0, _acmA->SetCodecFEC(false)); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); + Run(); + + EXPECT_EQ(0, _acmA->SetCodecFEC(true)); + _outFileB.Close(); + + // Codecs does not support internal FEC. + RegisterSendCodec('A', nameG722, 16000); + EXPECT_FALSE(_acmA->REDStatus()); + EXPECT_EQ(-1, _acmA->SetCodecFEC(true)); + EXPECT_FALSE(_acmA->CodecFEC()); + + RegisterSendCodec('A', nameISAC, 16000); + EXPECT_FALSE(_acmA->REDStatus()); + EXPECT_EQ(-1, _acmA->SetCodecFEC(true)); + EXPECT_FALSE(_acmA->CodecFEC()); } -int32_t TestFEC::SetVAD(bool enableDTX, bool enableVAD, ACMVADMode vadMode) { +int32_t TestRedFec::SetVAD(bool enableDTX, bool enableVAD, ACMVADMode vadMode) { return _acmA->SetVAD(enableDTX, enableVAD, vadMode); } -int16_t TestFEC::RegisterSendCodec(char side, char* codecName, - int32_t samplingFreqHz) { +int16_t TestRedFec::RegisterSendCodec(char side, char* codecName, + int32_t samplingFreqHz) { std::cout << std::flush; AudioCodingModule* myACM; switch (side) { @@ -230,7 +291,7 @@ int16_t TestFEC::RegisterSendCodec(char side, char* codecName, return 0; } -void TestFEC::Run() { +void TestRedFec::Run() { AudioFrame audioFrame; uint16_t msecPassed = 0; @@ -248,22 +309,22 @@ void TestFEC::Run() { msecPassed = 0; secPassed++; } - // Test that toggling FEC on and off works. + // Test that toggling RED on and off works. if (((secPassed % 5) == 4) && (msecPassed == 0) && (_testCntr > 14)) { - EXPECT_EQ(0, _acmA->SetFECStatus(false)); + EXPECT_EQ(0, _acmA->SetREDStatus(false)); } if (((secPassed % 5) == 4) && (msecPassed >= 990) && (_testCntr > 14)) { - EXPECT_EQ(0, _acmA->SetFECStatus(true)); + EXPECT_EQ(0, _acmA->SetREDStatus(true)); } } _inFileA.Rewind(); } -void TestFEC::OpenOutFile(int16_t test_number) { +void TestRedFec::OpenOutFile(int16_t test_number) { std::string file_name; std::stringstream file_stream; file_stream << webrtc::test::OutputPath(); - file_stream << "TestFEC_outFile_"; + file_stream << "TestRedFec_outFile_"; file_stream << test_number << ".pcm"; file_name = file_stream.str(); _outFileB.Open(file_name, 16000, "wb"); diff --git a/webrtc/modules/audio_coding/main/test/TestFEC.h b/webrtc/modules/audio_coding/main/test/TestRedFec.h similarity index 83% rename from webrtc/modules/audio_coding/main/test/TestFEC.h rename to webrtc/modules/audio_coding/main/test/TestRedFec.h index af3cdd7dc..30ced1e04 100644 --- a/webrtc/modules/audio_coding/main/test/TestFEC.h +++ b/webrtc/modules/audio_coding/main/test/TestRedFec.h @@ -8,9 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_MAIN_TESTFEC_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_TESTFEC_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_MAIN_TESTREDFEC_H_ +#define WEBRTC_MODULES_AUDIO_CODING_MAIN_TESTREDFEC_H_ +#include #include "webrtc/modules/audio_coding/main/test/ACMTest.h" #include "webrtc/modules/audio_coding/main/test/Channel.h" #include "webrtc/modules/audio_coding/main/test/PCMFile.h" @@ -20,10 +21,10 @@ namespace webrtc { class Config; -class TestFEC : public ACMTest { +class TestRedFec : public ACMTest { public: - explicit TestFEC(const Config& config); - ~TestFEC(); + explicit TestRedFec(); + ~TestRedFec(); void Perform(); private: @@ -47,4 +48,4 @@ class TestFEC : public ACMTest { } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_TESTFEC_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_TESTREDFEC_H_ diff --git a/webrtc/modules/audio_coding/main/test/TestStereo.cc b/webrtc/modules/audio_coding/main/test/TestStereo.cc index b26334c32..00c3631f4 100644 --- a/webrtc/modules/audio_coding/main/test/TestStereo.cc +++ b/webrtc/modules/audio_coding/main/test/TestStereo.cc @@ -15,7 +15,6 @@ #include #include "gtest/gtest.h" -#include "webrtc/common.h" #include "webrtc/common_types.h" #include "webrtc/engine_configurations.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" @@ -108,9 +107,9 @@ void TestPackStereo::set_lost_packet(bool lost) { lost_packet_ = lost; } -TestStereo::TestStereo(int test_mode, const Config& config) - : acm_a_(config.Get().Create(0)), - acm_b_(config.Get().Create(1)), +TestStereo::TestStereo(int test_mode) + : acm_a_(AudioCodingModule::Create(0)), + acm_b_(AudioCodingModule::Create(1)), channel_a2b_(NULL), test_cntr_(0), pack_size_samp_(0), @@ -808,6 +807,8 @@ void TestStereo::Run(TestPackStereo* channel, int in_channels, int out_channels, uint32_t time_stamp_diff; channel->reset_payload_size(); int error_count = 0; + int variable_bytes = 0; + int variable_packets = 0; while (1) { // Simulate packet loss by setting |packet_loss_| to "true" in @@ -839,11 +840,16 @@ void TestStereo::Run(TestPackStereo* channel, int in_channels, int out_channels, // Run sender side of ACM EXPECT_GT(acm_a_->Process(), -1); - // Verify that the received packet size matches the settings + // Verify that the received packet size matches the settings. rec_size = channel->payload_size(); if ((0 < rec_size) & (rec_size < 65535)) { - // Opus is variable rate, skip this test. - if (strcmp(send_codec_name_, "opus")) { + if (strcmp(send_codec_name_, "opus") == 0) { + // Opus is a variable rate codec, hence calculate the average packet + // size, and later make sure the average is in the right range. + variable_bytes += rec_size; + variable_packets++; + } else { + // For fixed rate codecs, check that packet size is correct. if ((rec_size != pack_size_bytes_ * out_channels) && (pack_size_bytes_ < 65535)) { error_count++; @@ -867,6 +873,13 @@ void TestStereo::Run(TestPackStereo* channel, int in_channels, int out_channels, EXPECT_EQ(0, error_count); + // Check that packet size is in the right range for variable rate codecs, + // such as Opus. + if (variable_packets > 0) { + variable_bytes /= variable_packets; + EXPECT_NEAR(variable_bytes, pack_size_bytes_, 3); + } + if (in_file_mono_->EndOfFile()) { in_file_mono_->Rewind(); } diff --git a/webrtc/modules/audio_coding/main/test/TestStereo.h b/webrtc/modules/audio_coding/main/test/TestStereo.h index 88320a0e5..03f80411b 100644 --- a/webrtc/modules/audio_coding/main/test/TestStereo.h +++ b/webrtc/modules/audio_coding/main/test/TestStereo.h @@ -20,8 +20,6 @@ namespace webrtc { -class Config; - enum StereoMonoMode { kNotSet, kMono, @@ -62,7 +60,7 @@ class TestPackStereo : public AudioPacketizationCallback { class TestStereo : public ACMTest { public: - TestStereo(int test_mode, const Config& config); + explicit TestStereo(int test_mode); ~TestStereo(); void Perform(); diff --git a/webrtc/modules/audio_coding/main/test/TestVADDTX.cc b/webrtc/modules/audio_coding/main/test/TestVADDTX.cc index 22e9696ff..d31e1d47a 100644 --- a/webrtc/modules/audio_coding/main/test/TestVADDTX.cc +++ b/webrtc/modules/audio_coding/main/test/TestVADDTX.cc @@ -12,7 +12,6 @@ #include -#include "webrtc/common.h" #include "webrtc/common_types.h" #include "webrtc/engine_configurations.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" @@ -23,11 +22,10 @@ namespace webrtc { -TestVADDTX::TestVADDTX(const Config& config) - : _acmA(config.Get().Create(0)), - _acmB(config.Get().Create(1)), - _channelA2B(NULL) { -} +TestVADDTX::TestVADDTX() + : _acmA(AudioCodingModule::Create(0)), + _acmB(AudioCodingModule::Create(1)), + _channelA2B(NULL) {} TestVADDTX::~TestVADDTX() { if (_channelA2B != NULL) { diff --git a/webrtc/modules/audio_coding/main/test/TestVADDTX.h b/webrtc/modules/audio_coding/main/test/TestVADDTX.h index e0aa6b813..f8c97e127 100644 --- a/webrtc/modules/audio_coding/main/test/TestVADDTX.h +++ b/webrtc/modules/audio_coding/main/test/TestVADDTX.h @@ -18,8 +18,6 @@ namespace webrtc { -class Config; - typedef struct { bool statusDTX; bool statusVAD; @@ -49,7 +47,7 @@ class ActivityMonitor : public ACMVADCallback { class TestVADDTX : public ACMTest { public: - explicit TestVADDTX(const Config& config); + TestVADDTX(); ~TestVADDTX(); void Perform(); diff --git a/webrtc/modules/audio_coding/main/test/Tester.cc b/webrtc/modules/audio_coding/main/test/Tester.cc index 581b7bd31..b6a554b9f 100644 --- a/webrtc/modules/audio_coding/main/test/Tester.cc +++ b/webrtc/modules/audio_coding/main/test/Tester.cc @@ -13,18 +13,17 @@ #include #include "testing/gtest/include/gtest/gtest.h" -#include "webrtc/common.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #include "webrtc/modules/audio_coding/main/test/APITest.h" #include "webrtc/modules/audio_coding/main/test/EncodeDecodeTest.h" #include "webrtc/modules/audio_coding/main/test/iSACTest.h" #include "webrtc/modules/audio_coding/main/test/opus_test.h" +#include "webrtc/modules/audio_coding/main/test/PacketLossTest.h" #include "webrtc/modules/audio_coding/main/test/TestAllCodecs.h" -#include "webrtc/modules/audio_coding/main/test/TestFEC.h" +#include "webrtc/modules/audio_coding/main/test/TestRedFec.h" #include "webrtc/modules/audio_coding/main/test/TestStereo.h" #include "webrtc/modules/audio_coding/main/test/TestVADDTX.h" #include "webrtc/modules/audio_coding/main/test/TwoWayCommunication.h" -#include "webrtc/modules/audio_coding/main/test/utility.h" #include "webrtc/system_wrappers/interface/trace.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/gtest_disable.h" @@ -39,21 +38,7 @@ TEST(AudioCodingModuleTest, TestAllCodecs) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + "acm_allcodecs_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::TestAllCodecs(ACM_TEST_MODE, config).Perform(); - Trace::ReturnTrace(); -} - -TEST(AudioCodingModuleTest, TestAllCodecsNewACM) { - Trace::CreateTrace(); - Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_allcodecs_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::TestAllCodecs(ACM_TEST_MODE, config).Perform(); + webrtc::TestAllCodecs(ACM_TEST_MODE).Perform(); Trace::ReturnTrace(); } @@ -61,43 +46,15 @@ TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestEncodeDecode)) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + "acm_encodedecode_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::EncodeDecodeTest(ACM_TEST_MODE, config).Perform(); - Trace::ReturnTrace(); -} - -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestEncodeDecodeNewACM)) { - Trace::CreateTrace(); - Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_encodedecode_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::EncodeDecodeTest(ACM_TEST_MODE, config).Perform(); + webrtc::EncodeDecodeTest(ACM_TEST_MODE).Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestFEC)) { +TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestRedFec)) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + "acm_fec_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::TestFEC(config).Perform(); - Trace::ReturnTrace(); -} - -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestFECNewACM)) { - Trace::CreateTrace(); - Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_fec_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::TestFEC(config).Perform(); + webrtc::TestRedFec().Perform(); Trace::ReturnTrace(); } @@ -105,21 +62,7 @@ TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestIsac)) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + "acm_isac_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::ISACTest(ACM_TEST_MODE, config).Perform(); - Trace::ReturnTrace(); -} - -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestIsacNewACM)) { - Trace::CreateTrace(); - Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_isac_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::ISACTest(ACM_TEST_MODE, config).Perform(); + webrtc::ISACTest(ACM_TEST_MODE).Perform(); Trace::ReturnTrace(); } @@ -127,87 +70,63 @@ TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TwoWayCommunication)) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + "acm_twowaycom_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::TwoWayCommunication(ACM_TEST_MODE, config).Perform(); + webrtc::TwoWayCommunication(ACM_TEST_MODE).Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TwoWayCommunicationNewACM)) { +TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestStereo)) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_twowaycom_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::TwoWayCommunication(ACM_TEST_MODE, config).Perform(); + "acm_stereo_trace.txt").c_str()); + webrtc::TestStereo(ACM_TEST_MODE).Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestStereo)) { +TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestVADDTX)) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_stereo_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::TestStereo(ACM_TEST_MODE, config).Perform(); + "acm_vaddtx_trace.txt").c_str()); + webrtc::TestVADDTX().Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestStereoNewACM)) { +TEST(AudioCodingModuleTest, TestOpus) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_stereo_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::TestStereo(ACM_TEST_MODE, config).Perform(); + "acm_opus_trace.txt").c_str()); + webrtc::OpusTest().Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestVADDTX)) { +TEST(AudioCodingModuleTest, TestPacketLoss) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_vaddtx_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::TestVADDTX(config).Perform(); + "acm_packetloss_trace.txt").c_str()); + webrtc::PacketLossTest(1, 10, 10, 1).Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestVADDTXNewACM)) { +TEST(AudioCodingModuleTest, TestPacketLossBurst) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_vaddtx_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::TestVADDTX(config).Perform(); + "acm_packetloss_burst_trace.txt").c_str()); + webrtc::PacketLossTest(1, 10, 10, 2).Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, TestOpus) { +TEST(AudioCodingModuleTest, TestPacketLossStereo) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_opus_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::OpusTest(config).Perform(); + "acm_packetloss_trace.txt").c_str()); + webrtc::PacketLossTest(2, 10, 10, 1).Perform(); Trace::ReturnTrace(); } -TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestOpusNewACM)) { +TEST(AudioCodingModuleTest, TestPacketLossStereoBurst) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + - "acm_opus_trace_new.txt").c_str()); - webrtc::Config config; - - UseNewAcm(&config); - webrtc::OpusTest(config).Perform(); + "acm_packetloss_burst_trace.txt").c_str()); + webrtc::PacketLossTest(2, 10, 10, 2).Perform(); Trace::ReturnTrace(); } @@ -218,14 +137,7 @@ TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestOpusNewACM)) { Trace::CreateTrace(); Trace::SetTraceFile((webrtc::test::OutputPath() + "acm_apitest_trace.txt").c_str()); - webrtc::Config config; - - UseLegacyAcm(&config); - webrtc::APITest(config).Perform(); - - UseNewAcm(&config); - webrtc::APITest(config).Perform(); - + webrtc::APITest().Perform(); Trace::ReturnTrace(); } #endif diff --git a/webrtc/modules/audio_coding/main/test/TwoWayCommunication.cc b/webrtc/modules/audio_coding/main/test/TwoWayCommunication.cc index fb3d6f487..81ef0c3ff 100644 --- a/webrtc/modules/audio_coding/main/test/TwoWayCommunication.cc +++ b/webrtc/modules/audio_coding/main/test/TwoWayCommunication.cc @@ -20,7 +20,6 @@ #include "gtest/gtest.h" #include "webrtc/engine_configurations.h" -#include "webrtc/common.h" #include "webrtc/common_types.h" #include "webrtc/modules/audio_coding/main/test/PCMFile.h" #include "webrtc/modules/audio_coding/main/test/utility.h" @@ -31,12 +30,12 @@ namespace webrtc { #define MAX_FILE_NAME_LENGTH_BYTE 500 -TwoWayCommunication::TwoWayCommunication(int testMode, const Config& config) - : _acmA(config.Get().Create(1)), - _acmB(config.Get().Create(2)), - _acmRefA(config.Get().Create(3)), - _acmRefB(config.Get().Create(4)), - _testMode(testMode) { } +TwoWayCommunication::TwoWayCommunication(int testMode) + : _acmA(AudioCodingModule::Create(1)), + _acmB(AudioCodingModule::Create(2)), + _acmRefA(AudioCodingModule::Create(3)), + _acmRefB(AudioCodingModule::Create(4)), + _testMode(testMode) {} TwoWayCommunication::~TwoWayCommunication() { delete _channel_A2B; diff --git a/webrtc/modules/audio_coding/main/test/TwoWayCommunication.h b/webrtc/modules/audio_coding/main/test/TwoWayCommunication.h index 0d1e514da..9e0b72498 100644 --- a/webrtc/modules/audio_coding/main/test/TwoWayCommunication.h +++ b/webrtc/modules/audio_coding/main/test/TwoWayCommunication.h @@ -20,11 +20,9 @@ namespace webrtc { -class Config; - class TwoWayCommunication : public ACMTest { public: - TwoWayCommunication(int testMode, const Config& config); + explicit TwoWayCommunication(int testMode); ~TwoWayCommunication(); void Perform(); diff --git a/webrtc/modules/audio_coding/main/test/delay_test.cc b/webrtc/modules/audio_coding/main/test/delay_test.cc index 63bfe2be4..2e7547212 100644 --- a/webrtc/modules/audio_coding/main/test/delay_test.cc +++ b/webrtc/modules/audio_coding/main/test/delay_test.cc @@ -35,7 +35,6 @@ DEFINE_string(input_file, "", "Input file, PCM16 32 kHz, optional."); DEFINE_int32(delay, 0, "Delay in millisecond."); DEFINE_int32(init_delay, 0, "Initial delay in millisecond."); DEFINE_bool(dtx, false, "Enable DTX at the sender side."); -DEFINE_bool(acm2, false, "Run the test with ACM2."); DEFINE_bool(packet_loss, false, "Apply packet loss, c.f. Channel{.cc, .h}."); DEFINE_bool(fec, false, "Use Forward Error Correction (FEC)."); @@ -64,9 +63,9 @@ struct TestSettings { class DelayTest { public: - explicit DelayTest(const Config& config) - : acm_a_(config.Get().Create(0)), - acm_b_(config.Get().Create(1)), + DelayTest() + : acm_a_(AudioCodingModule::Create(0)), + acm_b_(AudioCodingModule::Create(1)), channel_a2b_(new Channel), test_cntr_(0), encoding_sample_rate_hz_(8000) {} @@ -162,8 +161,8 @@ class DelayTest { void ConfigAcm(const AcmSettings& config) { ASSERT_EQ(0, acm_a_->SetVAD(config.dtx, config.dtx, VADAggr)) << "Failed to set VAD.\n"; - ASSERT_EQ(0, acm_a_->SetFECStatus(config.fec)) << - "Failed to set FEC.\n"; + ASSERT_EQ(0, acm_a_->SetREDStatus(config.fec)) << + "Failed to set RED.\n"; } void ConfigChannel(bool packet_loss) { @@ -245,7 +244,6 @@ class DelayTest { int main(int argc, char* argv[]) { google::ParseCommandLineFlags(&argc, &argv, true); - webrtc::Config config; webrtc::TestSettings test_setting; strcpy(test_setting.codec.name, FLAGS_codec.c_str()); @@ -266,13 +264,7 @@ int main(int argc, char* argv[]) { test_setting.acm.fec = FLAGS_fec; test_setting.packet_loss = FLAGS_packet_loss; - if (FLAGS_acm2) { - webrtc::UseNewAcm(&config); - } else { - webrtc::UseLegacyAcm(&config); - } - - webrtc::DelayTest delay_test(config); + webrtc::DelayTest delay_test; delay_test.Initialize(); delay_test.Perform(&test_setting, 1, 240, "delay_test"); return 0; diff --git a/webrtc/modules/audio_coding/main/test/dual_stream_unittest.cc b/webrtc/modules/audio_coding/main/test/dual_stream_unittest.cc index ba9bb6cb3..71657c9f4 100644 --- a/webrtc/modules/audio_coding/main/test/dual_stream_unittest.cc +++ b/webrtc/modules/audio_coding/main/test/dual_stream_unittest.cc @@ -9,7 +9,6 @@ */ #include "gtest/gtest.h" -#include "webrtc/common.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #include "webrtc/modules/audio_coding/main/test/PCMFile.h" @@ -22,9 +21,10 @@ namespace webrtc { -class DualStreamTest : public AudioPacketizationCallback { - public: - explicit DualStreamTest(const Config& config); +class DualStreamTest : public AudioPacketizationCallback, + public ::testing::Test { + protected: + DualStreamTest(); ~DualStreamTest(); void RunTest(int frame_size_primary_samples, @@ -35,8 +35,6 @@ class DualStreamTest : public AudioPacketizationCallback { void ApiTest(); - protected: - int32_t SendData(FrameType frameType, uint8_t payload_type, uint32_t timestamp, const uint8_t* payload_data, uint16_t payload_size, @@ -93,10 +91,10 @@ class DualStreamTest : public AudioPacketizationCallback { bool received_payload_[kMaxNumStreams]; }; -DualStreamTest::DualStreamTest(const Config& config) - : acm_dual_stream_(config.Get().Create(0)), - acm_ref_primary_(config.Get().Create(1)), - acm_ref_secondary_(config.Get().Create(2)), +DualStreamTest::DualStreamTest() + : acm_dual_stream_(AudioCodingModule::Create(0)), + acm_ref_primary_(AudioCodingModule::Create(1)), + acm_ref_secondary_(AudioCodingModule::Create(2)), payload_ref_is_stored_(), payload_dual_is_stored_(), timestamp_ref_(), @@ -388,17 +386,106 @@ int32_t DualStreamTest::SendData(FrameType frameType, uint8_t payload_type, return 0; } -void DualStreamTest::RunTest(int frame_size_primary_samples, - int num_channels_primary, - int sampling_rate, - bool start_in_sync, - int num_channels_input) { - InitializeSender( - frame_size_primary_samples, num_channels_primary, sampling_rate); - Perform(start_in_sync, num_channels_input); -}; +// Mono input, mono primary WB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimaryWb20Ms)) { + InitializeSender(20, 1, 16000); + Perform(true, 1); +} + +// Mono input, stereo primary WB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncMonoInput_StereoPrimaryWb20Ms)) { + InitializeSender(20, 2, 16000); + Perform(true, 1); +} + +// Mono input, mono primary SWB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimarySwb20Ms)) { + InitializeSender(20, 1, 32000); + Perform(true, 1); +} + +// Mono input, stereo primary SWB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncMonoInputStereoPrimarySwb20Ms)) { + InitializeSender(20, 2, 32000); + Perform(true, 1); +} + +// Mono input, mono primary WB 40 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimaryWb40Ms)) { + InitializeSender(40, 1, 16000); + Perform(true, 1); +} + +// Mono input, stereo primary WB 40 ms frame +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncMonoInputStereoPrimaryWb40Ms)) { + InitializeSender(40, 2, 16000); + Perform(true, 1); +} + +// Stereo input, mono primary WB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimaryWb20Ms)) { + InitializeSender(20, 1, 16000); + Perform(true, 2); +} + +// Stereo input, stereo primary WB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimaryWb20Ms)) { + InitializeSender(20, 2, 16000); + Perform(true, 2); +} + +// Stereo input, mono primary SWB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimarySwb20Ms)) { + InitializeSender(20, 1, 32000); + Perform(true, 2); +} + +// Stereo input, stereo primary SWB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimarySwb20Ms)) { + InitializeSender(20, 2, 32000); + Perform(true, 2); +} + +// Stereo input, mono primary WB 40 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimaryWb40Ms)) { + InitializeSender(40, 1, 16000); + Perform(true, 2); +} + +// Stereo input, stereo primary WB 40 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimaryWb40Ms)) { + InitializeSender(40, 2, 16000); + Perform(true, 2); +} + +// Asynchronous test, ACM is fed with data then secondary coder is registered. +// Mono input, mono primary WB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactAsyncMonoInputMonoPrimaryWb20Ms)) { + InitializeSender(20, 1, 16000); + Perform(false, 1); +} + +// Mono input, mono primary WB 20 ms frame. +TEST_F(DualStreamTest, + DISABLED_ON_ANDROID(BitExactAsyncMonoInputMonoPrimaryWb40Ms)) { + InitializeSender(40, 1, 16000); + Perform(false, 1); +} -void DualStreamTest::ApiTest() { +TEST_F(DualStreamTest, DISABLED_ON_ANDROID(Api)) { PopulateCodecInstances(20, 1, 16000); CodecInst my_codec; ASSERT_EQ(0, acm_dual_stream_->InitializeSender()); @@ -449,171 +536,4 @@ void DualStreamTest::ApiTest() { EXPECT_EQ(VADVeryAggr, vad_mode); } -namespace { - -DualStreamTest* CreateLegacy() { - Config config; - UseLegacyAcm(&config); - DualStreamTest* test = new DualStreamTest(config); - return test; -} - -DualStreamTest* CreateNew() { - Config config; - UseNewAcm(&config); - DualStreamTest* test = new DualStreamTest(config); - return test; -} - -} // namespace - -// Mono input, mono primary WB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimaryWb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 1, 16000, true, 1); - - test.reset(CreateNew()); - test->RunTest(20, 1, 16000, true, 1); -} - -// Mono input, stereo primary WB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncMonoInput_StereoPrimaryWb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 2, 16000, true, 1); - - test.reset(CreateNew()); - test->RunTest(20, 2, 16000, true, 1); -} - -// Mono input, mono primary SWB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimarySwb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 1, 32000, true, 1); - - test.reset(CreateNew()); - test->RunTest(20, 1, 32000, true, 1); -} - -// Mono input, stereo primary SWB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncMonoInputStereoPrimarySwb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 2, 32000, true, 1); - - test.reset(CreateNew()); - test->RunTest(20, 2, 32000, true, 1); -} - -// Mono input, mono primary WB 40 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimaryWb40Ms)) { - scoped_ptr test(CreateNew()); - test->RunTest(40, 1, 16000, true, 1); - - test.reset(CreateNew()); - test->RunTest(40, 1, 16000, true, 1); -} - -// Mono input, stereo primary WB 40 ms frame -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncMonoInputStereoPrimaryWb40Ms)) { - scoped_ptr test(CreateNew()); - test->RunTest(40, 2, 16000, true, 1); - - test.reset(CreateNew()); - test->RunTest(40, 2, 16000, true, 1); -} - -// Stereo input, mono primary WB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimaryWb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 1, 16000, true, 2); - - test.reset(CreateNew()); - test->RunTest(20, 1, 16000, true, 2); -} - -// Stereo input, stereo primary WB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimaryWb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 2, 16000, true, 2); - - test.reset(CreateNew()); - test->RunTest(20, 2, 16000, true, 2); -} - -// Stereo input, mono primary SWB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimarySwb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 1, 32000, true, 2); - - test.reset(CreateNew()); - test->RunTest(20, 1, 32000, true, 2); -} - -// Stereo input, stereo primary SWB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimarySwb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 2, 32000, true, 2); - - test.reset(CreateNew()); - test->RunTest(20, 2, 32000, true, 2); -} - -// Stereo input, mono primary WB 40 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimaryWb40Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(40, 1, 16000, true, 2); - - test.reset(CreateNew()); - test->RunTest(40, 1, 16000, true, 2); -} - -// Stereo input, stereo primary WB 40 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimaryWb40Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(40, 2, 16000, true, 2); - - test.reset(CreateNew()); - test->RunTest(40, 2, 16000, true, 2); -} - -// Asynchronous test, ACM is fed with data then secondary coder is registered. -// Mono input, mono primary WB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactAsyncMonoInputMonoPrimaryWb20Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(20, 1, 16000, false, 1); - - test.reset(CreateNew()); - test->RunTest(20, 1, 16000, false, 1); -} - -// Mono input, mono primary WB 20 ms frame. -TEST(DualStreamTest, - DISABLED_ON_ANDROID(BitExactAsyncMonoInputMonoPrimaryWb40Ms)) { - scoped_ptr test(CreateLegacy()); - test->RunTest(40, 1, 16000, false, 1); - - test.reset(CreateNew()); - test->RunTest(40, 1, 16000, false, 1); -} - -TEST(DualStreamTest, DISABLED_ON_ANDROID(ApiTest)) { - scoped_ptr test(CreateLegacy()); - test->ApiTest(); - - test.reset(CreateNew()); - test->ApiTest(); -} - } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/test/iSACTest.cc b/webrtc/modules/audio_coding/main/test/iSACTest.cc index f7fef4a80..c5da92e1e 100644 --- a/webrtc/modules/audio_coding/main/test/iSACTest.cc +++ b/webrtc/modules/audio_coding/main/test/iSACTest.cc @@ -86,11 +86,10 @@ int16_t SetISAConfig(ACMTestISACConfig& isacConfig, AudioCodingModule* acm, return 0; } -ISACTest::ISACTest(int testMode, const Config& config) - : _acmA(config.Get().Create(1)), - _acmB(config.Get().Create(2)), - _testMode(testMode) { -} +ISACTest::ISACTest(int testMode) + : _acmA(AudioCodingModule::Create(1)), + _acmB(AudioCodingModule::Create(2)), + _testMode(testMode) {} ISACTest::~ISACTest() {} diff --git a/webrtc/modules/audio_coding/main/test/iSACTest.h b/webrtc/modules/audio_coding/main/test/iSACTest.h index d9563dbd9..9fe6afffa 100644 --- a/webrtc/modules/audio_coding/main/test/iSACTest.h +++ b/webrtc/modules/audio_coding/main/test/iSACTest.h @@ -13,7 +13,6 @@ #include -#include "webrtc/common.h" #include "webrtc/common_types.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #include "webrtc/modules/audio_coding/main/test/ACMTest.h" @@ -27,8 +26,6 @@ namespace webrtc { -class Config; - struct ACMTestISACConfig { int32_t currentRateBitPerSec; int16_t currentFrameSizeMsec; @@ -42,7 +39,7 @@ struct ACMTestISACConfig { class ISACTest : public ACMTest { public: - ISACTest(int testMode, const Config& config); + explicit ISACTest(int testMode); ~ISACTest(); void Perform(); diff --git a/webrtc/modules/audio_coding/main/test/initial_delay_unittest.cc b/webrtc/modules/audio_coding/main/test/initial_delay_unittest.cc index 87fed6ca8..192539d85 100644 --- a/webrtc/modules/audio_coding/main/test/initial_delay_unittest.cc +++ b/webrtc/modules/audio_coding/main/test/initial_delay_unittest.cc @@ -16,7 +16,6 @@ #include #include "gtest/gtest.h" -#include "webrtc/common.h" #include "webrtc/common_types.h" #include "webrtc/engine_configurations.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" @@ -44,11 +43,11 @@ double FrameRms(AudioFrame& frame) { } -class InitialPlayoutDelayTest { - public: - explicit InitialPlayoutDelayTest(const Config& config) - : acm_a_(config.Get().Create(0)), - acm_b_(config.Get().Create(1)), +class InitialPlayoutDelayTest : public ::testing::Test { + protected: + InitialPlayoutDelayTest() + : acm_a_(AudioCodingModule::Create(0)), + acm_b_(AudioCodingModule::Create(1)), channel_a2b_(NULL) {} ~InitialPlayoutDelayTest() { @@ -162,72 +161,16 @@ class InitialPlayoutDelayTest { Channel* channel_a2b_; }; -namespace { - -InitialPlayoutDelayTest* CreateLegacy() { - Config config; - UseLegacyAcm(&config); - InitialPlayoutDelayTest* test = new InitialPlayoutDelayTest(config); - test->SetUp(); - return test; -} - -InitialPlayoutDelayTest* CreateNew() { - Config config; - UseNewAcm(&config); - InitialPlayoutDelayTest* test = new InitialPlayoutDelayTest(config); - test->SetUp(); - return test; -} +TEST_F(InitialPlayoutDelayTest, NbMono) { NbMono(); } -} // namespace +TEST_F(InitialPlayoutDelayTest, WbMono) { WbMono(); } -TEST(InitialPlayoutDelayTest, NbMono) { - scoped_ptr test(CreateLegacy()); - test->NbMono(); +TEST_F(InitialPlayoutDelayTest, SwbMono) { SwbMono(); } - test.reset(CreateNew()); - test->NbMono(); -} - -TEST(InitialPlayoutDelayTest, WbMono) { - scoped_ptr test(CreateLegacy()); - test->WbMono(); +TEST_F(InitialPlayoutDelayTest, NbStereo) { NbStereo(); } - test.reset(CreateNew()); - test->WbMono(); -} +TEST_F(InitialPlayoutDelayTest, WbStereo) { WbStereo(); } -TEST(InitialPlayoutDelayTest, SwbMono) { - scoped_ptr test(CreateLegacy()); - test->SwbMono(); - - test.reset(CreateNew()); - test->SwbMono(); -} - -TEST(InitialPlayoutDelayTest, NbStereo) { - scoped_ptr test(CreateLegacy()); - test->NbStereo(); - - test.reset(CreateNew()); - test->NbStereo(); -} - -TEST(InitialPlayoutDelayTest, WbStereo) { - scoped_ptr test(CreateLegacy()); - test->WbStereo(); - - test.reset(CreateNew()); - test->WbStereo(); -} - -TEST(InitialPlayoutDelayTest, SwbStereo) { - scoped_ptr test(CreateLegacy()); - test->SwbStereo(); - - test.reset(CreateNew()); - test->SwbStereo(); -} +TEST_F(InitialPlayoutDelayTest, SwbStereo) { SwbStereo(); } } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/test/opus_test.cc b/webrtc/modules/audio_coding/main/test/opus_test.cc index 027aeb045..261eb6133 100644 --- a/webrtc/modules/audio_coding/main/test/opus_test.cc +++ b/webrtc/modules/audio_coding/main/test/opus_test.cc @@ -15,13 +15,12 @@ #include #include "testing/gtest/include/gtest/gtest.h" -#include "webrtc/common.h" // Config. #include "webrtc/common_types.h" #include "webrtc/engine_configurations.h" #include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" -#include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" -#include "webrtc/modules/audio_coding/main/source/acm_opus.h" +#include "webrtc/modules/audio_coding/main/acm2/acm_codec_database.h" +#include "webrtc/modules/audio_coding/main/acm2/acm_opus.h" #include "webrtc/modules/audio_coding/main/test/TestStereo.h" #include "webrtc/modules/audio_coding/main/test/utility.h" #include "webrtc/system_wrappers/interface/trace.h" @@ -29,13 +28,12 @@ namespace webrtc { -OpusTest::OpusTest(const Config& config) - : acm_receiver_(config.Get().Create(0)), +OpusTest::OpusTest() + : acm_receiver_(AudioCodingModule::Create(0)), channel_a2b_(NULL), counter_(0), payload_type_(255), - rtp_timestamp_(0) { -} + rtp_timestamp_(0) {} OpusTest::~OpusTest() { if (channel_a2b_ != NULL) { @@ -213,8 +211,9 @@ void OpusTest::Run(TestPackStereo* channel, int channels, int bitrate, int frame_length, int percent_loss) { AudioFrame audio_frame; int32_t out_freq_hz_b = out_file_.SamplingFrequency(); - int16_t audio[480 * 12 * 2]; // Can hold 120 ms stereo audio. - int16_t out_audio[480 * 12 * 2]; // Can hold 120 ms stereo audio. + const int kBufferSizeSamples = 480 * 12 * 2; // Can hold 120 ms stereo audio. + int16_t audio[kBufferSizeSamples]; + int16_t out_audio[kBufferSizeSamples]; int16_t audio_type; int written_samples = 0; int read_samples = 0; @@ -254,11 +253,13 @@ void OpusTest::Run(TestPackStereo* channel, int channels, int bitrate, } // If input audio is sampled at 32 kHz, resampling to 48 kHz is required. - EXPECT_EQ(480, resampler_.Resample10Msec(audio_frame.data_, - audio_frame.sample_rate_hz_, - &audio[written_samples], - 48000, - channels)); + EXPECT_EQ(480, + resampler_.Resample10Msec(audio_frame.data_, + audio_frame.sample_rate_hz_, + 48000, + channels, + kBufferSizeSamples - written_samples, + &audio[written_samples])); written_samples += 480 * channels; // Sometimes we need to loop over the audio vector to produce the right diff --git a/webrtc/modules/audio_coding/main/test/opus_test.h b/webrtc/modules/audio_coding/main/test/opus_test.h index 08dce98a1..9ee2b9372 100644 --- a/webrtc/modules/audio_coding/main/test/opus_test.h +++ b/webrtc/modules/audio_coding/main/test/opus_test.h @@ -13,8 +13,8 @@ #include -#include "webrtc/modules/audio_coding/main/source/acm_opus.h" -#include "webrtc/modules/audio_coding/main/source/acm_resampler.h" +#include "webrtc/modules/audio_coding/main/acm2/acm_opus.h" +#include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" #include "webrtc/modules/audio_coding/main/test/ACMTest.h" #include "webrtc/modules/audio_coding/main/test/Channel.h" #include "webrtc/modules/audio_coding/main/test/PCMFile.h" @@ -23,11 +23,9 @@ namespace webrtc { -class Config; - class OpusTest : public ACMTest { public: - explicit OpusTest(const Config& config); + OpusTest(); ~OpusTest(); void Perform(); @@ -47,7 +45,7 @@ class OpusTest : public ACMTest { int counter_; uint8_t payload_type_; int rtp_timestamp_; - acm1::ACMResampler resampler_; + acm2::ACMResampler resampler_; WebRtcOpusEncInst* opus_mono_encoder_; WebRtcOpusEncInst* opus_stereo_encoder_; WebRtcOpusDecInst* opus_mono_decoder_; diff --git a/webrtc/modules/audio_coding/main/test/target_delay_unittest.cc b/webrtc/modules/audio_coding/main/test/target_delay_unittest.cc index f01e6ffba..5636bdf80 100644 --- a/webrtc/modules/audio_coding/main/test/target_delay_unittest.cc +++ b/webrtc/modules/audio_coding/main/test/target_delay_unittest.cc @@ -9,7 +9,6 @@ */ #include "gtest/gtest.h" -#include "webrtc/common.h" #include "webrtc/common_types.h" #include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" @@ -22,11 +21,9 @@ namespace webrtc { - -class TargetDelayTest { - public: - explicit TargetDelayTest(const Config& config) - : acm_(config.Get().Create(0)) {} +class TargetDelayTest : public ::testing::Test { + protected: + TargetDelayTest() : acm_(AudioCodingModule::Create(0)) {} ~TargetDelayTest() {} @@ -202,65 +199,24 @@ class TargetDelayTest { uint8_t payload_[kPayloadLenBytes]; }; - -namespace { - -TargetDelayTest* CreateLegacy() { - Config config; - UseLegacyAcm(&config); - TargetDelayTest* test = new TargetDelayTest(config); - test->SetUp(); - return test; +TEST_F(TargetDelayTest, DISABLED_ON_ANDROID(OutOfRangeInput)) { + OutOfRangeInput(); } -TargetDelayTest* CreateNew() { - Config config; - UseNewAcm(&config); - TargetDelayTest* test = new TargetDelayTest(config); - test->SetUp(); - return test; +TEST_F(TargetDelayTest, DISABLED_ON_ANDROID(NoTargetDelayBufferSizeChanges)) { + NoTargetDelayBufferSizeChanges(); } -} // namespace - -TEST(TargetDelayTest, DISABLED_ON_ANDROID(OutOfRangeInput)) { - scoped_ptr test(CreateLegacy()); - test->OutOfRangeInput(); - - test.reset(CreateNew()); - test->OutOfRangeInput(); +TEST_F(TargetDelayTest, DISABLED_ON_ANDROID(WithTargetDelayBufferNotChanging)) { + WithTargetDelayBufferNotChanging(); } -TEST(TargetDelayTest, DISABLED_ON_ANDROID(NoTargetDelayBufferSizeChanges)) { - scoped_ptr test(CreateLegacy()); - test->NoTargetDelayBufferSizeChanges(); - - test.reset(CreateNew()); - test->NoTargetDelayBufferSizeChanges(); +TEST_F(TargetDelayTest, DISABLED_ON_ANDROID(RequiredDelayAtCorrectRange)) { + RequiredDelayAtCorrectRange(); } -TEST(TargetDelayTest, DISABLED_ON_ANDROID(WithTargetDelayBufferNotChanging)) { - scoped_ptr test(CreateLegacy()); - test->WithTargetDelayBufferNotChanging(); - - test.reset(CreateNew()); - test->WithTargetDelayBufferNotChanging(); -} - -TEST(TargetDelayTest, DISABLED_ON_ANDROID(RequiredDelayAtCorrectRange)) { - scoped_ptr test(CreateLegacy()); - test->RequiredDelayAtCorrectRange(); - - test.reset(CreateNew()); - test->RequiredDelayAtCorrectRange(); -} - -TEST(TargetDelayTest, DISABLED_ON_ANDROID(TargetDelayBufferMinMax)) { - scoped_ptr test(CreateLegacy()); - test->TargetDelayBufferMinMax(); - - test.reset(CreateNew()); - test->TargetDelayBufferMinMax(); +TEST_F(TargetDelayTest, DISABLED_ON_ANDROID(TargetDelayBufferMinMax)) { + TargetDelayBufferMinMax(); } } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/test/utility.cc b/webrtc/modules/audio_coding/main/test/utility.cc index d6441ac6b..084895446 100644 --- a/webrtc/modules/audio_coding/main/test/utility.cc +++ b/webrtc/modules/audio_coding/main/test/utility.cc @@ -330,14 +330,4 @@ int32_t VADCallback::InFrameType(int16_t frameType) { return 0; } -void UseLegacyAcm(webrtc::Config* config) { - config->Set( - new webrtc::AudioCodingModuleFactory()); -} - -void UseNewAcm(webrtc::Config* config) { - config->Set( - new webrtc::NewAudioCodingModuleFactory()); -} - } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/Android.mk b/webrtc/modules/audio_coding/neteq/Android.mk deleted file mode 100644 index 84267becf..000000000 --- a/webrtc/modules/audio_coding/neteq/Android.mk +++ /dev/null @@ -1,73 +0,0 @@ -# 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. - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -include $(LOCAL_PATH)/../../../../android-webrtc.mk - -LOCAL_ARM_MODE := arm -LOCAL_MODULE_CLASS := STATIC_LIBRARIES -LOCAL_MODULE := libwebrtc_neteq -LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := \ - accelerate.c \ - automode.c \ - bgn_update.c \ - bufstats_decision.c \ - cng_internal.c \ - codec_db.c \ - correlator.c \ - dsp.c \ - dsp_helpfunctions.c \ - dtmf_buffer.c \ - dtmf_tonegen.c \ - expand.c \ - mcu_address_init.c \ - mcu_dsp_common.c \ - mcu_reset.c \ - merge.c \ - min_distortion.c \ - mix_voice_unvoice.c \ - mute_signal.c \ - normal.c \ - packet_buffer.c \ - peak_detection.c \ - preemptive_expand.c \ - random_vector.c \ - recin.c \ - recout.c \ - rtcp.c \ - rtp.c \ - set_fs.c \ - signal_mcu.c \ - split_and_insert.c \ - unmute_signal.c \ - webrtc_neteq.c - -# Flags passed to both C and C++ files. -LOCAL_CFLAGS := \ - $(MY_WEBRTC_COMMON_DEFS) \ - '-DNETEQ_VOICEENGINE_CODECS' - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/interface \ - $(LOCAL_PATH)/../codecs/cng/include \ - $(LOCAL_PATH)/../../.. \ - $(LOCAL_PATH)/../../../common_audio/signal_processing/include - -LOCAL_SHARED_LIBRARIES := \ - libcutils \ - libdl \ - libstlport - -ifndef NDK_ROOT -include external/stlport/libstlport.mk -endif -include $(BUILD_STATIC_LIBRARY) diff --git a/webrtc/modules/audio_coding/neteq/OWNERS b/webrtc/modules/audio_coding/neteq/OWNERS index b5c79cef4..072e75499 100644 --- a/webrtc/modules/audio_coding/neteq/OWNERS +++ b/webrtc/modules/audio_coding/neteq/OWNERS @@ -1,3 +1,11 @@ henrik.lundin@webrtc.org tina.legrand@webrtc.org turaj@webrtc.org +minyue@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_coding/neteq/accelerate.c b/webrtc/modules/audio_coding/neteq/accelerate.c deleted file mode 100644 index a345a8fdc..000000000 --- a/webrtc/modules/audio_coding/neteq/accelerate.c +++ /dev/null @@ -1,493 +0,0 @@ -/* - * 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 file contains the Accelerate algorithm that is used to reduce - * the delay by removing a part of the audio stream. - */ - -#include "dsp.h" - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" -#include "neteq_error_codes.h" - -#define ACCELERATE_CORR_LEN 50 -#define ACCELERATE_MIN_LAG 10 -#define ACCELERATE_MAX_LAG 60 -#define ACCELERATE_DOWNSAMPLED_LEN (ACCELERATE_CORR_LEN + ACCELERATE_MAX_LAG) - -/* Scratch usage: - - Type Name size startpos endpos - int16_t pw16_downSampSpeech 110 0 109 - int32_t pw32_corr 2*50 110 209 - int16_t pw16_corr 50 0 49 - - Total: 110+2*50 - */ - -#define SCRATCH_PW16_DS_SPEECH 0 -#define SCRATCH_PW32_CORR ACCELERATE_DOWNSAMPLED_LEN -#define SCRATCH_PW16_CORR 0 - -/**************************************************************************** - * WebRtcNetEQ_Accelerate(...) - * - * This function tries to shorten the audio data by removing one or several - * pitch periods. The operation is only carried out if the correlation is - * strong or if the signal energy is very low. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector. - * - decoded : Pointer to newly decoded speech. - * - len : Length of decoded speech. - * - BGNonly : If non-zero, Accelerate will only remove the last - * DEFAULT_TIME_ADJUST seconds of the input. - * No signal matching is done. - * - * Output: - * - inst : Updated instance - * - outData : Pointer to a memory space where the output data - * should be stored - * - pw16_len : Number of samples written to outData. - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_Accelerate(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - const int16_t *pw16_decoded, int len, - int16_t *pw16_outData, int16_t *pw16_len, - int16_t BGNonly) -{ - -#ifdef SCRATCH - /* Use scratch memory for internal temporary vectors */ - int16_t *pw16_downSampSpeech = pw16_scratchPtr + SCRATCH_PW16_DS_SPEECH; - int32_t *pw32_corr = (int32_t*) (pw16_scratchPtr + SCRATCH_PW32_CORR); - int16_t *pw16_corr = pw16_scratchPtr + SCRATCH_PW16_CORR; -#else - /* Allocate memory for temporary vectors */ - int16_t pw16_downSampSpeech[ACCELERATE_DOWNSAMPLED_LEN]; - int32_t pw32_corr[ACCELERATE_CORR_LEN]; - int16_t pw16_corr[ACCELERATE_CORR_LEN]; -#endif - int16_t w16_decodedMax = 0; - int16_t w16_tmp; - int16_t w16_tmp2; - int32_t w32_tmp; - int32_t w32_tmp2; - - const int16_t w16_startLag = ACCELERATE_MIN_LAG; - const int16_t w16_endLag = ACCELERATE_MAX_LAG; - const int16_t w16_corrLen = ACCELERATE_CORR_LEN; - const int16_t *pw16_vec1, *pw16_vec2; - int16_t *pw16_vectmp; - int16_t w16_inc, w16_startfact; - int16_t w16_bestIndex, w16_bestVal; - int16_t w16_VAD = 1; - int16_t fsMult; - int16_t fsMult120; - int32_t w32_en1, w32_en2, w32_cc; - int16_t w16_en1, w16_en2; - int16_t w16_en1Scale, w16_en2Scale; - int16_t w16_sqrtEn1En2; - int16_t w16_bestCorr = 0; - int ok; - -#ifdef NETEQ_STEREO - MasterSlaveInfo *msInfo = inst->msInfo; -#endif - - fsMult = WebRtcNetEQ_CalcFsMult(inst->fs); /* Calculate fs/8000 */ - - /* Pre-calculate common multiplication with fsMult */ - fsMult120 = (int16_t) WEBRTC_SPL_MUL_16_16(fsMult, 120); /* 15 ms */ - - inst->ExpandInst.w16_consecExp = 0; /* Last was not expand any more */ - - /* Sanity check for len variable; must be (almost) 30 ms - (120*fsMult + max(bestIndex)) */ - if (len < (int16_t) WEBRTC_SPL_MUL_16_16((120 + 119), fsMult)) - { - /* Length of decoded data too short */ - inst->w16_mode = MODE_UNSUCCESS_ACCELERATE; - *pw16_len = len; - - /* simply move all data from decoded to outData */ - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - - return NETEQ_OTHER_ERROR; - } - - /***********************************/ - /* Special operations for BGN only */ - /***********************************/ - - /* Check if "background noise only" flag is set */ - if (BGNonly) - { - /* special operation for BGN only; simply remove a chunk of data */ - w16_bestIndex = DEFAULT_TIME_ADJUST * WEBRTC_SPL_LSHIFT_W16(fsMult, 3); /* X*fs/1000 */ - - /* Sanity check for bestIndex */ - if (w16_bestIndex > len) - { /* not good, do nothing instead */ - inst->w16_mode = MODE_UNSUCCESS_ACCELERATE; - *pw16_len = len; - - /* simply move all data from decoded to outData */ - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - - return NETEQ_OTHER_ERROR; - } - - /* set length parameter */ - *pw16_len = len - w16_bestIndex; /* we remove bestIndex samples */ - - /* copy to output */ - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, *pw16_len); - - /* set mode */ - inst->w16_mode = MODE_LOWEN_ACCELERATE; - - /* update statistics */ - inst->statInst.accelerateLength += w16_bestIndex; - /* Short-term activity statistics. */ - inst->activity_stats.accelerate_bgn_samples += w16_bestIndex; - - return 0; - } /* end of special code for BGN mode */ - -#ifdef NETEQ_STEREO - - /* Sanity for msInfo */ - if (msInfo == NULL) - { - /* this should not happen here */ - return MASTER_SLAVE_ERROR; - } - - if (msInfo->msMode != NETEQ_SLAVE) - { - /* Find correlation lag only for non-slave instances */ - -#endif - - /****************************************************************/ - /* Find the strongest correlation lag by downsampling to 4 kHz, */ - /* calculating correlation for downsampled signal and finding */ - /* the strongest correlation peak. */ - /****************************************************************/ - - /* find maximum absolute value */ - w16_decodedMax = WebRtcSpl_MaxAbsValueW16(pw16_decoded, (int16_t) len); - - /* downsample the decoded speech to 4 kHz */ - ok = WebRtcNetEQ_DownSampleTo4kHz(pw16_decoded, len, inst->fs, pw16_downSampSpeech, - ACCELERATE_DOWNSAMPLED_LEN, 1 /* compensate delay*/); - if (ok != 0) - { - /* error */ - inst->w16_mode = MODE_UNSUCCESS_ACCELERATE; - *pw16_len = len; - /* simply move all data from decoded to outData */ - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - return NETEQ_OTHER_ERROR; - } - - /* - * Set scaling factor for cross correlation to protect against overflow - * (log2(50) => 6) - */ - w16_tmp = 6 - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_decodedMax, w16_decodedMax)); - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - /* Perform correlation from lag 10 to lag 60 in 4 kHz domain */ - WebRtcNetEQ_CrossCorr( - pw32_corr, &pw16_downSampSpeech[w16_endLag], - &pw16_downSampSpeech[w16_endLag - w16_startLag], w16_corrLen, - (int16_t) (w16_endLag - w16_startLag), w16_tmp, -1); - - /* Normalize correlation to 14 bits and put in a int16_t vector */ - w32_tmp = WebRtcSpl_MaxAbsValueW32(pw32_corr, w16_corrLen); - w16_tmp = 17 - WebRtcSpl_NormW32(w32_tmp); - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - WebRtcSpl_VectorBitShiftW32ToW16(pw16_corr, w16_corrLen, pw32_corr, w16_tmp); - -#ifdef NETEQ_STEREO - } /* end if (msInfo->msMode != NETEQ_SLAVE) */ - - if ((msInfo->msMode == NETEQ_MASTER) || (msInfo->msMode == NETEQ_MONO)) - { - /* Find the strongest correlation peak by using the parabolic fit method */ - WebRtcNetEQ_PeakDetection(pw16_corr, (int16_t) w16_corrLen, 1, fsMult, - &w16_bestIndex, &w16_bestVal); - /* 0 <= bestIndex <= (2*corrLen - 1)*fsMult = 99*fsMult */ - - /* Compensate bestIndex for displaced starting position */ - w16_bestIndex = w16_bestIndex + w16_startLag * WEBRTC_SPL_LSHIFT_W16(fsMult, 1); - /* 20*fsMult <= bestIndex <= 119*fsMult */ - - msInfo->bestIndex = w16_bestIndex; - } - else if (msInfo->msMode == NETEQ_SLAVE) - { - if (msInfo->extraInfo == ACC_FAIL) - { - /* Master has signaled an unsuccessful accelerate */ - w16_bestIndex = 0; - } - else - { - /* Get best index from master */ - w16_bestIndex = msInfo->bestIndex; - } - } - else - { - /* Invalid mode */ - return MASTER_SLAVE_ERROR; - } - -#else /* NETEQ_STEREO */ - - /* Find the strongest correlation peak by using the parabolic fit method */ - WebRtcNetEQ_PeakDetection(pw16_corr, (int16_t) w16_corrLen, 1, fsMult, - &w16_bestIndex, &w16_bestVal); - /* 0 <= bestIndex <= (2*corrLen - 1)*fsMult = 99*fsMult */ - - /* Compensate bestIndex for displaced starting position */ - w16_bestIndex = w16_bestIndex + w16_startLag * WEBRTC_SPL_LSHIFT_W16(fsMult, 1); - /* 20*fsMult <= bestIndex <= 119*fsMult */ - -#endif /* NETEQ_STEREO */ - -#ifdef NETEQ_STEREO - - if (msInfo->msMode != NETEQ_SLAVE) - { - /* Calculate correlation only for non-slave instances */ - -#endif /* NETEQ_STEREO */ - - /*****************************************************/ - /* Calculate correlation bestCorr for the found lag. */ - /* Also do a simple VAD decision. */ - /*****************************************************/ - - /* - * Calculate scaling to ensure that bestIndex samples can be square-summed - * without overflowing - */ - w16_tmp = (31 - - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_decodedMax, w16_decodedMax))); - w16_tmp += (31 - WebRtcSpl_NormW32(w16_bestIndex)); - w16_tmp -= 31; - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - /* vec1 starts at 15 ms minus one pitch period */ - pw16_vec1 = &pw16_decoded[fsMult120 - w16_bestIndex]; - /* vec2 start at 15 ms */ - pw16_vec2 = &pw16_decoded[fsMult120]; - - /* Calculate energies for vec1 and vec2 */ - w32_en1 = WebRtcNetEQ_DotW16W16((int16_t*) pw16_vec1, - (int16_t*) pw16_vec1, w16_bestIndex, w16_tmp); - w32_en2 = WebRtcNetEQ_DotW16W16((int16_t*) pw16_vec2, - (int16_t*) pw16_vec2, w16_bestIndex, w16_tmp); - - /* Calculate cross-correlation at the found lag */ - w32_cc = WebRtcNetEQ_DotW16W16((int16_t*) pw16_vec1, (int16_t*) pw16_vec2, - w16_bestIndex, w16_tmp); - - /* Check VAD constraint - ((en1+en2)/(2*bestIndex)) <= 8*inst->BGNInst.energy */ - w32_tmp = WEBRTC_SPL_RSHIFT_W32(w32_en1 + w32_en2, 4); /* (en1+en2)/(2*8) */ - if (inst->BGNInst.w16_initialized == 1) - { - w32_tmp2 = inst->BGNInst.w32_energy; - } - else - { - /* if BGN parameters have not been estimated, use a fixed threshold */ - w32_tmp2 = 75000; - } - w16_tmp2 = 16 - WebRtcSpl_NormW32(w32_tmp2); - w16_tmp2 = WEBRTC_SPL_MAX(0, w16_tmp2); - w32_tmp = WEBRTC_SPL_RSHIFT_W32(w32_tmp, w16_tmp2); - w16_tmp2 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_tmp2, w16_tmp2); - w32_tmp2 = WEBRTC_SPL_MUL_16_16(w16_bestIndex, w16_tmp2); - - /* Scale w32_tmp properly before comparing with w32_tmp2 */ - /* (w16_tmp is scaling before energy calculation, thus 2*w16_tmp) */ - if (WebRtcSpl_NormW32(w32_tmp) < WEBRTC_SPL_LSHIFT_W32(w16_tmp,1)) - { - /* Cannot scale only w32_tmp, must scale w32_temp2 too */ - int16_t tempshift = WebRtcSpl_NormW32(w32_tmp); - w32_tmp = WEBRTC_SPL_LSHIFT_W32(w32_tmp, tempshift); - w32_tmp2 = WEBRTC_SPL_RSHIFT_W32(w32_tmp2, - WEBRTC_SPL_LSHIFT_W32(w16_tmp,1) - tempshift); - } - else - { - w32_tmp = WEBRTC_SPL_LSHIFT_W32(w32_tmp, - WEBRTC_SPL_LSHIFT_W32(w16_tmp,1)); - } - - if (w32_tmp <= w32_tmp2) /*((en1+en2)/(2*bestIndex)) <= 8*inst->BGNInst.energy */ - { - /* The signal seems to be passive speech */ - w16_VAD = 0; - w16_bestCorr = 0; /* Correlation does not matter */ - } - else - { - /* The signal is active speech */ - w16_VAD = 1; - - /* Calculate correlation (cc/sqrt(en1*en2)) */ - - /* Start with calculating scale values */ - w16_en1Scale = 16 - WebRtcSpl_NormW32(w32_en1); - w16_en1Scale = WEBRTC_SPL_MAX(0, w16_en1Scale); - w16_en2Scale = 16 - WebRtcSpl_NormW32(w32_en2); - w16_en2Scale = WEBRTC_SPL_MAX(0, w16_en2Scale); - - /* Make sure total scaling is even (to simplify scale factor after sqrt) */ - if ((w16_en1Scale + w16_en2Scale) & 1) - { - w16_en1Scale += 1; - } - - /* Convert energies to int16_t */ - w16_en1 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_en1, w16_en1Scale); - w16_en2 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_en2, w16_en2Scale); - - /* Calculate energy product */ - w32_tmp = WEBRTC_SPL_MUL_16_16(w16_en1, w16_en2); - - /* Calculate square-root of energy product */ - w16_sqrtEn1En2 = (int16_t) WebRtcSpl_SqrtFloor(w32_tmp); - - /* Calculate cc/sqrt(en1*en2) in Q14 */ - w16_tmp = 14 - WEBRTC_SPL_RSHIFT_W16(w16_en1Scale+w16_en2Scale, 1); - w32_cc = WEBRTC_SPL_SHIFT_W32(w32_cc, w16_tmp); - w32_cc = WEBRTC_SPL_MAX(0, w32_cc); /* Don't divide with negative number */ - w16_bestCorr = (int16_t) WebRtcSpl_DivW32W16(w32_cc, w16_sqrtEn1En2); - w16_bestCorr = WEBRTC_SPL_MIN(16384, w16_bestCorr); /* set maximum to 1.0 */ - } - -#ifdef NETEQ_STEREO - - } /* end if (msInfo->msMode != NETEQ_SLAVE) */ - -#endif /* NETEQ_STEREO */ - - /************************************************/ - /* Check accelerate criteria and remove samples */ - /************************************************/ - - /* Check for strong correlation (>0.9) or passive speech */ -#ifdef NETEQ_STEREO - if ((((w16_bestCorr > 14746) || (w16_VAD == 0)) && (msInfo->msMode != NETEQ_SLAVE)) - || ((msInfo->msMode == NETEQ_SLAVE) && (msInfo->extraInfo != ACC_FAIL))) -#else - if ((w16_bestCorr > 14746) || (w16_VAD == 0)) -#endif - { - /* Do accelerate operation by overlap add */ - - /* - * Calculate cross-fading slope so that the fading factor goes from - * 1 (16384 in Q14) to 0 in one pitch period (bestIndex). - */ - w16_inc = (int16_t) WebRtcSpl_DivW32W16((int32_t) 16384, - (int16_t) (w16_bestIndex + 1)); /* in Q14 */ - - /* Initiate fading factor */ - w16_startfact = 16384 - w16_inc; - - /* vec1 starts at 15 ms minus one pitch period */ - pw16_vec1 = &pw16_decoded[fsMult120 - w16_bestIndex]; - /* vec2 start at 15 ms */ - pw16_vec2 = &pw16_decoded[fsMult120]; - - /* Copy unmodified part [0 to 15 ms minus 1 pitch period] */ - w16_tmp = (fsMult120 - w16_bestIndex); - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, w16_tmp); - - /* Generate interpolated part of length bestIndex (1 pitch period) */ - pw16_vectmp = pw16_outData + w16_tmp; /* start of interpolation output */ - /* Reuse mixing function from Expand */ - WebRtcNetEQ_MixVoiceUnvoice(pw16_vectmp, (int16_t*) pw16_vec1, - (int16_t*) pw16_vec2, &w16_startfact, w16_inc, w16_bestIndex); - - /* Move the last part (also unmodified) */ - /* Take from decoded at 15 ms + 1 pitch period */ - pw16_vec2 = &pw16_decoded[fsMult120 + w16_bestIndex]; - WEBRTC_SPL_MEMMOVE_W16(&pw16_outData[fsMult120], pw16_vec2, - (int16_t) (len - fsMult120 - w16_bestIndex)); - - /* Set the mode flag */ - if (w16_VAD) - { - inst->w16_mode = MODE_SUCCESS_ACCELERATE; - } - else - { - inst->w16_mode = MODE_LOWEN_ACCELERATE; - } - - /* Calculate resulting length = original length - pitch period */ - *pw16_len = len - w16_bestIndex; - - /* Update in-call statistics */ - inst->statInst.accelerateLength += w16_bestIndex; - /* Short-term activity statistics. */ - inst->activity_stats.accelarate_normal_samples += w16_bestIndex; - - return 0; - } - else - { - /* Accelerate not allowed */ - -#ifdef NETEQ_STEREO - /* Signal to slave(s) that this was unsuccessful */ - if (msInfo->msMode == NETEQ_MASTER) - { - msInfo->extraInfo = ACC_FAIL; - } -#endif - - /* Set mode flag to unsuccessful accelerate */ - inst->w16_mode = MODE_UNSUCCESS_ACCELERATE; - - /* Length is unmodified */ - *pw16_len = len; - - /* Simply move all data from decoded to outData */ - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - - return 0; - } -} - -#undef SCRATCH_PW16_DS_SPEECH -#undef SCRATCH_PW32_CORR -#undef SCRATCH_PW16_CORR diff --git a/webrtc/modules/audio_coding/neteq4/accelerate.cc b/webrtc/modules/audio_coding/neteq/accelerate.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/accelerate.cc rename to webrtc/modules/audio_coding/neteq/accelerate.cc index eb546e976..6acd778a2 100644 --- a/webrtc/modules/audio_coding/neteq4/accelerate.cc +++ b/webrtc/modules/audio_coding/neteq/accelerate.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/accelerate.h" +#include "webrtc/modules/audio_coding/neteq/accelerate.h" #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" diff --git a/webrtc/modules/audio_coding/neteq4/accelerate.h b/webrtc/modules/audio_coding/neteq/accelerate.h similarity index 87% rename from webrtc/modules/audio_coding/neteq4/accelerate.h rename to webrtc/modules/audio_coding/neteq/accelerate.h index 81f1abb53..2da999326 100644 --- a/webrtc/modules/audio_coding/neteq4/accelerate.h +++ b/webrtc/modules/audio_coding/neteq/accelerate.h @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_ACCELERATE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_ACCELERATE_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_ACCELERATE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_ACCELERATE_H_ #include -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/time_stretch.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/time_stretch.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -74,4 +74,4 @@ struct AccelerateFactory { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_ACCELERATE_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_ACCELERATE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/audio_classifier.cc b/webrtc/modules/audio_coding/neteq/audio_classifier.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/audio_classifier.cc rename to webrtc/modules/audio_coding/neteq/audio_classifier.cc index a272fbce3..cc4bc97c3 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_classifier.cc +++ b/webrtc/modules/audio_coding/neteq/audio_classifier.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_classifier.h" +#include "webrtc/modules/audio_coding/neteq/audio_classifier.h" #include #include diff --git a/webrtc/modules/audio_coding/neteq4/audio_classifier.h b/webrtc/modules/audio_coding/neteq/audio_classifier.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/audio_classifier.h rename to webrtc/modules/audio_coding/neteq/audio_classifier.h index e64e110d8..e7b7807db 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_classifier.h +++ b/webrtc/modules/audio_coding/neteq/audio_classifier.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_CLASSIFIER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_CLASSIFIER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_CLASSIFIER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_CLASSIFIER_H_ #if defined(__cplusplus) extern "C" { @@ -41,7 +41,7 @@ class AudioClassifier { bool Analysis(const int16_t* input, int input_length, int channels); // Gets the current classification : true = music, false = speech. - bool is_music() const { return is_music_; } + virtual bool is_music() const { return is_music_; } // Gets the current music probability. float music_probability() const { return music_probability_; } @@ -56,4 +56,4 @@ class AudioClassifier { } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_CLASSIFIER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_CLASSIFIER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/audio_classifier_unittest.cc b/webrtc/modules/audio_coding/neteq/audio_classifier_unittest.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/audio_classifier_unittest.cc rename to webrtc/modules/audio_coding/neteq/audio_classifier_unittest.cc index 0a6671816..cf623ca08 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_classifier_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/audio_classifier_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_classifier.h" +#include "webrtc/modules/audio_coding/neteq/audio_classifier.h" #include #include diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder.cc b/webrtc/modules/audio_coding/neteq/audio_decoder.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/audio_decoder.cc rename to webrtc/modules/audio_coding/neteq/audio_decoder.cc index 2a252e6f1..f539bb2e1 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_decoder.cc +++ b/webrtc/modules/audio_coding/neteq/audio_decoder.cc @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include -#include "webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h" +#include "webrtc/modules/audio_coding/neteq/audio_decoder_impl.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc b/webrtc/modules/audio_coding/neteq/audio_decoder_impl.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc rename to webrtc/modules/audio_coding/neteq/audio_decoder_impl.cc index 94e507e02..6c7269a35 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc +++ b/webrtc/modules/audio_coding/neteq/audio_decoder_impl.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h" +#include "webrtc/modules/audio_coding/neteq/audio_decoder_impl.h" #include #include // memmove diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h b/webrtc/modules/audio_coding/neteq/audio_decoder_impl.h similarity index 96% rename from webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h rename to webrtc/modules/audio_coding/neteq/audio_decoder_impl.h index 5df649a2f..265d660bd 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h +++ b/webrtc/modules/audio_coding/neteq/audio_decoder_impl.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_DECODER_IMPL_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_DECODER_IMPL_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_DECODER_IMPL_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_DECODER_IMPL_H_ #include @@ -18,8 +18,8 @@ // selection is made in the gypi file instead of in engine_configurations.h. #include "webrtc/engine_configurations.h" #endif -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -273,4 +273,4 @@ class AudioDecoderCng : public AudioDecoder { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_DECODER_IMPL_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_DECODER_IMPL_H_ diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_unittest.cc b/webrtc/modules/audio_coding/neteq/audio_decoder_unittest.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/audio_decoder_unittest.cc rename to webrtc/modules/audio_coding/neteq/audio_decoder_unittest.cc index dbd9d121f..05684ac76 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_decoder_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/audio_decoder_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h" +#include "webrtc/modules/audio_coding/neteq/audio_decoder_impl.h" #include #include @@ -54,6 +54,7 @@ class AudioDecoderTest : public ::testing::Test { // Create arrays. ASSERT_GT(data_length_, 0u) << "The test must set data_length_ > 0"; input_ = new int16_t[data_length_]; + // Longest encoded data is produced by PCM16b with 2 bytes per sample. encoded_ = new uint8_t[data_length_ * 2]; decoded_ = new int16_t[data_length_ * channels_]; // Open input file. @@ -172,24 +173,18 @@ class AudioDecoderTest : public ::testing::Test { // Encodes a payload and decodes it twice with decoder re-init before each // decode. Verifies that the decoded result is the same. void ReInitTest() { - uint8_t* encoded = encoded_; - uint8_t* encoded_copy = encoded_ + 2 * frame_size_; int16_t* output1 = decoded_; int16_t* output2 = decoded_ + frame_size_; InitEncoder(); - size_t enc_len = EncodeFrame(input_, frame_size_, encoded); + size_t enc_len = EncodeFrame(input_, frame_size_, encoded_); size_t dec_len; - // Copy payload since iSAC fix destroys it during decode. - // Issue: http://code.google.com/p/webrtc/issues/detail?id=845. - // TODO(hlundin): Remove if the iSAC bug gets fixed. - memcpy(encoded_copy, encoded, enc_len); AudioDecoder::SpeechType speech_type1, speech_type2; EXPECT_EQ(0, decoder_->Init()); - dec_len = decoder_->Decode(encoded, enc_len, output1, &speech_type1); + dec_len = decoder_->Decode(encoded_, enc_len, output1, &speech_type1); EXPECT_EQ(frame_size_ * channels_, dec_len); // Re-init decoder and decode again. EXPECT_EQ(0, decoder_->Init()); - dec_len = decoder_->Decode(encoded_copy, enc_len, output2, &speech_type2); + dec_len = decoder_->Decode(encoded_, enc_len, output2, &speech_type2); EXPECT_EQ(frame_size_ * channels_, dec_len); for (unsigned int n = 0; n < frame_size_; ++n) { ASSERT_EQ(output1[n], output2[n]) << "Exit test on first diff; n = " << n; diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_unittests.isolate b/webrtc/modules/audio_coding/neteq/audio_decoder_unittests.isolate similarity index 100% rename from webrtc/modules/audio_coding/neteq4/audio_decoder_unittests.isolate rename to webrtc/modules/audio_coding/neteq/audio_decoder_unittests.isolate diff --git a/webrtc/modules/audio_coding/neteq4/audio_multi_vector.cc b/webrtc/modules/audio_coding/neteq/audio_multi_vector.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/audio_multi_vector.cc rename to webrtc/modules/audio_coding/neteq/audio_multi_vector.cc index b49f8b0e8..5a208a697 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_multi_vector.cc +++ b/webrtc/modules/audio_coding/neteq/audio_multi_vector.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include diff --git a/webrtc/modules/audio_coding/neteq4/audio_multi_vector.h b/webrtc/modules/audio_coding/neteq/audio_multi_vector.h similarity index 94% rename from webrtc/modules/audio_coding/neteq4/audio_multi_vector.h rename to webrtc/modules/audio_coding/neteq/audio_multi_vector.h index 2d0a74949..908de936d 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_multi_vector.h +++ b/webrtc/modules/audio_coding/neteq/audio_multi_vector.h @@ -8,15 +8,15 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_MULTI_VECTOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_MULTI_VECTOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_MULTI_VECTOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_MULTI_VECTOR_H_ #include // Access to size_t. #include -#include "webrtc/modules/audio_coding/neteq4/audio_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_vector.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -131,4 +131,4 @@ class AudioMultiVector { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_MULTI_VECTOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_MULTI_VECTOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/audio_multi_vector_unittest.cc b/webrtc/modules/audio_coding/neteq/audio_multi_vector_unittest.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/audio_multi_vector_unittest.cc rename to webrtc/modules/audio_coding/neteq/audio_multi_vector_unittest.cc index be05a8260..947603852 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_multi_vector_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/audio_multi_vector_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include #include diff --git a/webrtc/modules/audio_coding/neteq4/audio_vector.cc b/webrtc/modules/audio_coding/neteq/audio_vector.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/audio_vector.cc rename to webrtc/modules/audio_coding/neteq/audio_vector.cc index cbd461630..d9fb4e58c 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_vector.cc +++ b/webrtc/modules/audio_coding/neteq/audio_vector.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_vector.h" +#include "webrtc/modules/audio_coding/neteq/audio_vector.h" #include diff --git a/webrtc/modules/audio_coding/neteq4/audio_vector.h b/webrtc/modules/audio_coding/neteq/audio_vector.h similarity index 94% rename from webrtc/modules/audio_coding/neteq4/audio_vector.h rename to webrtc/modules/audio_coding/neteq/audio_vector.h index 66bd518a8..f8aabdb8e 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_vector.h +++ b/webrtc/modules/audio_coding/neteq/audio_vector.h @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_VECTOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_VECTOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_VECTOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_VECTOR_H_ #include // Access to size_t. -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" @@ -117,4 +117,4 @@ class AudioVector { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_AUDIO_VECTOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_AUDIO_VECTOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/audio_vector_unittest.cc b/webrtc/modules/audio_coding/neteq/audio_vector_unittest.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/audio_vector_unittest.cc rename to webrtc/modules/audio_coding/neteq/audio_vector_unittest.cc index de5aac2d9..50da1fb46 100644 --- a/webrtc/modules/audio_coding/neteq4/audio_vector_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/audio_vector_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_vector.h" +#include "webrtc/modules/audio_coding/neteq/audio_vector.h" #include #include diff --git a/webrtc/modules/audio_coding/neteq/automode.c b/webrtc/modules/audio_coding/neteq/automode.c deleted file mode 100644 index 4dbd81ed6..000000000 --- a/webrtc/modules/audio_coding/neteq/automode.c +++ /dev/null @@ -1,783 +0,0 @@ -/* - * Copyright (c) 2012 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 file contains the implementation of automatic buffer level optimization. - */ - -#include "automode.h" - -#include - -#include "signal_processing_library.h" - -#include "neteq_defines.h" - -#ifdef NETEQ_DELAY_LOGGING -/* special code for offline delay logging */ -#include -#include "delay_logging.h" - -extern FILE *delay_fid2; /* file pointer to delay log file */ -#endif /* NETEQ_DELAY_LOGGING */ - -// These two functions are copied from module_common_types.h, but adapted for C. -int WebRtcNetEQ_IsNewerSequenceNumber(uint16_t sequence_number, - uint16_t prev_sequence_number) { - return sequence_number != prev_sequence_number && - ((uint16_t) (sequence_number - prev_sequence_number)) < 0x8000; -} - -int WebRtcNetEQ_IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) { - return timestamp != prev_timestamp && - ((uint32_t) (timestamp - prev_timestamp)) < 0x80000000; -} - -int WebRtcNetEQ_UpdateIatStatistics(AutomodeInst_t *inst, int maxBufLen, - uint16_t seqNumber, uint32_t timeStamp, - int32_t fsHz, int mdCodec, int streamingMode) -{ - uint32_t timeIat; /* inter-arrival time */ - int i; - int32_t tempsum = 0; /* temp summation */ - int32_t tempvar; /* temporary variable */ - int retval = 0; /* return value */ - int16_t packetLenSamp; /* packet speech length in samples */ - - /****************/ - /* Sanity check */ - /****************/ - - if (maxBufLen <= 1 || fsHz <= 0) - { - /* maxBufLen must be at least 2 and fsHz must both be strictly positive */ - return -1; - } - - /****************************/ - /* Update packet statistics */ - /****************************/ - - /* Try calculating packet length from current and previous timestamps */ - if (!WebRtcNetEQ_IsNewerTimestamp(timeStamp, inst->lastTimeStamp) || - !WebRtcNetEQ_IsNewerSequenceNumber(seqNumber, inst->lastSeqNo)) - { - /* Wrong timestamp or sequence order; revert to backup plan */ - packetLenSamp = inst->packetSpeechLenSamp; /* use stored value */ - } - else - { - /* calculate timestamps per packet */ - packetLenSamp = (int16_t) WebRtcSpl_DivU32U16(timeStamp - inst->lastTimeStamp, - seqNumber - inst->lastSeqNo); - } - - /* Check that the packet size is positive; if not, the statistics cannot be updated. */ - if (inst->firstPacketReceived && packetLenSamp > 0) - { /* packet size ok */ - - /* calculate inter-arrival time in integer packets (rounding down) */ - timeIat = WebRtcSpl_DivW32W16(inst->packetIatCountSamp, packetLenSamp); - - /* Special operations for streaming mode */ - if (streamingMode != 0) - { - /* - * Calculate IAT in Q8, including fractions of a packet (i.e., more accurate - * than timeIat). - */ - int16_t timeIatQ8 = (int16_t) WebRtcSpl_DivW32W16( - WEBRTC_SPL_LSHIFT_W32(inst->packetIatCountSamp, 8), packetLenSamp); - - /* - * Calculate cumulative sum iat with sequence number compensation (ideal arrival - * times makes this sum zero). - */ - inst->cSumIatQ8 += (timeIatQ8 - - WEBRTC_SPL_LSHIFT_W32(seqNumber - inst->lastSeqNo, 8)); - - /* subtract drift term */ - inst->cSumIatQ8 -= CSUM_IAT_DRIFT; - - /* ensure not negative */ - inst->cSumIatQ8 = WEBRTC_SPL_MAX(inst->cSumIatQ8, 0); - - /* remember max */ - if (inst->cSumIatQ8 > inst->maxCSumIatQ8) - { - inst->maxCSumIatQ8 = inst->cSumIatQ8; - inst->maxCSumUpdateTimer = 0; - } - - /* too long since the last maximum was observed; decrease max value */ - if (inst->maxCSumUpdateTimer > (uint32_t) WEBRTC_SPL_MUL_32_16(fsHz, - MAX_STREAMING_PEAK_PERIOD)) - { - inst->maxCSumIatQ8 -= 4; /* remove 1000*4/256 = 15.6 ms/s */ - } - } /* end of streaming mode */ - - /* check for discontinuous packet sequence and re-ordering */ - if (WebRtcNetEQ_IsNewerSequenceNumber(seqNumber, inst->lastSeqNo + 1)) - { - /* Compensate for gap in the sequence numbers. - * Reduce IAT with expected extra time due to lost packets, but ensure that - * the IAT is not negative. - */ - timeIat -= WEBRTC_SPL_MIN(timeIat, - (uint16_t) (seqNumber - (uint16_t) (inst->lastSeqNo + 1))); - } - else if (!WebRtcNetEQ_IsNewerSequenceNumber(seqNumber, inst->lastSeqNo)) - { - /* compensate for re-ordering */ - timeIat += (uint16_t) (inst->lastSeqNo + 1 - seqNumber); - } - - /* saturate IAT at maximum value */ - timeIat = WEBRTC_SPL_MIN( timeIat, MAX_IAT ); - - /* update iatProb = forgetting_factor * iatProb for all elements */ - for (i = 0; i <= MAX_IAT; i++) - { - int32_t tempHi, tempLo; /* Temporary variables */ - - /* - * Multiply iatProbFact (Q15) with iatProb (Q30) and right-shift 15 steps - * to come back to Q30. The operation is done in two steps: - */ - - /* - * 1) Multiply the high 16 bits (15 bits + sign) of iatProb. Shift iatProb - * 16 steps right to get the high 16 bits in a int16_t prior to - * multiplication, and left-shift with 1 afterwards to come back to - * Q30 = (Q15 * (Q30>>16)) << 1. - */ - tempHi = WEBRTC_SPL_MUL_16_16(inst->iatProbFact, - (int16_t) WEBRTC_SPL_RSHIFT_W32(inst->iatProb[i], 16)); - tempHi = WEBRTC_SPL_LSHIFT_W32(tempHi, 1); /* left-shift 1 step */ - - /* - * 2) Isolate and multiply the low 16 bits of iatProb. Right-shift 15 steps - * afterwards to come back to Q30 = (Q15 * Q30) >> 15. - */ - tempLo = inst->iatProb[i] & 0x0000FFFF; /* sift out the 16 low bits */ - tempLo = WEBRTC_SPL_MUL_16_U16(inst->iatProbFact, - (uint16_t) tempLo); - tempLo = WEBRTC_SPL_RSHIFT_W32(tempLo, 15); - - /* Finally, add the high and low parts */ - inst->iatProb[i] = tempHi + tempLo; - - /* Sum all vector elements while we are at it... */ - tempsum += inst->iatProb[i]; - } - - /* - * Increase the probability for the currently observed inter-arrival time - * with 1 - iatProbFact. The factor is in Q15, iatProb in Q30; - * hence, left-shift 15 steps to obtain result in Q30. - */ - inst->iatProb[timeIat] += (32768 - inst->iatProbFact) << 15; - - tempsum += (32768 - inst->iatProbFact) << 15; /* add to vector sum */ - - /* - * Update iatProbFact (changes only during the first seconds after reset) - * The factor converges to IAT_PROB_FACT. - */ - inst->iatProbFact += (IAT_PROB_FACT - inst->iatProbFact + 3) >> 2; - - /* iatProb should sum up to 1 (in Q30). */ - tempsum -= 1 << 30; /* should be zero */ - - /* Check if it does, correct if it doesn't. */ - if (tempsum > 0) - { - /* tempsum too large => decrease a few values in the beginning */ - i = 0; - while (i <= MAX_IAT && tempsum > 0) - { - /* Remove iatProb[i] / 16 from iatProb, but not more than tempsum */ - tempvar = WEBRTC_SPL_MIN(tempsum, inst->iatProb[i] >> 4); - inst->iatProb[i++] -= tempvar; - tempsum -= tempvar; - } - } - else if (tempsum < 0) - { - /* tempsum too small => increase a few values in the beginning */ - i = 0; - while (i <= MAX_IAT && tempsum < 0) - { - /* Add iatProb[i] / 16 to iatProb, but not more than tempsum */ - tempvar = WEBRTC_SPL_MIN(-tempsum, inst->iatProb[i] >> 4); - inst->iatProb[i++] += tempvar; - tempsum += tempvar; - } - } - - /* Calculate optimal buffer level based on updated statistics */ - tempvar = (int32_t) WebRtcNetEQ_CalcOptimalBufLvl(inst, fsHz, mdCodec, timeIat, - streamingMode); - if (tempvar > 0) - { - int high_lim_delay; - /* Convert the minimum delay from milliseconds to packets in Q8. - * |fsHz| is sampling rate in Hertz, and |packetLenSamp| - * is the number of samples per packet (according to the last - * decoding). - */ - int32_t minimum_delay_q8 = ((inst->minimum_delay_ms * - (fsHz / 1000)) << 8) / packetLenSamp; - - int32_t maximum_delay_q8 = ((inst->maximum_delay_ms * - (fsHz / 1000)) << 8) / packetLenSamp; - - inst->optBufLevel = tempvar; - - if (streamingMode != 0) - { - inst->optBufLevel = WEBRTC_SPL_MAX(inst->optBufLevel, - inst->maxCSumIatQ8); - } - - /* The required delay. */ - inst->required_delay_q8 = inst->optBufLevel; - - // Maintain the target delay. - inst->optBufLevel = WEBRTC_SPL_MAX(inst->optBufLevel, - minimum_delay_q8); - - if (maximum_delay_q8 > 0) { - // Make sure that max is at least one packet length. - maximum_delay_q8 = WEBRTC_SPL_MAX(maximum_delay_q8, (1 << 8)); - inst->optBufLevel = WEBRTC_SPL_MIN(inst->optBufLevel, - maximum_delay_q8); - } - /*********/ - /* Limit */ - /*********/ - - /* Subtract extra delay from maxBufLen */ - if (inst->extraDelayMs > 0 && inst->packetSpeechLenSamp > 0) - { - maxBufLen -= inst->extraDelayMs / inst->packetSpeechLenSamp * fsHz / 1000; - maxBufLen = WEBRTC_SPL_MAX(maxBufLen, 1); // sanity: at least one packet - } - - maxBufLen = WEBRTC_SPL_LSHIFT_W32(maxBufLen, 8); /* shift to Q8 */ - - /* Enforce upper limit; 75% of maxBufLen */ - /* 1/2 + 1/4 = 75% */ - high_lim_delay = (maxBufLen >> 1) + (maxBufLen >> 2); - inst->optBufLevel = WEBRTC_SPL_MIN(inst->optBufLevel, - high_lim_delay); - inst->required_delay_q8 = WEBRTC_SPL_MIN(inst->required_delay_q8, - high_lim_delay); - } - else - { - retval = (int) tempvar; - } - - } /* end if */ - - /*******************************/ - /* Update post-call statistics */ - /*******************************/ - - /* Calculate inter-arrival time in ms = packetIatCountSamp / (fsHz / 1000) */ - timeIat = WEBRTC_SPL_UDIV( - WEBRTC_SPL_UMUL_32_16(inst->packetIatCountSamp, (int16_t) 1000), - (uint32_t) fsHz); - - /* Increase counter corresponding to current inter-arrival time */ - if (timeIat > 2000) - { - inst->countIAT2000ms++; - } - else if (timeIat > 1000) - { - inst->countIAT1000ms++; - } - else if (timeIat > 500) - { - inst->countIAT500ms++; - } - - if (timeIat > inst->longestIATms) - { - /* update maximum value */ - inst->longestIATms = timeIat; - } - - /***********************************/ - /* Prepare for next packet arrival */ - /***********************************/ - - inst->packetIatCountSamp = 0; /* reset inter-arrival time counter */ - - inst->lastSeqNo = seqNumber; /* remember current sequence number */ - - inst->lastTimeStamp = timeStamp; /* remember current timestamp */ - - inst->firstPacketReceived = 1; - - return retval; -} - - -int16_t WebRtcNetEQ_CalcOptimalBufLvl(AutomodeInst_t *inst, int32_t fsHz, - int mdCodec, uint32_t timeIatPkts, - int streamingMode) -{ - - int32_t sum1 = 1 << 30; /* assign to 1 in Q30 */ - int16_t B; - uint16_t Bopt; - int i; - int32_t betaInv; /* optimization parameter */ - -#ifdef NETEQ_DELAY_LOGGING - /* special code for offline delay logging */ - int temp_var; -#endif - - /****************/ - /* Sanity check */ - /****************/ - - if (fsHz <= 0) - { - /* fsHz must be strictly positive */ - return -1; - } - - /***********************************************/ - /* Get betaInv parameter based on playout mode */ - /***********************************************/ - - if (streamingMode) - { - /* streaming (listen-only) mode */ - betaInv = AUTOMODE_STREAMING_BETA_INV_Q30; - } - else - { - /* normal mode */ - betaInv = AUTOMODE_BETA_INV_Q30; - } - - /*******************************************************************/ - /* Calculate optimal buffer level without considering jitter peaks */ - /*******************************************************************/ - - /* - * Find the B for which the probability of observing an inter-arrival time larger - * than or equal to B is less than or equal to betaInv. - */ - B = 0; /* start from the beginning of iatProb */ - sum1 -= inst->iatProb[B]; /* ensure that optimal level is not less than 1 */ - - do - { - /* - * Subtract the probabilities one by one until the sum is no longer greater - * than betaInv. - */ - sum1 -= inst->iatProb[++B]; - } - while ((sum1 > betaInv) && (B < MAX_IAT)); - - Bopt = B; /* This is our primary value for the optimal buffer level Bopt */ - - if (mdCodec) - { - /* - * Use alternative cost function when multiple description codec is in use. - * Do not have to re-calculate all points, just back off a few steps from - * previous value of B. - */ - int32_t sum2 = sum1; /* copy sum1 */ - - while ((sum2 <= betaInv + inst->iatProb[Bopt]) && (Bopt > 0)) - { - /* Go backwards in the sum until the modified cost function solution is found */ - sum2 += inst->iatProb[Bopt--]; - } - - Bopt++; /* This is the optimal level when using an MD codec */ - - /* Now, Bopt and B can have different values. */ - } - -#ifdef NETEQ_DELAY_LOGGING - /* special code for offline delay logging */ - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_OPTBUF; - if (fwrite( &temp_var, sizeof(int), 1, delay_fid2 ) != 1) { - return -1; - } - temp_var = (int) (Bopt * inst->packetSpeechLenSamp); -#endif - - /******************************************************************/ - /* Make levelFiltFact adaptive: Larger B <=> larger levelFiltFact */ - /******************************************************************/ - - switch (B) - { - case 0: - case 1: - { - inst->levelFiltFact = 251; - break; - } - case 2: - case 3: - { - inst->levelFiltFact = 252; - break; - } - case 4: - case 5: - case 6: - case 7: - { - inst->levelFiltFact = 253; - break; - } - default: /* B > 7 */ - { - inst->levelFiltFact = 254; - break; - } - } - - /************************/ - /* Peak mode operations */ - /************************/ - - /* Compare current IAT with peak threshold - * - * If IAT > optimal level + threshold (+1 for MD codecs) - * or if IAT > 2 * optimal level (note: optimal level is in Q8): - */ - if (timeIatPkts > (uint32_t) (Bopt + inst->peakThresholdPkt + (mdCodec != 0)) - || timeIatPkts > (uint32_t) WEBRTC_SPL_LSHIFT_U16(Bopt, 1)) - { - /* A peak is observed */ - - if (inst->peakIndex == -1) - { - /* this is the first peak; prepare for next peak */ - inst->peakIndex = 0; - /* set the mode-disable counter */ - inst->peakModeDisabled = WEBRTC_SPL_LSHIFT_W16(1, NUM_PEAKS_REQUIRED-2); - } - else if (inst->peakIatCountSamp - <= - (uint32_t) WEBRTC_SPL_MUL_32_16(fsHz, MAX_PEAK_PERIOD)) - { - /* This is not the first peak and the period time is valid */ - - /* store time elapsed since last peak */ - inst->peakPeriodSamp[inst->peakIndex] = inst->peakIatCountSamp; - - /* saturate height to 16 bits */ - inst->peakHeightPkt[inst->peakIndex] - = - (int16_t) WEBRTC_SPL_MIN(timeIatPkts, WEBRTC_SPL_WORD16_MAX); - - /* increment peakIndex and wrap/modulo */ - inst->peakIndex = (inst->peakIndex + 1) & PEAK_INDEX_MASK; - - /* process peak vectors */ - inst->curPeakHeight = 0; - inst->curPeakPeriod = 0; - - for (i = 0; i < NUM_PEAKS; i++) - { - /* Find maximum of peak heights and peak periods */ - inst->curPeakHeight - = WEBRTC_SPL_MAX(inst->curPeakHeight, inst->peakHeightPkt[i]); - inst->curPeakPeriod - = WEBRTC_SPL_MAX(inst->curPeakPeriod, inst->peakPeriodSamp[i]); - - } - - inst->peakModeDisabled >>= 1; /* decrease mode-disable "counter" */ - - } - else if (inst->peakIatCountSamp > (uint32_t) WEBRTC_SPL_MUL_32_16(fsHz, - WEBRTC_SPL_LSHIFT_W16(MAX_PEAK_PERIOD, 1))) - { - /* - * More than 2 * MAX_PEAK_PERIOD has elapsed since last peak; - * too long time => reset peak statistics - */ - inst->curPeakHeight = 0; - inst->curPeakPeriod = 0; - for (i = 0; i < NUM_PEAKS; i++) - { - inst->peakHeightPkt[i] = 0; - inst->peakPeriodSamp[i] = 0; - } - - inst->peakIndex = -1; /* Next peak is first peak */ - inst->peakIatCountSamp = 0; - } - - inst->peakIatCountSamp = 0; /* Reset peak interval timer */ - } /* end if peak is observed */ - - /* Evaluate peak mode conditions */ - - /* - * If not disabled (enough peaks have been observed) and - * time since last peak is less than two peak periods. - */ - inst->peakFound = 0; - if ((!inst->peakModeDisabled) && (inst->peakIatCountSamp - <= WEBRTC_SPL_LSHIFT_W32(inst->curPeakPeriod , 1))) - { - /* Engage peak mode */ - inst->peakFound = 1; - /* Set optimal buffer level to curPeakHeight (if it's not already larger) */ - Bopt = WEBRTC_SPL_MAX(Bopt, inst->curPeakHeight); - -#ifdef NETEQ_DELAY_LOGGING - /* special code for offline delay logging */ - temp_var = (int) -(Bopt * inst->packetSpeechLenSamp); -#endif - } - - /* Scale Bopt to Q8 */ - Bopt = WEBRTC_SPL_LSHIFT_U16(Bopt,8); - -#ifdef NETEQ_DELAY_LOGGING - /* special code for offline delay logging */ - if (fwrite( &temp_var, sizeof(int), 1, delay_fid2 ) != 1) { - return -1; - } -#endif - - /* Sanity check: Bopt must be strictly positive */ - if (Bopt <= 0) - { - Bopt = WEBRTC_SPL_LSHIFT_W16(1, 8); /* 1 in Q8 */ - } - - return Bopt; /* return value in Q8 */ -} - - -int WebRtcNetEQ_BufferLevelFilter(int32_t curSizeMs8, AutomodeInst_t *inst, - int sampPerCall, int16_t fsMult) -{ - - int16_t curSizeFrames; - - /****************/ - /* Sanity check */ - /****************/ - - if (sampPerCall <= 0 || fsMult <= 0) - { - /* sampPerCall and fsMult must both be strictly positive */ - return -1; - } - - /* Check if packet size has been detected */ - if (inst->packetSpeechLenSamp > 0) - { - /* - * Current buffer level in packet lengths - * = (curSizeMs8 * fsMult) / packetSpeechLenSamp - */ - curSizeFrames = (int16_t) WebRtcSpl_DivW32W16( - WEBRTC_SPL_MUL_32_16(curSizeMs8, fsMult), inst->packetSpeechLenSamp); - } - else - { - curSizeFrames = 0; - } - - /* Filter buffer level */ - if (inst->levelFiltFact > 0) /* check that filter factor is set */ - { - /* Filter: - * buffLevelFilt = levelFiltFact * buffLevelFilt - * + (1-levelFiltFact) * curSizeFrames - * - * levelFiltFact is in Q8 - */ - inst->buffLevelFilt = ((inst->levelFiltFact * inst->buffLevelFilt) >> 8) + - (256 - inst->levelFiltFact) * curSizeFrames; - } - - /* Account for time-scale operations (accelerate and pre-emptive expand) */ - if (inst->prevTimeScale) - { - /* - * Time-scaling has been performed since last filter update. - * Subtract the sampleMemory from buffLevelFilt after converting sampleMemory - * from samples to packets in Q8. Make sure that the filtered value is - * non-negative. - */ - inst->buffLevelFilt = WEBRTC_SPL_MAX( inst->buffLevelFilt - - WebRtcSpl_DivW32W16( - WEBRTC_SPL_LSHIFT_W32(inst->sampleMemory, 8), /* sampleMemory in Q8 */ - inst->packetSpeechLenSamp ), /* divide by packetSpeechLenSamp */ - 0); - - /* - * Reset flag and set timescaleHoldOff timer to prevent further time-scaling - * for some time. - */ - inst->prevTimeScale = 0; - inst->timescaleHoldOff = AUTOMODE_TIMESCALE_LIMIT; - } - - /* Update time counters and HoldOff timer */ - inst->packetIatCountSamp += sampPerCall; /* packet inter-arrival time */ - inst->peakIatCountSamp += sampPerCall; /* peak inter-arrival time */ - inst->timescaleHoldOff >>= 1; /* time-scaling limiter */ - inst->maxCSumUpdateTimer += sampPerCall; /* cumulative-sum timer */ - - return 0; - -} - - -int WebRtcNetEQ_SetPacketSpeechLen(AutomodeInst_t *inst, int16_t newLenSamp, - int32_t fsHz) -{ - - /* Sanity check for newLenSamp and fsHz */ - if (newLenSamp <= 0 || fsHz <= 0) - { - return -1; - } - - inst->packetSpeechLenSamp = newLenSamp; /* Store packet size in instance */ - - /* Make NetEQ wait for first regular packet before starting the timer */ - inst->lastPackCNGorDTMF = 1; - - inst->packetIatCountSamp = 0; /* Reset packet time counter */ - - /* - * Calculate peak threshold from packet size. The threshold is defined as - * the (fractional) number of packets that corresponds to PEAK_HEIGHT - * (in Q8 seconds). That is, threshold = PEAK_HEIGHT/256 * fsHz / packLen. - */ - inst->peakThresholdPkt = (uint16_t) WebRtcSpl_DivW32W16ResW16( - WEBRTC_SPL_MUL_16_16_RSFT(PEAK_HEIGHT, - (int16_t) WEBRTC_SPL_RSHIFT_W32(fsHz, 6), 2), inst->packetSpeechLenSamp); - - return 0; -} - - -int WebRtcNetEQ_ResetAutomode(AutomodeInst_t *inst, int maxBufLenPackets) -{ - - int i; - uint16_t tempprob = 0x4002; /* 16384 + 2 = 100000000000010 binary; */ - - /* Sanity check for maxBufLenPackets */ - if (maxBufLenPackets <= 1) - { - /* Invalid value; set to 10 instead (arbitary small number) */ - maxBufLenPackets = 10; - } - - /* Reset filtered buffer level */ - inst->buffLevelFilt = 0; - - /* Reset packet size to unknown */ - inst->packetSpeechLenSamp = 0; - - /* - * Flag that last packet was special payload, so that automode will treat the next speech - * payload as the first payload received. - */ - inst->lastPackCNGorDTMF = 1; - - /* Reset peak detection parameters */ - inst->peakModeDisabled = 1; /* disable peak mode */ - inst->peakIatCountSamp = 0; - inst->peakIndex = -1; /* indicates that no peak is registered */ - inst->curPeakHeight = 0; - inst->curPeakPeriod = 0; - for (i = 0; i < NUM_PEAKS; i++) - { - inst->peakHeightPkt[i] = 0; - inst->peakPeriodSamp[i] = 0; - } - - /* - * Set the iatProb PDF vector to an exponentially decaying distribution - * iatProb[i] = 0.5^(i+1), i = 0, 1, 2, ... - * iatProb is in Q30. - */ - for (i = 0; i <= MAX_IAT; i++) - { - /* iatProb[i] = 0.5^(i+1) = iatProb[i-1] / 2 */ - tempprob = WEBRTC_SPL_RSHIFT_U16(tempprob, 1); - /* store in PDF vector */ - inst->iatProb[i] = WEBRTC_SPL_LSHIFT_W32((int32_t) tempprob, 16); - } - - /* - * Calculate the optimal buffer level corresponding to the initial PDF. - * No need to call WebRtcNetEQ_CalcOptimalBufLvl() since we have just hard-coded - * all the variables that the buffer level depends on => we know the result - */ - inst->optBufLevel = WEBRTC_SPL_MIN(4, - (maxBufLenPackets >> 1) + (maxBufLenPackets >> 1)); /* 75% of maxBufLenPackets */ - inst->required_delay_q8 = inst->optBufLevel; - inst->levelFiltFact = 253; - - /* - * Reset the iat update forgetting factor to 0 to make the impact of the first - * incoming packets greater. - */ - inst->iatProbFact = 0; - - /* Reset packet inter-arrival time counter */ - inst->packetIatCountSamp = 0; - - /* Clear time-scaling related variables */ - inst->prevTimeScale = 0; - inst->timescaleHoldOff = AUTOMODE_TIMESCALE_LIMIT; /* don't allow time-scaling immediately */ - - inst->cSumIatQ8 = 0; - inst->maxCSumIatQ8 = 0; - - return 0; -} - -int32_t WebRtcNetEQ_AverageIAT(const AutomodeInst_t *inst) { - int i; - int32_t sum_q24 = 0; - assert(inst); - for (i = 0; i <= MAX_IAT; ++i) { - /* Shift 6 to fit worst case: 2^30 * 64. */ - sum_q24 += (inst->iatProb[i] >> 6) * i; - } - /* Subtract the nominal inter-arrival time 1 = 2^24 in Q24. */ - sum_q24 -= (1 << 24); - /* - * Multiply with 1000000 / 2^24 = 15625 / 2^18 to get in parts-per-million. - * Shift 7 to Q17 first, then multiply with 15625 and shift another 11. - */ - return ((sum_q24 >> 7) * 15625) >> 11; -} diff --git a/webrtc/modules/audio_coding/neteq/automode.h b/webrtc/modules/audio_coding/neteq/automode.h deleted file mode 100644 index c5dd829b8..000000000 --- a/webrtc/modules/audio_coding/neteq/automode.h +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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 file contains the functionality for automatic buffer level optimization. - */ - -#ifndef AUTOMODE_H -#define AUTOMODE_H - -#include "typedefs.h" - -/*************/ -/* Constants */ -/*************/ - -/* The beta parameter defines the trade-off between delay and underrun probability. */ -/* It is defined through its inverse in Q30 */ -#define AUTOMODE_BETA_INV_Q30 53687091 /* 1/20 in Q30 */ -#define AUTOMODE_STREAMING_BETA_INV_Q30 536871 /* 1/2000 in Q30 */ - -/* Forgetting factor for the inter-arrival time statistics */ -#define IAT_PROB_FACT 32745 /* 0.9993 in Q15 */ - -/* Maximum inter-arrival time to register (in "packet-times") */ -#define MAX_IAT 64 -#define PEAK_HEIGHT 20 /* 0.08s in Q8 */ - -/* The value (1<<5) sets maximum accelerate "speed" to about 100 ms/s */ -#define AUTOMODE_TIMESCALE_LIMIT (1<<5) - -/* Peak mode related parameters */ -/* Number of peaks in peak vector; must be a power of 2 */ -#define NUM_PEAKS 8 - -/* Must be NUM_PEAKS-1 */ -#define PEAK_INDEX_MASK 0x0007 - -/* Longest accepted peak distance */ -#define MAX_PEAK_PERIOD 10 -#define MAX_STREAMING_PEAK_PERIOD 600 /* 10 minutes */ - -/* Number of peaks required before peak mode can be engaged */ -#define NUM_PEAKS_REQUIRED 3 - -/* Drift term for cumulative sum */ -#define CSUM_IAT_DRIFT 2 - -/*******************/ -/* Automode struct */ -/*******************/ - -/* The automode struct is a sub-struct of the - bufstats-struct (BufstatsInst_t). */ - -typedef struct -{ - - /* Filtered current buffer level */ - uint16_t levelFiltFact; /* filter forgetting factor in Q8 */ - int buffLevelFilt; /* filtered buffer level in Q8 */ - - /* Inter-arrival time (iat) statistics */ - int32_t iatProb[MAX_IAT + 1]; /* iat probabilities in Q30 */ - int16_t iatProbFact; /* iat forgetting factor in Q15 */ - uint32_t packetIatCountSamp; /* time (in timestamps) elapsed since last - packet arrival, based on RecOut calls */ - int optBufLevel; /* current optimal buffer level in Q8 */ - - /* Packet related information */ - int16_t packetSpeechLenSamp; /* speech samples per incoming packet */ - int16_t lastPackCNGorDTMF; /* indicates that the last received packet - contained special information */ - uint16_t lastSeqNo; /* sequence number for last packet received */ - uint32_t lastTimeStamp; /* timestamp for the last packet received */ - int firstPacketReceived; /* set to zero implicitly when the instance is - filled with zeros */ - int32_t sampleMemory; /* memory position for keeping track of how many - samples we cut during expand */ - int16_t prevTimeScale; /* indicates that the last mode was an accelerate - or pre-emptive expand operation */ - uint32_t timescaleHoldOff; /* counter that is shifted one step right each - RecOut call; time-scaling allowed when it has - reached 0 */ - int16_t extraDelayMs; /* extra delay for sync with video */ - - int minimum_delay_ms; /* Desired delay, NetEq maintains this amount of - delay unless jitter statistics suggests a higher value. */ - int maximum_delay_ms; /* Max desired delay, NetEq will not go above this - amount of delay even if jitter statistics suggests a higher value. */ - - int required_delay_q8; /* Smallest delay required. This is computed - according to inter-arrival time and playout mode. It has the same unit - as |optBufLevel|. */ - - /* Peak-detection */ - /* vector with the latest peak periods (peak spacing in samples) */ - uint32_t peakPeriodSamp[NUM_PEAKS]; - /* vector with the latest peak heights (in packets) */ - int16_t peakHeightPkt[NUM_PEAKS]; - int16_t peakIndex; /* index for the vectors peakPeriodSamp and peakHeightPkt; - -1 if still waiting for first peak */ - uint16_t peakThresholdPkt; /* definition of peak (in packets); - calculated from PEAK_HEIGHT */ - uint32_t peakIatCountSamp; /* samples elapsed since last peak was observed */ - uint32_t curPeakPeriod; /* current maximum of peakPeriodSamp vector */ - int16_t curPeakHeight; /* derived from peakHeightPkt vector; - used as optimal buffer level in peak mode */ - int16_t peakModeDisabled; /* ==0 if peak mode can be engaged; >0 if not */ - uint16_t peakFound; /* 1 if peaks are detected and extra delay is applied; - * 0 otherwise. */ - - /* Post-call statistics */ - uint32_t countIAT500ms; /* number of times we got small network outage */ - uint32_t countIAT1000ms; /* number of times we got medium network outage */ - uint32_t countIAT2000ms; /* number of times we got large network outage */ - uint32_t longestIATms; /* mSec duration of longest network outage */ - - int16_t cSumIatQ8; /* cumulative sum of inter-arrival times */ - int16_t maxCSumIatQ8; /* max cumulative sum IAT */ - uint32_t maxCSumUpdateTimer;/* time elapsed since maximum was observed */ -} AutomodeInst_t; - -/*************/ -/* Functions */ -/*************/ - -/**************************************************************************** - * WebRtcNetEQ_UpdateIatStatistics(...) - * - * Update the packet inter-arrival time statistics when a new packet arrives. - * This function should be called for every arriving packet, with some - * exceptions when using DTX/VAD and DTMF. A new optimal buffer level is - * calculated after the update. - * - * Input: - * - inst : Automode instance - * - maxBufLen : Maximum number of packets the buffer can hold - * - seqNumber : RTP sequence number of incoming packet - * - timeStamp : RTP timestamp of incoming packet - * - fsHz : Sample rate in Hz - * - mdCodec : Non-zero if the current codec is a multiple- - * description codec - * - streamingMode : A non-zero value will increase jitter robustness (and delay) - * - * Output: - * - inst : Updated automode instance - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_UpdateIatStatistics(AutomodeInst_t *inst, int maxBufLen, - uint16_t seqNumber, uint32_t timeStamp, - int32_t fsHz, int mdCodec, int streamingMode); - -/**************************************************************************** - * WebRtcNetEQ_CalcOptimalBufLvl(...) - * - * Calculate the optimal buffer level based on packet inter-arrival time - * statistics. - * - * Input: - * - inst : Automode instance - * - fsHz : Sample rate in Hz - * - mdCodec : Non-zero if the current codec is a multiple- - * description codec - * - timeIatPkts : Currently observed inter-arrival time in packets - * - streamingMode : A non-zero value will increase jitter robustness (and delay) - * - * Output: - * - inst : Updated automode instance - * - * Return value : >0 - Optimal buffer level - * <0 - Error - */ - -int16_t WebRtcNetEQ_CalcOptimalBufLvl(AutomodeInst_t *inst, int32_t fsHz, - int mdCodec, uint32_t timeIatPkts, - int streamingMode); - -/**************************************************************************** - * WebRtcNetEQ_BufferLevelFilter(...) - * - * Update filtered buffer level. The function must be called once for each - * RecOut call, since the timing of automode hinges on counters that are - * updated by this function. - * - * Input: - * - curSizeMs8 : Total length of unused speech data in packet buffer - * and sync buffer, in ms * 8 - * - inst : Automode instance - * - sampPerCall : Number of samples per RecOut call - * - fsMult : Sample rate in Hz divided by 8000 - * - * Output: - * - inst : Updated automode instance - * - * Return value : 0 - Ok - * : <0 - Error - */ - -int WebRtcNetEQ_BufferLevelFilter(int32_t curSizeMs8, AutomodeInst_t *inst, - int sampPerCall, int16_t fsMult); - -/**************************************************************************** - * WebRtcNetEQ_SetPacketSpeechLen(...) - * - * Provide the number of speech samples extracted from a packet to the - * automode instance. Several of the calculations within automode depend - * on knowing the packet size. - * - * - * Input: - * - inst : Automode instance - * - newLenSamp : Number of samples per RecOut call - * - fsHz : Sample rate in Hz - * - * Output: - * - inst : Updated automode instance - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_SetPacketSpeechLen(AutomodeInst_t *inst, int16_t newLenSamp, - int32_t fsHz); - -/**************************************************************************** - * WebRtcNetEQ_ResetAutomode(...) - * - * Reset the automode instance. - * - * - * Input: - * - inst : Automode instance - * - maxBufLenPackets : Maximum number of packets that the packet - * buffer can hold (>1) - * - * Output: - * - inst : Updated automode instance - * - * Return value : 0 - Ok - */ - -int WebRtcNetEQ_ResetAutomode(AutomodeInst_t *inst, int maxBufLenPackets); - -/**************************************************************************** - * WebRtcNetEQ_AverageIAT(...) - * - * Calculate the average inter-arrival time based on current statistics. - * The average is expressed in parts per million relative the nominal. That is, - * if the average inter-arrival time is equal to the nominal frame time, - * the return value is zero. A positive value corresponds to packet spacing - * being too large, while a negative value means that the packets arrive with - * less spacing than expected. - * - * - * Input: - * - inst : Automode instance. - * - * Return value : Average relative inter-arrival time in samples. - */ - -int32_t WebRtcNetEQ_AverageIAT(const AutomodeInst_t *inst); - -#endif /* AUTOMODE_H */ diff --git a/webrtc/modules/audio_coding/neteq4/background_noise.cc b/webrtc/modules/audio_coding/neteq/background_noise.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/background_noise.cc rename to webrtc/modules/audio_coding/neteq/background_noise.cc index 2dfb3c1f3..e00c4f65e 100644 --- a/webrtc/modules/audio_coding/neteq4/background_noise.cc +++ b/webrtc/modules/audio_coding/neteq/background_noise.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" #include #include // memcpy @@ -16,8 +16,8 @@ #include // min, max #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/post_decode_vad.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/post_decode_vad.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/background_noise.h b/webrtc/modules/audio_coding/neteq/background_noise.h similarity index 90% rename from webrtc/modules/audio_coding/neteq4/background_noise.h rename to webrtc/modules/audio_coding/neteq/background_noise.h index ac5446bf7..8fb310ea8 100644 --- a/webrtc/modules/audio_coding/neteq4/background_noise.h +++ b/webrtc/modules/audio_coding/neteq/background_noise.h @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_BACKGROUND_NOISE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_BACKGROUND_NOISE_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_BACKGROUND_NOISE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_BACKGROUND_NOISE_H_ #include // size_t -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" @@ -126,7 +126,7 @@ class BackgroundNoise { int32_t residual_energy); size_t num_channels_; - scoped_array channel_parameters_; + scoped_ptr channel_parameters_; bool initialized_; NetEqBackgroundNoiseMode mode_; @@ -134,4 +134,4 @@ class BackgroundNoise { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_BACKGROUND_NOISE_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_BACKGROUND_NOISE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/background_noise_unittest.cc b/webrtc/modules/audio_coding/neteq/background_noise_unittest.cc similarity index 91% rename from webrtc/modules/audio_coding/neteq4/background_noise_unittest.cc rename to webrtc/modules/audio_coding/neteq/background_noise_unittest.cc index eb7b9fa1e..0aee62c97 100644 --- a/webrtc/modules/audio_coding/neteq4/background_noise_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/background_noise_unittest.cc @@ -10,7 +10,7 @@ // Unit tests for BackgroundNoise class. -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" #include "gtest/gtest.h" diff --git a/webrtc/modules/audio_coding/neteq/bgn_update.c b/webrtc/modules/audio_coding/neteq/bgn_update.c deleted file mode 100644 index 4d660ff55..000000000 --- a/webrtc/modules/audio_coding/neteq/bgn_update.c +++ /dev/null @@ -1,247 +0,0 @@ -/* - * 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 file contains the function for updating the background noise estimate. - */ - -#include "dsp.h" - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" - -/* Scratch usage: - Designed for BGN_LPC_ORDER <= 10 - - Type Name size startpos endpos - int32_t pw32_autoCorr 22 0 21 (Length (BGN_LPC_ORDER + 1)*2) - int16_t pw16_tempVec 10 22 31 (Length BGN_LPC_ORDER) - int16_t pw16_rc 10 32 41 (Length BGN_LPC_ORDER) - int16_t pw16_outVec 74 0 73 (Length BGN_LPC_ORDER + 64) - - Total: 74 - */ - -#if (BGN_LPC_ORDER > 10) && (defined SCRATCH) -#error BGN_LPC_ORDER is too large for current scratch memory allocation -#endif - -#define SCRATCH_PW32_AUTO_CORR 0 -#define SCRATCH_PW16_TEMP_VEC 22 -#define SCRATCH_PW16_RC 32 -#define SCRATCH_PW16_OUT_VEC 0 - -#define NETEQFIX_BGNFRAQINCQ16 229 /* 0.0035 in Q16 */ - -/**************************************************************************** - * WebRtcNetEQ_BGNUpdate(...) - * - * This function updates the background noise parameter estimates. - * - * Input: - * - inst : NetEQ instance, where the speech history is stored. - * - scratchPtr : Pointer to scratch vector. - * - * Output: - * - inst : Updated information about the BGN characteristics. - * - * Return value : No return value - */ - -void WebRtcNetEQ_BGNUpdate( -#ifdef SCRATCH - DSPInst_t *inst, int16_t *pw16_scratchPtr -#else - DSPInst_t *inst -#endif -) -{ - const int16_t w16_vecLen = 256; - BGNInst_t *BGN_Inst = &(inst->BGNInst); -#ifdef SCRATCH - int32_t *pw32_autoCorr = (int32_t*) (pw16_scratchPtr + SCRATCH_PW32_AUTO_CORR); - int16_t *pw16_tempVec = pw16_scratchPtr + SCRATCH_PW16_TEMP_VEC; - int16_t *pw16_rc = pw16_scratchPtr + SCRATCH_PW16_RC; - int16_t *pw16_outVec = pw16_scratchPtr + SCRATCH_PW16_OUT_VEC; -#else - int32_t pw32_autoCorr[BGN_LPC_ORDER + 1]; - int16_t pw16_tempVec[BGN_LPC_ORDER]; - int16_t pw16_outVec[BGN_LPC_ORDER + 64]; - int16_t pw16_rc[BGN_LPC_ORDER]; -#endif - int16_t pw16_A[BGN_LPC_ORDER + 1]; - int32_t w32_tmp; - int16_t *pw16_vec; - int16_t w16_maxSample; - int16_t w16_tmp, w16_tmp2; - int16_t w16_enSampleShift; - int32_t w32_en, w32_enBGN; - int32_t w32_enUpdateThreashold; - int16_t stability; - - pw16_vec = inst->pw16_speechHistory + inst->w16_speechHistoryLen - w16_vecLen; - -#ifdef NETEQ_VAD - if( !inst->VADInst.VADEnabled /* we are not using post-decode VAD */ - || inst->VADInst.VADDecision == 0 ) - { /* ... or, post-decode VAD says passive speaker */ -#endif /* NETEQ_VAD */ - - /*Insert zeros to guarantee that boundary values do not distort autocorrelation */ - WEBRTC_SPL_MEMCPY_W16(pw16_tempVec, pw16_vec - BGN_LPC_ORDER, BGN_LPC_ORDER); - WebRtcSpl_MemSetW16(pw16_vec - BGN_LPC_ORDER, 0, BGN_LPC_ORDER); - - w16_maxSample = WebRtcSpl_MaxAbsValueW16(pw16_vec, w16_vecLen); - w16_tmp = 8 /* log2(w16_veclen) = 8 */ - - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_maxSample, w16_maxSample)); - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - WebRtcNetEQ_CrossCorr(pw32_autoCorr, pw16_vec, pw16_vec, w16_vecLen, BGN_LPC_ORDER + 1, - w16_tmp, -1); - - /* Copy back data */ - WEBRTC_SPL_MEMCPY_W16(pw16_vec - BGN_LPC_ORDER, pw16_tempVec, BGN_LPC_ORDER); - - w16_enSampleShift = 8 - w16_tmp; /* Number of shifts to get energy/sample */ - /* pw32_autoCorr[0]>>w16_enSampleShift */ - w32_en = WEBRTC_SPL_RSHIFT_W32(pw32_autoCorr[0], w16_enSampleShift); - if ((w32_en < BGN_Inst->w32_energyUpdate -#ifdef NETEQ_VAD - /* post-decode VAD disabled and w32_en sufficiently low */ - && !inst->VADInst.VADEnabled) - /* ... or, post-decode VAD says passive speaker */ - || (inst->VADInst.VADEnabled && inst->VADInst.VADDecision == 0) -#else - ) /* just close the extra parenthesis */ -#endif /* NETEQ_VAD */ - ) - { - /* Generate LPC coefficients */ - if (pw32_autoCorr[0] > 0) - { - /* regardless of whether the filter is actually updated or not, - update energy threshold levels, since we have in fact observed - a low energy signal */ - if (w32_en < BGN_Inst->w32_energyUpdate) - { - /* Never get under 1.0 in average sample energy */ - BGN_Inst->w32_energyUpdate = WEBRTC_SPL_MAX(w32_en, 1); - BGN_Inst->w32_energyUpdateLow = 0; - } - - stability = WebRtcSpl_LevinsonDurbin(pw32_autoCorr, pw16_A, pw16_rc, BGN_LPC_ORDER); - /* Only update BGN if filter is stable */ - if (stability != 1) - { - return; - } - } - else - { - /* Do not update */ - return; - } - /* Generate the CNG gain factor by looking at the energy of the residual */ - WebRtcSpl_FilterMAFastQ12(pw16_vec + w16_vecLen - 64, pw16_outVec, pw16_A, - BGN_LPC_ORDER + 1, 64); - w32_enBGN = WebRtcNetEQ_DotW16W16(pw16_outVec, pw16_outVec, 64, 0); - /* Dot product should never overflow since it is BGN and residual! */ - - /* - * Check spectral flatness - * Comparing the residual variance with the input signal variance tells - * if the spectrum is flat or not. - * (20*w32_enBGN) >= (w32_en<<6) - * Also ensure that the energy is non-zero. - */ - if ((WEBRTC_SPL_MUL_32_16(w32_enBGN, 20) >= WEBRTC_SPL_LSHIFT_W32(w32_en, 6)) - && (w32_en > 0)) - { - /* spectrum is flat enough; save filter parameters */ - - WEBRTC_SPL_MEMCPY_W16(BGN_Inst->pw16_filter, pw16_A, BGN_LPC_ORDER+1); - WEBRTC_SPL_MEMCPY_W16(BGN_Inst->pw16_filterState, - pw16_vec + w16_vecLen - BGN_LPC_ORDER, BGN_LPC_ORDER); - - /* Save energy level */ - BGN_Inst->w32_energy = WEBRTC_SPL_MAX(w32_en, 1); - - /* Update energy threshold levels */ - /* Never get under 1.0 in average sample energy */ - BGN_Inst->w32_energyUpdate = WEBRTC_SPL_MAX(w32_en, 1); - BGN_Inst->w32_energyUpdateLow = 0; - - /* Normalize w32_enBGN to 29 or 30 bits before sqrt */ - w16_tmp2 = WebRtcSpl_NormW32(w32_enBGN) - 1; - if (w16_tmp2 & 0x1) - { - w16_tmp2 -= 1; /* Even number of shifts required */ - } - w32_enBGN = WEBRTC_SPL_SHIFT_W32(w32_enBGN, w16_tmp2); - - /* Calculate scale and shift factor */ - BGN_Inst->w16_scale = (int16_t) WebRtcSpl_SqrtFloor(w32_enBGN); - BGN_Inst->w16_scaleShift = 13 + ((6 + w16_tmp2) >> 1); /* RANDN table is in Q13, */ - /* 6=log2(64) */ - - BGN_Inst->w16_initialized = 1; - } - - } - else - { - /* - * Will only happen if post-decode VAD is disabled and w32_en is not low enough. - * Increase the threshold for update so that it increases by a factor 4 in four - * seconds. - * energy = energy * 1.0035 - */ - w32_tmp = WEBRTC_SPL_MUL_16_16_RSFT(NETEQFIX_BGNFRAQINCQ16, - BGN_Inst->w32_energyUpdateLow, 16); - w32_tmp += WEBRTC_SPL_MUL_16_16(NETEQFIX_BGNFRAQINCQ16, - (int16_t)(BGN_Inst->w32_energyUpdate & 0xFF)); - w32_tmp += (WEBRTC_SPL_MUL_16_16(NETEQFIX_BGNFRAQINCQ16, - (int16_t)((BGN_Inst->w32_energyUpdate>>8) & 0xFF)) << 8); - BGN_Inst->w32_energyUpdateLow += w32_tmp; - - BGN_Inst->w32_energyUpdate += WEBRTC_SPL_MUL_16_16(NETEQFIX_BGNFRAQINCQ16, - (int16_t)(BGN_Inst->w32_energyUpdate>>16)); - BGN_Inst->w32_energyUpdate += BGN_Inst->w32_energyUpdateLow >> 16; - BGN_Inst->w32_energyUpdateLow = (BGN_Inst->w32_energyUpdateLow & 0x0FFFF); - - /* Update maximum energy */ - /* Decrease by a factor 1/1024 each time */ - BGN_Inst->w32_energyMax = BGN_Inst->w32_energyMax - (BGN_Inst->w32_energyMax >> 10); - if (w32_en > BGN_Inst->w32_energyMax) - { - BGN_Inst->w32_energyMax = w32_en; - } - - /* Set update level to at the minimum 60.21dB lower then the maximum energy */ - w32_enUpdateThreashold = (BGN_Inst->w32_energyMax + 524288) >> 20; - if (w32_enUpdateThreashold > BGN_Inst->w32_energyUpdate) - { - BGN_Inst->w32_energyUpdate = w32_enUpdateThreashold; - } - } - -#ifdef NETEQ_VAD -} /* closing initial if-statement */ -#endif /* NETEQ_VAD */ - - return; -} - -#undef SCRATCH_PW32_AUTO_CORR -#undef SCRATCH_PW16_TEMP_VEC -#undef SCRATCH_PW16_RC -#undef SCRATCH_PW16_OUT_VEC - diff --git a/webrtc/modules/audio_coding/neteq4/buffer_level_filter.cc b/webrtc/modules/audio_coding/neteq/buffer_level_filter.cc similarity index 96% rename from webrtc/modules/audio_coding/neteq4/buffer_level_filter.cc rename to webrtc/modules/audio_coding/neteq/buffer_level_filter.cc index 70b493106..0388b1950 100644 --- a/webrtc/modules/audio_coding/neteq4/buffer_level_filter.cc +++ b/webrtc/modules/audio_coding/neteq/buffer_level_filter.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" #include // Provide access to std::max. diff --git a/webrtc/modules/audio_coding/neteq4/buffer_level_filter.h b/webrtc/modules/audio_coding/neteq/buffer_level_filter.h similarity index 85% rename from webrtc/modules/audio_coding/neteq4/buffer_level_filter.h rename to webrtc/modules/audio_coding/neteq/buffer_level_filter.h index 282ab7a22..48f7f564c 100644 --- a/webrtc/modules/audio_coding/neteq4/buffer_level_filter.h +++ b/webrtc/modules/audio_coding/neteq/buffer_level_filter.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_BUFFER_LEVEL_FILTER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_BUFFER_LEVEL_FILTER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_BUFFER_LEVEL_FILTER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_BUFFER_LEVEL_FILTER_H_ -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" namespace webrtc { @@ -44,4 +44,4 @@ class BufferLevelFilter { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_BUFFER_LEVEL_FILTER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_BUFFER_LEVEL_FILTER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/buffer_level_filter_unittest.cc b/webrtc/modules/audio_coding/neteq/buffer_level_filter_unittest.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/buffer_level_filter_unittest.cc rename to webrtc/modules/audio_coding/neteq/buffer_level_filter_unittest.cc index ddaf08d1d..9589099d4 100644 --- a/webrtc/modules/audio_coding/neteq4/buffer_level_filter_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/buffer_level_filter_unittest.cc @@ -10,7 +10,7 @@ // Unit tests for BufferLevelFilter class. -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" #include // Access to pow function. diff --git a/webrtc/modules/audio_coding/neteq/buffer_stats.h b/webrtc/modules/audio_coding/neteq/buffer_stats.h deleted file mode 100644 index 722f477ea..000000000 --- a/webrtc/modules/audio_coding/neteq/buffer_stats.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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. - */ - -/* - * Calculates and stores the packet buffer statistics. - */ - -#ifndef BUFFER_STATS_H -#define BUFFER_STATS_H - -#include "automode.h" -#include "webrtc_neteq.h" /* to define enum WebRtcNetEQPlayoutMode */ - -/* NetEQ related decisions */ -#define BUFSTATS_DO_NORMAL 0 -#define BUFSTATS_DO_ACCELERATE 1 -#define BUFSTATS_DO_MERGE 2 -#define BUFSTATS_DO_EXPAND 3 -#define BUFSTAT_REINIT 4 -#define BUFSTATS_DO_RFC3389CNG_PACKET 5 -#define BUFSTATS_DO_RFC3389CNG_NOPACKET 6 -#define BUFSTATS_DO_INTERNAL_CNG_NOPACKET 7 -#define BUFSTATS_DO_PREEMPTIVE_EXPAND 8 -#define BUFSTAT_REINIT_DECODER 9 -#define BUFSTATS_DO_DTMF_ONLY 10 -/* Decisions related to when NetEQ is switched off (or in FAX mode) */ -#define BUFSTATS_DO_ALTERNATIVE_PLC 11 -#define BUFSTATS_DO_ALTERNATIVE_PLC_INC_TS 12 -#define BUFSTATS_DO_AUDIO_REPETITION 13 -#define BUFSTATS_DO_AUDIO_REPETITION_INC_TS 14 - -/* Reinit decoder states after this number of expands (upon arrival of new packet) */ -#define REINIT_AFTER_EXPANDS 100 - -/* Wait no longer than this number of RecOut calls before using an "early" packet */ -#define MAX_WAIT_FOR_PACKET 10 - -/* CNG modes */ -#define CNG_OFF 0 -#define CNG_RFC3389_ON 1 -#define CNG_INTERNAL_ON 2 - -typedef struct -{ - - /* store statistical data here */ - int16_t w16_cngOn; /* remember if CNG is interrupted by other event (e.g. DTMF) */ - int16_t w16_noExpand; - int32_t uw32_CNGplayedTS; - - /* VQmon data */ - uint16_t avgDelayMsQ8; - int16_t maxDelayMs; - - AutomodeInst_t Automode_inst; - -} BufstatsInst_t; - -/**************************************************************************** - * WebRtcNetEQ_BufstatsDecision() - * - * Gives a decision about what action that is currently desired - * - * - * Input: - * inst: The bufstat instance - * cur_size: Current buffer size in ms in Q3 domain - * targetTS: The desired timestamp to start playout from - * availableTS: The closest future value available in buffer - * noPacket 1 if no packet is available, makes availableTS undefined - * prevPlayMode mode of last NetEq playout - * timestampsPerCall number of timestamp for 10ms - * - * Output: - * Returns: A decision, as defined above (see top of file) - * - */ - -uint16_t WebRtcNetEQ_BufstatsDecision(BufstatsInst_t *inst, int16_t frameSize, - int32_t cur_size, uint32_t targetTS, - uint32_t availableTS, int noPacket, - int cngPacket, int prevPlayMode, - enum WebRtcNetEQPlayoutMode playoutMode, - int timestampsPerCall, int NoOfExpandCalls, - int16_t fs_mult, - int16_t lastModeBGNonly, int playDtmf); - -#endif diff --git a/webrtc/modules/audio_coding/neteq/bufstats_decision.c b/webrtc/modules/audio_coding/neteq/bufstats_decision.c deleted file mode 100644 index 352e05077..000000000 --- a/webrtc/modules/audio_coding/neteq/bufstats_decision.c +++ /dev/null @@ -1,427 +0,0 @@ -/* - * 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 file contains the function where the main decision logic for buffer level - * adaptation happens. - */ - -#include "buffer_stats.h" - -#include - -#include "signal_processing_library.h" - -#include "automode.h" -#include "neteq_defines.h" -#include "neteq_error_codes.h" -#include "webrtc_neteq.h" - -#define NETEQ_BUFSTAT_20MS_Q7 2560 /* = 20 ms in Q7 */ - -uint16_t WebRtcNetEQ_BufstatsDecision(BufstatsInst_t *inst, int16_t frameSize, - int32_t cur_size, uint32_t targetTS, - uint32_t availableTS, int noPacket, - int cngPacket, int prevPlayMode, - enum WebRtcNetEQPlayoutMode playoutMode, - int timestampsPerCall, int NoOfExpandCalls, - int16_t fs_mult, - int16_t lastModeBGNonly, int playDtmf) -{ - - int currentDelayMs; - int32_t currSizeSamples = cur_size; - int extraDelayPacketsQ8 = 0; - - /* Avoid overflow if the buffer size should be really large (cur_size is limited 256ms) */ - int32_t curr_sizeQ7 = WEBRTC_SPL_LSHIFT_W32(cur_size, 4); - int level_limit_hi, level_limit_lo; - - inst->Automode_inst.prevTimeScale &= (prevPlayMode == MODE_SUCCESS_ACCELERATE - || prevPlayMode == MODE_LOWEN_ACCELERATE || prevPlayMode == MODE_SUCCESS_PREEMPTIVE - || prevPlayMode == MODE_LOWEN_PREEMPTIVE); - - if ((prevPlayMode != MODE_RFC3389CNG) && (prevPlayMode != MODE_CODEC_INTERNAL_CNG)) - { - /* - * Do not update buffer history if currently playing CNG - * since it will bias the filtered buffer level. - */ - WebRtcNetEQ_BufferLevelFilter(cur_size, &(inst->Automode_inst), timestampsPerCall, - fs_mult); - } - else - { - /* only update time counters */ - inst->Automode_inst.packetIatCountSamp += timestampsPerCall; /* packet inter-arrival time */ - inst->Automode_inst.peakIatCountSamp += timestampsPerCall; /* peak inter-arrival time */ - inst->Automode_inst.timescaleHoldOff >>= 1; /* time-scaling limiter */ - } - cur_size = WEBRTC_SPL_MIN(curr_sizeQ7, WEBRTC_SPL_WORD16_MAX); - - /* Calculate VQmon related variables */ - /* avgDelay = avgDelay*(511/512) + currentDelay*(1/512) (sample ms delay in Q8) */ - inst->avgDelayMsQ8 = (int16_t) (WEBRTC_SPL_MUL_16_16_RSFT(inst->avgDelayMsQ8,511,9) - + (cur_size >> 9)); - - /* Update maximum delay if needed */ - currentDelayMs = (curr_sizeQ7 >> 7); - if (currentDelayMs > inst->maxDelayMs) - { - inst->maxDelayMs = currentDelayMs; - } - - /* NetEQ is on with normal or steaming mode */ - if (playoutMode == kPlayoutOn || playoutMode == kPlayoutStreaming) - { - /* Guard for errors, so that it should not get stuck in error mode */ - if (prevPlayMode == MODE_ERROR) - { - if (noPacket) - { - return BUFSTATS_DO_EXPAND; - } - else - { - return BUFSTAT_REINIT; - } - } - - if (prevPlayMode != MODE_EXPAND && prevPlayMode != MODE_FADE_TO_BGN) - { - inst->w16_noExpand = 1; - } - else - { - inst->w16_noExpand = 0; - } - - if (cngPacket) - { - /* signed difference between wanted and available TS */ - int32_t diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS; - int32_t optimal_level_samp = (inst->Automode_inst.optBufLevel * - inst->Automode_inst.packetSpeechLenSamp) >> 8; - int32_t excess_waiting_time_samp = -diffTS - optimal_level_samp; - - if (excess_waiting_time_samp > optimal_level_samp / 2) - { - /* The waiting time for this packet will be longer than 1.5 - * times the wanted buffer delay. Advance the clock to cut - * waiting time down to the optimal. - */ - inst->uw32_CNGplayedTS += excess_waiting_time_samp; - diffTS += excess_waiting_time_samp; - } - - if ((diffTS) < 0 && (prevPlayMode == MODE_RFC3389CNG)) - { - /* Not time to play this packet yet. Wait another round before using this - * packet. Keep on playing CNG from previous CNG parameters. */ - return BUFSTATS_DO_RFC3389CNG_NOPACKET; - } - - /* otherwise, go for the CNG packet now */ - return BUFSTATS_DO_RFC3389CNG_PACKET; - } - - /*Check for expand/cng */ - if (noPacket) - { - if (inst->w16_cngOn == CNG_RFC3389_ON) - { - /* keep on playing CNG */ - return BUFSTATS_DO_RFC3389CNG_NOPACKET; - } - else if (inst->w16_cngOn == CNG_INTERNAL_ON) - { - /* keep on playing internal CNG */ - return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; - } - else if (playDtmf == 1) - { - /* we have not audio data, but can play DTMF */ - return BUFSTATS_DO_DTMF_ONLY; - } - else - { - /* nothing to play => do Expand */ - return BUFSTATS_DO_EXPAND; - } - } - - /* - * If the expand period was very long, reset NetEQ since it is likely that the - * sender was restarted. - */ - if (NoOfExpandCalls > REINIT_AFTER_EXPANDS) return BUFSTAT_REINIT_DECODER; - - /* Calculate extra delay in Q8 packets */ - if (inst->Automode_inst.extraDelayMs > 0 && inst->Automode_inst.packetSpeechLenSamp - > 0) - { - - /* (extra delay in samples in Q8) */ - extraDelayPacketsQ8 = - ((inst->Automode_inst.extraDelayMs * 8 * fs_mult) << 8) / - inst->Automode_inst.packetSpeechLenSamp; - } - - /* Check if needed packet is available */ - if (targetTS == availableTS) - { - - /* If last mode was not expand, and there is no DTMF to play */ - if (inst->w16_noExpand == 1 && playDtmf == 0) - { - /* If so check for accelerate */ - - level_limit_lo = ((inst->Automode_inst.optBufLevel) >> 1) /* 50 % */ - + ((inst->Automode_inst.optBufLevel) >> 2); /* ... + 25% = 75% */ - - /* set upper limit to optBufLevel, but make sure that window is at least 20ms */ - level_limit_hi = WEBRTC_SPL_MAX(inst->Automode_inst.optBufLevel, - level_limit_lo + - WebRtcSpl_DivW32W16ResW16((WEBRTC_SPL_MUL(20*8, fs_mult) << 8), - inst->Automode_inst.packetSpeechLenSamp)); - - /* if extra delay is non-zero, add it */ - if (extraDelayPacketsQ8 > 0) - { - level_limit_hi += extraDelayPacketsQ8; - level_limit_lo += extraDelayPacketsQ8; - } - - if (((inst->Automode_inst.buffLevelFilt >= level_limit_hi) && - (inst->Automode_inst.timescaleHoldOff == 0)) || - (inst->Automode_inst.buffLevelFilt >= level_limit_hi << 2)) - { - /* - * Buffer level higher than limit and time-scaling allowed, - * OR buffer level _really_ high. - */ - return BUFSTATS_DO_ACCELERATE; - } - else if ((inst->Automode_inst.buffLevelFilt < level_limit_lo) - && (inst->Automode_inst.timescaleHoldOff == 0)) - { - return BUFSTATS_DO_PREEMPTIVE_EXPAND; - } - } - return BUFSTATS_DO_NORMAL; - } - - /* Check for Merge */ - else if (availableTS > targetTS) - { - - /* Check that we do not play a packet "too early" */ - if ((prevPlayMode == MODE_EXPAND) - && (availableTS - targetTS - < (uint32_t) WEBRTC_SPL_MUL_16_16((int16_t)timestampsPerCall, - (int16_t)REINIT_AFTER_EXPANDS)) - && (NoOfExpandCalls < MAX_WAIT_FOR_PACKET) - && (availableTS - > targetTS - + WEBRTC_SPL_MUL_16_16((int16_t)timestampsPerCall, - (int16_t)NoOfExpandCalls)) - && (inst->Automode_inst.buffLevelFilt <= inst->Automode_inst.optBufLevel - + extraDelayPacketsQ8)) - { - if (playDtmf == 1) - { - /* we still have DTMF to play, so do not perform expand */ - return BUFSTATS_DO_DTMF_ONLY; - } - else - { - /* nothing to play */ - return BUFSTATS_DO_EXPAND; - } - } - - /* If previous was CNG period or BGNonly then no merge is needed */ - if ((prevPlayMode == MODE_RFC3389CNG) || (prevPlayMode == MODE_CODEC_INTERNAL_CNG) - || lastModeBGNonly) - { - /* - * Keep the same delay as before the CNG (or maximum 70 ms in buffer as safety - * precaution), but make sure that the number of samples in buffer is no - * higher than 4 times the optimal level. - */ - int32_t diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS; - int val = ((inst->Automode_inst.optBufLevel + - extraDelayPacketsQ8) * - inst->Automode_inst.packetSpeechLenSamp) >> 6; - if (diffTS >= 0 || val < currSizeSamples) - { - /* it is time to play this new packet */ - return BUFSTATS_DO_NORMAL; - } - else - { - /* it is too early to play this new packet => keep on playing CNG */ - if (prevPlayMode == MODE_RFC3389CNG) - { - return BUFSTATS_DO_RFC3389CNG_NOPACKET; - } - else if (prevPlayMode == MODE_CODEC_INTERNAL_CNG) - { - return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; - } - else if (playDtmf == 1) - { - /* we have not audio data, but can play DTMF */ - return BUFSTATS_DO_DTMF_ONLY; - } - else /* lastModeBGNonly */ - { - /* signal expand, but this will result in BGN again */ - return BUFSTATS_DO_EXPAND; - } - } - } - - /* Do not merge unless we have done a Expand before (for complexity reasons) */ - if ((inst->w16_noExpand == 0) || ((frameSize < timestampsPerCall) && (cur_size - > NETEQ_BUFSTAT_20MS_Q7))) - { - return BUFSTATS_DO_MERGE; - } - else if (playDtmf == 1) - { - /* play DTMF instead of expand */ - return BUFSTATS_DO_DTMF_ONLY; - } - else - { - return BUFSTATS_DO_EXPAND; - } - } - } - else - { /* kPlayoutOff or kPlayoutFax */ - if (cngPacket) - { - if (((int32_t) ((inst->uw32_CNGplayedTS + targetTS) - availableTS)) >= 0) - { - /* time to play this packet now */ - return BUFSTATS_DO_RFC3389CNG_PACKET; - } - else - { - /* wait before playing this packet */ - return BUFSTATS_DO_RFC3389CNG_NOPACKET; - } - } - if (noPacket) - { - /* - * No packet => - * 1. If in CNG mode play as usual - * 2. Otherwise use other method to generate data and hold TS value - */ - if (inst->w16_cngOn == CNG_RFC3389_ON) - { - /* keep on playing CNG */ - return BUFSTATS_DO_RFC3389CNG_NOPACKET; - } - else if (inst->w16_cngOn == CNG_INTERNAL_ON) - { - /* keep on playing internal CNG */ - return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; - } - else - { - /* nothing to play => invent some data to play out */ - if (playoutMode == kPlayoutOff) - { - return BUFSTATS_DO_ALTERNATIVE_PLC; - } - else if (playoutMode == kPlayoutFax) - { - return BUFSTATS_DO_AUDIO_REPETITION; - } - else - { - /* UNDEFINED, should not get here... */ - assert(0); - return BUFSTAT_REINIT; - } - } - } - else if (targetTS == availableTS) - { - return BUFSTATS_DO_NORMAL; - } - else - { - if (((int32_t) ((inst->uw32_CNGplayedTS + targetTS) - availableTS)) >= 0) - { - return BUFSTATS_DO_NORMAL; - } - else if (playoutMode == kPlayoutOff) - { - /* - * If currently playing CNG, continue with that. Don't increase TS - * since uw32_CNGplayedTS will be increased. - */ - if (inst->w16_cngOn == CNG_RFC3389_ON) - { - return BUFSTATS_DO_RFC3389CNG_NOPACKET; - } - else if (inst->w16_cngOn == CNG_INTERNAL_ON) - { - return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; - } - else - { - /* - * Otherwise, do PLC and increase TS while waiting for the time to - * play this packet. - */ - return BUFSTATS_DO_ALTERNATIVE_PLC_INC_TS; - } - } - else if (playoutMode == kPlayoutFax) - { - /* - * If currently playing CNG, continue with that don't increase TS since - * uw32_CNGplayedTS will be increased. - */ - if (inst->w16_cngOn == CNG_RFC3389_ON) - { - return BUFSTATS_DO_RFC3389CNG_NOPACKET; - } - else if (inst->w16_cngOn == CNG_INTERNAL_ON) - { - return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; - } - else - { - /* - * Otherwise, do audio repetition and increase TS while waiting for the - * time to play this packet. - */ - return BUFSTATS_DO_AUDIO_REPETITION_INC_TS; - } - } - else - { - /* UNDEFINED, should not get here... */ - assert(0); - return BUFSTAT_REINIT; - } - } - } - /* We should not get here (but sometimes we do anyway...) */ - return BUFSTAT_REINIT; -} - diff --git a/webrtc/modules/audio_coding/neteq/cng_internal.c b/webrtc/modules/audio_coding/neteq/cng_internal.c deleted file mode 100644 index cb4878fee..000000000 --- a/webrtc/modules/audio_coding/neteq/cng_internal.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 file contains the function for obtaining comfort noise from noise parameters - * according to IETF RFC 3389. - */ - -#include "dsp.h" - -#include "signal_processing_library.h" -#include "webrtc_cng.h" - -#include "dsp_helpfunctions.h" -#include "neteq_error_codes.h" - -/**************************************************************************** - * WebRtcNetEQ_Cng(...) - * - * This function produces CNG according to RFC 3389. - * - * Input: - * - inst : NetEQ DSP instance - * - len : Number of samples to produce (max 640 or - * 640 - fsHz*5/8000 for first-time CNG, governed by - * the definition of WEBRTC_CNG_MAX_OUTSIZE_ORDER in - * webrtc_cng.h) - * - * Output: - * - pw16_outData : Output CNG - * - * Return value : 0 - Ok - * <0 - Error - */ - -#ifdef NETEQ_CNG_CODEC -/* Must compile NetEQ with CNG support to enable this function */ - -int WebRtcNetEQ_Cng(DSPInst_t *inst, int16_t *pw16_outData, int len) -{ - int16_t w16_winMute = 0; /* mixing factor for overlap data */ - int16_t w16_winUnMute = 0; /* mixing factor for comfort noise */ - int16_t w16_winMuteInc = 0; /* mixing factor increment (negative) */ - int16_t w16_winUnMuteInc = 0; /* mixing factor increment */ - int i; - - /* - * Check if last RecOut call was other than RFC3389, - * that is, this call is the first of a CNG period. - */ - if (inst->w16_mode != MODE_RFC3389CNG) - { - /* Reset generation and overlap slightly with old data */ - - /* Generate len samples + overlap */ - if (WebRtcCng_Generate(inst->CNG_Codec_inst, pw16_outData, - (int16_t) (len + inst->ExpandInst.w16_overlap), 1) < 0) - { - /* error returned */ - return -WebRtcCng_GetErrorCodeDec(inst->CNG_Codec_inst); - } - - /* Set windowing parameters depending on sample rate */ - if (inst->fs == 8000) - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_8KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_8KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_8KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_8KHZ_INC; -#ifdef NETEQ_WIDEBAND - } - else if (inst->fs == 16000) - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_16KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_16KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_16KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_16KHZ_INC; -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - } - else if (inst->fs == 32000) - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_32KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_32KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_32KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_32KHZ_INC; -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - } - else if (inst->fs == 48000) - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_48KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_48KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_48KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_48KHZ_INC; -#endif - } - else - { - /* Unsupported sample rate (should not be possible) */ - return NETEQ_OTHER_ERROR; - } - - /* Do overlap add between new vector and overlap */ - for (i = 0; i < inst->ExpandInst.w16_overlap; i++) - { - /* overlapVec[i] = WinMute * overlapVec[i] + WinUnMute * outData[i] */ - inst->ExpandInst.pw16_overlapVec[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32( - WEBRTC_SPL_MUL_16_16( - inst->ExpandInst.pw16_overlapVec[i], w16_winMute) + - WEBRTC_SPL_MUL_16_16(pw16_outData[i], w16_winUnMute) - + 16384, 15); /* shift with proper rounding */ - - w16_winMute += w16_winMuteInc; /* decrease mute factor (inc<0) */ - w16_winUnMute += w16_winUnMuteInc; /* increase unmute factor (inc>0) */ - - } - - /* - * Shift the contents of the outData buffer by overlap samples, since we - * already used these first samples in the overlapVec above - */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_outData+inst->ExpandInst.w16_overlap, len); - - } - else - { - /* This is a subsequent CNG call; no special overlap needed */ - - /* Generate len samples */ - if (WebRtcCng_Generate(inst->CNG_Codec_inst, pw16_outData, (int16_t) len, 0) < 0) - { - /* error returned */ - return -WebRtcCng_GetErrorCodeDec(inst->CNG_Codec_inst); - } - } - - return 0; - -} - -#endif /* NETEQ_CNG_CODEC */ - diff --git a/webrtc/modules/audio_coding/neteq/codec_db.c b/webrtc/modules/audio_coding/neteq/codec_db.c deleted file mode 100644 index bb34f5e58..000000000 --- a/webrtc/modules/audio_coding/neteq/codec_db.c +++ /dev/null @@ -1,782 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* - * Implementation of the codec database. - */ - -#include "codec_db.h" - -#include /* to define NULL */ - -#include "signal_processing_library.h" - -#include "neteq_error_codes.h" - -/* - * Resets the codec database. - */ - -int WebRtcNetEQ_DbReset(CodecDbInst_t *inst) -{ - int i; - - WebRtcSpl_MemSetW16((int16_t*) inst, 0, - sizeof(CodecDbInst_t) / sizeof(int16_t)); - - for (i = 0; i < NUM_TOTAL_CODECS; i++) - { - inst->position[i] = -1; - } - - for (i = 0; i < NUM_CODECS; i++) - { - inst->payloadType[i] = -1; - } - - for (i = 0; i < NUM_CNG_CODECS; i++) - { - inst->CNGpayloadType[i] = -1; - } - - return 0; -} - -/* - * Adds a new codec to the database. - */ - -int WebRtcNetEQ_DbAdd(CodecDbInst_t *inst, enum WebRtcNetEQDecoder codec, - int16_t payloadType, FuncDecode funcDecode, - FuncDecode funcDecodeRCU, FuncDecodePLC funcDecodePLC, - FuncDecodeInit funcDecodeInit, FuncAddLatePkt funcAddLatePkt, - FuncGetMDinfo funcGetMDinfo, FuncGetPitchInfo funcGetPitch, - FuncUpdBWEst funcUpdBWEst, FuncDurationEst funcDurationEst, - FuncGetErrorCode funcGetErrorCode, void* codec_state, - uint16_t codec_fs) -{ - - int temp; - int insertCNGcodec = 0, overwriteCNGcodec = 0, CNGpos = -1; - -#ifndef NETEQ_RED_CODEC - if (codec == kDecoderRED) - { - return CODEC_DB_UNSUPPORTED_CODEC; - } -#endif - if (((int) codec <= (int) kDecoderReservedStart) || ((int) codec - >= (int) kDecoderReservedEnd)) - { - return CODEC_DB_UNSUPPORTED_CODEC; - } - - if ((codec_fs != 8000) -#ifdef NETEQ_WIDEBAND - &&(codec_fs!=16000) -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - &&(codec_fs!=32000) -#endif -#if defined(NETEQ_48KHZ_WIDEBAND) || defined(NETEQ_OPUS_CODEC) - &&(codec_fs!=48000) -#endif - ) - { - return CODEC_DB_UNSUPPORTED_FS; - } - - /* Ensure that the codec type is supported */ - switch (codec) - { -#ifdef NETEQ_PCM16B_CODEC - case kDecoderPCM16B : - case kDecoderPCM16B_2ch : -#endif -#ifdef NETEQ_G711_CODEC - case kDecoderPCMu : - case kDecoderPCMa : - case kDecoderPCMu_2ch : - case kDecoderPCMa_2ch : -#endif -#ifdef NETEQ_ILBC_CODEC - case kDecoderILBC : -#endif -#ifdef NETEQ_ISAC_CODEC - case kDecoderISAC : -#endif -#ifdef NETEQ_ISAC_SWB_CODEC - case kDecoderISACswb : -#endif -#ifdef NETEQ_ISAC_FB_CODEC - case kDecoderISACfb : -#endif -#ifdef NETEQ_OPUS_CODEC - case kDecoderOpus : -#endif -#ifdef NETEQ_G722_CODEC - case kDecoderG722 : - case kDecoderG722_2ch : -#endif -#ifdef NETEQ_WIDEBAND - case kDecoderPCM16Bwb : - case kDecoderPCM16Bwb_2ch : -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - case kDecoderPCM16Bswb32kHz : - case kDecoderPCM16Bswb32kHz_2ch : -#endif -#ifdef NETEQ_CNG_CODEC - case kDecoderCNG : -#endif -#ifdef NETEQ_ATEVENT_DECODE - case kDecoderAVT : -#endif -#ifdef NETEQ_RED_CODEC - case kDecoderRED : -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - case kDecoderPCM16Bswb48kHz : -#endif -#ifdef NETEQ_ARBITRARY_CODEC - case kDecoderArbitrary: -#endif -#ifdef NETEQ_G729_CODEC - case kDecoderG729: -#endif -#ifdef NETEQ_G729_1_CODEC - case kDecoderG729_1 : -#endif -#ifdef NETEQ_G726_CODEC - case kDecoderG726_16 : - case kDecoderG726_24 : - case kDecoderG726_32 : - case kDecoderG726_40 : -#endif -#ifdef NETEQ_G722_1_CODEC - case kDecoderG722_1_16 : - case kDecoderG722_1_24 : - case kDecoderG722_1_32 : -#endif -#ifdef NETEQ_G722_1C_CODEC - case kDecoderG722_1C_24 : - case kDecoderG722_1C_32 : - case kDecoderG722_1C_48 : -#endif -#ifdef NETEQ_SPEEX_CODEC - case kDecoderSPEEX_8 : - case kDecoderSPEEX_16 : -#endif -#ifdef NETEQ_CELT_CODEC - case kDecoderCELT_32 : - case kDecoderCELT_32_2ch : -#endif -#ifdef NETEQ_GSMFR_CODEC - case kDecoderGSMFR : -#endif -#ifdef NETEQ_AMR_CODEC - case kDecoderAMR : -#endif -#ifdef NETEQ_AMRWB_CODEC - case kDecoderAMRWB : -#endif - { - /* If we end up here, the inserted codec is supported => Do nothing */ - break; - } - default: - { - /* If we get to this point, the inserted codec is not supported */ - return CODEC_DB_UNSUPPORTED_CODEC; - } - } - - /* Check to see if payload type is taken */ - if (WebRtcNetEQ_DbGetCodec(inst, payloadType) > 0) - { - return CODEC_DB_PAYLOAD_TAKEN; - } - - /* Special case for CNG codecs */ - if (codec == kDecoderCNG) - { - /* check if this is first CNG codec to be registered */ - if (WebRtcNetEQ_DbGetPayload(inst, codec) == CODEC_DB_NOT_EXIST2) - { - /* no other CNG codec found */ - insertCNGcodec = 1; - } - - /* find the appropriate insert position in CNG payload vector */ - switch (codec_fs) - { - case 8000: - CNGpos = 0; - /* - * The 8 kHz CNG payload type is the one associated with the regular codec DB - * should override any other setting. - * Overwrite if this isn't the first CNG - */ - overwriteCNGcodec = !insertCNGcodec; - break; -#ifdef NETEQ_WIDEBAND - case 16000: - CNGpos = 1; - break; -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - case 32000: - CNGpos = 2; - break; -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - case 48000: - CNGpos = 3; - break; -#endif - default: - /* If we get to this point, the inserted codec is not supported */ - return CODEC_DB_UNSUPPORTED_CODEC; - } - - /* insert CNG payload type */ - inst->CNGpayloadType[CNGpos] = payloadType; - - } - - if ((codec != kDecoderCNG) || (insertCNGcodec == 1) || (overwriteCNGcodec == 1)) - { - /* Check if we have reached the maximum numbers of simultaneous codecs */ - if (inst->nrOfCodecs == NUM_CODECS) return CODEC_DB_FULL; - - /* Check that codec has not already been initialized to DB => - remove it and reinitialize according to new spec */ - if ((inst->position[codec] != -1) && (overwriteCNGcodec != 1)) - { /* if registering multiple CNG codecs, don't remove, just overwrite */ - WebRtcNetEQ_DbRemove(inst, codec); - } - - if (overwriteCNGcodec == 1) - { - temp = inst->position[codec]; - } - else - { - temp = inst->nrOfCodecs; /* Store this codecs position */ - inst->position[codec] = temp; - inst->nrOfCodecs++; - } - - inst->payloadType[temp] = payloadType; - - /* Copy to database */ - inst->codec_state[temp] = codec_state; - inst->funcDecode[temp] = funcDecode; - inst->funcDecodeRCU[temp] = funcDecodeRCU; - inst->funcAddLatePkt[temp] = funcAddLatePkt; - inst->funcDecodeInit[temp] = funcDecodeInit; - inst->funcDecodePLC[temp] = funcDecodePLC; - inst->funcGetMDinfo[temp] = funcGetMDinfo; - inst->funcGetPitch[temp] = funcGetPitch; - inst->funcUpdBWEst[temp] = funcUpdBWEst; - inst->funcDurationEst[temp] = funcDurationEst; - inst->funcGetErrorCode[temp] = funcGetErrorCode; - inst->codec_fs[temp] = codec_fs; - - } - - return 0; -} - -/* - * Removes a codec from the database. - */ - -int WebRtcNetEQ_DbRemove(CodecDbInst_t *inst, enum WebRtcNetEQDecoder codec) -{ - int i; - int pos = -1; - -#ifndef NETEQ_RED_CODEC - if (codec == kDecoderRED) - { - return CODEC_DB_UNSUPPORTED_CODEC; - } -#endif - if (((int) codec <= (int) kDecoderReservedStart) || ((int) codec - >= (int) kDecoderReservedEnd)) - { - return CODEC_DB_UNSUPPORTED_CODEC; - } - - pos = inst->position[codec]; - if (pos == -1) - { - return CODEC_DB_NOT_EXIST4; - } - else - { - /* Remove this codec */ - inst->position[codec] = -1; - for (i = pos; i < (inst->nrOfCodecs - 1); i++) - { - inst->payloadType[i] = inst->payloadType[i + 1]; - inst->codec_state[i] = inst->codec_state[i + 1]; - inst->funcDecode[i] = inst->funcDecode[i + 1]; - inst->funcDecodeRCU[i] = inst->funcDecodeRCU[i + 1]; - inst->funcAddLatePkt[i] = inst->funcAddLatePkt[i + 1]; - inst->funcDecodeInit[i] = inst->funcDecodeInit[i + 1]; - inst->funcDecodePLC[i] = inst->funcDecodePLC[i + 1]; - inst->funcGetMDinfo[i] = inst->funcGetMDinfo[i + 1]; - inst->funcGetPitch[i] = inst->funcGetPitch[i + 1]; - inst->funcDurationEst[i] = inst->funcDurationEst[i + 1]; - inst->funcUpdBWEst[i] = inst->funcUpdBWEst[i + 1]; - inst->funcGetErrorCode[i] = inst->funcGetErrorCode[i + 1]; - inst->codec_fs[i] = inst->codec_fs[i + 1]; - } - inst->payloadType[i] = -1; - inst->codec_state[i] = NULL; - inst->funcDecode[i] = NULL; - inst->funcDecodeRCU[i] = NULL; - inst->funcAddLatePkt[i] = NULL; - inst->funcDecodeInit[i] = NULL; - inst->funcDecodePLC[i] = NULL; - inst->funcGetMDinfo[i] = NULL; - inst->funcGetPitch[i] = NULL; - inst->funcDurationEst[i] = NULL; - inst->funcUpdBWEst[i] = NULL; - inst->funcGetErrorCode[i] = NULL; - inst->codec_fs[i] = 0; - /* Move down all the codecs above this one */ - for (i = 0; i < NUM_TOTAL_CODECS; i++) - { - if (inst->position[i] >= pos) - { - inst->position[i] = inst->position[i] - 1; - } - } - inst->nrOfCodecs--; - - if (codec == kDecoderCNG) - { - /* also remove all registered CNG payload types */ - for (i = 0; i < NUM_CNG_CODECS; i++) - { - inst->CNGpayloadType[i] = -1; - } - } - } - return 0; -} - -/* - * Get the decoder function pointers for a codec. - */ - -int WebRtcNetEQ_DbGetPtrs(CodecDbInst_t *inst, enum WebRtcNetEQDecoder codec, - CodecFuncInst_t *ptr_inst) -{ - - int pos = inst->position[codec]; - if ((codec <= kDecoderReservedStart) || (codec >= kDecoderReservedEnd) || (codec - > NUM_TOTAL_CODECS)) - { - /* ERROR */ - pos = -1; - } - if (pos >= 0) - { - ptr_inst->codec_state = inst->codec_state[pos]; - ptr_inst->funcAddLatePkt = inst->funcAddLatePkt[pos]; - ptr_inst->funcDecode = inst->funcDecode[pos]; - ptr_inst->funcDecodeRCU = inst->funcDecodeRCU[pos]; - ptr_inst->funcDecodeInit = inst->funcDecodeInit[pos]; - ptr_inst->funcDecodePLC = inst->funcDecodePLC[pos]; - ptr_inst->funcGetMDinfo = inst->funcGetMDinfo[pos]; - ptr_inst->funcUpdBWEst = inst->funcUpdBWEst[pos]; - ptr_inst->funcGetErrorCode = inst->funcGetErrorCode[pos]; - ptr_inst->codec_fs = inst->codec_fs[pos]; - return 0; - } - else - { - WebRtcSpl_MemSetW16((int16_t*) ptr_inst, 0, - sizeof(CodecFuncInst_t) / sizeof(int16_t)); - return CODEC_DB_NOT_EXIST1; - } -} - -/* - * Returns payload number given a codec identifier. - */ - -int WebRtcNetEQ_DbGetPayload(CodecDbInst_t *inst, enum WebRtcNetEQDecoder codecID) -{ - if (inst->position[codecID] == -1) - return CODEC_DB_NOT_EXIST2; - else - return (inst->payloadType[inst->position[codecID]]); - -} - -/* - * Returns codec identifier given a payload number. - * Returns -1 if the payload type does not exist. - */ - -int WebRtcNetEQ_DbGetCodec(const CodecDbInst_t *inst, int payloadType) -{ - int i, pos; - - for (i = 0; i < NUM_TOTAL_CODECS; i++) - { - pos = inst->position[i]; - if (pos != -1) - { - if (inst->payloadType[pos] == payloadType) return i; - } - } - - /* did not find payload type */ - /* check if it's a CNG codec */ - if (WebRtcNetEQ_DbIsCNGPayload(inst, payloadType)) - { - return kDecoderCNG; - } - - /* found no match */ - return CODEC_DB_NOT_EXIST3; -} - -/* - * Extracts the Payload Split information of the codec with the specified payloadType. - */ - -int WebRtcNetEQ_DbGetSplitInfo(SplitInfo_t *inst, enum WebRtcNetEQDecoder codecID, - int codedsize) -{ - - switch (codecID) - { -#ifdef NETEQ_ISAC_CODEC - case kDecoderISAC: -#endif -#ifdef NETEQ_ISAC_SWB_CODEC - case kDecoderISACswb: -#endif -#ifdef NETEQ_ISAC_FB_CODEC - case kDecoderISACfb: -#endif -#ifdef NETEQ_OPUS_CODEC - case kDecoderOpus: -#endif -#ifdef NETEQ_ARBITRARY_CODEC - case kDecoderArbitrary: -#endif -#ifdef NETEQ_AMR_CODEC - case kDecoderAMR: -#endif -#ifdef NETEQ_AMRWB_CODEC - case kDecoderAMRWB: -#endif -#ifdef NETEQ_G726_CODEC - /* Treat G726 as non-splittable to simplify the implementation */ - case kDecoderG726_16: - case kDecoderG726_24: - case kDecoderG726_32: - case kDecoderG726_40: -#endif -#ifdef NETEQ_SPEEX_CODEC - case kDecoderSPEEX_8: - case kDecoderSPEEX_16: -#endif -#ifdef NETEQ_CELT_CODEC - case kDecoderCELT_32 : - case kDecoderCELT_32_2ch : -#endif -#ifdef NETEQ_G729_1_CODEC - case kDecoderG729_1: -#endif - { - /* These codecs' payloads are not splittable */ - inst->deltaBytes = NO_SPLIT; - return 0; - } - - /* - * Sample based coders are a special case. - * In this case, deltaTime signals the number of bytes per timestamp unit times 2 - * in log2 domain. - */ -#if (defined NETEQ_G711_CODEC) - case kDecoderPCMu: - case kDecoderPCMa: - case kDecoderPCMu_2ch: - case kDecoderPCMa_2ch: - { - inst->deltaBytes = -12; - inst->deltaTime = 1; - return 0; - } -#endif -#if (defined NETEQ_G722_CODEC) - case kDecoderG722: - case kDecoderG722_2ch: - { - inst->deltaBytes = -14; - inst->deltaTime = 0; - return 0; - } -#endif -#if (defined NETEQ_PCM16B_CODEC) - case kDecoderPCM16B: - case kDecoderPCM16B_2ch: - { - inst->deltaBytes = -12; - inst->deltaTime = 2; - return 0; - } -#endif -#if ((defined NETEQ_PCM16B_CODEC)&&(defined NETEQ_WIDEBAND)) - case kDecoderPCM16Bwb: - case kDecoderPCM16Bwb_2ch: - { - inst->deltaBytes = -14; - inst->deltaTime = 2; - return 0; - } -#endif -#if ((defined NETEQ_PCM16B_CODEC)&&(defined NETEQ_32KHZ_WIDEBAND)) - case kDecoderPCM16Bswb32kHz: - case kDecoderPCM16Bswb32kHz_2ch: - { - inst->deltaBytes = -18; - inst->deltaTime = 2; - return 0; - } -#endif -#if ((defined NETEQ_PCM16B_CODEC)&&(defined NETEQ_48KHZ_WIDEBAND)) - case kDecoderPCM16Bswb48kHz: - { - inst->deltaBytes = -22; - inst->deltaTime = 2; - return 0; - } -#endif - - /* Splittable payloads */ -#ifdef NETEQ_G722_1_CODEC - case kDecoderG722_1_16: - { - inst->deltaBytes = 40; - inst->deltaTime = 320; - return 0; - } - case kDecoderG722_1_24: - { - inst->deltaBytes = 60; - inst->deltaTime = 320; - return 0; - } - case kDecoderG722_1_32: - { - inst->deltaBytes = 80; - inst->deltaTime = 320; - return 0; - } -#endif -#ifdef NETEQ_G722_1C_CODEC - case kDecoderG722_1C_24: - { - inst->deltaBytes = 60; - inst->deltaTime = 640; - return 0; - } - case kDecoderG722_1C_32: - { - inst->deltaBytes = 80; - inst->deltaTime = 640; - return 0; - } - case kDecoderG722_1C_48: - { - inst->deltaBytes = 120; - inst->deltaTime = 640; - return 0; - } -#endif -#ifdef NETEQ_G729_CODEC - case kDecoderG729: - { - inst->deltaBytes = 10; - inst->deltaTime = 80; - return 0; - } -#endif -#ifdef NETEQ_ILBC_CODEC - case kDecoderILBC: - { - /* Check for splitting of iLBC packets. - * If payload size is a multiple of 50 bytes it should be split into 30ms frames. - * If payload size is a multiple of 38 bytes it should be split into 20ms frames. - * Least common multiplier between 38 and 50 is 950, so the payload size must be less than - * 950 bytes in order to resolve the frames unambiguously. - * Currently max 12 frames in one bundle. - */ - switch (codedsize) - { - case 50: - case 100: - case 150: - case 200: - case 250: - case 300: - case 350: - case 400: - case 450: - case 500: - case 550: - case 600: - { - inst->deltaBytes = 50; - inst->deltaTime = 240; - break; - } - case 38: - case 76: - case 114: - case 152: - case 190: - case 228: - case 266: - case 304: - case 342: - case 380: - case 418: - case 456: - { - inst->deltaBytes = 38; - inst->deltaTime = 160; - break; - } - default: - { - return AMBIGUOUS_ILBC_FRAME_SIZE; /* Something not supported... */ - } - } - return 0; - } -#endif -#ifdef NETEQ_GSMFR_CODEC - case kDecoderGSMFR: - { - inst->deltaBytes = 33; - inst->deltaTime = 160; - return 0; - } -#endif - default: - { /*Unknown codec */ - inst->deltaBytes = NO_SPLIT; - return CODEC_DB_UNKNOWN_CODEC; - } - } /* end of switch */ -} - -/* - * Returns 1 if codec is multiple description, 0 otherwise. - * NOTE: This function is a stub, since there currently are no MD codecs. - */ -int WebRtcNetEQ_DbIsMDCodec(enum WebRtcNetEQDecoder codecID) -{ - if (0) /* Add test for MD codecs here */ - return 1; - else - return 0; -} - -/* - * Returns 1 if payload type is registered as a CNG codec, 0 otherwise - */ -int WebRtcNetEQ_DbIsCNGPayload(const CodecDbInst_t *inst, int payloadType) -{ -#ifdef NETEQ_CNG_CODEC - int i; - - for(i=0; iCNGpayloadType[i] != -1) && (inst->CNGpayloadType[i] == payloadType) ) - { - return 1; - } - } -#endif - - return 0; - -} - -/* - * Return the sample rate for the codec with the given payload type, 0 if error - */ -uint16_t WebRtcNetEQ_DbGetSampleRate(CodecDbInst_t *inst, int payloadType) -{ - int i; - CodecFuncInst_t codecInst; - - /* Sanity */ - if (inst == NULL) - { - /* return 0 Hz */ - return 0; - } - - /* Check among CNG payloads */ - for (i = 0; i < NUM_CNG_CODECS; i++) - { - if ((inst->CNGpayloadType[i] != -1) && (inst->CNGpayloadType[i] == payloadType)) - { - switch (i) - { -#ifdef NETEQ_WIDEBAND - case 1: - return 16000; -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - case 2: - return 32000; -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - case 3: - return 48000; -#endif - default: - return 8000; - } - } - } - - /* Not a CNG payload, check the other payloads */ - i = WebRtcNetEQ_DbGetCodec(inst, payloadType); - if (i >= 0) - { - if (WebRtcNetEQ_DbGetPtrs(inst, (enum WebRtcNetEQDecoder) i, &codecInst) != 0) - { - /* Unexpected error, return 0 Hz */ - return 0; - } - return codecInst.codec_fs; - } - - /* If we end up here, we got an error, return 0 Hz */ - return 0; - -} - diff --git a/webrtc/modules/audio_coding/neteq/codec_db.h b/webrtc/modules/audio_coding/neteq/codec_db.h deleted file mode 100644 index cc4b48e6f..000000000 --- a/webrtc/modules/audio_coding/neteq/codec_db.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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. - */ - -/* - * Interface for the codec database. - */ - -#ifndef CODEC_DB_H -#define CODEC_DB_H - -#include "typedefs.h" - -#include "webrtc_neteq.h" -#include "codec_db_defines.h" -#include "neteq_defines.h" - -#if defined(NETEQ_48KHZ_WIDEBAND) - #define NUM_CNG_CODECS 4 -#elif defined(NETEQ_32KHZ_WIDEBAND) - #define NUM_CNG_CODECS 3 -#elif defined(NETEQ_WIDEBAND) - #define NUM_CNG_CODECS 2 -#else - #define NUM_CNG_CODECS 1 -#endif - -typedef struct -{ - - int16_t position[NUM_TOTAL_CODECS]; - int16_t nrOfCodecs; - - int16_t payloadType[NUM_CODECS]; - FuncDecode funcDecode[NUM_CODECS]; - FuncDecode funcDecodeRCU[NUM_CODECS]; - FuncDecodePLC funcDecodePLC[NUM_CODECS]; - FuncDecodeInit funcDecodeInit[NUM_CODECS]; - FuncAddLatePkt funcAddLatePkt[NUM_CODECS]; - FuncGetMDinfo funcGetMDinfo[NUM_CODECS]; - FuncGetPitchInfo funcGetPitch[NUM_CODECS]; - FuncUpdBWEst funcUpdBWEst[NUM_CODECS]; - FuncDurationEst funcDurationEst[NUM_CODECS]; - FuncGetErrorCode funcGetErrorCode[NUM_CODECS]; - void * codec_state[NUM_CODECS]; - uint16_t codec_fs[NUM_CODECS]; - int16_t CNGpayloadType[NUM_CNG_CODECS]; - -} CodecDbInst_t; - -#define NO_SPLIT -1 /* codec payload cannot be split */ - -typedef struct -{ - int16_t deltaBytes; - int16_t deltaTime; -} SplitInfo_t; - -/* - * Resets the codec database. - */ -int WebRtcNetEQ_DbReset(CodecDbInst_t *inst); - -/* - * Adds a new codec to the database. - */ -int WebRtcNetEQ_DbAdd(CodecDbInst_t *inst, enum WebRtcNetEQDecoder codec, - int16_t payloadType, FuncDecode funcDecode, - FuncDecode funcDecodeRCU, FuncDecodePLC funcDecodePLC, - FuncDecodeInit funcDecodeInit, FuncAddLatePkt funcAddLatePkt, - FuncGetMDinfo funcGetMDinfo, FuncGetPitchInfo funcGetPitch, - FuncUpdBWEst funcUpdBWEst, FuncDurationEst funcDurationEst, - FuncGetErrorCode funcGetErrorCode, void* codec_state, - uint16_t codec_fs); - -/* - * Removes a codec from the database. - */ -int WebRtcNetEQ_DbRemove(CodecDbInst_t *inst, enum WebRtcNetEQDecoder codec); - -/* - * Get the decoder function pointers for a codec. - */ -int WebRtcNetEQ_DbGetPtrs(CodecDbInst_t *inst, enum WebRtcNetEQDecoder, - CodecFuncInst_t *ptr_inst); - -/* - * Returns payload number given a codec identifier. - */ - -int WebRtcNetEQ_DbGetPayload(CodecDbInst_t *inst, enum WebRtcNetEQDecoder codecID); - -/* - * Returns codec identifier given a payload number. - */ - -int WebRtcNetEQ_DbGetCodec(const CodecDbInst_t *inst, int payloadType); - -/* - * Extracts the Payload Split information of the codec with the specified payloadType. - */ - -int WebRtcNetEQ_DbGetSplitInfo(SplitInfo_t *inst, enum WebRtcNetEQDecoder codecID, - int codedsize); - -/* - * Returns 1 if codec is multiple description type, 0 otherwise. - */ -int WebRtcNetEQ_DbIsMDCodec(enum WebRtcNetEQDecoder codecID); - -/* - * Returns 1 if payload type is registered as a CNG codec, 0 otherwise. - */ -int WebRtcNetEQ_DbIsCNGPayload(const CodecDbInst_t *inst, int payloadType); - -/* - * Return the sample rate for the codec with the given payload type, 0 if error. - */ -uint16_t WebRtcNetEQ_DbGetSampleRate(CodecDbInst_t *inst, int payloadType); - -#endif - diff --git a/webrtc/modules/audio_coding/neteq/codec_db_defines.h b/webrtc/modules/audio_coding/neteq/codec_db_defines.h deleted file mode 100644 index d97306a33..000000000 --- a/webrtc/modules/audio_coding/neteq/codec_db_defines.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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. - */ - -/* - * Some definitions related to the codec database. - */ - -#ifndef CODEC_DB_DEFINES_H -#define CODEC_DB_DEFINES_H - -#include "typedefs.h" - -#define NUM_CODECS 47 /* probably too large with the limited set of supported codecs*/ -#define NUM_TOTAL_CODECS kDecoderReservedEnd - -/* - * Pointer to decoder function. - */ -typedef int16_t (*FuncDecode)(void* state, int16_t* encoded, int16_t len, - int16_t* decoded, int16_t* speechType); - -/* - * Pointer to PLC function. - */ -typedef int16_t (*FuncDecodePLC)(void* state, int16_t* decodec, - int16_t frames); - -/* - * Pointer to decoder init function. - */ -typedef int16_t (*FuncDecodeInit)(void* state); - -/* - * Pointer to add late packet function. - */ -typedef int16_t - (*FuncAddLatePkt)(void* state, int16_t* encoded, int16_t len); - -/* - * Pointer to get MD infofunction. - */ -typedef int16_t (*FuncGetMDinfo)(void* state); - -/* - * Pointer to pitch info function. - * Return 0 for unvoiced, -1 if pitch not availiable. - */ -typedef int16_t (*FuncGetPitchInfo)(void* state, int16_t* encoded, - int16_t* length); - -/* - * Pointer to the update bandwidth estimate function - */ -typedef int16_t (*FuncUpdBWEst)(void* state, const uint16_t *encoded, - int32_t packet_size, - uint16_t rtp_seq_number, uint32_t send_ts, - uint32_t arr_ts); - -/* - * Pointer to the frame size estimate function. - * Returns the estimated number of samples in the packet. - */ -typedef int (*FuncDurationEst)(void* state, const uint8_t* payload, - int payload_length_bytes); - -/* - * Pointer to error code function - */ -typedef int16_t (*FuncGetErrorCode)(void* state); - -typedef struct CodecFuncInst_t_ -{ - - FuncDecode funcDecode; - FuncDecode funcDecodeRCU; - FuncDecodePLC funcDecodePLC; - FuncDecodeInit funcDecodeInit; - FuncAddLatePkt funcAddLatePkt; - FuncGetMDinfo funcGetMDinfo; - FuncUpdBWEst funcUpdBWEst; /* Currently in use for the ISAC family (without LC) only*/ - FuncDurationEst funcDurationEst; - FuncGetErrorCode funcGetErrorCode; - void * codec_state; - uint16_t codec_fs; - uint32_t timeStamp; - -} CodecFuncInst_t; - -#endif - diff --git a/webrtc/modules/audio_coding/neteq4/comfort_noise.cc b/webrtc/modules/audio_coding/neteq/comfort_noise.cc similarity index 94% rename from webrtc/modules/audio_coding/neteq4/comfort_noise.cc rename to webrtc/modules/audio_coding/neteq/comfort_noise.cc index 360767af1..31bb40c92 100644 --- a/webrtc/modules/audio_coding/neteq4/comfort_noise.cc +++ b/webrtc/modules/audio_coding/neteq/comfort_noise.cc @@ -8,15 +8,15 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/comfort_noise.h" +#include "webrtc/modules/audio_coding/neteq/comfort_noise.h" #include #include "webrtc/modules/audio_coding/codecs/cng/include/webrtc_cng.h" -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/dsp_helper.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/dsp_helper.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/comfort_noise.h b/webrtc/modules/audio_coding/neteq/comfort_noise.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/comfort_noise.h rename to webrtc/modules/audio_coding/neteq/comfort_noise.h index 7e7c294ff..d46559624 100644 --- a/webrtc/modules/audio_coding/neteq4/comfort_noise.h +++ b/webrtc/modules/audio_coding/neteq/comfort_noise.h @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_COMFORT_NOISE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_COMFORT_NOISE_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_COMFORT_NOISE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_COMFORT_NOISE_H_ -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -70,4 +70,4 @@ class ComfortNoise { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_COMFORT_NOISE_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_COMFORT_NOISE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/comfort_noise_unittest.cc b/webrtc/modules/audio_coding/neteq/comfort_noise_unittest.cc similarity index 80% rename from webrtc/modules/audio_coding/neteq4/comfort_noise_unittest.cc rename to webrtc/modules/audio_coding/neteq/comfort_noise_unittest.cc index 0e8497171..6a1bbe0d1 100644 --- a/webrtc/modules/audio_coding/neteq4/comfort_noise_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/comfort_noise_unittest.cc @@ -10,11 +10,11 @@ // Unit tests for ComfortNoise class. -#include "webrtc/modules/audio_coding/neteq4/comfort_noise.h" +#include "webrtc/modules/audio_coding/neteq/comfort_noise.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq/correlator.c b/webrtc/modules/audio_coding/neteq/correlator.c deleted file mode 100644 index 0a4404a43..000000000 --- a/webrtc/modules/audio_coding/neteq/correlator.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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 "dsp.h" - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" - -/* Scratch usage: - - Type Name size startpos endpos - int16_t pw16_corrVec 62 0 61 - int16_t pw16_data_ds 124 0 123 - int32_t pw32_corr 2*54 124 231 - - Total: 232 - */ - -#define SCRATCH_pw16_corrVec 0 -#define SCRATCH_pw16_data_ds 0 -#define SCRATCH_pw32_corr 124 - -#define NETEQ_CORRELATOR_DSVECLEN 124 /* 124 = 60 + 10 + 54 */ - -int16_t WebRtcNetEQ_Correlator(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_data, - int16_t w16_dataLen, - int16_t *pw16_corrOut, - int16_t *pw16_corrScale) -{ - int16_t w16_corrLen = 60; -#ifdef SCRATCH - int16_t *pw16_data_ds = pw16_scratchPtr + SCRATCH_pw16_corrVec; - int32_t *pw32_corr = (int32_t*) (pw16_scratchPtr + SCRATCH_pw32_corr); - /* int16_t *pw16_corrVec = pw16_scratchPtr + SCRATCH_pw16_corrVec;*/ -#else - int16_t pw16_data_ds[NETEQ_CORRELATOR_DSVECLEN]; - int32_t pw32_corr[54]; - /* int16_t pw16_corrVec[4+54+4];*/ -#endif - /* int16_t *pw16_corr=&pw16_corrVec[4];*/ - int16_t w16_maxVal; - int32_t w32_maxVal; - int16_t w16_normVal; - int16_t w16_normVal2; - /* int16_t w16_corrUpsLen;*/ - int16_t *pw16_B = NULL; - int16_t w16_Blen = 0; - int16_t w16_factor = 0; - - /* Set constants depending on frequency used */ - if (inst->fs == 8000) - { - w16_Blen = 3; - w16_factor = 2; - pw16_B = (int16_t*) WebRtcNetEQ_kDownsample8kHzTbl; -#ifdef NETEQ_WIDEBAND - } - else if (inst->fs==16000) - { - w16_Blen = 5; - w16_factor = 4; - pw16_B = (int16_t*)WebRtcNetEQ_kDownsample16kHzTbl; -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - } - else if (inst->fs==32000) - { - w16_Blen = 7; - w16_factor = 8; - pw16_B = (int16_t*)WebRtcNetEQ_kDownsample32kHzTbl; -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - } - else /* if inst->fs==48000 */ - { - w16_Blen = 7; - w16_factor = 12; - pw16_B = (int16_t*)WebRtcNetEQ_kDownsample48kHzTbl; -#endif - } - - /* Downsample data in order to work on a 4 kHz sampled signal */ - WebRtcSpl_DownsampleFast( - pw16_data + w16_dataLen - (NETEQ_CORRELATOR_DSVECLEN * w16_factor), - (int16_t) (NETEQ_CORRELATOR_DSVECLEN * w16_factor), pw16_data_ds, - NETEQ_CORRELATOR_DSVECLEN, pw16_B, w16_Blen, w16_factor, (int16_t) 0); - - /* Normalize downsampled vector to using entire 16 bit */ - w16_maxVal = WebRtcSpl_MaxAbsValueW16(pw16_data_ds, 124); - w16_normVal = 16 - WebRtcSpl_NormW32((int32_t) w16_maxVal); - WebRtcSpl_VectorBitShiftW16(pw16_data_ds, NETEQ_CORRELATOR_DSVECLEN, pw16_data_ds, - w16_normVal); - - /* Correlate from lag 10 to lag 60 (20..120 in NB and 40..240 in WB) */ - - WebRtcNetEQ_CrossCorr( - pw32_corr, &pw16_data_ds[NETEQ_CORRELATOR_DSVECLEN - w16_corrLen], - &pw16_data_ds[NETEQ_CORRELATOR_DSVECLEN - w16_corrLen - 10], 60, 54, - 6 /*maxValue... shifts*/, -1); - - /* - * Move data from w32 to w16 vector. - * Normalize downsampled vector to using all 14 bits - */ - w32_maxVal = WebRtcSpl_MaxAbsValueW32(pw32_corr, 54); - w16_normVal2 = 18 - WebRtcSpl_NormW32(w32_maxVal); - w16_normVal2 = WEBRTC_SPL_MAX(w16_normVal2, 0); - - WebRtcSpl_VectorBitShiftW32ToW16(pw16_corrOut, 54, pw32_corr, w16_normVal2); - - /* Total scale factor (right shifts) of correlation value */ - *pw16_corrScale = 2 * w16_normVal + 6 + w16_normVal2; - - return (50 + 1); -} - -#undef SCRATCH_pw16_corrVec -#undef SCRATCH_pw16_data_ds -#undef SCRATCH_pw32_corr - diff --git a/webrtc/modules/audio_coding/neteq4/decision_logic.cc b/webrtc/modules/audio_coding/neteq/decision_logic.cc similarity index 91% rename from webrtc/modules/audio_coding/neteq4/decision_logic.cc rename to webrtc/modules/audio_coding/neteq/decision_logic.cc index 04b886a2e..5fb054c78 100644 --- a/webrtc/modules/audio_coding/neteq4/decision_logic.cc +++ b/webrtc/modules/audio_coding/neteq/decision_logic.cc @@ -8,17 +8,17 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/decision_logic.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic.h" #include -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" -#include "webrtc/modules/audio_coding/neteq4/decision_logic_fax.h" -#include "webrtc/modules/audio_coding/neteq4/decision_logic_normal.h" -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic_fax.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic_normal.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" #include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -145,8 +145,8 @@ Operations DecisionLogic::GetDecision(const SyncBuffer& sync_buffer, reset_decoder); } -void DecisionLogic::ExpandDecision(bool is_expand_decision) { - if (is_expand_decision) { +void DecisionLogic::ExpandDecision(Operations operation) { + if (operation == kExpand) { num_consecutive_expands_++; } else { num_consecutive_expands_ = 0; diff --git a/webrtc/modules/audio_coding/neteq4/decision_logic.h b/webrtc/modules/audio_coding/neteq/decision_logic.h similarity index 93% rename from webrtc/modules/audio_coding/neteq4/decision_logic.h rename to webrtc/modules/audio_coding/neteq/decision_logic.h index aca5ca405..672ce939d 100644 --- a/webrtc/modules/audio_coding/neteq4/decision_logic.h +++ b/webrtc/modules/audio_coding/neteq/decision_logic.h @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_H_ -#include "webrtc/modules/audio_coding/neteq4/defines.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/defines.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -92,7 +92,7 @@ class DecisionLogic { // not. Note that this is necessary, since an expand decision can be changed // to kNormal in NetEqImpl::GetDecision if there is still enough data in the // sync buffer. - void ExpandDecision(bool is_expand_decision); + virtual void ExpandDecision(Operations operation); // Adds |value| to |sample_memory_|. void AddSampleMemory(int32_t value) { @@ -165,4 +165,4 @@ class DecisionLogic { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_H_ diff --git a/webrtc/modules/audio_coding/neteq4/decision_logic_fax.cc b/webrtc/modules/audio_coding/neteq/decision_logic_fax.cc similarity index 94% rename from webrtc/modules/audio_coding/neteq4/decision_logic_fax.cc rename to webrtc/modules/audio_coding/neteq/decision_logic_fax.cc index 00c8bcf4a..08a4c4cb6 100644 --- a/webrtc/modules/audio_coding/neteq4/decision_logic_fax.cc +++ b/webrtc/modules/audio_coding/neteq/decision_logic_fax.cc @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/decision_logic_fax.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic_fax.h" #include #include -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/decision_logic_fax.h b/webrtc/modules/audio_coding/neteq/decision_logic_fax.h similarity index 88% rename from webrtc/modules/audio_coding/neteq4/decision_logic_fax.h rename to webrtc/modules/audio_coding/neteq/decision_logic_fax.h index 1ccd35244..01a948fa4 100644 --- a/webrtc/modules/audio_coding/neteq4/decision_logic_fax.h +++ b/webrtc/modules/audio_coding/neteq/decision_logic_fax.h @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_FAX_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_FAX_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_FAX_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_FAX_H_ -#include "webrtc/modules/audio_coding/neteq4/decision_logic.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -60,4 +60,4 @@ class DecisionLogicFax : public DecisionLogic { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_FAX_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_FAX_H_ diff --git a/webrtc/modules/audio_coding/neteq4/decision_logic_normal.cc b/webrtc/modules/audio_coding/neteq/decision_logic_normal.cc similarity index 94% rename from webrtc/modules/audio_coding/neteq4/decision_logic_normal.cc rename to webrtc/modules/audio_coding/neteq/decision_logic_normal.cc index a70f23b76..97a8843ae 100644 --- a/webrtc/modules/audio_coding/neteq4/decision_logic_normal.cc +++ b/webrtc/modules/audio_coding/neteq/decision_logic_normal.cc @@ -8,18 +8,18 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/decision_logic_normal.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic_normal.h" #include #include -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" #include "webrtc/modules/interface/module_common_types.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/decision_logic_normal.h b/webrtc/modules/audio_coding/neteq/decision_logic_normal.h similarity index 83% rename from webrtc/modules/audio_coding/neteq4/decision_logic_normal.h rename to webrtc/modules/audio_coding/neteq/decision_logic_normal.h index 783b001fc..a339d160f 100644 --- a/webrtc/modules/audio_coding/neteq4/decision_logic_normal.h +++ b/webrtc/modules/audio_coding/neteq/decision_logic_normal.h @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_NORMAL_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_NORMAL_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_NORMAL_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_NORMAL_H_ -#include "webrtc/modules/audio_coding/neteq4/decision_logic.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -38,6 +38,10 @@ class DecisionLogicNormal : public DecisionLogic { virtual ~DecisionLogicNormal() {} protected: + static const int kAllowMergeWithoutExpandMs = 20; // 20 ms. + static const int kReinitAfterExpands = 100; + static const int kMaxWaitForPacket = 10; + // Returns the operation that should be done next. |sync_buffer| and |expand| // are provided for reference. |decoder_frame_length| is the number of samples // obtained from the last decoded frame. If there is a packet available, the @@ -54,31 +58,28 @@ class DecisionLogicNormal : public DecisionLogic { Modes prev_mode, bool play_dtmf, bool* reset_decoder); - private: - static const int kAllowMergeWithoutExpandMs = 20; // 20 ms. - static const int kReinitAfterExpands = 100; - static const int kMaxWaitForPacket = 10; + // Returns the operation to do given that the expected packet is not + // available, but a packet further into the future is at hand. + virtual Operations FuturePacketAvailable( + const SyncBuffer& sync_buffer, + const Expand& expand, + int decoder_frame_length, Modes prev_mode, + uint32_t target_timestamp, + uint32_t available_timestamp, + bool play_dtmf); - // Returns the operation given that the next available packet is a comfort - // noise payload (RFC 3389 only, not codec-internal). - Operations CngOperation(Modes prev_mode, uint32_t target_timestamp, - uint32_t available_timestamp); + // Returns the operation to do given that the expected packet is available. + virtual Operations ExpectedPacketAvailable(Modes prev_mode, bool play_dtmf); // Returns the operation given that no packets are available (except maybe // a DTMF event, flagged by setting |play_dtmf| true). - Operations NoPacket(bool play_dtmf); + virtual Operations NoPacket(bool play_dtmf); - // Returns the operation to do given that the expected packet is available. - Operations ExpectedPacketAvailable(Modes prev_mode, bool play_dtmf); - - // Returns the operation to do given that the expected packet is not - // available, but a packet further into the future is at hand. - Operations FuturePacketAvailable(const SyncBuffer& sync_buffer, - const Expand& expand, - int decoder_frame_length, Modes prev_mode, - uint32_t target_timestamp, - uint32_t available_timestamp, - bool play_dtmf); + private: + // Returns the operation given that the next available packet is a comfort + // noise payload (RFC 3389 only, not codec-internal). + Operations CngOperation(Modes prev_mode, uint32_t target_timestamp, + uint32_t available_timestamp); // Checks if enough time has elapsed since the last successful timescale // operation was done (i.e., accelerate or preemptive expand). @@ -103,4 +104,4 @@ class DecisionLogicNormal : public DecisionLogic { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECISION_LOGIC_NORMAL_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_NORMAL_H_ diff --git a/webrtc/modules/audio_coding/neteq4/decision_logic_unittest.cc b/webrtc/modules/audio_coding/neteq/decision_logic_unittest.cc similarity index 82% rename from webrtc/modules/audio_coding/neteq4/decision_logic_unittest.cc rename to webrtc/modules/audio_coding/neteq/decision_logic_unittest.cc index d596c0519..f9056a6cb 100644 --- a/webrtc/modules/audio_coding/neteq4/decision_logic_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/decision_logic_unittest.cc @@ -11,12 +11,12 @@ // Unit tests for DecisionLogic class and derived classes. #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/decision_logic.h" -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" -#include "webrtc/modules/audio_coding/neteq4/delay_peak_detector.h" -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" namespace webrtc { @@ -24,7 +24,7 @@ TEST(DecisionLogic, CreateAndDestroy) { int fs_hz = 8000; int output_size_samples = fs_hz / 100; // Samples per 10 ms. DecoderDatabase decoder_database; - PacketBuffer packet_buffer(10, 1000); + PacketBuffer packet_buffer(10); DelayPeakDetector delay_peak_detector; DelayManager delay_manager(240, &delay_peak_detector); BufferLevelFilter buffer_level_filter; diff --git a/webrtc/modules/audio_coding/neteq4/decoder_database.cc b/webrtc/modules/audio_coding/neteq/decoder_database.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/decoder_database.cc rename to webrtc/modules/audio_coding/neteq/decoder_database.cc index 8d87519b2..5049962b4 100644 --- a/webrtc/modules/audio_coding/neteq4/decoder_database.cc +++ b/webrtc/modules/audio_coding/neteq/decoder_database.cc @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" #include #include // pair -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/decoder_database.h b/webrtc/modules/audio_coding/neteq/decoder_database.h similarity index 93% rename from webrtc/modules/audio_coding/neteq4/decoder_database.h rename to webrtc/modules/audio_coding/neteq/decoder_database.h index 9effd525d..8a03f2123 100644 --- a/webrtc/modules/audio_coding/neteq4/decoder_database.h +++ b/webrtc/modules/audio_coding/neteq/decoder_database.h @@ -8,15 +8,15 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECODER_DATABASE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECODER_DATABASE_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECODER_DATABASE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECODER_DATABASE_H_ #include +#include "webrtc/base/constructormagic.h" #include "webrtc/common_types.h" // NULL -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/modules/audio_coding/neteq4/packet.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -155,4 +155,4 @@ class DecoderDatabase { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DECODER_DATABASE_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DECODER_DATABASE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/decoder_database_unittest.cc b/webrtc/modules/audio_coding/neteq/decoder_database_unittest.cc similarity index 96% rename from webrtc/modules/audio_coding/neteq4/decoder_database_unittest.cc rename to webrtc/modules/audio_coding/neteq/decoder_database_unittest.cc index 76f5a099e..d0c6f5ae8 100644 --- a/webrtc/modules/audio_coding/neteq4/decoder_database_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/decoder_database_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" #include #include @@ -18,7 +18,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_audio_decoder.h" #include "webrtc/test/testsupport/gtest_disable.h" namespace webrtc { @@ -68,11 +68,11 @@ TEST(DecoderDatabase, GetRtpPayloadType) { db.GetRtpPayloadType(kDecoderISAC)); // iSAC is not registered. } -TEST(DecoderDatabase, DISABLED_ON_ANDROID(GetDecoder)) { +TEST(DecoderDatabase, GetDecoder) { DecoderDatabase db; const uint8_t kPayloadType = 0; EXPECT_EQ(DecoderDatabase::kOK, - db.RegisterPayload(kPayloadType, kDecoderILBC)); + db.RegisterPayload(kPayloadType, kDecoderPCM16B)); AudioDecoder* dec = db.GetDecoder(kPayloadType); ASSERT_TRUE(dec != NULL); } diff --git a/webrtc/modules/audio_coding/neteq4/defines.h b/webrtc/modules/audio_coding/neteq/defines.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/defines.h rename to webrtc/modules/audio_coding/neteq/defines.h index b6f9eb2bc..33d1bd9c3 100644 --- a/webrtc/modules/audio_coding/neteq4/defines.h +++ b/webrtc/modules/audio_coding/neteq/defines.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DEFINES_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DEFINES_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DEFINES_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DEFINES_H_ namespace webrtc { @@ -48,4 +48,4 @@ enum Modes { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DEFINES_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DEFINES_H_ diff --git a/webrtc/modules/audio_coding/neteq/delay_logging.h b/webrtc/modules/audio_coding/neteq/delay_logging.h deleted file mode 100644 index 04b1c4015..000000000 --- a/webrtc/modules/audio_coding/neteq/delay_logging.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -/* - * Contains definitions for the delay logging functionality. Only used for debugging and - * tracing purposes. - */ - -#ifndef DELAY_LOGGING_H -#define DELAY_LOGGING_H - -#define NETEQ_DELAY_LOGGING_VERSION_STRING "2.0" - -#define NETEQ_DELAY_LOGGING_SIGNAL_RECIN 1 -#define NETEQ_DELAY_LOGGING_SIGNAL_FLUSH 2 -#define NETEQ_DELAY_LOGGING_SIGNAL_CLOCK 3 -#define NETEQ_DELAY_LOGGING_SIGNAL_EOF 4 -#define NETEQ_DELAY_LOGGING_SIGNAL_DECODE 5 -#define NETEQ_DELAY_LOGGING_SIGNAL_CHANGE_FS 6 -#define NETEQ_DELAY_LOGGING_SIGNAL_MERGE_INFO 7 -#define NETEQ_DELAY_LOGGING_SIGNAL_EXPAND_INFO 8 -#define NETEQ_DELAY_LOGGING_SIGNAL_ACCELERATE_INFO 9 -#define NETEQ_DELAY_LOGGING_SIGNAL_PREEMPTIVE_INFO 10 -#define NETEQ_DELAY_LOGGING_SIGNAL_OPTBUF 11 -#define NETEQ_DELAY_LOGGING_SIGNAL_DECODE_ONE_DESC 12 - -#endif diff --git a/webrtc/modules/audio_coding/neteq4/delay_manager.cc b/webrtc/modules/audio_coding/neteq/delay_manager.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/delay_manager.cc rename to webrtc/modules/audio_coding/neteq/delay_manager.cc index e80b9de51..a935561ef 100644 --- a/webrtc/modules/audio_coding/neteq4/delay_manager.cc +++ b/webrtc/modules/audio_coding/neteq/delay_manager.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" #include #include @@ -16,7 +16,7 @@ #include // max, min #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" -#include "webrtc/modules/audio_coding/neteq4/delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/delay_peak_detector.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/system_wrappers/interface/logging.h" diff --git a/webrtc/modules/audio_coding/neteq4/delay_manager.h b/webrtc/modules/audio_coding/neteq/delay_manager.h similarity index 95% rename from webrtc/modules/audio_coding/neteq4/delay_manager.h rename to webrtc/modules/audio_coding/neteq/delay_manager.h index ed1e87b19..96b5e19eb 100644 --- a/webrtc/modules/audio_coding/neteq4/delay_manager.h +++ b/webrtc/modules/audio_coding/neteq/delay_manager.h @@ -8,15 +8,15 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DELAY_MANAGER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DELAY_MANAGER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DELAY_MANAGER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DELAY_MANAGER_H_ #include // Provide access to size_t. #include -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -161,4 +161,4 @@ class DelayManager { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DELAY_MANAGER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DELAY_MANAGER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/delay_manager_unittest.cc b/webrtc/modules/audio_coding/neteq/delay_manager_unittest.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/delay_manager_unittest.cc rename to webrtc/modules/audio_coding/neteq/delay_manager_unittest.cc index 482a65c9a..6f9733234 100644 --- a/webrtc/modules/audio_coding/neteq4/delay_manager_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/delay_manager_unittest.cc @@ -10,13 +10,13 @@ // Unit tests for DelayManager class. -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" #include #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_delay_peak_detector.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/delay_peak_detector.cc b/webrtc/modules/audio_coding/neteq/delay_peak_detector.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/delay_peak_detector.cc rename to webrtc/modules/audio_coding/neteq/delay_peak_detector.cc index fd5b9c08f..5996d7d19 100644 --- a/webrtc/modules/audio_coding/neteq4/delay_peak_detector.cc +++ b/webrtc/modules/audio_coding/neteq/delay_peak_detector.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/delay_peak_detector.h" #include // max diff --git a/webrtc/modules/audio_coding/neteq4/delay_peak_detector.h b/webrtc/modules/audio_coding/neteq/delay_peak_detector.h similarity index 89% rename from webrtc/modules/audio_coding/neteq4/delay_peak_detector.h rename to webrtc/modules/audio_coding/neteq/delay_peak_detector.h index dfdd2537d..8bf6aba8b 100644 --- a/webrtc/modules/audio_coding/neteq4/delay_peak_detector.h +++ b/webrtc/modules/audio_coding/neteq/delay_peak_detector.h @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DELAY_PEAK_DETECTOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DELAY_PEAK_DETECTOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DELAY_PEAK_DETECTOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DELAY_PEAK_DETECTOR_H_ #include // size_t #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" namespace webrtc { @@ -73,4 +73,4 @@ class DelayPeakDetector { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DELAY_PEAK_DETECTOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DELAY_PEAK_DETECTOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/delay_peak_detector_unittest.cc b/webrtc/modules/audio_coding/neteq/delay_peak_detector_unittest.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/delay_peak_detector_unittest.cc rename to webrtc/modules/audio_coding/neteq/delay_peak_detector_unittest.cc index a3b48209c..080309be0 100644 --- a/webrtc/modules/audio_coding/neteq4/delay_peak_detector_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/delay_peak_detector_unittest.cc @@ -10,7 +10,7 @@ // Unit tests for DelayPeakDetector class. -#include "webrtc/modules/audio_coding/neteq4/delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/delay_peak_detector.h" #include "gtest/gtest.h" diff --git a/webrtc/modules/audio_coding/neteq/dsp.c b/webrtc/modules/audio_coding/neteq/dsp.c deleted file mode 100644 index ea2fa87d5..000000000 --- a/webrtc/modules/audio_coding/neteq/dsp.c +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Copyright (c) 2012 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 file contains some DSP initialization functions and - * constant table definitions. - */ - -#include "dsp.h" - -#include "signal_processing_library.h" - -#include "neteq_error_codes.h" - -/* Filter coefficients used when downsampling from the indicated - sample rates (8, 16, 32, 48 kHz) to 4 kHz. - Coefficients are in Q12. */ - -/* {0.3, 0.4, 0.3} */ -const int16_t WebRtcNetEQ_kDownsample8kHzTbl[] = { 1229, 1638, 1229 }; - -#ifdef NETEQ_WIDEBAND -/* {0.15, 0.2, 0.3, 0.2, 0.15} */ -const int16_t WebRtcNetEQ_kDownsample16kHzTbl[] = -{ 614, 819, 1229, 819, 614}; -#endif - -#ifdef NETEQ_32KHZ_WIDEBAND -/* {0.1425, 0.1251, 0.1525, 0.1628, 0.1525, 0.1251, 0.1425} */ -const int16_t WebRtcNetEQ_kDownsample32kHzTbl[] = -{ 584, 512, 625, 667, 625, 512, 584}; -#endif - -#ifdef NETEQ_48KHZ_WIDEBAND -/* {0.2487, 0.0952, 0.1042, 0.1074, 0.1042, 0.0952, 0.2487} */ -const int16_t WebRtcNetEQ_kDownsample48kHzTbl[] = -{ 1019, 390, 427, 440, 427, 390, 1019}; -#endif - -/* Constants used in expand function WebRtcNetEQ_Expand */ - -/* Q12: -1.264421 + 4.8659148*x - 4.0092827*x^2 + 1.4100529*x^3 */ -const int16_t WebRtcNetEQ_kMixFractionFuncTbl[4] = { -5179, 19931, -16422, 5776 }; - -/* Tabulated divisions to save complexity */ -/* 1049/{0, .., 6} */ -const int16_t WebRtcNetEQ_k1049div[7] = { 0, 1049, 524, 349, 262, 209, 174 }; - -/* 2097/{0, .., 6} */ -const int16_t WebRtcNetEQ_k2097div[7] = { 0, 2097, 1048, 699, 524, 419, 349 }; - -/* 5243/{0, .., 6} */ -const int16_t WebRtcNetEQ_k5243div[7] = { 0, 5243, 2621, 1747, 1310, 1048, 873 }; - -#ifdef WEBRTC_NETEQ_40BITACC_TEST -/* - * Run NetEQ with simulated 40-bit accumulator to run bit-exact to a DSP - * implementation where the main (spl and NetEQ) functions have been - * 40-bit optimized. For testing purposes. - */ - -/**************************************************************************** - * WebRtcNetEQ_40BitAccCrossCorr(...) - * - * Calculates the Cross correlation between two sequences seq1 and seq2. Seq1 - * is fixed and seq2 slides as the pointer is increased with step - * - * Input: - * - seq1 : First sequence (fixed throughout the correlation) - * - seq2 : Second sequence (slided step_seq2 for each - * new correlation) - * - dimSeq : Number of samples to use in the cross correlation. - * Should be no larger than 1024 to avoid overflow. - * - dimCrossCorr : Number of CrossCorrelations to calculate (start - * position for seq2 is updated for each new one) - * - rShift : Number of right shifts to use - * - step_seq2 : How many (positive or negative) steps the seq2 - * pointer should be updated for each new cross - * correlation value - * - * Output: - * - crossCorr : The cross correlation in Q-rShift - */ - -void WebRtcNetEQ_40BitAccCrossCorr(int32_t *crossCorr, - int16_t *seq1, - int16_t *seq2, - int16_t dimSeq, - int16_t dimCrossCorr, - int16_t rShift, - int16_t step_seq2) -{ - int i, j; - int16_t *seq1Ptr, *seq2Ptr; - int64_t acc; - - for (i = 0; i < dimCrossCorr; i++) - { - /* Set the pointer to the static vector, set the pointer to - the sliding vector and initialize crossCorr */ - seq1Ptr = seq1; - seq2Ptr = seq2 + (step_seq2 * i); - acc = 0; - - /* Perform the cross correlation */ - for (j = 0; j < dimSeq; j++) - { - acc += WEBRTC_SPL_MUL_16_16((*seq1Ptr), (*seq2Ptr)); - seq1Ptr++; - seq2Ptr++; - } - - (*crossCorr) = (int32_t) (acc >> rShift); - crossCorr++; - } -} - -/**************************************************************************** - * WebRtcNetEQ_40BitAccDotW16W16(...) - * - * Calculates the dot product between two vectors (int16_t) - * - * Input: - * - vector1 : Vector 1 - * - vector2 : Vector 2 - * - len : Number of samples in vector - * Should be no larger than 1024 to avoid overflow. - * - scaling : The number of left shifts required to avoid overflow - * in the dot product - * Return value : The dot product - */ - -int32_t WebRtcNetEQ_40BitAccDotW16W16(int16_t *vector1, - int16_t *vector2, - int len, - int scaling) -{ - int32_t sum; - int i; - int64_t acc; - - acc = 0; - for (i = 0; i < len; i++) - { - acc += WEBRTC_SPL_MUL_16_16(*vector1++, *vector2++); - } - - sum = (int32_t) (acc >> scaling); - - return(sum); -} - -#endif /* WEBRTC_NETEQ_40BITACC_TEST */ - -/**************************************************************************** - * WebRtcNetEQ_DSPInit(...) - * - * Initializes DSP side of NetEQ. - * - * Input: - * - inst : NetEq DSP instance - * - fs : Initial sample rate (may change when decoding data) - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - * : non-zero - error - */ - -int WebRtcNetEQ_DSPInit(DSPInst_t *inst, uint16_t fs) -{ - - int res = 0; - int16_t fs_mult; - - /* Pointers and values to save before clearing the instance */ -#ifdef NETEQ_CNG_CODEC - void *savedPtr1 = inst->CNG_Codec_inst; -#endif - void *savedPtr2 = inst->pw16_readAddress; - void *savedPtr3 = inst->pw16_writeAddress; - void *savedPtr4 = inst->main_inst; -#ifdef NETEQ_VAD - void *savedVADptr = inst->VADInst.VADState; - VADInitFunction savedVADinit = inst->VADInst.initFunction; - VADSetmodeFunction savedVADsetmode = inst->VADInst.setmodeFunction; - VADFunction savedVADfunc = inst->VADInst.VADFunction; - int16_t savedVADEnabled = inst->VADInst.VADEnabled; - int savedVADMode = inst->VADInst.VADMode; -#endif /* NETEQ_VAD */ - DSPStats_t saveStats; - int16_t saveMsPerCall = inst->millisecondsPerCall; - enum BGNMode saveBgnMode = inst->BGNInst.bgnMode; -#ifdef NETEQ_STEREO - MasterSlaveInfo* saveMSinfo = inst->msInfo; -#endif - - /* copy contents of statInst to avoid clearing */WEBRTC_SPL_MEMCPY_W16(&saveStats, &(inst->statInst), - sizeof(DSPStats_t)/sizeof(int16_t)); - - /* check that the sample rate is valid */ - if ((fs != 8000) -#ifdef NETEQ_WIDEBAND - &&(fs!=16000) -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - &&(fs!=32000) -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - &&(fs!=48000) -#endif - ) - { - /* invalid rate */ - return (CODEC_DB_UNSUPPORTED_FS); - } - - /* calcualte fs/8000 */ - fs_mult = WebRtcSpl_DivW32W16ResW16(fs, 8000); - - /* Set everything to zero since most variables should be zero at start */ - WebRtcSpl_MemSetW16((int16_t *) inst, 0, sizeof(DSPInst_t) / sizeof(int16_t)); - - /* Restore saved pointers */ -#ifdef NETEQ_CNG_CODEC - inst->CNG_Codec_inst = (CNG_dec_inst *)savedPtr1; -#endif - inst->pw16_readAddress = (int16_t *) savedPtr2; - inst->pw16_writeAddress = (int16_t *) savedPtr3; - inst->main_inst = savedPtr4; -#ifdef NETEQ_VAD - inst->VADInst.VADState = savedVADptr; - inst->VADInst.initFunction = savedVADinit; - inst->VADInst.setmodeFunction = savedVADsetmode; - inst->VADInst.VADFunction = savedVADfunc; - inst->VADInst.VADEnabled = savedVADEnabled; - inst->VADInst.VADMode = savedVADMode; -#endif /* NETEQ_VAD */ - - /* Initialize main part */ - inst->fs = fs; - inst->millisecondsPerCall = saveMsPerCall; - inst->timestampsPerCall = inst->millisecondsPerCall * 8 * fs_mult; - inst->ExpandInst.w16_overlap = 5 * fs_mult; - inst->endPosition = 565 * fs_mult; - inst->curPosition = inst->endPosition - inst->ExpandInst.w16_overlap; - inst->w16_seedInc = 1; - inst->uw16_seed = 777; - inst->w16_muteFactor = 16384; /* 1.0 in Q14 */ - inst->w16_frameLen = 3 * inst->timestampsPerCall; /* Dummy initialize to 30ms */ - - inst->w16_speechHistoryLen = 256 * fs_mult; - inst->pw16_speechHistory = &inst->speechBuffer[inst->endPosition - - inst->w16_speechHistoryLen]; - inst->ExpandInst.pw16_overlapVec = &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - - inst->ExpandInst.w16_overlap]); - - /* Reusage of memory in speechBuffer inside Expand */ - inst->ExpandInst.pw16_expVecs[0] = &inst->speechBuffer[0]; - inst->ExpandInst.pw16_expVecs[1] = &inst->speechBuffer[126 * fs_mult]; - inst->ExpandInst.pw16_arState = &inst->speechBuffer[2 * 126 * fs_mult]; - inst->ExpandInst.pw16_arFilter = &inst->speechBuffer[2 * 126 * fs_mult - + UNVOICED_LPC_ORDER]; - /* Ends at 2*126*fs_mult+UNVOICED_LPC_ORDER+(UNVOICED_LPC_ORDER+1) */ - - inst->ExpandInst.w16_expandMuteFactor = 16384; /* 1.0 in Q14 */ - - /* Initialize BGN part */ - inst->BGNInst.pw16_filter[0] = 4096; - inst->BGNInst.w16_scale = 20000; - inst->BGNInst.w16_scaleShift = 24; - inst->BGNInst.w32_energyUpdate = 500000; - inst->BGNInst.w32_energyUpdateLow = 0; - inst->BGNInst.w32_energy = 2500; - inst->BGNInst.w16_initialized = 0; - inst->BGNInst.bgnMode = saveBgnMode; - - /* Recreate statistics counters */WEBRTC_SPL_MEMCPY_W16(&(inst->statInst), &saveStats, - sizeof(DSPStats_t)/sizeof(int16_t)); - -#ifdef NETEQ_STEREO - /* Write back the pointer. */ - inst->msInfo = saveMSinfo; -#endif - -#ifdef NETEQ_CNG_CODEC - if (inst->CNG_Codec_inst!=NULL) - { - /* initialize comfort noise generator */ - res |= WebRtcCng_InitDec(inst->CNG_Codec_inst); - } -#endif - -#ifdef NETEQ_VAD - /* initialize PostDecode VAD instance - (don't bother checking for NULL instance, this is done inside init function) */ - res |= WebRtcNetEQ_InitVAD(&inst->VADInst, fs); -#endif /* NETEQ_VAD */ - - return (res); -} - -/**************************************************************************** - * WebRtcNetEQ_AddressInit(...) - * - * Initializes the shared-memory communication on the DSP side. - * - * Input: - * - inst : NetEQ DSP instance - * - data2McuAddress : Pointer to memory where DSP writes / MCU reads - * - data2DspAddress : Pointer to memory where MCU writes / DSP reads - * - mainInst : NetEQ main instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - */ - -int WebRtcNetEQ_AddressInit(DSPInst_t *inst, const void *data2McuAddress, - const void *data2DspAddress, const void *mainInst) -{ - - /* set shared-memory addresses in the DSP instance */ - inst->pw16_readAddress = (int16_t *) data2DspAddress; - inst->pw16_writeAddress = (int16_t *) data2McuAddress; - - /* set pointer to main NetEQ instance */ - inst->main_inst = (void *) mainInst; - - /* set output frame size to 10 ms = 80 samples in narrowband */ - inst->millisecondsPerCall = 10; - inst->timestampsPerCall = 80; - - return (0); - -} - -/**************************************************************************** - * NETEQDSP_clearInCallStats(...) - * - * Reset in-call statistics variables on DSP side. - * - * Input: - * - inst : NetEQ DSP instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - */ - -int WebRtcNetEQ_ClearInCallStats(DSPInst_t *inst) -{ - /* Reset statistics counters */ - inst->statInst.accelerateLength = 0; - inst->statInst.expandLength = 0; - inst->statInst.preemptiveLength = 0; - inst->statInst.addedSamples = 0; - return (0); -} - -/**************************************************************************** - * WebRtcNetEQ_ClearPostCallStats(...) - * - * Reset post-call statistics variables on DSP side. - * - * Input: - * - inst : NetEQ DSP instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - */ - -int WebRtcNetEQ_ClearPostCallStats(DSPInst_t *inst) -{ - - /* Reset statistics counters */ - inst->statInst.expandedVoiceSamples = 0; - inst->statInst.expandedNoiseSamples = 0; - return (0); -} - -/**************************************************************************** - * WebRtcNetEQ_ClearActivityStats(...) - * - * Reset processing activity statistics. - * - * Input: - * - inst : NetEQ DSP instance - * - * Output: - * - inst : Updated instance - * - */ - -void WebRtcNetEQ_ClearActivityStats(DSPInst_t *inst) { - memset(&inst->activity_stats, 0, sizeof(ActivityStats)); -} - -#ifdef NETEQ_VAD - -/**************************************************************************** - * WebRtcNetEQ_InitVAD(...) - * - * Initializes post-decode VAD instance. - * - * Input: - * - VADinst : PostDecodeVAD instance - * - fs : Initial sample rate - * - * Output: - * - VADinst : Updated instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_InitVAD(PostDecodeVAD_t *VADInst, uint16_t fs) -{ - - int res = 0; - - /* initially, disable the post-decode VAD */ - VADInst->VADEnabled = 0; - - if (VADInst->VADState != NULL /* if VAD state is provided */ - && VADInst->initFunction != NULL /* and all function ... */ - && VADInst->setmodeFunction != NULL /* ... pointers ... */ - && VADInst->VADFunction != NULL) /* ... are defined */ - { - res = VADInst->initFunction( VADInst->VADState ); /* call VAD init function */ - res |= WebRtcNetEQ_SetVADModeInternal( VADInst, VADInst->VADMode ); - - if (res!=0) - { - /* something is wrong; play it safe and set the VADstate to NULL */ - VADInst->VADState = NULL; - } - else if (fs<=16000) - { - /* enable VAD if NB or WB (VAD cannot handle SWB) */ - VADInst->VADEnabled = 1; - } - } - - /* reset SID/CNG interval counter */ - VADInst->SIDintervalCounter = 0; - - /* initialize with active-speaker decision */ - VADInst->VADDecision = 1; - - return(res); - -} - -/**************************************************************************** - * WebRtcNetEQ_SetVADModeInternal(...) - * - * Set the VAD mode in the VAD struct, and communicate it to the VAD instance - * if it exists. - * - * Input: - * - VADinst : PostDecodeVAD instance - * - mode : Mode number passed on to the VAD function - * - * Output: - * - VADinst : Updated instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_SetVADModeInternal(PostDecodeVAD_t *VADInst, int mode) -{ - - int res = 0; - - VADInst->VADMode = mode; - - if (VADInst->VADState != NULL) - { - /* call setmode function */ - res = VADInst->setmodeFunction(VADInst->VADState, mode); - } - - return(res); - -} - -#endif /* NETEQ_VAD */ - -/**************************************************************************** - * WebRtcNetEQ_FlushSpeechBuffer(...) - * - * Flush the speech buffer. - * - * Input: - * - inst : NetEq DSP instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - * : non-zero - error - */ - -int WebRtcNetEQ_FlushSpeechBuffer(DSPInst_t *inst) -{ - int16_t fs_mult; - - /* calcualte fs/8000 */ - fs_mult = WebRtcSpl_DivW32W16ResW16(inst->fs, 8000); - - /* clear buffer */ - WebRtcSpl_MemSetW16(inst->speechBuffer, 0, SPEECH_BUF_SIZE); - inst->endPosition = 565 * fs_mult; - inst->curPosition = inst->endPosition - inst->ExpandInst.w16_overlap; - - return 0; -} - diff --git a/webrtc/modules/audio_coding/neteq/dsp.h b/webrtc/modules/audio_coding/neteq/dsp.h deleted file mode 100644 index 9371938d5..000000000 --- a/webrtc/modules/audio_coding/neteq/dsp.h +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright (c) 2012 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 file contains some DSP initialization functions, - * constant table definitions and other parameters. - * Also contains definitions of all DSP-side data structures. - */ - - -#ifndef DSP_H -#define DSP_H - -#include "typedefs.h" - -#include "webrtc_cng.h" - -#include "codec_db_defines.h" -#include "neteq_defines.h" -#include "neteq_statistics.h" - -#ifdef NETEQ_ATEVENT_DECODE -#include "dtmf_tonegen.h" -#endif - - - -/*****************************/ -/* Pre-processor definitions */ -/*****************************/ - -/* FSMULT is the sample rate divided by 8000 */ -#if defined(NETEQ_48KHZ_WIDEBAND) - #define FSMULT 6 -#elif defined(NETEQ_32KHZ_WIDEBAND) - #define FSMULT 4 -#elif defined(NETEQ_WIDEBAND) - #define FSMULT 2 -#else - #define FSMULT 1 -#endif - -/* Size of the speech buffer (or synchronization buffer). */ -/* 60 ms decoding + 10 ms syncbuff + 0.625ms lookahead */ -#define SPEECH_BUF_SIZE (565 * FSMULT) - -/* Misc definitions */ -#define BGN_LPC_ORDER (4 + FSMULT) /* 5, 6, 8, or 10 */ -#define UNVOICED_LPC_ORDER 6 -#define RANDVEC_NO_OF_SAMPLES 256 - -/* Number of milliseconds to remove/add during accelerate/pre-emptive expand - under BGNonly operation */ -#define DEFAULT_TIME_ADJUST 8 - -/* Number of RecOut calls without CNG/SID before re-enabling post-decode VAD */ -#define POST_DECODE_VAD_AUTO_ENABLE 3000 - -/* 8kHz windowing in Q15 (over 5 samples) */ -#define NETEQ_OVERLAP_WINMUTE_8KHZ_START 27307 -#define NETEQ_OVERLAP_WINMUTE_8KHZ_INC -5461 -#define NETEQ_OVERLAP_WINUNMUTE_8KHZ_START 5461 -#define NETEQ_OVERLAP_WINUNMUTE_8KHZ_INC 5461 -/* 16kHz windowing in Q15 (over 10 samples) */ -#define NETEQ_OVERLAP_WINMUTE_16KHZ_START 29789 -#define NETEQ_OVERLAP_WINMUTE_16KHZ_INC -2979 -#define NETEQ_OVERLAP_WINUNMUTE_16KHZ_START 2979 -#define NETEQ_OVERLAP_WINUNMUTE_16KHZ_INC 2979 -/* 32kHz windowing in Q15 (over 20 samples) */ -#define NETEQ_OVERLAP_WINMUTE_32KHZ_START 31208 -#define NETEQ_OVERLAP_WINMUTE_32KHZ_INC -1560 -#define NETEQ_OVERLAP_WINUNMUTE_32KHZ_START 1560 -#define NETEQ_OVERLAP_WINUNMUTE_32KHZ_INC 1560 -/* 48kHz windowing in Q15 (over 30 samples) */ -#define NETEQ_OVERLAP_WINMUTE_48KHZ_START 31711 -#define NETEQ_OVERLAP_WINMUTE_48KHZ_INC -1057 -#define NETEQ_OVERLAP_WINUNMUTE_48KHZ_START 1057 -#define NETEQ_OVERLAP_WINUNMUTE_48KHZ_INC 1057 - -/* Fade BGN towards zero after this many Expand calls */ -#define FADE_BGN_TIME 200 - - -/*******************/ -/* Constant tables */ -/*******************/ - -extern const int16_t WebRtcNetEQ_kDownsample8kHzTbl[]; -extern const int16_t WebRtcNetEQ_kDownsample16kHzTbl[]; -extern const int16_t WebRtcNetEQ_kDownsample32kHzTbl[]; -extern const int16_t WebRtcNetEQ_kDownsample48kHzTbl[]; -extern const int16_t WebRtcNetEQ_kRandnTbl[]; -extern const int16_t WebRtcNetEQ_kMixFractionFuncTbl[]; -extern const int16_t WebRtcNetEQ_k1049div[]; -extern const int16_t WebRtcNetEQ_k2097div[]; -extern const int16_t WebRtcNetEQ_k5243div[]; - - - -/************/ -/* Typedefs */ -/************/ - -enum BGNMode -{ - BGN_ON, /* default "normal" behavior with eternal noise */ - BGN_FADE, /* noise fades to zero after some time */ - BGN_OFF /* background noise is always zero */ -}; - -#ifdef NETEQ_STEREO -enum MasterSlaveMode -{ - NETEQ_MONO, /* stand-alone instance */ - NETEQ_MASTER, /* master instance in a spatial/stereo configuration */ - NETEQ_SLAVE /* slave instance in a spatial/stereo configuration */ -}; - -enum MasterSlaveExtraInfo -{ - NO_INFO, /* no info to convey */ - ACC_FAIL, /* signal that accelerate failed */ - PE_EXP_FAIL, /* signal that pre-emptive expand failed */ - DTMF_OVERDUB, /* signal that DTMF overdub is generated */ - DTMF_ONLY /* signal that DTMF only is played */ -}; -#endif - -/****************************/ -/* DSP-side data structures */ -/****************************/ - -/* Background noise (BGN) instance for storing BGN parameters - (sub-instance of NETEQDSP_inst) */ -typedef struct BGNInst_t_ -{ - - int32_t w32_energy; - int32_t w32_energyMax; - int32_t w32_energyUpdate; - int32_t w32_energyUpdateLow; - int16_t pw16_filterState[BGN_LPC_ORDER]; - int16_t pw16_filter[BGN_LPC_ORDER + 1]; - int16_t w16_mutefactor; - int16_t w16_scale; - int16_t w16_scaleShift; - int16_t w16_initialized; - enum BGNMode bgnMode; - -} BGNInst_t; - -/* Expansion instance (sub-instance of NETEQDSP_inst) */ -typedef struct ExpandInst_t_ -{ - - int16_t w16_overlap; /* Constant, 5 for NB and 10 for WB */ - int16_t w16_consecExp; /* Number of consecutive expand calls */ - int16_t *pw16_arFilter; /* length [UNVOICED_LPC_ORDER+1] */ - int16_t *pw16_arState; /* length [UNVOICED_LPC_ORDER] */ - int16_t w16_arGain; - int16_t w16_arGainScale; - int16_t w16_vFraction; /* Q14 */ - int16_t w16_currentVFraction; /* Q14 */ - int16_t *pw16_expVecs[2]; - int16_t w16_lags[3]; - int16_t w16_maxLag; - int16_t *pw16_overlapVec; /* last samples of speech history */ - int16_t w16_lagsDirection; - int16_t w16_lagsPosition; - int16_t w16_expandMuteFactor; /* Q14 */ - int16_t w16_stopMuting; - int16_t w16_onset; - int16_t w16_muteSlope; /* Q20 */ - -} ExpandInst_t; - -#ifdef NETEQ_VAD - -/* - * VAD function pointer types, replicating the typedefs in webrtc_neteq_internal.h. - * These function pointers match the definitions of WebRtc VAD functions WebRtcVad_Init, - * WebRtcVad_set_mode and WebRtcVad_Process, respectively, all found in webrtc_vad.h. - */ -typedef int (*VADInitFunction)(void *VAD_inst); -typedef int (*VADSetmodeFunction)(void *VAD_inst, int mode); -typedef int (*VADFunction)(void *VAD_inst, int fs, int16_t *frame, - int frameLen); - -/* Post-decode VAD instance (sub-instance of NETEQDSP_inst) */ -typedef struct PostDecodeVAD_t_ -{ - - void *VADState; /* pointer to a VAD instance */ - - int16_t VADEnabled; /* 1 if enabled, 0 if disabled */ - int VADMode; /* mode parameter to pass to the VAD function */ - int VADDecision; /* 1 for active, 0 for passive */ - int16_t SIDintervalCounter; /* reset when decoding CNG/SID frame, - increment for each recout call */ - - /* Function pointers */ - VADInitFunction initFunction; /* VAD init function */ - VADSetmodeFunction setmodeFunction; /* VAD setmode function */ - VADFunction VADFunction; /* VAD function */ - -} PostDecodeVAD_t; - -#endif /* NETEQ_VAD */ - -#ifdef NETEQ_STEREO -#define MAX_MS_DECODES 10 - -typedef struct -{ - /* Stand-alone, master, or slave */ - enum MasterSlaveMode msMode; - - enum MasterSlaveExtraInfo extraInfo; - - uint16_t instruction; - int16_t distLag; - int16_t corrLag; - int16_t bestIndex; - - uint32_t endTimestamp; - uint16_t samplesLeftWithOverlap; - -} MasterSlaveInfo; -#endif - - -/* "Main" NetEQ DSP instance */ -typedef struct DSPInst_t_ -{ - - /* MCU/DSP Communication layer */ - int16_t *pw16_readAddress; - int16_t *pw16_writeAddress; - void *main_inst; - - /* Output frame size in ms and samples */ - int16_t millisecondsPerCall; - int16_t timestampsPerCall; - - /* - * Example of speech buffer - * - * ----------------------------------------------------------- - * | History T-60 to T | Future | - * ----------------------------------------------------------- - * ^ ^ - * | | - * curPosition endPosition - * - * History is gradually shifted out to the left when inserting - * new data at the end. - */ - - int16_t speechBuffer[SPEECH_BUF_SIZE]; /* History/future speech buffer */ - int curPosition; /* Next sample to play */ - int endPosition; /* Position that ends future data */ - uint32_t endTimestamp; /* Timestamp value at end of future data */ - uint32_t videoSyncTimestamp; /* (Estimated) timestamp of the last - played sample (usually same as - endTimestamp-(endPosition-curPosition) - except during Expand and CNG) */ - uint16_t fs; /* sample rate in Hz */ - int16_t w16_frameLen; /* decoder frame length in samples */ - int16_t w16_mode; /* operation used during last RecOut call */ - int16_t w16_muteFactor; /* speech mute factor in Q14 */ - int16_t *pw16_speechHistory; /* beginning of speech history during Expand */ - int16_t w16_speechHistoryLen; /* 256 for NB and 512 for WB */ - - /* random noise seed parameters */ - int16_t w16_seedInc; - uint32_t uw16_seed; - - /* VQmon related variable */ - int16_t w16_concealedTS; - - /*****************/ - /* Sub-instances */ - /*****************/ - - /* Decoder data */ - CodecFuncInst_t codec_ptr_inst; - -#ifdef NETEQ_CNG_CODEC - /* CNG "decoder" instance */ - CNG_dec_inst *CNG_Codec_inst; -#endif /* NETEQ_CNG_CODEC */ - -#ifdef NETEQ_ATEVENT_DECODE - /* DTMF generator instance */ - dtmf_tone_inst_t DTMFInst; -#endif /* NETEQ_CNG_CODEC */ - -#ifdef NETEQ_VAD - /* Post-decode VAD instance */ - PostDecodeVAD_t VADInst; -#endif /* NETEQ_VAD */ - - /* Expand instance (defined above) */ - ExpandInst_t ExpandInst; - - /* Background noise instance (defined above) */ - BGNInst_t BGNInst; - - /* Internal statistics instance */ - DSPStats_t statInst; - - /* Internal instance for short-term processing activity. */ - ActivityStats activity_stats; - -#ifdef NETEQ_STEREO - /* Pointer to Master/Slave info */ - MasterSlaveInfo *msInfo; -#endif - -} DSPInst_t; - - -/*************************/ -/* Function declarations */ -/*************************/ - -/**************************************************************************** - * WebRtcNetEQ_DSPInit(...) - * - * Initializes DSP side of NetEQ. - * - * Input: - * - inst : NetEq DSP instance - * - fs : Initial sample rate (may change when decoding data) - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - * : non-zero - error - */ - -int WebRtcNetEQ_DSPInit(DSPInst_t *inst, uint16_t fs); - -/**************************************************************************** - * WebRtcNetEQ_AddressInit(...) - * - * Initializes the shared-memory communication on the DSP side. - * - * Input: - * - inst : NetEQ DSP instance - * - data2McuAddress : Pointer to memory where DSP writes / MCU reads - * - data2DspAddress : Pointer to memory where MCU writes / DSP reads - * - mainInst : NetEQ main instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - */ - -int WebRtcNetEQ_AddressInit(DSPInst_t *inst, const void *data2McuAddress, - const void *data2DspAddress, const void *mainInst); - -/**************************************************************************** - * WebRtcNetEQ_ClearInCallStats(...) - * - * Reset in-call statistics variables on DSP side. - * - * Input: - * - inst : NetEQ DSP instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - */ - -int WebRtcNetEQ_ClearInCallStats(DSPInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_ClearPostCallStats(...) - * - * Reset post-call statistics variables on DSP side. - * - * Input: - * - inst : NetEQ DSP instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - */ - -int WebRtcNetEQ_ClearPostCallStats(DSPInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_ClearActivityStats(...) - * - * Reset processing activity statistics. - * - * Input: - * - inst : NetEQ DSP instance - * - * Output: - * - inst : Updated instance - * - */ - -void WebRtcNetEQ_ClearActivityStats(DSPInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_RecOutInternal(...) - * - * This function asks NetEQ for more speech/audio data. - * - * Input: - * - inst : NetEQ instance, i.e. the user that requests more - * speech/audio data. - * - outdata : Pointer to a memory space where the output data - * should be stored. - * - BGNonly : If non-zero, RecOut will only produce background - * noise. It will still draw packets from the packet - * buffer, but they will never be decoded. - * - av_sync : 1 if NetEQ is in AV-sync, 0 otherwise. - * - * Output: - * - inst : Updated user information - * - len : Number of samples that were outputted from NetEq - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RecOutInternal(DSPInst_t *inst, int16_t *pw16_outData, - int16_t *pw16_len, int16_t BGNonly, int av_sync); - -/**************************************************************************** - * WebRtcNetEQ_Normal(...) - * - * This function has the possibility to modify data that is played out in Normal - * mode, for example adjust the gain of the signal. The length of the signal - * can not be changed. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector - * - decoded : Pointer to vector of new data from decoder - * - len : Number of input samples - * - * Output: - * - inst : Updated user information - * - pw16_len : Pointer to varibale where the number of samples - * produced will be written - * - * Return value : >=0 - Number of samples written to outData - * -1 - Error - */ - -int WebRtcNetEQ_Normal(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_decoded, int16_t len, - int16_t *pw16_outData, int16_t *pw16_len); - -/**************************************************************************** - * WebRtcNetEQ_Expand(...) - * - * This function produces one "chunk" of expansion data (PLC audio). The - * lenght of the produced audio depends on the speech history. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector - * - BGNonly : If non-zero, Expand will only produce background - * noise. - * - pw16_len : Desired number of samples (only for BGN mode). - * - * Output: - * - inst : Updated user information - * - outdata : Pointer to a memory space where the output data - * should be stored - * - pw16_len : Number of samples that were outputted from NetEq - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_Expand(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_outData, int16_t *pw16_len, - int16_t BGNonly); - -/**************************************************************************** - * WebRtcNetEQ_GenerateBGN(...) - * - * This function generates and writes len samples of background noise to the - * output vector. The Expand function will be called repeteadly until the - * correct number of samples is produced. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector - * - len : Desired length of produced BGN. - * - * - * Output: - * - pw16_outData : Pointer to a memory space where the output data - * should be stored - * - * Return value : >=0 - Number of noise samples produced and written - * to output - * -1 - Error - */ - -int WebRtcNetEQ_GenerateBGN(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_outData, int16_t len); - -/**************************************************************************** - * WebRtcNetEQ_PreEmptiveExpand(...) - * - * This function tries to extend the audio data by repeating one or several - * pitch periods. The operation is only carried out if the correlation is - * strong or if the signal energy is very low. The algorithm is the - * reciprocal of the Accelerate algorithm. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector. - * - decoded : Pointer to newly decoded speech. - * - len : Length of decoded speech. - * - oldDataLen : Length of the part of decoded that has already been played out. - * - BGNonly : If non-zero, Pre-emptive Expand will only copy - * the first DEFAULT_TIME_ADJUST seconds of the - * input and append to the end. No signal matching is - * done. - * - * Output: - * - inst : Updated instance - * - outData : Pointer to a memory space where the output data - * should be stored. The vector must be at least - * min(len + 120*fs/8000, NETEQ_MAX_OUTPUT_SIZE) - * elements long. - * - pw16_len : Number of samples written to outData. - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_PreEmptiveExpand(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - const int16_t *pw16_decoded, int len, int oldDataLen, - int16_t *pw16_outData, int16_t *pw16_len, - int16_t BGNonly); - -/**************************************************************************** - * WebRtcNetEQ_Accelerate(...) - * - * This function tries to shorten the audio data by removing one or several - * pitch periods. The operation is only carried out if the correlation is - * strong or if the signal energy is very low. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector. - * - decoded : Pointer to newly decoded speech. - * - len : Length of decoded speech. - * - BGNonly : If non-zero, Accelerate will only remove the last - * DEFAULT_TIME_ADJUST seconds of the intput. - * No signal matching is done. - * - * - * Output: - * - inst : Updated instance - * - outData : Pointer to a memory space where the output data - * should be stored - * - pw16_len : Number of samples written to outData. - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_Accelerate(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - const int16_t *pw16_decoded, int len, - int16_t *pw16_outData, int16_t *pw16_len, - int16_t BGNonly); - -/**************************************************************************** - * WebRtcNetEQ_Merge(...) - * - * This function is used to merge new data from the decoder to the exisiting - * stream in the synchronization buffer. The merge operation is typically - * done after a packet loss, where the end of the expanded data does not - * fit naturally with the new decoded data. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector. - * - decoded : Pointer to new decoded speech. - * - len : Number of samples in pw16_decoded. - * - * - * Output: - * - inst : Updated user information - * - outData : Pointer to a memory space where the output data - * should be stored - * - pw16_len : Number of samples written to pw16_outData - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_Merge(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_decoded, int len, int16_t *pw16_outData, - int16_t *pw16_len); - -/**************************************************************************** - * WebRtcNetEQ_Cng(...) - * - * This function produces CNG according to RFC 3389 - * - * Input: - * - inst : NetEQ DSP instance - * - len : Number of samples to produce - * - * Output: - * - pw16_outData : Output CNG - * - * Return value : 0 - Ok - * <0 - Error - */ - -#ifdef NETEQ_CNG_CODEC -/* Must compile NetEQ with CNG support to enable this function */ - -int WebRtcNetEQ_Cng(DSPInst_t *inst, int16_t *pw16_outData, int len); - -#endif /* NETEQ_CNG_CODEC */ - -/**************************************************************************** - * WebRtcNetEQ_BGNUpdate(...) - * - * This function updates the background noise parameter estimates. - * - * Input: - * - inst : NetEQ instance, where the speech history is stored. - * - scratchPtr : Pointer to scratch vector. - * - * Output: - * - inst : Updated information about the BGN characteristics. - * - * Return value : No return value - */ - -void WebRtcNetEQ_BGNUpdate( -#ifdef SCRATCH - DSPInst_t *inst, int16_t *pw16_scratchPtr -#else - DSPInst_t *inst -#endif - ); - -#ifdef NETEQ_VAD -/* Functions used by post-decode VAD */ - -/**************************************************************************** - * WebRtcNetEQ_InitVAD(...) - * - * Initializes post-decode VAD instance. - * - * Input: - * - VADinst : PostDecodeVAD instance - * - fs : Initial sample rate - * - * Output: - * - VADinst : Updated instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_InitVAD(PostDecodeVAD_t *VADInst, uint16_t fs); - -/**************************************************************************** - * WebRtcNetEQ_SetVADModeInternal(...) - * - * Set the VAD mode in the VAD struct, and communicate it to the VAD instance - * if it exists. - * - * Input: - * - VADinst : PostDecodeVAD instance - * - mode : Mode number passed on to the VAD function - * - * Output: - * - VADinst : Updated instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_SetVADModeInternal(PostDecodeVAD_t *VADInst, int mode); - -#endif /* NETEQ_VAD */ - -/**************************************************************************** - * WebRtcNetEQ_FlushSpeechBuffer(...) - * - * Flush the speech buffer. - * - * Input: - * - inst : NetEq DSP instance - * - * Output: - * - inst : Updated instance - * - * Return value : 0 - ok - * : non-zero - error - */ - -int WebRtcNetEQ_FlushSpeechBuffer(DSPInst_t *inst); - -#ifndef WEBRTC_NETEQ_40BITACC_TEST - -#include "signal_processing_library.h" -/* Map to regular SPL functions */ -#define WebRtcNetEQ_CrossCorr WebRtcSpl_CrossCorrelation -#define WebRtcNetEQ_DotW16W16 WebRtcSpl_DotProductWithScale - -#else /* WEBRTC_NETEQ_40BITACC_TEST defined */ -/* Run NetEQ with simulated 40-bit accumulator to run bit-exact to a DSP - implementation where the main (splib and NetEQ) functions have been - 40-bit optimized. */ - -/* Map to special 40-bit optimized functions, defined below */ -#define WebRtcNetEQ_CrossCorr WebRtcNetEQ_40BitAccCrossCorr -#define WebRtcNetEQ_DotW16W16 WebRtcNetEQ_40BitAccDotW16W16 - -/**************************************************************************** - * WebRtcNetEQ_40BitAccCrossCorr(...) - * - * Calculates the Cross correlation between two sequences seq1 and seq2. Seq1 - * is fixed and seq2 slides as the pointer is increased with step - * - * Input: - * - seq1 : First sequence (fixed throughout the correlation) - * - seq2 : Second sequence (slided step_seq2 for each - * new correlation) - * - dimSeq : Number of samples to use in the cross correlation. - * Should be no larger than 1024 to avoid overflow. - * - dimCrossCorr : Number of CrossCorrelations to calculate (start - * position for seq2 is updated for each new one) - * - rShift : Number of right shifts to use - * - step_seq2 : How many (positive or negative) steps the seq2 - * pointer should be updated for each new cross - * correlation value - * - * Output: - * - crossCorr : The cross correlation in Q-rShift - */ - -void WebRtcNetEQ_40BitAccCrossCorr(int32_t *crossCorr, int16_t *seq1, - int16_t *seq2, int16_t dimSeq, - int16_t dimCrossCorr, int16_t rShift, - int16_t step_seq2); - -/**************************************************************************** - * WebRtcNetEQ_40BitAccDotW16W16(...) - * - * Calculates the dot product between two vectors (int16_t) - * - * Input: - * - vector1 : Vector 1 - * - vector2 : Vector 2 - * - len : Number of samples in vector - * Should be no larger than 1024 to avoid overflow. - * - scaling : The number of right shifts (after multiplication) - * required to avoid overflow in the dot product. - * Return value : The dot product - */ - -int32_t WebRtcNetEQ_40BitAccDotW16W16(int16_t *vector1, int16_t *vector2, - int len, int scaling); - -#endif /* WEBRTC_NETEQ_40BITACC_TEST */ - -#endif /* DSP_H */ diff --git a/webrtc/modules/audio_coding/neteq4/dsp_helper.cc b/webrtc/modules/audio_coding/neteq/dsp_helper.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/dsp_helper.cc rename to webrtc/modules/audio_coding/neteq/dsp_helper.cc index e1aa0e53d..7451ae26f 100644 --- a/webrtc/modules/audio_coding/neteq4/dsp_helper.cc +++ b/webrtc/modules/audio_coding/neteq/dsp_helper.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/dsp_helper.h" +#include "webrtc/modules/audio_coding/neteq/dsp_helper.h" #include #include // Access to memset. diff --git a/webrtc/modules/audio_coding/neteq4/dsp_helper.h b/webrtc/modules/audio_coding/neteq/dsp_helper.h similarity index 95% rename from webrtc/modules/audio_coding/neteq4/dsp_helper.h rename to webrtc/modules/audio_coding/neteq/dsp_helper.h index 60cd995d8..af4f4d6c8 100644 --- a/webrtc/modules/audio_coding/neteq4/dsp_helper.h +++ b/webrtc/modules/audio_coding/neteq/dsp_helper.h @@ -8,13 +8,13 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DSP_HELPER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DSP_HELPER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DSP_HELPER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DSP_HELPER_H_ #include // Access to size_t. -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -133,4 +133,4 @@ class DspHelper { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DSP_HELPER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DSP_HELPER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/dsp_helper_unittest.cc b/webrtc/modules/audio_coding/neteq/dsp_helper_unittest.cc similarity index 95% rename from webrtc/modules/audio_coding/neteq4/dsp_helper_unittest.cc rename to webrtc/modules/audio_coding/neteq/dsp_helper_unittest.cc index 852c2ec92..cbceff619 100644 --- a/webrtc/modules/audio_coding/neteq4/dsp_helper_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/dsp_helper_unittest.cc @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/dsp_helper.h" +#include "webrtc/modules/audio_coding/neteq/dsp_helper.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq/dsp_helpfunctions.c b/webrtc/modules/audio_coding/neteq/dsp_helpfunctions.c deleted file mode 100644 index ef721d559..000000000 --- a/webrtc/modules/audio_coding/neteq/dsp_helpfunctions.c +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 file contains some help functions that did not fit elsewhere. - */ - -#include "dsp_helpfunctions.h" - - -int16_t WebRtcNetEQ_CalcFsMult(uint16_t fsHz) -{ - switch (fsHz) - { - case 8000: - { - return 1; - } - case 16000: - { - return 2; - } - case 32000: - { - return 4; - } - case 48000: - { - return 6; - } - default: - { - return 1; - } - } -} - - -int WebRtcNetEQ_DownSampleTo4kHz(const int16_t *in, int inLen, uint16_t inFsHz, - int16_t *out, int outLen, int compensateDelay) -{ - int16_t *B; /* filter coefficients */ - int16_t Blen; /* number of coefficients */ - int16_t filterDelay; /* phase delay in samples */ - int16_t factor; /* conversion rate (inFsHz/8000) */ - int ok; - - /* Set constants depending on frequency used */ - /* NOTE: The phase delay values are wrong compared to the true phase delay - of the filters. However, the error is preserved (through the +1 term) - for consistency. */ - switch (inFsHz) - { - case 8000: - { - Blen = 3; - factor = 2; - B = (int16_t*) WebRtcNetEQ_kDownsample8kHzTbl; - filterDelay = 1 + 1; - break; - } -#ifdef NETEQ_WIDEBAND - case 16000: - { - Blen = 5; - factor = 4; - B = (int16_t*) WebRtcNetEQ_kDownsample16kHzTbl; - filterDelay = 2 + 1; - break; - } -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - case 32000: - { - Blen = 7; - factor = 8; - B = (int16_t*) WebRtcNetEQ_kDownsample32kHzTbl; - filterDelay = 3 + 1; - break; - } -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - case 48000: - { - Blen = 7; - factor = 12; - B = (int16_t*) WebRtcNetEQ_kDownsample48kHzTbl; - filterDelay = 3 + 1; - break; - } -#endif - default: - { - /* unsupported or wrong sample rate */ - return -1; - } - } - - if (!compensateDelay) - { - /* disregard delay compensation */ - filterDelay = 0; - } - - ok = WebRtcSpl_DownsampleFast((int16_t*) &in[Blen - 1], - (int16_t) (inLen - (Blen - 1)), /* number of input samples */ - out, (int16_t) outLen, /* number of output samples to produce */ - B, Blen, factor, filterDelay); /* filter parameters */ - - return ok; /* return value is -1 if input signal is too short */ - -} - diff --git a/webrtc/modules/audio_coding/neteq/dsp_helpfunctions.h b/webrtc/modules/audio_coding/neteq/dsp_helpfunctions.h deleted file mode 100644 index 11119f1b8..000000000 --- a/webrtc/modules/audio_coding/neteq/dsp_helpfunctions.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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. - */ - -/* - * Various help functions used by the DSP functions. - */ - -#ifndef DSP_HELPFUNCTIONS_H -#define DSP_HELPFUNCTIONS_H - -#include "typedefs.h" - -#include "dsp.h" - -/**************************************************************************** - * WebRtcNetEQ_Correlator(...) - * - * Calculate signal correlation. - * - * Input: - * - inst : DSP instance - * - data : Speech history to do expand from (older history in data[-4..-1]) - * - dataLen : Length of data - * - * Output: - * - corrOut : CC of downsampled signal - * - corrScale : Scale factor for correlation (-Qdomain) - * - * Return value : Length of correlated data - */ - -int16_t WebRtcNetEQ_Correlator(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_data, int16_t w16_dataLen, - int16_t *pw16_corrOut, - int16_t *pw16_corrScale); - -/**************************************************************************** - * WebRtcNetEQ_PeakDetection(...) - * - * Peak detection with parabolic fit. - * - * Input: - * - data : Data sequence for peak detection - * - dataLen : Length of data - * - nmbPeaks : Number of peaks to detect - * - fs_mult : Sample rate multiplier - * - * Output: - * - corrIndex : Index of the peak - * - winner : Value of the peak - * - * Return value : 0 for ok - */ - -int16_t WebRtcNetEQ_PeakDetection(int16_t *pw16_data, int16_t w16_dataLen, - int16_t w16_nmbPeaks, int16_t fs_mult, - int16_t *pw16_corrIndex, - int16_t *pw16_winners); - -/**************************************************************************** - * WebRtcNetEQ_PrblFit(...) - * - * Three-point parbola fit. - * - * Input: - * - 3pts : Three input samples - * - fs_mult : Sample rate multiplier - * - * Output: - * - Ind : Index of the peak - * - outVal : Value of the peak - * - * Return value : 0 for ok - */ - -int16_t WebRtcNetEQ_PrblFit(int16_t *pw16_3pts, int16_t *pw16_Ind, - int16_t *pw16_outVal, int16_t fs_mult); - -/**************************************************************************** - * WebRtcNetEQ_MinDistortion(...) - * - * Find the lag that results in minimum distortion. - * - * Input: - * - data : Start of speech to perform distortion on, second vector is assumed - * to be data[-Lag] - * - minLag : Start lag - * - maxLag : End lag - * - len : Length to correlate - * - * Output: - * - dist : Distorion value - * - * Return value : Lag for minimum distortion - */ - -int16_t WebRtcNetEQ_MinDistortion(const int16_t *pw16_data, - int16_t w16_minLag, int16_t w16_maxLag, - int16_t len, int32_t *pw16_dist); - -/**************************************************************************** - * WebRtcNetEQ_RandomVec(...) - * - * Generate random vector. - * - * Input: - * - seed : Current seed (input/output) - * - len : Number of samples to generate - * - incVal : Jump step - * - * Output: - * - randVec : Generated random vector - */ - -void WebRtcNetEQ_RandomVec(uint32_t *w32_seed, int16_t *pw16_randVec, - int16_t w16_len, int16_t w16_incval); - -/**************************************************************************** - * WebRtcNetEQ_MixVoiceUnvoice(...) - * - * Mix voiced and unvoiced signal. - * - * Input: - * - voicedVec : Voiced input signal - * - unvoicedVec : Unvoiced input signal - * - current_vfraction : Current mixing factor - * - vfraction_change : Mixing factor change per sample - * - N : Number of samples - * - * Output: - * - outData : Mixed signal - */ - -void WebRtcNetEQ_MixVoiceUnvoice(int16_t *pw16_outData, int16_t *pw16_voicedVec, - int16_t *pw16_unvoicedVec, - int16_t *w16_current_vfraction, - int16_t w16_vfraction_change, int16_t N); - -/**************************************************************************** - * WebRtcNetEQ_UnmuteSignal(...) - * - * Gradually reduce attenuation. - * - * Input: - * - inVec : Input signal - * - startMuteFact : Starting attenuation - * - unmuteFact : Factor to "unmute" with (Q20) - * - N : Number of samples - * - * Output: - * - outVec : Output signal - */ - -void WebRtcNetEQ_UnmuteSignal(int16_t *pw16_inVec, int16_t *startMuteFact, - int16_t *pw16_outVec, int16_t unmuteFact, - int16_t N); - -/**************************************************************************** - * WebRtcNetEQ_MuteSignal(...) - * - * Gradually increase attenuation. - * - * Input: - * - inout : Input/output signal - * - muteSlope : Slope of muting - * - N : Number of samples - */ - -void WebRtcNetEQ_MuteSignal(int16_t *pw16_inout, int16_t muteSlope, - int16_t N); - -/**************************************************************************** - * WebRtcNetEQ_CalcFsMult(...) - * - * Calculate the sample rate divided by 8000. - * - * Input: - * - fsHz : Sample rate in Hz in {8000, 16000, 32000, 48000}. - * - * Return value : fsHz/8000 for the valid values, 1 for other inputs - */ - -int16_t WebRtcNetEQ_CalcFsMult(uint16_t fsHz); - -/**************************************************************************** - * WebRtcNetEQ_DownSampleTo4kHz(...) - * - * Lowpass filter and downsample a signal to 4 kHz sample rate. - * - * Input: - * - in : Input signal samples. - * - inLen : Number of input samples. - * - inFsHz : Input sample rate in Hz. - * - outLen : Desired number of samples in decimated signal. - * - compensateDelay : If non-zero, compensate for the phase delay of - * of the anti-alias filter. - * - * Output: - * - out : Output signal samples. - * - * Return value : 0 - Ok - * -1 - Error - * - */ - -int WebRtcNetEQ_DownSampleTo4kHz(const int16_t *in, int inLen, uint16_t inFsHz, - int16_t *out, int outLen, int compensateDelay); - -#endif - diff --git a/webrtc/modules/audio_coding/neteq/dtmf_buffer.c b/webrtc/modules/audio_coding/neteq/dtmf_buffer.c deleted file mode 100644 index 1788635c7..000000000 --- a/webrtc/modules/audio_coding/neteq/dtmf_buffer.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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. - */ - -/* - * Implementation of packet buffer for DTMF messages. - */ - -#include "dtmf_buffer.h" - -#include "typedefs.h" /* to define endianness */ -#include "signal_processing_library.h" - -#include "neteq_error_codes.h" - - -#ifdef NETEQ_ATEVENT_DECODE - -int16_t WebRtcNetEQ_DtmfRemoveEvent(dtmf_inst_t *DTMFdec_inst) -{ - - int i; - for (i = 0; i < 3; i++) - { - DTMFdec_inst->EventQueue[i] = DTMFdec_inst->EventQueue[i + 1]; - DTMFdec_inst->EventQueueVolume[i] = DTMFdec_inst->EventQueueVolume[i + 1]; - DTMFdec_inst->EventQueueEnded[i] = DTMFdec_inst->EventQueueEnded[i + 1]; - DTMFdec_inst->EventQueueStartTime[i] = DTMFdec_inst->EventQueueStartTime[i + 1]; - DTMFdec_inst->EventQueueEndTime[i] = DTMFdec_inst->EventQueueEndTime[i + 1]; - } - DTMFdec_inst->EventBufferSize--; - DTMFdec_inst->EventQueue[3] = -1; - DTMFdec_inst->EventQueueVolume[3] = 0; - DTMFdec_inst->EventQueueEnded[3] = 0; - DTMFdec_inst->EventQueueStartTime[3] = 0; - DTMFdec_inst->EventQueueEndTime[3] = 0; - - return 0; -} - -int16_t WebRtcNetEQ_DtmfDecoderInit(dtmf_inst_t *DTMFdec_inst, uint16_t fs, - int16_t MaxPLCtime) -{ - int i; - if (((fs != 8000) && (fs != 16000) && (fs != 32000) && (fs != 48000)) || (MaxPLCtime < 0)) - { - return DTMF_DEC_PARAMETER_ERROR; - } - if (fs == 8000) - DTMFdec_inst->framelen = 80; - else if (fs == 16000) - DTMFdec_inst->framelen = 160; - else if (fs == 32000) - DTMFdec_inst->framelen = 320; - else - /* fs == 48000 */ - DTMFdec_inst->framelen = 480; - - DTMFdec_inst->MaxPLCtime = MaxPLCtime; - DTMFdec_inst->CurrentPLCtime = 0; - DTMFdec_inst->EventBufferSize = 0; - for (i = 0; i < 4; i++) - { - DTMFdec_inst->EventQueue[i] = -1; - DTMFdec_inst->EventQueueVolume[i] = 0; - DTMFdec_inst->EventQueueEnded[i] = 0; - DTMFdec_inst->EventQueueStartTime[i] = 0; - DTMFdec_inst->EventQueueEndTime[i] = 0; - } - return 0; -} - -int16_t WebRtcNetEQ_DtmfInsertEvent(dtmf_inst_t *DTMFdec_inst, - const int16_t *encoded, int16_t len, - uint32_t timeStamp) -{ - - int i; - int16_t value; - const int16_t *EventStart; - int16_t endEvent; - int16_t Volume; - int16_t Duration; - int16_t position = -1; - - /* Extract event */ - if (len == 4) - { - EventStart = encoded; -#ifdef WEBRTC_ARCH_BIG_ENDIAN - value=((*EventStart)>>8); - endEvent=((*EventStart)&0x80)>>7; - Volume=((*EventStart)&0x3F); - Duration=EventStart[1]; -#else - value = ((*EventStart) & 0xFF); - endEvent = ((*EventStart) & 0x8000) >> 15; - Volume = ((*EventStart) & 0x3F00) >> 8; - Duration = (((((uint16_t) EventStart[1]) >> 8) & 0xFF) - | (((uint16_t) (EventStart[1] & 0xFF)) << 8)); -#endif - /* Only events between 0-15 are supported (DTMF tones) */ - if ((value < 0) || (value > 15)) - { - return 0; - } - - /* Discard all DTMF tones with really low volume (<-36dbm0) */ - if (Volume > 36) - { - return 0; - } - - /*Are there any unended events of the same type? */ - for (i = 0; i < DTMFdec_inst->EventBufferSize; i++) - { - /* Going through the whole queue even when we have found a match will - ensure that we add to the latest applicable event */ - if ((DTMFdec_inst->EventQueue[i] == value) && (!DTMFdec_inst->EventQueueEnded[i] - || endEvent)) position = i; - } - if (position > -1) - { - DTMFdec_inst->EventQueueVolume[position] = Volume; - if ((timeStamp + Duration) > DTMFdec_inst->EventQueueEndTime[position]) DTMFdec_inst->EventQueueEndTime[position] - = DTMFdec_inst->EventQueueStartTime[position] + Duration; - if (endEvent) DTMFdec_inst->EventQueueEnded[position] = 1; - } - else - { - if (DTMFdec_inst->EventBufferSize == MAX_DTMF_QUEUE_SIZE) - { /* Buffer full */ - /* Remove one event */ - DTMFdec_inst->EventBufferSize--; - } - /* Store data in the instance on a new position*/ - DTMFdec_inst->EventQueue[DTMFdec_inst->EventBufferSize] = value; - DTMFdec_inst->EventQueueVolume[DTMFdec_inst->EventBufferSize] = Volume; - DTMFdec_inst->EventQueueEnded[DTMFdec_inst->EventBufferSize] = endEvent; - DTMFdec_inst->EventQueueStartTime[DTMFdec_inst->EventBufferSize] = timeStamp; - DTMFdec_inst->EventQueueEndTime[DTMFdec_inst->EventBufferSize] = timeStamp - + Duration; - DTMFdec_inst->EventBufferSize++; - } - return 0; - } - return DTMF_INSERT_ERROR; -} - -int16_t WebRtcNetEQ_DtmfDecode(dtmf_inst_t *DTMFdec_inst, int16_t *event, - int16_t *volume, uint32_t currTimeStamp) -{ - - if (DTMFdec_inst->EventBufferSize < 1) return 0; /* No events to play */ - - /* We have events, is it time to play them? */ - if (currTimeStamp < DTMFdec_inst->EventQueueStartTime[0]) - { - /*No, just return zero */ - return 0; - } - - /* Continue on the event that is currently ongoing */ - *event = DTMFdec_inst->EventQueue[0]; - *volume = DTMFdec_inst->EventQueueVolume[0]; - - if (DTMFdec_inst->EventQueueEndTime[0] >= (currTimeStamp + DTMFdec_inst->framelen)) - { - - /* Still at least framLen to play */ - - DTMFdec_inst->CurrentPLCtime = 0; - if ((DTMFdec_inst->EventQueueEndTime[0] == (currTimeStamp + DTMFdec_inst->framelen)) - && (DTMFdec_inst->EventQueueEnded[0])) - { /* We are done */ - /*Remove the event from Queue*/ - WebRtcNetEQ_DtmfRemoveEvent(DTMFdec_inst); - } - return DTMFdec_inst->framelen; - - } - else - { - if ((DTMFdec_inst->EventQueueEnded[0]) || (DTMFdec_inst->EventQueue[1] > -1)) - { - /* - * Less than frameLen to play and end of event or already received next event. - * Give our a whole frame size of audio to simplify things. - */ - - /*Remove the event from Queue*/ - WebRtcNetEQ_DtmfRemoveEvent(DTMFdec_inst); - DTMFdec_inst->CurrentPLCtime = 0; - - return DTMFdec_inst->framelen; - - } - else - { - /* Less than frameLen to play and not end of event. */ - DTMFdec_inst->CurrentPLCtime = (int16_t) (currTimeStamp - - DTMFdec_inst->EventQueueEndTime[0]); - - if ((DTMFdec_inst->CurrentPLCtime > DTMFdec_inst->MaxPLCtime) - || (DTMFdec_inst->CurrentPLCtime < -DTMFdec_inst->MaxPLCtime)) - { - /*Remove the event from queue*/ - WebRtcNetEQ_DtmfRemoveEvent(DTMFdec_inst); - DTMFdec_inst->CurrentPLCtime = 0; - } - - /* If we have a new event that it's time to play */ - if ((DTMFdec_inst->EventQueue[1] > -1) && (DTMFdec_inst->EventQueueStartTime[1] - >= (currTimeStamp + DTMFdec_inst->framelen))) - { - /*Remove the event from queue*/ - WebRtcNetEQ_DtmfRemoveEvent(DTMFdec_inst); - DTMFdec_inst->CurrentPLCtime = 0; - } - - return DTMFdec_inst->framelen; - } - } -} - -#endif diff --git a/webrtc/modules/audio_coding/neteq4/dtmf_buffer.cc b/webrtc/modules/audio_coding/neteq/dtmf_buffer.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/dtmf_buffer.cc rename to webrtc/modules/audio_coding/neteq/dtmf_buffer.cc index 1c81ad940..91debee14 100644 --- a/webrtc/modules/audio_coding/neteq4/dtmf_buffer.cc +++ b/webrtc/modules/audio_coding/neteq/dtmf_buffer.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/dtmf_buffer.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_buffer.h" #include #include // max diff --git a/webrtc/modules/audio_coding/neteq/dtmf_buffer.h b/webrtc/modules/audio_coding/neteq/dtmf_buffer.h index 99c9e6a49..5dd31c2d2 100644 --- a/webrtc/modules/audio_coding/neteq/dtmf_buffer.h +++ b/webrtc/modules/audio_coding/neteq/dtmf_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * Copyright (c) 2012 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 @@ -8,94 +8,109 @@ * be found in the AUTHORS file in the root of the source tree. */ -/* - * Packet buffer for DTMF messages. - */ - -#ifndef DTMF_BUFFER_H -#define DTMF_BUFFER_H - -#include "typedefs.h" - -#include "neteq_defines.h" - -/* Include this code only if ATEVENT (DTMF) is defined in */ -#ifdef NETEQ_ATEVENT_DECODE - -#define MAX_DTMF_QUEUE_SIZE 4 - -typedef struct dtmf_inst_t_ -{ - int16_t MaxPLCtime; - int16_t CurrentPLCtime; - int16_t EventQueue[MAX_DTMF_QUEUE_SIZE]; - int16_t EventQueueVolume[MAX_DTMF_QUEUE_SIZE]; - int16_t EventQueueEnded[MAX_DTMF_QUEUE_SIZE]; - uint32_t EventQueueStartTime[MAX_DTMF_QUEUE_SIZE]; - uint32_t EventQueueEndTime[MAX_DTMF_QUEUE_SIZE]; - int16_t EventBufferSize; - int16_t framelen; -} dtmf_inst_t; - -/**************************************************************************** - * WebRtcNetEQ_DtmfDecoderInit(...) - * - * This function initializes a DTMF instance. - * - * Input: - * - DTMF_decinst_t : DTMF instance - * - fs : The sample rate used for the DTMF - * - MaxPLCtime : Maximum length for a PLC before zeros should be inserted - * - * Return value : 0 - Ok - * -1 - Error - */ - -int16_t WebRtcNetEQ_DtmfDecoderInit(dtmf_inst_t *DTMFdec_inst, uint16_t fs, - int16_t MaxPLCtime); - -/**************************************************************************** - * WebRtcNetEQ_DtmfInsertEvent(...) - * - * This function decodes a packet with DTMF frames. - * - * Input: - * - DTMFdec_inst : DTMF instance - * - encoded : Encoded DTMF frame(s) - * - len : Bytes in encoded vector - * - * - * Return value : 0 - Ok - * -1 - Error - */ - -int16_t WebRtcNetEQ_DtmfInsertEvent(dtmf_inst_t *DTMFdec_inst, - const int16_t *encoded, int16_t len, - uint32_t timeStamp); - -/**************************************************************************** - * WebRtcNetEQ_DtmfDecode(...) - * - * This function decodes a packet with DTMF frame(s). Output will be the - * event that should be played for next 10 ms. - * - * Input: - * - DTMFdec_inst : DTMF instance - * - currTimeStamp : The current playout timestamp - * - * Output: - * - event : Event number to be played - * - volume : Event volume to be played - * - * Return value : >0 - There is a event to be played - * 0 - No event to be played - * -1 - Error - */ - -int16_t WebRtcNetEQ_DtmfDecode(dtmf_inst_t *DTMFdec_inst, int16_t *event, - int16_t *volume, uint32_t currTimeStamp); - -#endif /* NETEQ_ATEVENT_DECODE */ - -#endif /* DTMF_BUFFER_H */ - +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DTMF_BUFFER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DTMF_BUFFER_H_ + +#include +#include // size_t + +#include "webrtc/base/constructormagic.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +struct DtmfEvent { + uint32_t timestamp; + int event_no; + int volume; + int duration; + bool end_bit; + + // Constructors + DtmfEvent() + : timestamp(0), + event_no(0), + volume(0), + duration(0), + end_bit(false) { + } + DtmfEvent(uint32_t ts, int ev, int vol, int dur, bool end) + : timestamp(ts), + event_no(ev), + volume(vol), + duration(dur), + end_bit(end) { + } +}; + +// This is the buffer holding DTMF events while waiting for them to be played. +class DtmfBuffer { + public: + enum BufferReturnCodes { + kOK = 0, + kInvalidPointer, + kPayloadTooShort, + kInvalidEventParameters, + kInvalidSampleRate + }; + + // Set up the buffer for use at sample rate |fs_hz|. + explicit DtmfBuffer(int fs_hz) { + SetSampleRate(fs_hz); + } + + virtual ~DtmfBuffer() {} + + // Flushes the buffer. + virtual void Flush() { buffer_.clear(); } + + // Static method to parse 4 bytes from |payload| as a DTMF event (RFC 4733) + // and write the parsed information into the struct |event|. Input variable + // |rtp_timestamp| is simply copied into the struct. + static int ParseEvent(uint32_t rtp_timestamp, + const uint8_t* payload, + int payload_length_bytes, + DtmfEvent* event); + + // Inserts |event| into the buffer. The method looks for a matching event and + // merges the two if a match is found. + virtual int InsertEvent(const DtmfEvent& event); + + // Checks if a DTMF event should be played at time |current_timestamp|. If so, + // the method returns true; otherwise false. The parameters of the event to + // play will be written to |event|. + virtual bool GetEvent(uint32_t current_timestamp, DtmfEvent* event); + + // Number of events in the buffer. + virtual size_t Length() const { return buffer_.size(); } + + virtual bool Empty() const { return buffer_.empty(); } + + // Set a new sample rate. + virtual int SetSampleRate(int fs_hz); + + private: + typedef std::list DtmfList; + + int max_extrapolation_samples_; + int frame_len_samples_; // TODO(hlundin): Remove this later. + + // Compares two events and returns true if they are the same. + static bool SameEvent(const DtmfEvent& a, const DtmfEvent& b); + + // Merges |event| to the event pointed out by |it|. The method checks that + // the two events are the same (using the SameEvent method), and merges them + // if that was the case, returning true. If the events are not the same, false + // is returned. + bool MergeEvents(DtmfList::iterator it, const DtmfEvent& event); + + // Method used by the sort algorithm to rank events in the buffer. + static bool CompareEvents(const DtmfEvent& a, const DtmfEvent& b); + + DtmfList buffer_; + + DISALLOW_COPY_AND_ASSIGN(DtmfBuffer); +}; + +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DTMF_BUFFER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/dtmf_buffer_unittest.cc b/webrtc/modules/audio_coding/neteq/dtmf_buffer_unittest.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/dtmf_buffer_unittest.cc rename to webrtc/modules/audio_coding/neteq/dtmf_buffer_unittest.cc index 0b5ed65b8..83f981386 100644 --- a/webrtc/modules/audio_coding/neteq4/dtmf_buffer_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/dtmf_buffer_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/dtmf_buffer.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_buffer.h" #ifdef WIN32 #include // ntohl() diff --git a/webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.cc b/webrtc/modules/audio_coding/neteq/dtmf_tone_generator.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.cc rename to webrtc/modules/audio_coding/neteq/dtmf_tone_generator.cc index c85534e9b..34c615d70 100644 --- a/webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.cc +++ b/webrtc/modules/audio_coding/neteq/dtmf_tone_generator.cc @@ -28,7 +28,7 @@ // 852 Hz 7 8 9 14 // 941 Hz 10 0 11 15 -#include "webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h" #include diff --git a/webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h b/webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h similarity index 85% rename from webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h rename to webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h index e93f0b883..fc1e5e4ad 100644 --- a/webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h +++ b/webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DTMF_TONE_GENERATOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DTMF_TONE_GENERATOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_DTMF_TONE_GENERATOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_DTMF_TONE_GENERATOR_H_ -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -53,4 +53,4 @@ class DtmfToneGenerator { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DTMF_TONE_GENERATOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_DTMF_TONE_GENERATOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/dtmf_tone_generator_unittest.cc b/webrtc/modules/audio_coding/neteq/dtmf_tone_generator_unittest.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/dtmf_tone_generator_unittest.cc rename to webrtc/modules/audio_coding/neteq/dtmf_tone_generator_unittest.cc index 37e8bbda9..94f79dc34 100644 --- a/webrtc/modules/audio_coding/neteq4/dtmf_tone_generator_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/dtmf_tone_generator_unittest.cc @@ -10,12 +10,12 @@ // Unit tests for DtmfToneGenerator class. -#include "webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h" #include #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq/dtmf_tonegen.c b/webrtc/modules/audio_coding/neteq/dtmf_tonegen.c deleted file mode 100644 index 8ea413c76..000000000 --- a/webrtc/modules/audio_coding/neteq/dtmf_tonegen.c +++ /dev/null @@ -1,367 +0,0 @@ -/* - * 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 file contains the DTMF tone generator and its parameters. - * - * A sinusoid is generated using the recursive oscillator model - * - * y[n] = sin(w*n + phi) = 2*cos(w) * y[n-1] - y[n-2] - * = a * y[n-1] - y[n-2] - * - * initialized with - * y[-2] = 0 - * y[-1] = sin(w) - * - * A DTMF signal is a combination of two sinusoids, depending - * on which event is sent (i.e, which key is pressed). The following - * table maps each key (event codes in parentheses) into two tones: - * - * 1209 Hz 1336 Hz 1477 Hz 1633 Hz - * 697 Hz 1 (ev. 1) 2 (ev. 2) 3 (ev. 3) A (ev. 12) - * 770 Hz 4 (ev. 4) 5 (ev. 5) 6 (ev. 6) B (ev. 13) - * 852 Hz 7 (ev. 7) 8 (ev. 8) 9 (ev. 9) C (ev. 14) - * 941 Hz * (ev. 10) 0 (ev. 0) # (ev. 11) D (ev. 15) - * - * The two tones are added to form the DTMF signal. - * - */ - -#include "dtmf_tonegen.h" - -#include "signal_processing_library.h" - -#include "neteq_error_codes.h" - -#ifdef NETEQ_ATEVENT_DECODE -/* Must compile NetEQ with DTMF support to enable the functionality */ - -/*******************/ -/* Constant tables */ -/*******************/ - -/* - * All tables corresponding to the oscillator model are organized so that - * the coefficients for a specific frequency is found in the same position - * in every table. The positions for the tones follow this layout: - * - * dummyVector[8] = - * { - * 697 Hz, 770 Hz, 852 Hz, 941 Hz, - * 1209 Hz, 1336 Hz, 1477 Hz, 1633 Hz - * }; - */ - -/* - * Tables for the constant a = 2*cos(w) = 2*cos(2*pi*f/fs) - * in the oscillator model, for 8, 16, 32 and 48 kHz sample rate. - * Table values in Q14. - */ - -const int16_t WebRtcNetEQ_dtfm_aTbl8Khz[8] = -{ - 27980, 26956, 25701, 24219, - 19073, 16325, 13085, 9315 -}; - -#ifdef NETEQ_WIDEBAND -const int16_t WebRtcNetEQ_dtfm_aTbl16Khz[8]= -{ - 31548, 31281, 30951, 30556, - 29144, 28361, 27409, 26258 -}; -#endif - -#ifdef NETEQ_32KHZ_WIDEBAND -const int16_t WebRtcNetEQ_dtfm_aTbl32Khz[8]= -{ - 32462, 32394, 32311, 32210, - 31849, 31647, 31400, 31098 -}; -#endif - -#ifdef NETEQ_48KHZ_WIDEBAND -const int16_t WebRtcNetEQ_dtfm_aTbl48Khz[8]= -{ - 32632, 32602, 32564, 32520, - 32359, 32268, 32157, 32022 -}; -#endif - -/* - * Initialization values y[-1] = sin(w) = sin(2*pi*f/fs), for 8, 16, 32 and 48 kHz sample rate. - * Table values in Q14. - */ - -const int16_t WebRtcNetEQ_dtfm_yInitTab8Khz[8] = -{ - 8528, 9315, 10163, 11036, - 13323, 14206,15021, 15708 -}; - -#ifdef NETEQ_WIDEBAND -const int16_t WebRtcNetEQ_dtfm_yInitTab16Khz[8]= -{ - 4429, 4879, 5380, 5918, - 7490, 8207, 8979, 9801 -}; -#endif - -#ifdef NETEQ_32KHZ_WIDEBAND -const int16_t WebRtcNetEQ_dtfm_yInitTab32Khz[8]= -{ - 2235, 2468, 2728, 3010, - 3853, 4249, 4685, 5164 -}; -#endif - -#ifdef NETEQ_48KHZ_WIDEBAND -const int16_t WebRtcNetEQ_dtfm_yInitTab48Khz[8]= -{ - 1493, 1649, 1823, 2013, - 2582, 2851, 3148, 3476 -}; -#endif - -/* Volume in dBm0 from 0 to -63, where 0 is the first table entry. - Everything below -36 is discarded, wherefore the table stops at -36. - Table entries are in Q14. - */ - -const int16_t WebRtcNetEQ_dtfm_dBm0[37] = { 16141, 14386, 12821, 11427, 10184, 9077, 8090, - 7210, 6426, 5727, 5104, 4549, 4054, 3614, - 3221, 2870, 2558, 2280, 2032, 1811, 1614, - 1439, 1282, 1143, 1018, 908, 809, 721, 643, - 573, 510, 455, 405, 361, 322, 287, 256 }; - -/**************************************************************************** - * WebRtcNetEQ_DTMFGenerate(...) - * - * Generate 10 ms DTMF signal according to input parameters. - * - * Input: - * - DTMFdecInst : DTMF instance - * - value : DTMF event number (0-15) - * - volume : Volume of generated signal (0-36) - * Volume is given in negative dBm0, i.e., volume == 0 - * means 0 dBm0 while volume == 36 mean -36 dBm0. - * - sampFreq : Sample rate in Hz - * - * Output: - * - signal : Pointer to vector where DTMF signal is stored; - * Vector must be at least sampFreq/100 samples long. - * - DTMFdecInst : Updated DTMF instance - * - * Return value : >0 - Number of samples written to signal - * : <0 - error - */ - -int16_t WebRtcNetEQ_DTMFGenerate(dtmf_tone_inst_t *DTMFdecInst, int16_t value, - int16_t volume, int16_t *signal, - uint16_t sampFreq, int16_t extFrameLen) -{ - const int16_t *aTbl; /* pointer to a-coefficient table */ - const int16_t *yInitTable; /* pointer to initialization value table */ - int16_t a1 = 0; /* a-coefficient for first tone (low tone) */ - int16_t a2 = 0; /* a-coefficient for second tone (high tone) */ - int i; - int frameLen; /* number of samples to generate */ - int lowIndex = 0; /* Default to avoid compiler warnings. */ - int highIndex = 4; /* Default to avoid compiler warnings. */ - int32_t tempVal; - int16_t tempValLow; - int16_t tempValHigh; - - /* Sanity check for volume */ - if ((volume < 0) || (volume > 36)) - { - return DTMF_DEC_PARAMETER_ERROR; - } - - /* Sanity check for extFrameLen */ - if (extFrameLen < -1) - { - return DTMF_DEC_PARAMETER_ERROR; - } - - /* Select oscillator coefficient tables based on sample rate */ - if (sampFreq == 8000) - { - aTbl = WebRtcNetEQ_dtfm_aTbl8Khz; - yInitTable = WebRtcNetEQ_dtfm_yInitTab8Khz; - frameLen = 80; -#ifdef NETEQ_WIDEBAND - } - else if (sampFreq == 16000) - { - aTbl = WebRtcNetEQ_dtfm_aTbl16Khz; - yInitTable = WebRtcNetEQ_dtfm_yInitTab16Khz; - frameLen = 160; -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - } - else if (sampFreq == 32000) - { - aTbl = WebRtcNetEQ_dtfm_aTbl32Khz; - yInitTable = WebRtcNetEQ_dtfm_yInitTab32Khz; - frameLen = 320; -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - } - else if (sampFreq == 48000) - { - aTbl = WebRtcNetEQ_dtfm_aTbl48Khz; - yInitTable = WebRtcNetEQ_dtfm_yInitTab48Khz; - frameLen = 480; -#endif - } - else - { - /* unsupported sample rate */ - return DTMF_GEN_UNKNOWN_SAMP_FREQ; - } - - if (extFrameLen >= 0) - { - frameLen = extFrameLen; - } - - /* select low frequency based on event value */ - switch (value) - { - case 1: - case 2: - case 3: - case 12: /* first row on keypad */ - { - lowIndex = 0; /* low frequency: 697 Hz */ - break; - } - case 4: - case 5: - case 6: - case 13: /* second row on keypad */ - { - lowIndex = 1; /* low frequency: 770 Hz */ - break; - } - case 7: - case 8: - case 9: - case 14: /* third row on keypad */ - { - lowIndex = 2; /* low frequency: 852 Hz */ - break; - } - case 0: - case 10: - case 11: - case 15: /* fourth row on keypad */ - { - lowIndex = 3; /* low frequency: 941 Hz */ - break; - } - default: - { - return DTMF_DEC_PARAMETER_ERROR; - } - } /* end switch */ - - /* select high frequency based on event value */ - switch (value) - { - case 1: - case 4: - case 7: - case 10: /* first column on keypad */ - { - highIndex = 4; /* high frequency: 1209 Hz */ - break; - } - case 2: - case 5: - case 8: - case 0: /* second column on keypad */ - { - highIndex = 5;/* high frequency: 1336 Hz */ - break; - } - case 3: - case 6: - case 9: - case 11: /* third column on keypad */ - { - highIndex = 6;/* high frequency: 1477 Hz */ - break; - } - case 12: - case 13: - case 14: - case 15: /* fourth column on keypad (special) */ - { - highIndex = 7;/* high frequency: 1633 Hz */ - break; - } - } /* end switch */ - - /* select coefficients based on results from switches above */ - a1 = aTbl[lowIndex]; /* coefficient for first (low) tone */ - a2 = aTbl[highIndex]; /* coefficient for second (high) tone */ - - if (DTMFdecInst->reinit) - { - /* set initial values for the recursive model */ - DTMFdecInst->oldOutputLow[0] = yInitTable[lowIndex]; - DTMFdecInst->oldOutputLow[1] = 0; - DTMFdecInst->oldOutputHigh[0] = yInitTable[highIndex]; - DTMFdecInst->oldOutputHigh[1] = 0; - - /* reset reinit flag */ - DTMFdecInst->reinit = 0; - } - - /* generate signal sample by sample */ - for (i = 0; i < frameLen; i++) - { - - /* Use rescursion formula y[n] = a*y[n-1] - y[n-2] */ - tempValLow - = (int16_t) (((WEBRTC_SPL_MUL_16_16(a1, DTMFdecInst->oldOutputLow[1]) - + 8192) >> 14) - DTMFdecInst->oldOutputLow[0]); - tempValHigh - = (int16_t) (((WEBRTC_SPL_MUL_16_16(a2, DTMFdecInst->oldOutputHigh[1]) - + 8192) >> 14) - DTMFdecInst->oldOutputHigh[0]); - - /* Update recursion memory */ - DTMFdecInst->oldOutputLow[0] = DTMFdecInst->oldOutputLow[1]; - DTMFdecInst->oldOutputLow[1] = tempValLow; - DTMFdecInst->oldOutputHigh[0] = DTMFdecInst->oldOutputHigh[1]; - DTMFdecInst->oldOutputHigh[1] = tempValHigh; - - /* scale high tone with 32768 (15 left shifts) - and low tone with 23171 (3dB lower than high tone) */ - tempVal = WEBRTC_SPL_MUL_16_16(DTMF_AMP_LOW, tempValLow) - + WEBRTC_SPL_LSHIFT_W32((int32_t)tempValHigh, 15); - - /* Norm the signal to Q14 (with proper rounding) */ - tempVal = (tempVal + 16384) >> 15; - - /* Scale the signal to correct dbM0 value */ - signal[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32( - (WEBRTC_SPL_MUL_16_16(tempVal, WebRtcNetEQ_dtfm_dBm0[volume]) - + 8192), 14); /* volume value is in Q14; use proper rounding */ - } - - return frameLen; - -} - -#endif /* NETEQ_ATEVENT_DECODE */ - diff --git a/webrtc/modules/audio_coding/neteq/dtmf_tonegen.h b/webrtc/modules/audio_coding/neteq/dtmf_tonegen.h deleted file mode 100644 index 5f4489940..000000000 --- a/webrtc/modules/audio_coding/neteq/dtmf_tonegen.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 file contains the DTMF tone generator function. - */ - -#ifndef DTMF_TONEGEN_H -#define DTMF_TONEGEN_H - -#include "typedefs.h" - -#include "neteq_defines.h" - -#ifdef NETEQ_ATEVENT_DECODE -/* Must compile NetEQ with DTMF support to enable the functionality */ - -#define DTMF_AMP_LOW 23171 /* 3 dB lower than the high frequency */ - -/* The DTMF generator struct (part of DSP main struct DSPInst_t) */ -typedef struct dtmf_tone_inst_t_ -{ - - int16_t reinit; /* non-zero if the oscillator model should - be reinitialized for next event */ - int16_t oldOutputLow[2]; /* oscillator recursion history (low tone) */ - int16_t oldOutputHigh[2]; /* oscillator recursion history (high tone) */ - - int lastDtmfSample; /* index to the first non-DTMF sample in the - speech history, if non-negative */ -}dtmf_tone_inst_t; - -/**************************************************************************** - * WebRtcNetEQ_DTMFGenerate(...) - * - * Generate 10 ms DTMF signal according to input parameters. - * - * Input: - * - DTMFdecInst : DTMF instance - * - value : DTMF event number (0-15) - * - volume : Volume of generated signal (0-36) - * Volume is given in negative dBm0, i.e., volume == 0 - * means 0 dBm0 while volume == 36 mean -36 dBm0. - * - sampFreq : Sample rate in Hz - * - * Output: - * - signal : Pointer to vector where DTMF signal is stored; - * Vector must be at least sampFreq/100 samples long. - * - DTMFdecInst : Updated DTMF instance - * - * Return value : >0 - Number of samples written to signal - * : <0 - Error - */ - -int16_t WebRtcNetEQ_DTMFGenerate(dtmf_tone_inst_t *DTMFdecInst, - int16_t value, - int16_t volume, - int16_t *signal, - uint16_t sampFreq, - int16_t frameLen -); - -#endif /* NETEQ_ATEVENT_DECODE */ - -#endif /* DTMF_TONEGEN_H */ - diff --git a/webrtc/modules/audio_coding/neteq/expand.c b/webrtc/modules/audio_coding/neteq/expand.c deleted file mode 100644 index 9959f9222..000000000 --- a/webrtc/modules/audio_coding/neteq/expand.c +++ /dev/null @@ -1,1220 +0,0 @@ -/* - * 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 is the function to expand from the speech history, to produce concealment data or - * increasing delay. - */ - -#include "dsp.h" - -#include - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" -#include "neteq_error_codes.h" - -#define CHECK_NO_OF_CORRMAX 3 -#define DISTLEN 20 -#define LPCANALASYSLEN 160 - -/* Scratch usage: - - Type Name size startpos endpos - (First part of first expand) - int16_t pw16_bestCorrIndex 3 0 2 - int16_t pw16_bestCorr 3 3 5 - int16_t pw16_bestDistIndex 3 6 8 - int16_t pw16_bestDist 3 9 11 - int16_t pw16_corrVec 102*fs/8000 12 11+102*fs/8000 - func WebRtcNetEQ_Correlator 232 12+102*fs/8000 243+102*fs/8000 - - (Second part of first expand) - int32_t pw32_corr2 99*fs/8000+1 0 99*fs/8000 - int32_t pw32_autoCorr 2*7 0 13 - int16_t pw16_rc 6 14 19 - - Signal combination: - int16_t pw16_randVec 30+120*fs/8000 0 29+120*fs/8000 - int16_t pw16_scaledRandVec 125*fs/8000 30+120*fs/8000 29+245*fs/8000 - int16_t pw16_unvoicedVecSpace 10+125*fs/8000 30+245*fs/8000 39+370*fs/8000 - - Total: 40+370*fs/8000 (size depends on UNVOICED_LPC_ORDER and BGN_LPC_ORDER) - */ - -#if ((BGN_LPC_ORDER > 10) || (UNVOICED_LPC_ORDER > 10)) && (defined SCRATCH) -#error BGN_LPC_ORDER and/or BGN_LPC_ORDER are too large for current scratch memory allocation -#endif - -#define SCRATCH_PW16_BEST_CORR_INDEX 0 -#define SCRATCH_PW16_BEST_CORR 3 -#define SCRATCH_PW16_BEST_DIST_INDEX 6 -#define SCRATCH_PW16_BEST_DIST 9 -#define SCRATCH_PW16_CORR_VEC 12 -#define SCRATCH_PW16_CORR2 0 -#define SCRATCH_PW32_AUTO_CORR 0 -#define SCRATCH_PW16_RC 14 -#define SCRATCH_PW16_RAND_VEC 0 - -#if (defined(NETEQ_48KHZ_WIDEBAND)) -#define SCRATCH_NETEQDSP_CORRELATOR 624 -#define SCRATCH_PW16_SCALED_RAND_VEC 750 -#define SCRATCH_PW16_UNVOICED_VEC_SPACE 1500 -#elif (defined(NETEQ_32KHZ_WIDEBAND)) -#define SCRATCH_NETEQDSP_CORRELATOR 420 -#define SCRATCH_PW16_SCALED_RAND_VEC 510 -#define SCRATCH_PW16_UNVOICED_VEC_SPACE 1010 -#elif (defined(NETEQ_WIDEBAND)) -#define SCRATCH_NETEQDSP_CORRELATOR 216 -#define SCRATCH_PW16_SCALED_RAND_VEC 270 -#define SCRATCH_PW16_UNVOICED_VEC_SPACE 520 -#else /* NB */ -#define SCRATCH_NETEQDSP_CORRELATOR 114 -#define SCRATCH_PW16_SCALED_RAND_VEC 150 -#define SCRATCH_PW16_UNVOICED_VEC_SPACE 275 -#endif - -/**************************************************************************** - * WebRtcNetEQ_Expand(...) - * - * This function produces one "chunk" of expansion data (PLC audio). The - * length of the produced audio depends on the speech history. - * - * Input: - * - inst : DSP instance - * - scratchPtr : Pointer to scratch vector - * - outdata : Pointer to a memory space where the output data - * should be stored - * - BGNonly : If non-zero, "expand" will only produce background noise. - * - pw16_len : Desired number of samples (only for BGN mode). - * - * Output: - * - inst : Updated instance - * - pw16_len : Number of samples that were output from NetEq - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_Expand(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_outData, int16_t *pw16_len, - int16_t BGNonly) -{ - - int16_t fs_mult; - ExpandInst_t *ExpandState = &(inst->ExpandInst); - BGNInst_t *BGNState = &(inst->BGNInst); - int i; -#ifdef SCRATCH - int16_t *pw16_randVec = pw16_scratchPtr + SCRATCH_PW16_RAND_VEC; - int16_t *pw16_scaledRandVec = pw16_scratchPtr + SCRATCH_PW16_SCALED_RAND_VEC; - int16_t *pw16_unvoicedVecSpace = pw16_scratchPtr + SCRATCH_PW16_UNVOICED_VEC_SPACE; -#else - int16_t pw16_randVec[FSMULT * 120 + 30]; /* 150 for NB and 270 for WB */ - int16_t pw16_scaledRandVec[FSMULT * 125]; /* 125 for NB and 250 for WB */ - int16_t pw16_unvoicedVecSpace[BGN_LPC_ORDER + FSMULT * 125]; -#endif - /* 125 for NB and 250 for WB etc. Reuse pw16_outData[] for this vector */ - int16_t *pw16_voicedVecStorage = pw16_outData; - int16_t *pw16_voicedVec = &pw16_voicedVecStorage[ExpandState->w16_overlap]; - int16_t *pw16_unvoicedVec = pw16_unvoicedVecSpace + UNVOICED_LPC_ORDER; - int16_t *pw16_cngVec = pw16_unvoicedVecSpace + BGN_LPC_ORDER; - int16_t w16_expVecsLen, w16_lag = 0, w16_expVecPos; - int16_t w16_randLen; - int16_t w16_vfractionChange; /* in Q14 */ - int16_t w16_winMute = 0, w16_winMuteInc = 0, w16_winUnMute = 0, w16_winUnMuteInc = 0; - int32_t w32_tmp; - int16_t w16_tmp, w16_tmp2; - int16_t stability; - enum BGNMode bgnMode = inst->BGNInst.bgnMode; - - /* Pre-calculate common multiplications with fs_mult */ - int16_t fsMult4; - int16_t fsMult20; - int16_t fsMult120; - int16_t fsMultDistLen; - int16_t fsMultLPCAnalasysLen; - -#ifdef NETEQ_STEREO - MasterSlaveInfo *msInfo = inst->msInfo; -#endif - - /* fs is uint16_t (to hold fs=48000) */ - fs_mult = WebRtcNetEQ_CalcFsMult(inst->fs); /* calculate fs/8000 */ - - /* Pre-calculate common multiplications with fs_mult */ - fsMult4 = (int16_t) WEBRTC_SPL_MUL_16_16(fs_mult, 4); - fsMult20 = (int16_t) WEBRTC_SPL_MUL_16_16(fs_mult, 20); - fsMult120 = (int16_t) WEBRTC_SPL_MUL_16_16(fs_mult, 120); - fsMultDistLen = (int16_t) WEBRTC_SPL_MUL_16_16(fs_mult, DISTLEN); - fsMultLPCAnalasysLen = (int16_t) WEBRTC_SPL_MUL_16_16(fs_mult, LPCANALASYSLEN); - - /* - * Perform all the initial setup if it's the first expansion. - * If background noise (BGN) only, this setup is not needed. - */ - if (ExpandState->w16_consecExp == 0 && !BGNonly) - { - /* Setup more variables */ -#ifdef SCRATCH - int32_t *pw32_autoCorr = (int32_t*) (pw16_scratchPtr - + SCRATCH_PW32_AUTO_CORR); - int16_t *pw16_rc = pw16_scratchPtr + SCRATCH_PW16_RC; - int16_t *pw16_bestCorrIndex = pw16_scratchPtr + SCRATCH_PW16_BEST_CORR_INDEX; - int16_t *pw16_bestCorr = pw16_scratchPtr + SCRATCH_PW16_BEST_CORR; - int16_t *pw16_bestDistIndex = pw16_scratchPtr + SCRATCH_PW16_BEST_DIST_INDEX; - int16_t *pw16_bestDist = pw16_scratchPtr + SCRATCH_PW16_BEST_DIST; - int16_t *pw16_corrVec = pw16_scratchPtr + SCRATCH_PW16_CORR_VEC; - int32_t *pw32_corr2 = (int32_t*) (pw16_scratchPtr + SCRATCH_PW16_CORR2); -#else - int32_t pw32_autoCorr[UNVOICED_LPC_ORDER+1]; - int16_t pw16_rc[UNVOICED_LPC_ORDER]; - int16_t pw16_corrVec[FSMULT*102]; /* 102 for NB */ - int16_t pw16_bestCorrIndex[CHECK_NO_OF_CORRMAX]; - int16_t pw16_bestCorr[CHECK_NO_OF_CORRMAX]; - int16_t pw16_bestDistIndex[CHECK_NO_OF_CORRMAX]; - int16_t pw16_bestDist[CHECK_NO_OF_CORRMAX]; - int32_t pw32_corr2[(99*FSMULT)+1]; -#endif - int32_t pw32_bestDist[CHECK_NO_OF_CORRMAX]; - int16_t w16_ind = 0; - int16_t w16_corrVecLen; - int16_t w16_corrScale; - int16_t w16_distScale; - int16_t w16_indMin, w16_indMax; - int16_t w16_len; - int32_t w32_en1, w32_en2, w32_cc; - int16_t w16_en1Scale, w16_en2Scale; - int16_t w16_en1, w16_en2; - int32_t w32_en1_mul_en2; - int16_t w16_sqrt_en1en2; - int16_t w16_ccShiftL; - int16_t w16_bestcorr; /* Correlation in Q14 */ - int16_t *pw16_vec1, *pw16_vec2; - int16_t w16_factor; - int16_t w16_DistLag, w16_CorrLag, w16_diffLag; - int16_t w16_energyLen; - int16_t w16_slope; - int16_t w16_startInd; - int16_t w16_noOfcorr2; - int16_t w16_scale; - - /* Initialize some variables */ - ExpandState->w16_lagsDirection = 1; - ExpandState->w16_lagsPosition = -1; - ExpandState->w16_expandMuteFactor = 16384; /* Start from 1.0 (Q14) */ - BGNState->w16_mutefactor = 0; /* Start with 0 gain for BGN (value in Q14) */ - inst->w16_seedInc = 1; - -#ifdef NETEQ_STEREO - /* Sanity for msInfo */ - if (msInfo == NULL) - { - /* this should not happen here */ - return MASTER_SLAVE_ERROR; - } - - /* - * Do not calculate correlations for slave instance(s) - * unless lag info from master is corrupt - */ - if ((msInfo->msMode != NETEQ_SLAVE) - || ((msInfo->distLag <= 0) || (msInfo->corrLag <= 0))) - { -#endif - /* Calculate correlation vector in downsampled domain (4 kHz sample rate) */ - w16_corrVecLen = WebRtcNetEQ_Correlator(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQDSP_CORRELATOR, -#endif - inst->pw16_speechHistory, inst->w16_speechHistoryLen, pw16_corrVec, - &w16_corrScale); - - /* Find peaks in correlation vector using parabolic fit method */ - WebRtcNetEQ_PeakDetection(pw16_corrVec, w16_corrVecLen, CHECK_NO_OF_CORRMAX, fs_mult, - pw16_bestCorrIndex, pw16_bestCorr); - - /* - * Adjust peak locations; cross-correlation lags start at 2.5 ms - * (20*fs_mult samples) - */ - pw16_bestCorrIndex[0] += fsMult20; - pw16_bestCorrIndex[1] += fsMult20; - pw16_bestCorrIndex[2] += fsMult20; - - /* Calculate distortion around the 3 (CHECK_NO_OF_CORRMAX) best lags */ - w16_distScale = 0; - for (i = 0; i < CHECK_NO_OF_CORRMAX; i++) - { - w16_tmp = fsMult20; - w16_tmp2 = pw16_bestCorrIndex[i] - fsMult4; - w16_indMin = WEBRTC_SPL_MAX(w16_tmp, w16_tmp2); - w16_tmp = fsMult120 - 1; - w16_tmp2 = pw16_bestCorrIndex[i] + fsMult4; - w16_indMax = WEBRTC_SPL_MIN(w16_tmp, w16_tmp2); - - pw16_bestDistIndex[i] = WebRtcNetEQ_MinDistortion( - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - fsMultDistLen]), - w16_indMin, w16_indMax, fsMultDistLen, &pw32_bestDist[i]); - - w16_distScale - = WEBRTC_SPL_MAX(16 - WebRtcSpl_NormW32(pw32_bestDist[i]), w16_distScale); - - } - - /* Shift the distortion values to fit in int16_t */ - WebRtcSpl_VectorBitShiftW32ToW16(pw16_bestDist, CHECK_NO_OF_CORRMAX, pw32_bestDist, - w16_distScale); - - /* - * Find index of maximum criteria, where crit[i] = bestCorr[i])/(bestDist[i]) - * Do this by a cross multiplication. - */ - - w32_en1 = WEBRTC_SPL_MUL_16_16((int32_t) pw16_bestCorr[0],pw16_bestDist[1]); - w32_en2 = WEBRTC_SPL_MUL_16_16((int32_t) pw16_bestCorr[1],pw16_bestDist[0]); - if (w32_en1 >= w32_en2) - { - /* 0 wins over 1 */ - w32_en1 - = WEBRTC_SPL_MUL_16_16((int32_t) pw16_bestCorr[0], pw16_bestDist[2]); - w32_en2 - = WEBRTC_SPL_MUL_16_16((int32_t) pw16_bestCorr[2], pw16_bestDist[0]); - if (w32_en1 >= w32_en2) - { - /* 0 wins over 2 */ - w16_ind = 0; - } - else - { - /* 2 wins over 0 */ - w16_ind = 2; - } - } - else - { - /* 1 wins over 0 */ - w32_en1 - = WEBRTC_SPL_MUL_16_16((int32_t) pw16_bestCorr[1],pw16_bestDist[2]); - w32_en2 - = WEBRTC_SPL_MUL_16_16((int32_t) pw16_bestCorr[2],pw16_bestDist[1]); - if ((int32_t) w32_en1 >= (int32_t) w32_en2) - { - /* 1 wins over 2 */ - w16_ind = 1; - } - else - { - /* 2 wins over 1 */ - w16_ind = 2; - } - } - -#ifdef NETEQ_STEREO - } - - /* Store DistLag and CorrLag of the position with highest criteria */ - if ((msInfo->msMode == NETEQ_MASTER) || (msInfo->msMode == NETEQ_MONO) - || ((msInfo->msMode == NETEQ_SLAVE) && (msInfo->distLag <= 0 || msInfo->corrLag - <= 0))) - { - /* lags not provided externally */ - w16_DistLag = pw16_bestDistIndex[w16_ind]; - w16_CorrLag = pw16_bestCorrIndex[w16_ind]; - if (msInfo->msMode == NETEQ_MASTER) - { - msInfo->distLag = w16_DistLag; - msInfo->corrLag = w16_CorrLag; - } - } - else if (msInfo->msMode == NETEQ_SLAVE) - { - /* lags provided externally (from master) */ - w16_DistLag = msInfo->distLag; - w16_CorrLag = msInfo->corrLag; - - /* sanity for lag values */ - if ((w16_DistLag <= 0) || (w16_CorrLag <= 0)) - { - return MASTER_SLAVE_ERROR; - } - } - else - { - /* Invalid mode */ - return MASTER_SLAVE_ERROR; - } -#else /* not NETEQ_STEREO */ - w16_DistLag = pw16_bestDistIndex[w16_ind]; - w16_CorrLag = pw16_bestCorrIndex[w16_ind]; -#endif - - ExpandState->w16_maxLag = WEBRTC_SPL_MAX(w16_DistLag, w16_CorrLag); - - /* Calculate the exact best correlation (in the range within CorrLag-DistLag) */ - w16_len = w16_DistLag + 10; - w16_len = WEBRTC_SPL_MIN(w16_len, fsMult120); - w16_len = WEBRTC_SPL_MAX(w16_len, 60 * fs_mult); - - w16_startInd = WEBRTC_SPL_MIN(w16_DistLag, w16_CorrLag); - w16_noOfcorr2 = WEBRTC_SPL_ABS_W16((w16_DistLag-w16_CorrLag)) + 1; - /* w16_noOfcorr2 maximum value is 99*fs_mult + 1 */ - - /* Calculate suitable scaling */ - w16_tmp - = WebRtcSpl_MaxAbsValueW16( - &inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_len - w16_startInd - - w16_noOfcorr2], - (int16_t) (w16_len + w16_startInd + w16_noOfcorr2 - 1)); - w16_corrScale = ((31 - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_tmp, w16_tmp))) - + (31 - WebRtcSpl_NormW32(w16_len))) - 31; - w16_corrScale = WEBRTC_SPL_MAX(0, w16_corrScale); - - /* - * Perform the correlation, store in pw32_corr2 - */ - - WebRtcNetEQ_CrossCorr(pw32_corr2, - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_len]), - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_len - w16_startInd]), - w16_len, w16_noOfcorr2, w16_corrScale, -1); - - /* Find maximizing index */ - w16_ind = WebRtcSpl_MaxIndexW32(pw32_corr2, w16_noOfcorr2); - w32_cc = pw32_corr2[w16_ind]; /* this is maximum correlation */ - w16_ind = w16_ind + w16_startInd; /* correct index for start offset */ - - /* Calculate energies */ - w32_en1 = WebRtcNetEQ_DotW16W16( - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_len]), - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_len]), w16_len, - w16_corrScale); - w32_en2 = WebRtcNetEQ_DotW16W16( - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_len - w16_ind]), - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_len - w16_ind]), - w16_len, w16_corrScale); - - /* Calculate the correlation value w16_bestcorr */ - if ((w32_en1 > 0) && (w32_en2 > 0)) - { - w16_en1Scale = 16 - WebRtcSpl_NormW32(w32_en1); - w16_en1Scale = WEBRTC_SPL_MAX(0, w16_en1Scale); - w16_en2Scale = 16 - WebRtcSpl_NormW32(w32_en2); - w16_en2Scale = WEBRTC_SPL_MAX(0, w16_en2Scale); - /* Make sure total scaling is even (to simplify scale factor after sqrt) */ - if ((w16_en1Scale + w16_en2Scale) & 1) - { - /* if sum is odd */ - w16_en1Scale += 1; - } - w16_en1 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_en1, w16_en1Scale); - w16_en2 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_en2, w16_en2Scale); - w32_en1_mul_en2 = WEBRTC_SPL_MUL_16_16(w16_en1, w16_en2); - w16_sqrt_en1en2 = (int16_t) WebRtcSpl_SqrtFloor(w32_en1_mul_en2); - - /* Calculate cc/sqrt(en1*en2) in Q14 */ - w16_ccShiftL = 14 - ((w16_en1Scale + w16_en2Scale) >> 1); - w32_cc = WEBRTC_SPL_SHIFT_W32(w32_cc, w16_ccShiftL); - w16_bestcorr = (int16_t) WebRtcSpl_DivW32W16(w32_cc, w16_sqrt_en1en2); - w16_bestcorr = WEBRTC_SPL_MIN(16384, w16_bestcorr); /* set maximum to 1.0 */ - - } - else - { - /* if either en1 or en2 is zero */ - w16_bestcorr = 0; - } - - /* - * Extract the two vectors, pw16_expVecs[0][] and pw16_expVecs[1][], - * from the SpeechHistory[] - */ - w16_expVecsLen = ExpandState->w16_maxLag + ExpandState->w16_overlap; - pw16_vec1 = &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - w16_expVecsLen]); - pw16_vec2 = pw16_vec1 - w16_DistLag; - /* Normalize the second vector to the same energy as the first */ - w32_en1 = WebRtcNetEQ_DotW16W16(pw16_vec1, pw16_vec1, w16_expVecsLen, w16_corrScale); - w32_en2 = WebRtcNetEQ_DotW16W16(pw16_vec2, pw16_vec2, w16_expVecsLen, w16_corrScale); - - /* - * Confirm that energy factor sqrt(w32_en1/w32_en2) is within difference 0.5 - 2.0 - * w32_en1/w32_en2 within 0.25 - 4 - */ - if (((w32_en1 >> 2) < w32_en2) && ((w32_en1) > (w32_en2 >> 2))) - { - - /* Energy constraint fulfilled => use both vectors and scale them accordingly */ - w16_en2Scale = 16 - WebRtcSpl_NormW32(w32_en2); - w16_en2Scale = WEBRTC_SPL_MAX(0, w16_en2Scale); - w16_en1Scale = w16_en2Scale - 13; - - /* calculate w32_en1/w32_en2 in Q13 */ - w32_en1_mul_en2 = WebRtcSpl_DivW32W16( - WEBRTC_SPL_SHIFT_W32(w32_en1, -w16_en1Scale), - (int16_t) (WEBRTC_SPL_RSHIFT_W32(w32_en2, w16_en2Scale))); - - /* calculate factor in Q13 (sqrt of en1/en2 in Q26) */ - w16_factor = (int16_t) WebRtcSpl_SqrtFloor( - WEBRTC_SPL_LSHIFT_W32(w32_en1_mul_en2, 13)); - - /* Copy the two vectors and give them the same energy */ - - WEBRTC_SPL_MEMCPY_W16(ExpandState->pw16_expVecs[0], pw16_vec1, w16_expVecsLen); - WebRtcSpl_AffineTransformVector(ExpandState->pw16_expVecs[1], pw16_vec2, - w16_factor, 4096, 13, w16_expVecsLen); - - } - else - { - /* Energy change constraint not fulfilled => only use last vector */ - - WEBRTC_SPL_MEMCPY_W16(ExpandState->pw16_expVecs[0], pw16_vec1, w16_expVecsLen); - WEBRTC_SPL_MEMCPY_W16(ExpandState->pw16_expVecs[1], ExpandState->pw16_expVecs[0], - w16_expVecsLen); - - /* Set the w16_factor since it is used by muting slope */ - if (((w32_en1 >> 2) < w32_en2) || (w32_en2 == 0)) - { - w16_factor = 4096; /* 0.5 in Q13*/ - } - else - { - w16_factor = 16384; /* 2.0 in Q13*/ - } - } - - /* Set the 3 lag values */ - w16_diffLag = w16_DistLag - w16_CorrLag; - if (w16_diffLag == 0) - { - /* DistLag and CorrLag are equal */ - ExpandState->w16_lags[0] = w16_DistLag; - ExpandState->w16_lags[1] = w16_DistLag; - ExpandState->w16_lags[2] = w16_DistLag; - } - else - { - /* DistLag and CorrLag are not equal; use different combinations of the two */ - ExpandState->w16_lags[0] = w16_DistLag; /* DistLag only */ - ExpandState->w16_lags[1] = ((w16_DistLag + w16_CorrLag) >> 1); /* 50/50 */ - /* Third lag, move one half-step towards CorrLag (in both cases) */ - if (w16_diffLag > 0) - { - ExpandState->w16_lags[2] = (w16_DistLag + w16_CorrLag - 1) >> 1; - } - else - { - ExpandState->w16_lags[2] = (w16_DistLag + w16_CorrLag + 1) >> 1; - } - } - - /************************************************* - * Calculate the LPC and the gain of the filters * - *************************************************/ - - /* Calculate scale value needed for autocorrelation */ - w16_tmp = WebRtcSpl_MaxAbsValueW16( - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - fsMultLPCAnalasysLen]), - fsMultLPCAnalasysLen); - - w16_tmp = 16 - WebRtcSpl_NormW32(w16_tmp); - w16_tmp = WEBRTC_SPL_MIN(w16_tmp,0); - w16_tmp = (w16_tmp << 1) + 7; - w16_tmp = WEBRTC_SPL_MAX(w16_tmp,0); - - /* set w16_ind to simplify the following expressions */ - w16_ind = inst->w16_speechHistoryLen - fsMultLPCAnalasysLen - UNVOICED_LPC_ORDER; - - /* store first UNVOICED_LPC_ORDER samples in pw16_rc */ - - WEBRTC_SPL_MEMCPY_W16(pw16_rc, &inst->pw16_speechHistory[w16_ind], UNVOICED_LPC_ORDER); - - /* set first samples to zero */ - WebRtcSpl_MemSetW16(&inst->pw16_speechHistory[w16_ind], 0, UNVOICED_LPC_ORDER); - - /* Calculate UNVOICED_LPC_ORDER+1 lags of the ACF */ - - WebRtcNetEQ_CrossCorr( - pw32_autoCorr, &(inst->pw16_speechHistory[w16_ind + UNVOICED_LPC_ORDER]), - &(inst->pw16_speechHistory[w16_ind + UNVOICED_LPC_ORDER]), fsMultLPCAnalasysLen, - UNVOICED_LPC_ORDER + 1, w16_tmp, -1); - - /* Recover the stored samples from pw16_rc */ - - WEBRTC_SPL_MEMCPY_W16(&inst->pw16_speechHistory[w16_ind], pw16_rc, UNVOICED_LPC_ORDER); - - if (pw32_autoCorr[0] > 0) - { /* check that variance is positive */ - - /* estimate AR filter parameters using Levinson-Durbin algorithm - (UNVOICED_LPC_ORDER+1 filter coefficients) */ - stability = WebRtcSpl_LevinsonDurbin(pw32_autoCorr, ExpandState->pw16_arFilter, - pw16_rc, UNVOICED_LPC_ORDER); - - /* Only update BGN if filter is stable */ - if (stability != 1) - { - /* Set first coefficient to 4096 (1.0 in Q12)*/ - ExpandState->pw16_arFilter[0] = 4096; - /* Set remaining UNVOICED_LPC_ORDER coefficients to zero */ - WebRtcSpl_MemSetW16(ExpandState->pw16_arFilter + 1, 0, UNVOICED_LPC_ORDER); - } - - } - - if (w16_DistLag < 40) - { - w16_energyLen = 2 * w16_DistLag; - } - else - { - w16_energyLen = w16_DistLag; - } - w16_randLen = w16_energyLen + 30; /* Startup part */ - - /* Extract a noise segment */ - if (w16_randLen <= RANDVEC_NO_OF_SAMPLES) - { - WEBRTC_SPL_MEMCPY_W16(pw16_randVec, - (int16_t*) WebRtcNetEQ_kRandnTbl, w16_randLen); - } - else - { /* only applies to SWB where length could be larger than 256 */ -#if FSMULT >= 2 /* Makes pw16_randVec longer than RANDVEC_NO_OF_SAMPLES. */ - WEBRTC_SPL_MEMCPY_W16(pw16_randVec, (int16_t*) WebRtcNetEQ_kRandnTbl, - RANDVEC_NO_OF_SAMPLES); - inst->w16_seedInc = (inst->w16_seedInc + 2) & (RANDVEC_NO_OF_SAMPLES - 1); - assert(w16_randLen <= FSMULT * 120 + 30); - WebRtcNetEQ_RandomVec(&inst->uw16_seed, &pw16_randVec[RANDVEC_NO_OF_SAMPLES], - (int16_t) (w16_randLen - RANDVEC_NO_OF_SAMPLES), inst->w16_seedInc); -#else - assert(0); -#endif - } - - /* Set up state vector and calculate scale factor for unvoiced filtering */ - - WEBRTC_SPL_MEMCPY_W16(ExpandState->pw16_arState, - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - UNVOICED_LPC_ORDER]), - UNVOICED_LPC_ORDER); - WEBRTC_SPL_MEMCPY_W16(pw16_unvoicedVec - UNVOICED_LPC_ORDER, - &(inst->pw16_speechHistory[inst->w16_speechHistoryLen - 128 - UNVOICED_LPC_ORDER]), - UNVOICED_LPC_ORDER); - WebRtcSpl_FilterMAFastQ12(&inst->pw16_speechHistory[inst->w16_speechHistoryLen - 128], - pw16_unvoicedVec, ExpandState->pw16_arFilter, UNVOICED_LPC_ORDER + 1, 128); - if (WebRtcSpl_MaxAbsValueW16(pw16_unvoicedVec, 128) > 4000) - { - w16_scale = 4; - } - else - { - w16_scale = 0; - } - w32_tmp = WebRtcNetEQ_DotW16W16(pw16_unvoicedVec, pw16_unvoicedVec, 128, w16_scale); - - /* Normalize w32_tmp to 28 or 29 bits to preserve sqrt() accuracy */ - w16_tmp = WebRtcSpl_NormW32(w32_tmp) - 3; - w16_tmp += ((w16_tmp & 0x1) ^ 0x1); /* Make sure we do an odd number of shifts since we - from earlier have 7 shifts from dividing with 128.*/ - w32_tmp = WEBRTC_SPL_SHIFT_W32(w32_tmp, w16_tmp); - w32_tmp = WebRtcSpl_SqrtFloor(w32_tmp); - ExpandState->w16_arGainScale = 13 + ((w16_tmp + 7 - w16_scale) >> 1); - ExpandState->w16_arGain = (int16_t) w32_tmp; - - /******************************************************************** - * Calculate vfraction from bestcorr * - * if (bestcorr>0.480665) * - * vfraction = ((bestcorr-0.4)/(1-0.4)).^2 * - * else vfraction = 0 * - * * - * approximation (coefficients in Q12): * - * if (x>0.480665) (y(x)<0.3) * - * y(x) = -1.264421 + 4.8659148*x - 4.0092827*x^2 + 1.4100529*x^3 * - * else y(x) = 0; * - ********************************************************************/ - - if (w16_bestcorr > 7875) - { - /* if x>0.480665 */ - int16_t w16_x1, w16_x2, w16_x3; - w16_x1 = w16_bestcorr; - w32_tmp = WEBRTC_SPL_MUL_16_16((int32_t) w16_x1, w16_x1); - w16_x2 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_tmp, 14); - w32_tmp = WEBRTC_SPL_MUL_16_16(w16_x1, w16_x2); - w16_x3 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_tmp, 14); - w32_tmp - = (int32_t) WEBRTC_SPL_LSHIFT_W32((int32_t) WebRtcNetEQ_kMixFractionFuncTbl[0], 14); - w32_tmp - += (int32_t) WEBRTC_SPL_MUL_16_16(WebRtcNetEQ_kMixFractionFuncTbl[1], w16_x1); - w32_tmp - += (int32_t) WEBRTC_SPL_MUL_16_16(WebRtcNetEQ_kMixFractionFuncTbl[2], w16_x2); - w32_tmp - += (int32_t) WEBRTC_SPL_MUL_16_16(WebRtcNetEQ_kMixFractionFuncTbl[3], w16_x3); - ExpandState->w16_vFraction = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_tmp, 12); - ExpandState->w16_vFraction = WEBRTC_SPL_MIN(ExpandState->w16_vFraction, 16384); - ExpandState->w16_vFraction = WEBRTC_SPL_MAX(ExpandState->w16_vFraction, 0); - } - else - { - ExpandState->w16_vFraction = 0; - } - - /*********************************************************************** - * Calculate muting slope, reuse value from earlier scaling of ExpVecs * - ***********************************************************************/ - w16_slope = w16_factor; - - if (w16_slope > 12288) - { - /* w16_slope > 1.5 ? */ - /* Calculate (1-(1/slope))/w16_DistLag = (slope-1)/(w16_DistLag*slope) */ - w32_tmp = w16_slope - 8192; - w32_tmp = WEBRTC_SPL_LSHIFT_W32(w32_tmp, 12); /* Value in Q25 (13+12=25) */ - w16_tmp = (int16_t) WEBRTC_SPL_MUL_16_16_RSFT(w16_DistLag, - w16_slope, 8); /* Value in Q5 (13-8=5) */ - w16_tmp = (int16_t) WebRtcSpl_DivW32W16(w32_tmp, - w16_tmp); /* Res in Q20 (25-5=20) */ - - if (w16_slope > 14746) - { /* w16_slope > 1.8 ? */ - ExpandState->w16_muteSlope = (w16_tmp + 1) >> 1; - } - else - { - ExpandState->w16_muteSlope = (w16_tmp + 4) >> 3; - } - ExpandState->w16_onset = 1; - } - else if (ExpandState->w16_vFraction > 13107) - { - /* w16_vFraction > 0.8 ? */ - if (w16_slope > 8028) - { - /* w16_vFraction > 0.98 ? */ - ExpandState->w16_muteSlope = 0; - } - else - { - /* Calculate (1-slope)/w16_DistLag */ - w32_tmp = 8192 - w16_slope; - w32_tmp = WEBRTC_SPL_LSHIFT_W32(w32_tmp, 7); /* Value in Q20 (13+7=20) */ - ExpandState->w16_muteSlope = (int16_t) WebRtcSpl_DivW32W16(w32_tmp, - w16_DistLag); /* Res in Q20 (20-0=20) */ - } - ExpandState->w16_onset = 0; - } - else - { - /* - * Use the minimum of 0.005 (0.9 on 50 samples in NB and the slope) - * and ((1-slope)/w16_DistLag) - */ - w32_tmp = 8192 - w16_slope; - w32_tmp = WEBRTC_SPL_LSHIFT_W32(w32_tmp, 7); /* Value in Q20 (13+7=20) */ - w32_tmp = WEBRTC_SPL_MAX(w32_tmp, 0); - ExpandState->w16_muteSlope = (int16_t) WebRtcSpl_DivW32W16(w32_tmp, - w16_DistLag); /* Res in Q20 (20-0=20) */ - w16_tmp = WebRtcNetEQ_k5243div[fs_mult]; /* 0.005/fs_mult = 5243/fs_mult */ - ExpandState->w16_muteSlope = WEBRTC_SPL_MAX(w16_tmp, ExpandState->w16_muteSlope); - ExpandState->w16_onset = 0; - } - } - else - { - /* This is not the first Expansion, parameters are already estimated. */ - - /* Extract a noise segment */ - if (BGNonly) /* If we should produce nothing but background noise */ - { - if (*pw16_len > 0) - { - /* - * Set length to input parameter length, but not more than length - * of pw16_randVec - */ - w16_lag = WEBRTC_SPL_MIN(*pw16_len, FSMULT * 120 + 30); - } - else - { - /* set length to 15 ms */ - w16_lag = fsMult120; - } - w16_randLen = w16_lag; - } - else - { - w16_randLen = ExpandState->w16_maxLag; - } - - if (w16_randLen <= RANDVEC_NO_OF_SAMPLES) - { - inst->w16_seedInc = (inst->w16_seedInc + 2) & (RANDVEC_NO_OF_SAMPLES - 1); - WebRtcNetEQ_RandomVec(&inst->uw16_seed, pw16_randVec, w16_randLen, - inst->w16_seedInc); - } - else - { /* only applies to SWB where length could be larger than 256 */ -#if FSMULT >= 2 /* Makes pw16_randVec longer than RANDVEC_NO_OF_SAMPLES. */ - inst->w16_seedInc = (inst->w16_seedInc + 2) & (RANDVEC_NO_OF_SAMPLES - 1); - WebRtcNetEQ_RandomVec(&inst->uw16_seed, pw16_randVec, RANDVEC_NO_OF_SAMPLES, - inst->w16_seedInc); - inst->w16_seedInc = (inst->w16_seedInc + 2) & (RANDVEC_NO_OF_SAMPLES - 1); - assert(w16_randLen <= FSMULT * 120 + 30); - WebRtcNetEQ_RandomVec(&inst->uw16_seed, &pw16_randVec[RANDVEC_NO_OF_SAMPLES], - (int16_t) (w16_randLen - RANDVEC_NO_OF_SAMPLES), inst->w16_seedInc); -#else - assert(0); -#endif - } - } /* end if(first expand or BGNonly) ... else ... */ - - if (!BGNonly) /* Voiced and unvoiced parts not used if generating BGN only */ - { - - /************************************************* - * Generate signal * - *************************************************/ - - /* - * Voiced part - */ - - /* Linearly mute the use_vfraction value from 1 to vfraction */ - if (ExpandState->w16_consecExp == 0) - { - ExpandState->w16_currentVFraction = 16384; /* 1.0 in Q14 */ - } - - ExpandState->w16_lagsPosition = ExpandState->w16_lagsPosition - + ExpandState->w16_lagsDirection; - - /* Change direction if needed */ - if (ExpandState->w16_lagsPosition == 0) - { - ExpandState->w16_lagsDirection = 1; - } - if (ExpandState->w16_lagsPosition == 2) - { - ExpandState->w16_lagsDirection = -1; - } - - /* Generate a weighted vector with the selected lag */ - w16_expVecsLen = ExpandState->w16_maxLag + ExpandState->w16_overlap; - w16_lag = ExpandState->w16_lags[ExpandState->w16_lagsPosition]; - /* Copy lag+overlap data */ - w16_expVecPos = w16_expVecsLen - w16_lag - ExpandState->w16_overlap; - w16_tmp = w16_lag + ExpandState->w16_overlap; - if (ExpandState->w16_lagsPosition == 0) - { - WEBRTC_SPL_MEMCPY_W16(pw16_voicedVecStorage, - &(ExpandState->pw16_expVecs[0][w16_expVecPos]), w16_tmp); - } - else if (ExpandState->w16_lagsPosition == 1) - { - WebRtcSpl_ScaleAndAddVectorsWithRound(&ExpandState->pw16_expVecs[0][w16_expVecPos], 3, - &ExpandState->pw16_expVecs[1][w16_expVecPos], 1, 2, pw16_voicedVecStorage, - w16_tmp); - - } - else if (ExpandState->w16_lagsPosition == 2) - { - WebRtcSpl_ScaleAndAddVectorsWithRound(&ExpandState->pw16_expVecs[0][w16_expVecPos], 1, - &ExpandState->pw16_expVecs[1][w16_expVecPos], 1, 1, pw16_voicedVecStorage, - w16_tmp); - } - - if (inst->fs == 8000) - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_8KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_8KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_8KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_8KHZ_INC; -#ifdef NETEQ_WIDEBAND - } - else if (inst->fs == 16000) - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_16KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_16KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_16KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_16KHZ_INC; -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - } - else if (inst->fs == 32000) - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_32KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_32KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_32KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_32KHZ_INC; -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - } - else /* if (inst->fs==48000) */ - { - /* Windowing in Q15 */ - w16_winMute = NETEQ_OVERLAP_WINMUTE_48KHZ_START; - w16_winMuteInc = NETEQ_OVERLAP_WINMUTE_48KHZ_INC; - w16_winUnMute = NETEQ_OVERLAP_WINUNMUTE_48KHZ_START; - w16_winUnMuteInc = NETEQ_OVERLAP_WINUNMUTE_48KHZ_INC; -#endif - } - - /* Smooth the expanded if it has not been muted to or vfraction is larger than 0.5 */ - if ((ExpandState->w16_expandMuteFactor > 819) && (ExpandState->w16_currentVFraction - > 8192)) - { - for (i = 0; i < ExpandState->w16_overlap; i++) - { - /* Do overlap add between new vector and overlap */ - ExpandState->pw16_overlapVec[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32( - WEBRTC_SPL_MUL_16_16(ExpandState->pw16_overlapVec[i], w16_winMute) + - WEBRTC_SPL_MUL_16_16( - WEBRTC_SPL_MUL_16_16_RSFT(ExpandState->w16_expandMuteFactor, - pw16_voicedVecStorage[i], 14), w16_winUnMute) + 16384, 15); - w16_winMute += w16_winMuteInc; - w16_winUnMute += w16_winUnMuteInc; - } - } - else if (ExpandState->w16_expandMuteFactor == 0 -#ifdef NETEQ_STEREO - && msInfo->msMode == NETEQ_MONO /* only if mono mode is selected */ -#endif - ) - { - /* if ExpandState->w16_expandMuteFactor = 0 => all is CNG component - set the output length to 15ms (for best CNG production) */ - w16_tmp = fsMult120; - ExpandState->w16_maxLag = w16_tmp; - ExpandState->w16_lags[0] = w16_tmp; - ExpandState->w16_lags[1] = w16_tmp; - ExpandState->w16_lags[2] = w16_tmp; - } - - /* - * Unvoiced part - */ - - WEBRTC_SPL_MEMCPY_W16(pw16_unvoicedVec - UNVOICED_LPC_ORDER, - ExpandState->pw16_arState, - UNVOICED_LPC_ORDER); - if (ExpandState->w16_arGainScale > 0) - { - w32_tmp = ((int32_t) 1) << (ExpandState->w16_arGainScale - 1); - } - else - { - w32_tmp = 0; - } - - /* Note that shift value can be >16 which complicates things for some DSPs */ - WebRtcSpl_AffineTransformVector(pw16_scaledRandVec, pw16_randVec, - ExpandState->w16_arGain, w32_tmp, ExpandState->w16_arGainScale, w16_lag); - - WebRtcSpl_FilterARFastQ12(pw16_scaledRandVec, pw16_unvoicedVec, - ExpandState->pw16_arFilter, UNVOICED_LPC_ORDER + 1, w16_lag); - - WEBRTC_SPL_MEMCPY_W16(ExpandState->pw16_arState, - &(pw16_unvoicedVec[w16_lag - UNVOICED_LPC_ORDER]), - UNVOICED_LPC_ORDER); - - /* - * Voiced + Unvoiced - */ - - /* For lag = - <=31*fs_mult => go from 1 to 0 in about 8 ms - (>=31..<=63)*fs_mult => go from 1 to 0 in about 16 ms - >=64*fs_mult => go from 1 to 0 in about 32 ms - */ - w16_tmp = (31 - WebRtcSpl_NormW32(ExpandState->w16_maxLag)) - 5; /* getbits(w16_maxLag) -5 */ - w16_vfractionChange = (int16_t) WEBRTC_SPL_RSHIFT_W32(256, w16_tmp); - if (ExpandState->w16_stopMuting == 1) - { - w16_vfractionChange = 0; - } - - /* Create combined signal (unmuted) by shifting in more and more of unvoiced part */ - w16_tmp = 8 - w16_tmp; /* getbits(w16_vfractionChange) */ - w16_tmp = (ExpandState->w16_currentVFraction - ExpandState->w16_vFraction) >> w16_tmp; - w16_tmp = WEBRTC_SPL_MIN(w16_tmp, w16_lag); - WebRtcNetEQ_MixVoiceUnvoice(pw16_outData, pw16_voicedVec, pw16_unvoicedVec, - &ExpandState->w16_currentVFraction, w16_vfractionChange, w16_tmp); - - if (w16_tmp < w16_lag) - { - if (w16_vfractionChange != 0) - { - ExpandState->w16_currentVFraction = ExpandState->w16_vFraction; - } - w16_tmp2 = 16384 - ExpandState->w16_currentVFraction; - WebRtcSpl_ScaleAndAddVectorsWithRound(pw16_voicedVec + w16_tmp, - ExpandState->w16_currentVFraction, pw16_unvoicedVec + w16_tmp, w16_tmp2, 14, - pw16_outData + w16_tmp, (int16_t) (w16_lag - w16_tmp)); - } - - /* Select muting factor */ - if (ExpandState->w16_consecExp == 3) - { - /* 0.95 on 50 samples in NB (0.0010/fs_mult in Q20) */ - ExpandState->w16_muteSlope = WEBRTC_SPL_MAX(ExpandState->w16_muteSlope, - WebRtcNetEQ_k1049div[fs_mult]); - } - if (ExpandState->w16_consecExp == 7) - { - /* 0.90 on 50 samples in NB (0.0020/fs_mult in Q20) */ - ExpandState->w16_muteSlope = WEBRTC_SPL_MAX(ExpandState->w16_muteSlope, - WebRtcNetEQ_k2097div[fs_mult]); - } - - /* Mute segment according to slope value */ - if ((ExpandState->w16_consecExp != 0) || (ExpandState->w16_onset != 1)) - { - /* Mute to the previous level, then continue with the muting */ - WebRtcSpl_AffineTransformVector(pw16_outData, pw16_outData, - ExpandState->w16_expandMuteFactor, 8192, 14, w16_lag); - - if ((ExpandState->w16_stopMuting != 1)) - { - WebRtcNetEQ_MuteSignal(pw16_outData, ExpandState->w16_muteSlope, w16_lag); - - w16_tmp = 16384 - (int16_t) ((WEBRTC_SPL_MUL_16_16(w16_lag, - ExpandState->w16_muteSlope) + 8192) >> 6); /* 20-14 = 6 */ - w16_tmp = (int16_t) ((WEBRTC_SPL_MUL_16_16(w16_tmp, - ExpandState->w16_expandMuteFactor) + 8192) >> 14); - - /* Guard against getting stuck with very small (but sometimes audible) gain */ - if ((ExpandState->w16_consecExp > 3) && (w16_tmp - >= ExpandState->w16_expandMuteFactor)) - { - ExpandState->w16_expandMuteFactor = 0; - } - else - { - ExpandState->w16_expandMuteFactor = w16_tmp; - } - } - } - - } /* end if(!BGNonly) */ - - /* - * BGN - */ - - if (BGNState->w16_initialized == 1) - { - /* BGN parameters are initialized; use them */ - - WEBRTC_SPL_MEMCPY_W16(pw16_cngVec - BGN_LPC_ORDER, - BGNState->pw16_filterState, - BGN_LPC_ORDER); - - if (BGNState->w16_scaleShift > 1) - { - w32_tmp = ((int32_t) 1) << (BGNState->w16_scaleShift - 1); - } - else - { - w32_tmp = 0; - } - - /* Scale random vector to correct energy level */ - /* Note that shift value can be >16 which complicates things for some DSPs */ - WebRtcSpl_AffineTransformVector(pw16_scaledRandVec, pw16_randVec, - BGNState->w16_scale, w32_tmp, BGNState->w16_scaleShift, w16_lag); - - WebRtcSpl_FilterARFastQ12(pw16_scaledRandVec, pw16_cngVec, BGNState->pw16_filter, - BGN_LPC_ORDER + 1, w16_lag); - - WEBRTC_SPL_MEMCPY_W16(BGNState->pw16_filterState, - &(pw16_cngVec[w16_lag-BGN_LPC_ORDER]), - BGN_LPC_ORDER); - - /* Unmute the insertion of background noise */ - - if (bgnMode == BGN_FADE && ExpandState->w16_consecExp >= FADE_BGN_TIME - && BGNState->w16_mutefactor > 0) - { - /* fade BGN to zero */ - /* calculate muting slope, approx 2^18/fsHz */ - int16_t muteFactor; - if (fs_mult == 1) - { - muteFactor = -32; - } - else if (fs_mult == 2) - { - muteFactor = -16; - } - else if (fs_mult == 4) - { - muteFactor = -8; - } - else - { - muteFactor = -5; - } - /* use UnmuteSignal function with negative slope */ - WebRtcNetEQ_UnmuteSignal(pw16_cngVec, &BGNState->w16_mutefactor, /* In Q14 */ - pw16_cngVec, muteFactor, /* In Q20 */ - w16_lag); - } - else if (BGNState->w16_mutefactor < 16384 && !BGNonly) - { - /* if (w16_mutefactor < 1) and not BGN only (since then we use no muting) */ - - /* - * If BGN_OFF, or if BNG_FADE has started fading, - * mutefactor should not be increased. - */ - if (ExpandState->w16_stopMuting != 1 && bgnMode != BGN_OFF && !(bgnMode - == BGN_FADE && ExpandState->w16_consecExp >= FADE_BGN_TIME)) - { - WebRtcNetEQ_UnmuteSignal(pw16_cngVec, &BGNState->w16_mutefactor, /* In Q14 */ - pw16_cngVec, ExpandState->w16_muteSlope, /* In Q20 */ - w16_lag); - } - else - { - /* BGN_ON and stop muting, or - * BGN_OFF (mute factor is always 0), or - * BGN_FADE has reached 0 */ - WebRtcSpl_AffineTransformVector(pw16_cngVec, pw16_cngVec, - BGNState->w16_mutefactor, 8192, 14, w16_lag); - } - } - } - else - { - /* BGN parameters have not been initialized; use zero noise */ - WebRtcSpl_MemSetW16(pw16_cngVec, 0, w16_lag); - } - - if (BGNonly) - { - /* Copy BGN to outdata */ - for (i = 0; i < w16_lag; i++) - { - pw16_outData[i] = pw16_cngVec[i]; - } - } - else - { - /* Add CNG vector to the Voiced + Unvoiced vectors */ - for (i = 0; i < w16_lag; i++) - { - pw16_outData[i] = pw16_outData[i] + pw16_cngVec[i]; - } - - /* increase call number */ - ExpandState->w16_consecExp = ExpandState->w16_consecExp + 1; - if (ExpandState->w16_consecExp < 0) /* Guard against overflow */ - ExpandState->w16_consecExp = FADE_BGN_TIME; /* "Arbitrary" large num of expands */ - } - - inst->w16_mode = MODE_EXPAND; - *pw16_len = w16_lag; - - /* Update in-call and post-call statistics */ - if (ExpandState->w16_stopMuting != 1 || BGNonly) - { - /* - * Only do this if StopMuting != 1 or if explicitly BGNonly, otherwise Expand is - * called from Merge or Normal and special measures must be taken. - */ - inst->statInst.expandLength += (uint32_t) *pw16_len; - if (ExpandState->w16_expandMuteFactor == 0 || BGNonly) - { - /* Only noise expansion */ - inst->statInst.expandedNoiseSamples += *pw16_len; - /* Short-term activity statistics. */ - inst->activity_stats.expand_bgn_samples += *pw16_len; - } - else - { - /* Voice expand (note: not necessarily _voiced_) */ - inst->statInst.expandedVoiceSamples += *pw16_len; - /* Short-term activity statistics. */ - inst->activity_stats.expand_normal_samples += *pw16_len; - } - } - - return 0; -} - -/**************************************************************************** - * WebRtcNetEQ_GenerateBGN(...) - * - * This function generates and writes len samples of background noise to the - * output vector. The Expand function will be called repeatedly until the - * correct number of samples is produced. - * - * Input: - * - inst : NetEq instance, i.e. the user that requests more - * speech/audio data - * - scratchPtr : Pointer to scratch vector - * - len : Desired length of produced BGN. - * - * - * Output: - * - pw16_outData : Pointer to a memory space where the output data - * should be stored - * - * Return value : >=0 - Number of noise samples produced and written - * to output - * -1 - Error - */ - -int WebRtcNetEQ_GenerateBGN(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_outData, int16_t len) -{ - - int16_t pos = 0; - int16_t tempLen = len; - - while (tempLen > 0) - { - /* while we still need more noise samples, call Expand to obtain background noise */ - WebRtcNetEQ_Expand(inst, -#ifdef SCRATCH - pw16_scratchPtr, -#endif - &pw16_outData[pos], &tempLen, 1 /*BGNonly*/); - - pos += tempLen; /* we got this many samples */ - tempLen = len - pos; /* this is the number of samples we still need */ - } - - return pos; -} - -#undef SCRATCH_PW16_BEST_CORR_INDEX -#undef SCRATCH_PW16_BEST_CORR -#undef SCRATCH_PW16_BEST_DIST_INDEX -#undef SCRATCH_PW16_BEST_DIST -#undef SCRATCH_PW16_CORR_VEC -#undef SCRATCH_PW16_CORR2 -#undef SCRATCH_PW32_AUTO_CORR -#undef SCRATCH_PW16_RC -#undef SCRATCH_PW16_RAND_VEC -#undef SCRATCH_NETEQDSP_CORRELATOR -#undef SCRATCH_PW16_SCALED_RAND_VEC -#undef SCRATCH_PW16_UNVOICED_VEC_SPACE - diff --git a/webrtc/modules/audio_coding/neteq4/expand.cc b/webrtc/modules/audio_coding/neteq/expand.cc similarity index 86% rename from webrtc/modules/audio_coding/neteq4/expand.cc rename to webrtc/modules/audio_coding/neteq/expand.cc index cba99243d..14a779822 100644 --- a/webrtc/modules/audio_coding/neteq4/expand.cc +++ b/webrtc/modules/audio_coding/neteq/expand.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/expand.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" #include #include // memset @@ -17,10 +17,10 @@ #include // numeric_limits #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" -#include "webrtc/modules/audio_coding/neteq4/dsp_helper.h" -#include "webrtc/modules/audio_coding/neteq4/random_vector.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/dsp_helper.h" +#include "webrtc/modules/audio_coding/neteq/random_vector.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { @@ -56,20 +56,9 @@ int Expand::Process(AudioMultiVector* output) { // This is not the first expansion, parameters are already estimated. // Extract a noise segment. int16_t rand_length = max_lag_; - // TODO(hlundin): This if-statement should not be needed. Should be just - // as good to generate all of the vector in one call in either case. - if (rand_length <= RandomVector::kRandomTableSize) { - random_vector_->IncreaseSeedIncrement(2); - random_vector_->Generate(rand_length, random_vector); - } else { - // This only applies to SWB where length could be larger than 256. - assert(rand_length <= kMaxSampleRate / 8000 * 120 + 30); - random_vector_->IncreaseSeedIncrement(2); - random_vector_->Generate(RandomVector::kRandomTableSize, random_vector); - random_vector_->IncreaseSeedIncrement(2); - random_vector_->Generate(rand_length - RandomVector::kRandomTableSize, - &random_vector[RandomVector::kRandomTableSize]); - } + // This only applies to SWB where length could be larger than 256. + assert(rand_length <= kMaxSampleRate / 8000 * 120 + 30); + GenerateRandomVector(2, rand_length, random_vector); } @@ -262,82 +251,12 @@ int Expand::Process(AudioMultiVector* output) { } // Background noise part. - // TODO(hlundin): Move to separate method? In BackgroundNoise class? - if (background_noise_->initialized()) { - // Use background noise parameters. - memcpy(noise_vector - kNoiseLpcOrder, - background_noise_->FilterState(channel_ix), - sizeof(int16_t) * kNoiseLpcOrder); - - if (background_noise_->ScaleShift(channel_ix) > 1) { - add_constant = 1 << (background_noise_->ScaleShift(channel_ix) - 1); - } else { - add_constant = 0; - } - - // Scale random vector to correct energy level. - WebRtcSpl_AffineTransformVector( - scaled_random_vector, random_vector, - background_noise_->Scale(channel_ix), add_constant, - background_noise_->ScaleShift(channel_ix), - static_cast(current_lag)); - - WebRtcSpl_FilterARFastQ12(scaled_random_vector, noise_vector, - background_noise_->Filter(channel_ix), - kNoiseLpcOrder + 1, - static_cast(current_lag)); - - background_noise_->SetFilterState( - channel_ix, - &(noise_vector[current_lag - kNoiseLpcOrder]), - kNoiseLpcOrder); - - // Unmute the background noise. - int16_t bgn_mute_factor = background_noise_->MuteFactor(channel_ix); - NetEqBackgroundNoiseMode bgn_mode = background_noise_->mode(); - if (bgn_mode == kBgnFade && - consecutive_expands_ >= kMaxConsecutiveExpands && - bgn_mute_factor > 0) { - // Fade BGN to zero. - // Calculate muting slope, approximately -2^18 / fs_hz. - int16_t mute_slope; - if (fs_hz_ == 8000) { - mute_slope = -32; - } else if (fs_hz_ == 16000) { - mute_slope = -16; - } else if (fs_hz_ == 32000) { - mute_slope = -8; - } else { - mute_slope = -5; - } - // Use UnmuteSignal function with negative slope. - // |bgn_mute_factor| is in Q14. |mute_slope| is in Q20. - DspHelper::UnmuteSignal(noise_vector, current_lag, &bgn_mute_factor, - mute_slope, noise_vector); - } else if (bgn_mute_factor < 16384) { - // If mode is kBgnOff, or if kBgnFade has started fading, - // Use regular |mute_slope|. - if (!stop_muting_ && bgn_mode != kBgnOff && - !(bgn_mode == kBgnFade && - consecutive_expands_ >= kMaxConsecutiveExpands)) { - DspHelper::UnmuteSignal(noise_vector, static_cast(current_lag), - &bgn_mute_factor, parameters.mute_slope, - noise_vector); - } else { - // kBgnOn and stop muting, or - // kBgnOff (mute factor is always 0), or - // kBgnFade has reached 0. - WebRtcSpl_AffineTransformVector(noise_vector, noise_vector, - bgn_mute_factor, 8192, 14, - static_cast(current_lag)); - } - } - // Update mute_factor in BackgroundNoise class. - background_noise_->SetMuteFactor(channel_ix, bgn_mute_factor); - } else { - // BGN parameters have not been initialized; use zero noise. - memset(noise_vector, 0, sizeof(int16_t) * current_lag); - } + GenerateBackgroundNoise(random_vector, + channel_ix, + channel_parameters_[channel_ix].mute_slope, + TooManyExpands(), + current_lag, + unvoiced_array_memory); // Add background noise to the combined voiced-unvoiced signal. for (size_t i = 0; i < current_lag; i++) { @@ -353,11 +272,8 @@ int Expand::Process(AudioMultiVector* output) { } // Increase call number and cap it. - ++consecutive_expands_; - if (consecutive_expands_ > kMaxConsecutiveExpands) { - consecutive_expands_ = kMaxConsecutiveExpands; - } - + consecutive_expands_ = consecutive_expands_ >= kMaxConsecutiveExpands ? + kMaxConsecutiveExpands : consecutive_expands_ + 1; return 0; } @@ -373,6 +289,24 @@ void Expand::SetParametersForMergeAfterExpand() { stop_muting_ = true; } +void Expand::InitializeForAnExpandPeriod() { + lag_index_direction_ = 1; + current_lag_index_ = -1; + stop_muting_ = false; + random_vector_->set_seed_increment(1); + consecutive_expands_ = 0; + for (size_t ix = 0; ix < num_channels_; ++ix) { + channel_parameters_[ix].current_voice_mix_factor = 16384; // 1.0 in Q14. + channel_parameters_[ix].mute_factor = 16384; // 1.0 in Q14. + // Start with 0 gain for background noise. + background_noise_->SetMuteFactor(ix, 0); + } +} + +bool Expand::TooManyExpands() { + return consecutive_expands_ >= kMaxConsecutiveExpands; +} + void Expand::AnalyzeSignal(int16_t* random_vector) { int32_t auto_correlation[kUnvoicedLpcOrder + 1]; int16_t reflection_coeff[kUnvoicedLpcOrder]; @@ -400,18 +334,8 @@ void Expand::AnalyzeSignal(int16_t* random_vector) { const int16_t* audio_history = &(*sync_buffer_)[0][sync_buffer_->Size() - signal_length]; - // Initialize some member variables. - lag_index_direction_ = 1; - current_lag_index_ = -1; - stop_muting_ = false; - random_vector_->set_seed_increment(1); - consecutive_expands_ = 0; - for (size_t ix = 0; ix < num_channels_; ++ix) { - channel_parameters_[ix].current_voice_mix_factor = 16384; // 1.0 in Q14. - channel_parameters_[ix].mute_factor = 16384; // 1.0 in Q14. - // Start with 0 gain for background noise. - background_noise_->SetMuteFactor(ix, 0); - } + // Initialize. + InitializeForAnExpandPeriod(); // Calculate correlation in downsampled domain (4 kHz sample rate). int16_t correlation_scale; @@ -873,5 +797,108 @@ Expand* ExpandFactory::Create(BackgroundNoise* background_noise, num_channels); } +// TODO(turajs): This can be moved to BackgroundNoise class. +void Expand::GenerateBackgroundNoise(int16_t* random_vector, + size_t channel, + int16_t mute_slope, + bool too_many_expands, + size_t num_noise_samples, + int16_t* buffer) { + static const int kNoiseLpcOrder = BackgroundNoise::kMaxLpcOrder; + int16_t scaled_random_vector[kMaxSampleRate / 8000 * 125]; + assert(kMaxSampleRate / 8000 * 125 >= (int)num_noise_samples); + int16_t* noise_samples = &buffer[kNoiseLpcOrder]; + if (background_noise_->initialized()) { + // Use background noise parameters. + memcpy(noise_samples - kNoiseLpcOrder, + background_noise_->FilterState(channel), + sizeof(int16_t) * kNoiseLpcOrder); + + int dc_offset = 0; + if (background_noise_->ScaleShift(channel) > 1) { + dc_offset = 1 << (background_noise_->ScaleShift(channel) - 1); + } + + // Scale random vector to correct energy level. + WebRtcSpl_AffineTransformVector( + scaled_random_vector, random_vector, + background_noise_->Scale(channel), dc_offset, + background_noise_->ScaleShift(channel), + static_cast(num_noise_samples)); + + WebRtcSpl_FilterARFastQ12(scaled_random_vector, noise_samples, + background_noise_->Filter(channel), + kNoiseLpcOrder + 1, + static_cast(num_noise_samples)); + + background_noise_->SetFilterState( + channel, + &(noise_samples[num_noise_samples - kNoiseLpcOrder]), + kNoiseLpcOrder); + + // Unmute the background noise. + int16_t bgn_mute_factor = background_noise_->MuteFactor(channel); + NetEqBackgroundNoiseMode bgn_mode = background_noise_->mode(); + if (bgn_mode == kBgnFade && too_many_expands && bgn_mute_factor > 0) { + // Fade BGN to zero. + // Calculate muting slope, approximately -2^18 / fs_hz. + int16_t mute_slope; + if (fs_hz_ == 8000) { + mute_slope = -32; + } else if (fs_hz_ == 16000) { + mute_slope = -16; + } else if (fs_hz_ == 32000) { + mute_slope = -8; + } else { + mute_slope = -5; + } + // Use UnmuteSignal function with negative slope. + // |bgn_mute_factor| is in Q14. |mute_slope| is in Q20. + DspHelper::UnmuteSignal(noise_samples, + num_noise_samples, + &bgn_mute_factor, + mute_slope, + noise_samples); + } else if (bgn_mute_factor < 16384) { + // If mode is kBgnOff, or if kBgnFade has started fading, + // Use regular |mute_slope|. + if (!stop_muting_ && bgn_mode != kBgnOff && + !(bgn_mode == kBgnFade && too_many_expands)) { + DspHelper::UnmuteSignal(noise_samples, + static_cast(num_noise_samples), + &bgn_mute_factor, + mute_slope, + noise_samples); + } else { + // kBgnOn and stop muting, or + // kBgnOff (mute factor is always 0), or + // kBgnFade has reached 0. + WebRtcSpl_AffineTransformVector(noise_samples, noise_samples, + bgn_mute_factor, 8192, 14, + static_cast(num_noise_samples)); + } + } + // Update mute_factor in BackgroundNoise class. + background_noise_->SetMuteFactor(channel, bgn_mute_factor); + } else { + // BGN parameters have not been initialized; use zero noise. + memset(noise_samples, 0, sizeof(int16_t) * num_noise_samples); + } +} + +void Expand::GenerateRandomVector(int seed_increment, + size_t length, + int16_t* random_vector) { + // TODO(turajs): According to hlundin The loop should not be needed. Should be + // just as good to generate all of the vector in one call. + size_t samples_generated = 0; + const size_t kMaxRandSamples = RandomVector::kRandomTableSize; + while(samples_generated < length) { + size_t rand_length = std::min(length - samples_generated, kMaxRandSamples); + random_vector_->IncreaseSeedIncrement(seed_increment); + random_vector_->Generate(rand_length, &random_vector[samples_generated]); + samples_generated += rand_length; + } +} } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/expand.h b/webrtc/modules/audio_coding/neteq/expand.h similarity index 77% rename from webrtc/modules/audio_coding/neteq4/expand.h rename to webrtc/modules/audio_coding/neteq/expand.h index 4de8d7c55..1acf951b9 100644 --- a/webrtc/modules/audio_coding/neteq4/expand.h +++ b/webrtc/modules/audio_coding/neteq/expand.h @@ -8,13 +8,13 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_EXPAND_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_EXPAND_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_EXPAND_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_EXPAND_H_ #include -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" @@ -36,12 +36,13 @@ class Expand { RandomVector* random_vector, int fs, size_t num_channels) - : background_noise_(background_noise), + : random_vector_(random_vector), sync_buffer_(sync_buffer), - random_vector_(random_vector), first_expand_(true), fs_hz_(fs), num_channels_(num_channels), + consecutive_expands_(0), + background_noise_(background_noise), overlap_length_(5 * fs / 8000), lag_index_direction_(0), current_lag_index_(0), @@ -57,19 +58,19 @@ class Expand { virtual ~Expand() {} // Resets the object. - void Reset(); + virtual void Reset(); // The main method to produce concealment data. The data is appended to the // end of |output|. - int Process(AudioMultiVector* output); + virtual int Process(AudioMultiVector* output); // Prepare the object to do extra expansion during normal operation following // a period of expands. - void SetParametersForNormalAfterExpand(); + virtual void SetParametersForNormalAfterExpand(); // Prepare the object to do extra expansion during merge operation following // a period of expands. - void SetParametersForMergeAfterExpand(); + virtual void SetParametersForMergeAfterExpand(); // Sets the mute factor for |channel| to |value|. void SetMuteFactor(int16_t value, size_t channel) { @@ -84,9 +85,38 @@ class Expand { } // Accessors and mutators. - size_t overlap_length() const { return overlap_length_; } + virtual size_t overlap_length() const { return overlap_length_; } int16_t max_lag() const { return max_lag_; } + protected: + static const int kMaxConsecutiveExpands = 200; + void GenerateRandomVector(int seed_increment, + size_t length, + int16_t* random_vector); + + void GenerateBackgroundNoise(int16_t* random_vector, + size_t channel, + int16_t mute_slope, + bool too_many_expands, + size_t num_noise_samples, + int16_t* buffer); + + // Initializes member variables at the beginning of an expand period. + void InitializeForAnExpandPeriod(); + + bool TooManyExpands(); + + // Analyzes the signal history in |sync_buffer_|, and set up all parameters + // necessary to produce concealment data. + void AnalyzeSignal(int16_t* random_vector); + + RandomVector* random_vector_; + SyncBuffer* sync_buffer_; + bool first_expand_; + const int fs_hz_; + const size_t num_channels_; + int consecutive_expands_; + private: static const int kUnvoicedLpcOrder = 6; static const int kNumCorrelationCandidates = 3; @@ -94,7 +124,6 @@ class Expand { static const int kLpcAnalysisLength = 160; static const int kMaxSampleRate = 48000; static const int kNumLags = 3; - static const int kMaxConsecutiveExpands = 200; struct ChannelParameters { // Constructor. @@ -122,10 +151,6 @@ class Expand { int16_t mute_slope; /* Q20 */ }; - // Analyze the signal history in |sync_buffer_|, and set up all parameters - // necessary to produce concealment data. - void AnalyzeSignal(int16_t* random_vector); - // Calculate the auto-correlation of |input|, with length |input_length| // samples. The correlation is calculated from a downsampled version of // |input|, and is written to |output|. The scale factor is written to @@ -136,19 +161,13 @@ class Expand { void UpdateLagIndex(); BackgroundNoise* background_noise_; - SyncBuffer* sync_buffer_; - RandomVector* random_vector_; - bool first_expand_; - const int fs_hz_; - const size_t num_channels_; const size_t overlap_length_; - int consecutive_expands_; int16_t max_lag_; size_t expand_lags_[kNumLags]; int lag_index_direction_; int current_lag_index_; bool stop_muting_; - scoped_array channel_parameters_; + scoped_ptr channel_parameters_; DISALLOW_COPY_AND_ASSIGN(Expand); }; @@ -165,4 +184,4 @@ struct ExpandFactory { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_EXPAND_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_EXPAND_H_ diff --git a/webrtc/modules/audio_coding/neteq4/expand_unittest.cc b/webrtc/modules/audio_coding/neteq/expand_unittest.cc similarity index 82% rename from webrtc/modules/audio_coding/neteq4/expand_unittest.cc rename to webrtc/modules/audio_coding/neteq/expand_unittest.cc index 353af2cf4..bd39f408f 100644 --- a/webrtc/modules/audio_coding/neteq4/expand_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/expand_unittest.cc @@ -10,12 +10,12 @@ // Unit tests for Expand class. -#include "webrtc/modules/audio_coding/neteq4/expand.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" -#include "webrtc/modules/audio_coding/neteq4/random_vector.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/random_vector.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h b/webrtc/modules/audio_coding/neteq/interface/audio_decoder.h similarity index 94% rename from webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h rename to webrtc/modules/audio_coding/neteq/interface/audio_decoder.h index 6b4b19127..9a2fb8b46 100644 --- a/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h +++ b/webrtc/modules/audio_coding/neteq/interface/audio_decoder.h @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_INTERFACE_AUDIO_DECODER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_INTERFACE_AUDIO_DECODER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_INTERFACE_AUDIO_DECODER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_INTERFACE_AUDIO_DECODER_H_ #include // NULL -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -149,4 +149,4 @@ class AudioDecoder { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_INTERFACE_AUDIO_DECODER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_INTERFACE_AUDIO_DECODER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/interface/neteq.h b/webrtc/modules/audio_coding/neteq/interface/neteq.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/interface/neteq.h rename to webrtc/modules/audio_coding/neteq/interface/neteq.h index 466882a5f..c67ab12c6 100644 --- a/webrtc/modules/audio_coding/neteq4/interface/neteq.h +++ b/webrtc/modules/audio_coding/neteq/interface/neteq.h @@ -8,16 +8,16 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_INTERFACE_NETEQ_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_INTERFACE_NETEQ_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_INTERFACE_NETEQ_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_INTERFACE_NETEQ_H_ #include // Provide access to size_t. #include +#include "webrtc/base/constructormagic.h" #include "webrtc/common_types.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -67,6 +67,20 @@ enum NetEqBackgroundNoiseMode { // This is the interface class for NetEq. class NetEq { public: + struct Config { + Config() + : sample_rate_hz(16000), + enable_audio_classifier(false), + max_packets_in_buffer(50), + // |max_delay_ms| has the same effect as calling SetMaximumDelay(). + max_delay_ms(2000) {} + + int sample_rate_hz; // Initial vale. Will change with input data. + bool enable_audio_classifier; + int max_packets_in_buffer; + int max_delay_ms; + }; + enum ReturnCodes { kOK = 0, kFail = -1, @@ -98,17 +112,13 @@ class NetEq { kFrameSplitError, kRedundancySplitError, kPacketBufferCorruption, - kOversizePacket, kSyncPacketNotAccepted }; - static const int kMaxNumPacketsInBuffer = 50; // TODO(hlundin): Remove. - static const int kMaxBytesInBuffer = 113280; // TODO(hlundin): Remove. - - // Creates a new NetEq object, starting at the sample rate |sample_rate_hz|. - // (Note that it will still change the sample rate depending on what payloads - // are being inserted; |sample_rate_hz| is just for startup configuration.) - static NetEq* Create(int sample_rate_hz); + // Creates a new NetEq object, with parameters set in |config|. The |config| + // object will only have to be valid for the duration of the call to this + // method. + static NetEq* Create(const NetEq::Config& config); virtual ~NetEq() {} @@ -152,11 +162,10 @@ class NetEq { // Provides an externally created decoder object |decoder| to insert in the // decoder database. The decoder implements a decoder of type |codec| and - // associates it with |rtp_payload_type|. The decoder operates at the - // frequency |sample_rate_hz|. Returns kOK on success, kFail on failure. + // associates it with |rtp_payload_type|. Returns kOK on success, + // kFail on failure. virtual int RegisterExternalDecoder(AudioDecoder* decoder, enum NetEqDecoder codec, - int sample_rate_hz, uint8_t rtp_payload_type) = 0; // Removes |rtp_payload_type| from the codec database. Returns 0 on success, @@ -171,7 +180,8 @@ class NetEq { // Sets a maximum delay in milliseconds for packet buffer. The latency will // not exceed the given value, even required delay (given the channel - // conditions) is higher. + // conditions) is higher. Calling this method has the same effect as setting + // the |max_delay_ms| value in the NetEq::Config struct. virtual bool SetMaximumDelay(int delay_ms) = 0; // The smallest latency required. This is computed bases on inter-arrival @@ -218,8 +228,9 @@ class NetEq { // Disables post-decode VAD. virtual void DisableVad() = 0; - // Returns the RTP timestamp for the last sample delivered by GetAudio(). - virtual uint32_t PlayoutTimestamp() = 0; + // Gets the RTP timestamp for the last sample delivered by GetAudio(). + // Returns true if the RTP timestamp is valid, otherwise false. + virtual bool GetPlayoutTimestamp(uint32_t* timestamp) = 0; // Not implemented. virtual int SetTargetNumberOfChannels() = 0; @@ -241,9 +252,7 @@ class NetEq { // Current usage of packet-buffer and it's limits. virtual void PacketBufferStatistics(int* current_num_packets, - int* max_num_packets, - int* current_memory_size_bytes, - int* max_memory_size_bytes) const = 0; + int* max_num_packets) const = 0; // Get sequence number and timestamp of the latest RTP. // This method is to facilitate NACK. @@ -264,4 +273,4 @@ class NetEq { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_INTERFACE_NETEQ_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_INTERFACE_NETEQ_H_ diff --git a/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h b/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h deleted file mode 100644 index c2a01340b..000000000 --- a/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (c) 2012 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 is the main API for NetEQ. Helper macros are located in webrtc_neteq_help_macros.h, - * while some internal API functions are found in webrtc_neteq_internal.h. - */ - -#include "typedefs.h" - -#ifndef WEBRTC_NETEQ_H -#define WEBRTC_NETEQ_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -/********************************************************** - * Definitions - */ - -enum WebRtcNetEQDecoder -{ - kDecoderReservedStart, - kDecoderPCMu, - kDecoderPCMa, - kDecoderPCMu_2ch, - kDecoderPCMa_2ch, - kDecoderILBC, - kDecoderISAC, - kDecoderISACswb, - kDecoderISACfb, - kDecoderPCM16B, - kDecoderPCM16Bwb, - kDecoderPCM16Bswb32kHz, - kDecoderPCM16Bswb48kHz, - kDecoderPCM16B_2ch, - kDecoderPCM16Bwb_2ch, - kDecoderPCM16Bswb32kHz_2ch, - kDecoderG722, - kDecoderG722_2ch, - kDecoderRED, - kDecoderAVT, - kDecoderCNG, - kDecoderArbitrary, - kDecoderG729, - kDecoderG729_1, - kDecoderG726_16, - kDecoderG726_24, - kDecoderG726_32, - kDecoderG726_40, - kDecoderG722_1_16, - kDecoderG722_1_24, - kDecoderG722_1_32, - kDecoderG722_1C_24, - kDecoderG722_1C_32, - kDecoderG722_1C_48, - kDecoderOpus, - kDecoderSPEEX_8, - kDecoderSPEEX_16, - kDecoderCELT_32, - kDecoderCELT_32_2ch, - kDecoderGSMFR, - kDecoderAMR, - kDecoderAMRWB, - kDecoderReservedEnd -}; - -enum WebRtcNetEQNetworkType -{ - kUDPNormal, - kUDPVideoSync, - kTCPNormal, - kTCPLargeJitter, - kTCPXLargeJitter -}; - -enum WebRtcNetEQOutputType -{ - kOutputNormal, - kOutputPLC, - kOutputCNG, - kOutputPLCtoCNG, - kOutputVADPassive -}; - -enum WebRtcNetEQPlayoutMode -{ - kPlayoutOn, kPlayoutOff, kPlayoutFax, kPlayoutStreaming -}; - -/* Available modes for background noise (inserted after long expands) */ -enum WebRtcNetEQBGNMode -{ - kBGNOn, /* default "normal" behavior with eternal noise */ - kBGNFade, /* noise fades to zero after some time */ - kBGNOff -/* background noise is always zero */ -}; - -/************************************************* - * Definitions of decoder calls and the default - * API function calls for each codec - */ - -typedef int16_t (*WebRtcNetEQ_FuncDecode)(void* state, int16_t* encoded, - int16_t len, int16_t* decoded, - int16_t* speechType); -typedef int16_t (*WebRtcNetEQ_FuncDecodePLC)(void* state, int16_t* decoded, - int16_t frames); -typedef int16_t (*WebRtcNetEQ_FuncDecodeInit)(void* state); -typedef int16_t (*WebRtcNetEQ_FuncAddLatePkt)(void* state, int16_t* encoded, - int16_t len); -typedef int16_t (*WebRtcNetEQ_FuncGetMDinfo)(void* state); -typedef int16_t (*WebRtcNetEQ_FuncGetPitchInfo)(void* state, int16_t* encoded, - int16_t* length); -typedef int16_t (*WebRtcNetEQ_FuncUpdBWEst)(void* state, const uint16_t *encoded, - int32_t packet_size, - uint16_t rtp_seq_number, - uint32_t send_ts, - uint32_t arr_ts); -typedef int (*WebRtcNetEQ_FuncDurationEst)(void* state, const uint8_t* payload, - int payload_length_bytes); -typedef int16_t (*WebRtcNetEQ_FuncGetErrorCode)(void* state); - -/********************************************************** - * Structures - */ - -typedef struct -{ - enum WebRtcNetEQDecoder codec; - int16_t payloadType; - WebRtcNetEQ_FuncDecode funcDecode; - WebRtcNetEQ_FuncDecode funcDecodeRCU; - WebRtcNetEQ_FuncDecodePLC funcDecodePLC; - WebRtcNetEQ_FuncDecodeInit funcDecodeInit; - WebRtcNetEQ_FuncAddLatePkt funcAddLatePkt; - WebRtcNetEQ_FuncGetMDinfo funcGetMDinfo; - WebRtcNetEQ_FuncGetPitchInfo funcGetPitch; - WebRtcNetEQ_FuncUpdBWEst funcUpdBWEst; - WebRtcNetEQ_FuncDurationEst funcDurationEst; - WebRtcNetEQ_FuncGetErrorCode funcGetErrorCode; - void* codec_state; - uint16_t codec_fs; -} WebRtcNetEQ_CodecDef; - -typedef struct -{ - uint16_t fraction_lost; - uint32_t cum_lost; - uint32_t ext_max; - uint32_t jitter; -} WebRtcNetEQ_RTCPStat; - -/********************************************************** - * NETEQ Functions - */ - -/* Info functions */ - -#define WEBRTC_NETEQ_MAX_ERROR_NAME 40 -int WebRtcNetEQ_GetErrorCode(void *inst); -int WebRtcNetEQ_GetErrorName(int errorCode, char *errorName, int maxStrLen); - -/* Instance memory assign functions */ - -int WebRtcNetEQ_AssignSize(int *sizeinbytes); -int WebRtcNetEQ_Assign(void **inst, void *NETEQ_inst_Addr); -int WebRtcNetEQ_GetRecommendedBufferSize(void *inst, const enum WebRtcNetEQDecoder *codec, - int noOfCodecs, enum WebRtcNetEQNetworkType nwType, - int *MaxNoOfPackets, int *sizeinbytes, - int* per_packet_overhead_bytes); -int WebRtcNetEQ_AssignBuffer(void *inst, int MaxNoOfPackets, void *NETEQ_Buffer_Addr, - int sizeinbytes); - -/* Init functions */ - -int WebRtcNetEQ_Init(void *inst, uint16_t fs); -int WebRtcNetEQ_SetAVTPlayout(void *inst, int PlayoutAVTon); -int WebRtcNetEQ_SetExtraDelay(void *inst, int DelayInMs); -int WebRtcNetEQ_SetPlayoutMode(void *inst, enum WebRtcNetEQPlayoutMode playoutMode); -int WebRtcNetEQ_SetBGNMode(void *inst, enum WebRtcNetEQBGNMode bgnMode); -int WebRtcNetEQ_GetBGNMode(const void *inst, enum WebRtcNetEQBGNMode *bgnMode); - -/* Codec Database functions */ - -int WebRtcNetEQ_CodecDbReset(void *inst); -int WebRtcNetEQ_CodecDbAdd(void *inst, WebRtcNetEQ_CodecDef *codecInst); -int WebRtcNetEQ_CodecDbRemove(void *inst, enum WebRtcNetEQDecoder codec); -int WebRtcNetEQ_CodecDbGetSizeInfo(void *inst, int16_t *UsedEntries, - int16_t *MaxEntries); -int WebRtcNetEQ_CodecDbGetCodecInfo(void *inst, int16_t Entry, - enum WebRtcNetEQDecoder *codec); - -/* Real-time functions */ - -int WebRtcNetEQ_RecIn(void *inst, int16_t *p_w16datagramstart, int16_t w16_RTPlen, - uint32_t uw32_timeRec); -int WebRtcNetEQ_RecOut(void *inst, int16_t *pw16_outData, int16_t *pw16_len); -int WebRtcNetEQ_GetRTCPStats(void *inst, WebRtcNetEQ_RTCPStat *RTCP_inst); -int WebRtcNetEQ_GetRTCPStatsNoReset(void *inst, WebRtcNetEQ_RTCPStat *RTCP_inst); -int WebRtcNetEQ_GetSpeechTimeStamp(void *inst, uint32_t *timestamp); -int WebRtcNetEQ_DecodedRtpInfo(const void* inst, - int* sequence_number, - uint32_t* timestamp); -int WebRtcNetEQ_GetSpeechOutputType(void *inst, enum WebRtcNetEQOutputType *outputType); - -/* VQmon related functions */ -int WebRtcNetEQ_VQmonRecOutStatistics(void *inst, uint16_t *validVoiceDurationMs, - uint16_t *concealedVoiceDurationMs, - uint8_t *concealedVoiceFlags); -int WebRtcNetEQ_VQmonGetConfiguration(void *inst, uint16_t *absMaxDelayMs, - uint8_t *adaptationRate); -int WebRtcNetEQ_VQmonGetRxStatistics(void *inst, uint16_t *avgDelayMs, - uint16_t *maxDelayMs); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h b/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h deleted file mode 100644 index bd9332810..000000000 --- a/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (c) 2012 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 file contains some helper macros that can be used when loading the - * NetEQ codec database. - */ - -#ifndef WEBRTC_NETEQ_HELP_MACROS_H -#define WEBRTC_NETEQ_HELP_MACROS_H - -#ifndef NULL -#define NULL 0 -#endif - -/********************************************************** - * Help macros for NetEQ initialization - */ - -#define SET_CODEC_PAR(inst,decoder,pt,state,fs) \ - inst.codec=decoder; \ - inst.payloadType=pt; \ - inst.codec_state=state; \ - inst.codec_fs=fs; - -#define SET_PCMU_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG711_DecodeU; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=WebRtcG711_DurationEst; \ - inst.funcGetErrorCode=NULL; - -#define SET_PCMA_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG711_DecodeA; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=WebRtcG711_DurationEst; \ - inst.funcGetErrorCode=NULL; - -#define SET_ILBC_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcIlbcfix_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcIlbcfix_NetEqPlc; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcIlbcfix_Decoderinit30Ms; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_ISAC_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcIsac_Decode; \ - inst.funcDecodeRCU=(WebRtcNetEQ_FuncDecode)WebRtcIsac_DecodeRcu; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcIsac_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=(WebRtcNetEQ_FuncUpdBWEst)WebRtcIsac_UpdateBwEstimate; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=(WebRtcNetEQ_FuncGetErrorCode)WebRtcIsac_GetErrorCode; - -#define SET_ISACfix_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcIsacfix_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcIsacfix_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=(WebRtcNetEQ_FuncUpdBWEst)WebRtcIsacfix_UpdateBwEstimate; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=(WebRtcNetEQ_FuncGetErrorCode)WebRtcIsacfix_GetErrorCode; - -#define SET_ISACSWB_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcIsac_Decode; \ - inst.funcDecodeRCU=(WebRtcNetEQ_FuncDecode)WebRtcIsac_DecodeRcu; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcIsac_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=(WebRtcNetEQ_FuncUpdBWEst)WebRtcIsac_UpdateBwEstimate; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=(WebRtcNetEQ_FuncGetErrorCode)WebRtcIsac_GetErrorCode; - -#define SET_ISACFB_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcIsac_Decode; \ - inst.funcDecodeRCU=(WebRtcNetEQ_FuncDecode)WebRtcIsac_DecodeRcu; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcIsac_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=(WebRtcNetEQ_FuncUpdBWEst)WebRtcIsac_UpdateBwEstimate; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=(WebRtcNetEQ_FuncGetErrorCode)WebRtcIsac_GetErrorCode; - -#define SET_G729_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG729_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcG729_DecodePlc; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG729_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G729_1_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG7291_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG7291_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=(WebRtcNetEQ_FuncUpdBWEst)WebRtcG7291_DecodeBwe; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_PCM16B_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcPcm16b_DecodeW16; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_PCM16B_WB_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcPcm16b_DecodeW16; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_PCM16B_SWB32_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcPcm16b_DecodeW16; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_PCM16B_SWB48_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcPcm16b_DecodeW16; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G722_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG722_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG722_DecoderInit;\ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G722_1_16_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG7221_Decode16; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcG7221_DecodePlc16; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG7221_DecoderInit16; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G722_1_24_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG7221_Decode24; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcG7221_DecodePlc24; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG7221_DecoderInit24; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G722_1_32_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG7221_Decode32; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcG7221_DecodePlc32; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG7221_DecoderInit32; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G722_1C_24_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG7221C_Decode24; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcG7221C_DecodePlc24; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG7221C_DecoderInit24; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G722_1C_32_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG7221C_Decode32; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcG7221C_DecodePlc32; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG7221C_DecoderInit32; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G722_1C_48_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG7221C_Decode48; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcG7221C_DecodePlc48; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG7221C_DecoderInit48; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_AMR_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcAmr_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcAmr_DecodePlc; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcAmr_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_AMRWB_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcAmrWb_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcAmrWb_DecodePlc; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcAmrWb_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_GSMFR_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcGSMFR_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcGSMFR_DecodePlc; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcGSMFR_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G726_16_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG726_decode16; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG726_decoderinit16; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G726_24_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG726_decode24; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG726_decoderinit24; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G726_32_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG726_decode32; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG726_decoderinit32; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_G726_40_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcG726_decode40; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcG726_decoderinit40; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_OPUS_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcOpus_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcOpus_DecodePlcMaster; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcOpus_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=(WebRtcNetEQ_FuncDurationEst)WebRtcOpus_DurationEst; \ - inst.funcGetErrorCode=NULL; - -#define SET_OPUSSLAVE_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcOpus_DecodeSlave; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcOpus_DecodePlcSlave; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcOpus_DecoderInitSlave; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=(WebRtcNetEQ_FuncDurationEst)WebRtcOpus_DurationEst; \ - inst.funcGetErrorCode=NULL; - -#define SET_SPEEX_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcSpeex_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=(WebRtcNetEQ_FuncDecodePLC)WebRtcSpeex_DecodePlc; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcSpeex_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_CELT_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcCelt_Decode; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcCelt_DecoderInit; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_CELTSLAVE_FUNCTIONS(inst) \ - inst.funcDecode=(WebRtcNetEQ_FuncDecode)WebRtcCelt_DecodeSlave; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=(WebRtcNetEQ_FuncDecodeInit)WebRtcCelt_DecoderInitSlave; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_RED_FUNCTIONS(inst) \ - inst.funcDecode=NULL; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_AVT_FUNCTIONS(inst) \ - inst.funcDecode=NULL; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#define SET_CNG_FUNCTIONS(inst) \ - inst.funcDecode=NULL; \ - inst.funcDecodeRCU=NULL; \ - inst.funcDecodePLC=NULL; \ - inst.funcDecodeInit=NULL; \ - inst.funcAddLatePkt=NULL; \ - inst.funcGetMDinfo=NULL; \ - inst.funcGetPitch=NULL; \ - inst.funcUpdBWEst=NULL; \ - inst.funcDurationEst=NULL; \ - inst.funcGetErrorCode=NULL; - -#endif /* WEBRTC_NETEQ_HELP_MACROS_H */ - diff --git a/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h b/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h deleted file mode 100644 index c46a3f627..000000000 --- a/webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (c) 2012 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 file contains the internal API functions. - */ - -#include "typedefs.h" - -#ifndef WEBRTC_NETEQ_INTERNAL_H -#define WEBRTC_NETEQ_INTERNAL_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -typedef struct -{ - uint8_t payloadType; - uint16_t sequenceNumber; - uint32_t timeStamp; - uint32_t SSRC; - uint8_t markerBit; -} WebRtcNetEQ_RTPInfo; - -/**************************************************************************** - * WebRtcNetEQ_RecInRTPStruct(...) - * - * Alternative RecIn function, used when the RTP data has already been - * parsed into an RTP info struct (WebRtcNetEQ_RTPInfo). - * - * Input: - * - inst : NetEQ instance - * - rtpInfo : Pointer to RTP info - * - payloadPtr : Pointer to the RTP payload (first byte after header) - * - payloadLenBytes : Length (in bytes) of the payload in payloadPtr - * - timeRec : Receive time (in timestamps of the used codec) - * - * Return value : 0 - Ok - * -1 - Error - */ -int WebRtcNetEQ_RecInRTPStruct(void *inst, WebRtcNetEQ_RTPInfo *rtpInfo, - const uint8_t *payloadPtr, int16_t payloadLenBytes, - uint32_t timeRec); - -/**************************************************************************** - * WebRtcNetEQ_GetMasterSlaveInfoSize(...) - * - * Get size in bytes for master/slave struct msInfo used in - * WebRtcNetEQ_RecOutMasterSlave. - * - * Return value : Struct size in bytes - * - */ - -int WebRtcNetEQ_GetMasterSlaveInfoSize(); - -/**************************************************************************** - * WebRtcNetEQ_RecOutMasterSlave(...) - * - * RecOut function for running several NetEQ instances in master/slave mode. - * One master can be used to control several slaves. - * The MasterSlaveInfo struct must be allocated outside NetEQ. - * Use function WebRtcNetEQ_GetMasterSlaveInfoSize to get the size needed. - * - * Input: - * - inst : NetEQ instance - * - isMaster : Non-zero indicates that this is the master channel - * - msInfo : (slave only) Information from master - * - * Output: - * - inst : Updated NetEQ instance - * - pw16_outData : Pointer to vector where output should be written - * - pw16_len : Pointer to variable where output length is returned - * - msInfo : (master only) Information to slave(s) - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RecOutMasterSlave(void *inst, int16_t *pw16_outData, - int16_t *pw16_len, void *msInfo, - int16_t isMaster); - -typedef struct -{ - uint16_t currentBufferSize; /* Current jitter buffer size in ms. */ - uint16_t preferredBufferSize; /* Preferred buffer size in ms. */ - uint16_t jitterPeaksFound; /* 1 if adding extra delay due to peaky - * jitter; 0 otherwise. */ - uint16_t currentPacketLossRate; /* Loss rate (network + late) (Q14). */ - uint16_t currentDiscardRate; /* Late loss rate (Q14). */ - uint16_t currentExpandRate; /* Fraction (of original stream) of - * synthesized speech inserted through - * expansion (in Q14). */ - uint16_t currentPreemptiveRate; /* Fraction of data inserted through - * pre-emptive expansion (in Q14). */ - uint16_t currentAccelerateRate; /* Fraction of data removed through - * acceleration (in Q14). */ - int32_t clockDriftPPM; /* Average clock-drift in parts-per- - * million (positive or negative). */ - int addedSamples; /* Number of zero samples added in off - * mode */ -} WebRtcNetEQ_NetworkStatistics; - -/* - * Get the "in-call" statistics from NetEQ. - * The statistics are reset after the query. - */ -int WebRtcNetEQ_GetNetworkStatistics(void *inst, WebRtcNetEQ_NetworkStatistics *stats); - - -typedef struct { - /* Samples removed from background noise only segments. */ - int accelerate_bgn_samples; - - /* Samples removed from normal audio segments. */ - int accelerate_normal_samples; - - /* Number of samples synthesized during background noise only segments. */ - int expand_bgn_sampels; - - /* Number of samples synthesized during normal audio segments. */ - int expand_normal_samples; - - /* Number of samples synthesized during background noise only segments, - * in preemptive mode. */ - int preemptive_expand_bgn_samples; - - /* Number of samples synthesized during normal audio segments, in preemptive - * mode. */ - int preemptive_expand_normal_samples; - - /* Number of samples synthesized during background noise only segments, - * while merging. */ - int merge_expand_bgn_samples; - - /* Number of samples synthesized during normal audio segments, while - * merging. */ - int merge_expand_normal_samples; -} WebRtcNetEQ_ProcessingActivity; - -/* - * Get the processing activities from NetEQ. - * The statistics are reset after the query. - * This API is meant to obtain processing activities in high granularity, - * e.g. per RecOut() call. - */ -void WebRtcNetEQ_GetProcessingActivity(void* inst, - WebRtcNetEQ_ProcessingActivity* stat); - -/* - * Get the raw waiting times for decoded frames. The function writes the last - * recorded waiting times (from frame arrival to frame decoding) to the memory - * pointed to by waitingTimeMs. The number of elements written is in the return - * value. No more than maxLength elements are written. Statistics are reset on - * each query. - */ -int WebRtcNetEQ_GetRawFrameWaitingTimes(void *inst, - int max_length, - int* waiting_times_ms); - -/***********************************************/ -/* Functions for post-decode VAD functionality */ -/***********************************************/ - -/* NetEQ must be compiled with the flag NETEQ_VAD enabled for these functions to work. */ - -/* - * VAD function pointer types - * - * These function pointers match the definitions of webrtc VAD functions WebRtcVad_Init, - * WebRtcVad_set_mode and WebRtcVad_Process, respectively, all found in webrtc_vad.h. - */ -typedef int (*WebRtcNetEQ_VADInitFunction)(void *VAD_inst); -typedef int (*WebRtcNetEQ_VADSetmodeFunction)(void *VAD_inst, int mode); -typedef int (*WebRtcNetEQ_VADFunction)(void *VAD_inst, int fs, - int16_t *frame, int frameLen); - -/**************************************************************************** - * WebRtcNetEQ_SetVADInstance(...) - * - * Provide a pointer to an allocated VAD instance. If function is never - * called or it is called with NULL pointer as VAD_inst, the post-decode - * VAD functionality is disabled. Also provide pointers to init, setmode - * and VAD functions. These are typically pointers to WebRtcVad_Init, - * WebRtcVad_set_mode and WebRtcVad_Process, respectively, all found in the - * interface file webrtc_vad.h. - * - * Input: - * - NetEQ_inst : NetEQ instance - * - VADinst : VAD instance - * - initFunction : Pointer to VAD init function - * - setmodeFunction : Pointer to VAD setmode function - * - VADfunction : Pointer to VAD function - * - * Output: - * - NetEQ_inst : Updated NetEQ instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_SetVADInstance(void *NetEQ_inst, void *VAD_inst, - WebRtcNetEQ_VADInitFunction initFunction, - WebRtcNetEQ_VADSetmodeFunction setmodeFunction, - WebRtcNetEQ_VADFunction VADFunction); - -/**************************************************************************** - * WebRtcNetEQ_SetVADMode(...) - * - * Pass an aggressiveness mode parameter to the post-decode VAD instance. - * If this function is never called, mode 0 (quality mode) is used as default. - * - * Input: - * - inst : NetEQ instance - * - mode : mode parameter (same range as WebRtc VAD mode) - * - * Output: - * - inst : Updated NetEQ instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_SetVADMode(void *NetEQ_inst, int mode); - -/**************************************************************************** - * WebRtcNetEQ_RecOutNoDecode(...) - * - * Special RecOut that does not do any decoding. - * - * Input: - * - inst : NetEQ instance - * - * Output: - * - inst : Updated NetEQ instance - * - pw16_outData : Pointer to vector where output should be written - * - pw16_len : Pointer to variable where output length is returned - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RecOutNoDecode(void *inst, int16_t *pw16_outData, - int16_t *pw16_len); - -/**************************************************************************** - * WebRtcNetEQ_FlushBuffers(...) - * - * Flush packet and speech buffers. Does not reset codec database or - * jitter statistics. - * - * Input: - * - inst : NetEQ instance - * - * Output: - * - inst : Updated NetEQ instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_FlushBuffers(void *inst); - -/***************************************************************************** - * void WebRtcNetEq_EnableAVSync(...) - * - * Enable AV-sync. If Enabled, NetEq will screen for sync payloads. For - * each sync payload a silence frame is generated. - * - * Input: - * - inst : NetEQ instance - * - enable : non-zero to enable, otherwise disabled. - * - * Output: - * - inst : Updated NetEQ instance - * - */ - -void WebRtcNetEQ_EnableAVSync(void* inst, int enable); - -/**************************************************************************** - * WebRtcNetEQ_RecInSyncRTP(...) - * - * Insert a sync packet with the given RTP specification. - * - * Input: - * - inst : NetEQ instance - * - rtpInfo : Pointer to RTP info - * - receive_timestamp : Receive time (in timestamps of the used codec) - * - * Output: - * - inst : Updated NetEQ instance - * - * Return value : if succeeded it returns the number of bytes pushed - * in, otherwise returns -1. - */ - -int WebRtcNetEQ_RecInSyncRTP(void* inst, - WebRtcNetEQ_RTPInfo* rtp_info, - uint32_t receive_timestamp); - -/* - * Set a minimum latency for the jitter buffer. The overall delay is the max of - * |minimum_delay_ms| and the latency that is internally computed based on the - * inter-arrival times. - */ -int WebRtcNetEQ_SetMinimumDelay(void *inst, int minimum_delay_ms); - -/* - * Set a maximum latency for the jitter buffer. The overall delay is the min of - * |maximum_delay_ms| and the latency that is internally computed based on the - * inter-arrival times. - */ -int WebRtcNetEQ_SetMaximumDelay(void *inst, int maximum_delay_ms); - -/* - * Get the least required delay in milliseconds given inter-arrival times - * and playout mode. - */ -int WebRtcNetEQ_GetRequiredDelayMs(const void* inst); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/webrtc/modules/audio_coding/neteq/mcu.h b/webrtc/modules/audio_coding/neteq/mcu.h deleted file mode 100644 index 931e6dcf5..000000000 --- a/webrtc/modules/audio_coding/neteq/mcu.h +++ /dev/null @@ -1,300 +0,0 @@ -/* - * 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. - */ - -/* - * MCU struct and functions related to the MCU side operations. - */ - -#ifndef MCU_H -#define MCU_H - -#include "typedefs.h" - -#include "codec_db.h" -#include "rtcp.h" -#include "packet_buffer.h" -#include "buffer_stats.h" -#include "neteq_statistics.h" - -#ifdef NETEQ_ATEVENT_DECODE -#include "dtmf_buffer.h" -#endif - -#define MAX_ONE_DESC 5 /* cannot do more than this many consecutive one-descriptor decodings */ -#define MAX_LOSS_REPORT_PERIOD 60 /* number of seconds between auto-reset */ - -enum TsScaling -{ - kTSnoScaling = 0, - kTSscalingTwo, - kTSscalingTwoThirds, - kTSscalingFourThirds -}; - -enum { kLenWaitingTimes = 100 }; - -typedef struct -{ - - int16_t current_Codec; - int16_t current_Payload; - uint32_t timeStamp; /* Next timestamp that should be played */ - int16_t millisecondsPerCall; - uint16_t timestampsPerCall; /* Output chunk size */ - uint16_t fs; - uint32_t ssrc; /* Current ssrc */ - int16_t new_codec; - int16_t first_packet; - - /* MCU/DSP Communication layer */ - int16_t *pw16_readAddress; - int16_t *pw16_writeAddress; - void *main_inst; - - CodecDbInst_t codec_DB_inst; /* Information about all the codecs, i.e. which - functions to use and which codpoints that - have been assigned */ - SplitInfo_t PayloadSplit_inst; /* Information about how the current codec - payload should be splitted */ - WebRtcNetEQ_RTCP_t RTCP_inst; /* RTCP statistics */ - PacketBuf_t PacketBuffer_inst; /* The packet buffer */ - BufstatsInst_t BufferStat_inst; /* Statistics that are used to make decision - for what the DSP should perform */ -#ifdef NETEQ_ATEVENT_DECODE - dtmf_inst_t DTMF_inst; -#endif - int NoOfExpandCalls; - int16_t AVT_PlayoutOn; - enum WebRtcNetEQPlayoutMode NetEqPlayoutMode; - - int16_t one_desc; /* Number of times running on one desc */ - - uint32_t lostTS; /* Number of timestamps lost */ - uint32_t lastReportTS; /* Timestamp elapsed since last report was given */ - - int waiting_times[kLenWaitingTimes]; /* Waiting time statistics storage. */ - int len_waiting_times; - int next_waiting_time_index; - - uint32_t externalTS; - uint32_t internalTS; - int16_t TSscalingInitialized; - enum TsScaling scalingFactor; - - /* AV-sync enabled. In AV-sync NetEq screens packets for specific sync - * packets. Sync packets are not decoded by a decoder but generate all-zero - * signal with the same number of samples as previously decoded payload. - * Also in AV-sync mode the sample-size of a sync payload is reported as - * previous frame-size. */ - int av_sync; - -#ifdef NETEQ_STEREO - int usingStereo; -#endif - - /* The sequence number of the latest decoded RTP payload. */ - int decoded_packet_sequence_number; - uint32_t decoded_packet_timestamp; -} MCUInst_t; - -/**************************************************************************** - * WebRtcNetEQ_McuReset(...) - * - * Reset the MCU instance. - * - * Input: - * - inst : MCU instance - * - * Return value : 0 - Ok - * <0 - Error - */ -int WebRtcNetEQ_McuReset(MCUInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_ResetMcuInCallStats(...) - * - * Reset MCU-side statistics variables for the in-call statistics. - * - * Input: - * - inst : MCU instance - * - * Return value : 0 - Ok - * <0 - Error - */ -int WebRtcNetEQ_ResetMcuInCallStats(MCUInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_ResetWaitingTimeStats(...) - * - * Reset waiting-time statistics. - * - * Input: - * - inst : MCU instance. - * - * Return value : n/a - */ -void WebRtcNetEQ_ResetWaitingTimeStats(MCUInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_LogWaitingTime(...) - * - * Log waiting-time to the statistics. - * - * Input: - * - inst : MCU instance. - * - waiting_time : Waiting time in "RecOut calls" (i.e., 1 call = 10 ms). - * - * Return value : n/a - */ -void WebRtcNetEQ_StoreWaitingTime(MCUInst_t *inst, int waiting_time); - -/**************************************************************************** - * WebRtcNetEQ_ResetMcuJitterStat(...) - * - * Reset MCU-side statistics variables for the post-call statistics. - * - * Input: - * - inst : MCU instance - * - * Return value : 0 - Ok - * <0 - Error - */ -int WebRtcNetEQ_ResetMcuJitterStat(MCUInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_McuAddressInit(...) - * - * Initializes MCU with read address and write address. - * - * Input: - * - inst : MCU instance - * - Data2McuAddress : Pointer to MCU address - * - Data2DspAddress : Pointer to DSP address - * - main_inst : Pointer to NetEQ main instance - * - * Return value : 0 - Ok - * <0 - Error - */ -int WebRtcNetEQ_McuAddressInit(MCUInst_t *inst, void * Data2McuAddress, - void * Data2DspAddress, void *main_inst); - -/**************************************************************************** - * WebRtcNetEQ_McuSetFs(...) - * - * Initializes MCU with read address and write address. - * - * Input: - * - inst : MCU instance - * - fs_hz : Sample rate in Hz -- 8000, 16000, 32000, (48000) - * - * Return value : 0 - Ok - * <0 - Error - */ -int WebRtcNetEQ_McuSetFs(MCUInst_t *inst, uint16_t fs_hz); - -/**************************************************************************** - * WebRtcNetEQ_SignalMcu(...) - * - * Signal the MCU that data is available and ask for a RecOut decision. - * - * Input: - * - inst : MCU instance - * - av_sync : 1 if NetEQ is in AV-sync mode, otherwise 0. - * - * Return value : 0 - Ok - * <0 - Error - */ -int WebRtcNetEQ_SignalMcu(MCUInst_t *inst); - -/**************************************************************************** - * WebRtcNetEQ_RecInInternal(...) - * - * This function inserts a packet into the jitter buffer. - * - * Input: - * - MCU_inst : MCU instance - * - RTPpacket : The RTP packet, parsed into NetEQ's internal RTP struct - * - uw32_timeRec : Time stamp for the arrival of the packet (not RTP timestamp) - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RecInInternal(MCUInst_t *MCU_inst, RTPPacket_t *RTPpacket, - uint32_t uw32_timeRec); - -/**************************************************************************** - * WebRtcNetEQ_RecInInternal(...) - * - * Split the packet according to split_inst and inserts the parts into - * Buffer_inst. - * - * Input: - * - MCU_inst : MCU instance - * - RTPpacket : The RTP packet, parsed into NetEQ's internal RTP struct - * - uw32_timeRec : Time stamp for the arrival of the packet (not RTP timestamp) - * - av_sync : indicates if AV-sync is enabled, 1 enabled, - * 0 disabled. - * - * Return value : 0 - Ok - * -1 - Error - */ -int WebRtcNetEQ_SplitAndInsertPayload(RTPPacket_t* packet, - PacketBuf_t* Buffer_inst, - SplitInfo_t* split_inst, - int16_t* flushed, - int av_sync); - -/**************************************************************************** - * WebRtcNetEQ_GetTimestampScaling(...) - * - * Update information about timestamp scaling for a payload type - * in MCU_inst->scalingFactor. - * - * Input: - * - MCU_inst : MCU instance - * - rtpPayloadType : RTP payload number - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_GetTimestampScaling(MCUInst_t *MCU_inst, int rtpPayloadType); - -/**************************************************************************** - * WebRtcNetEQ_ScaleTimestampExternalToInternal(...) - * - * Convert from external to internal timestamp using current scaling info. - * - * Input: - * - MCU_inst : MCU instance - * - externalTS : External timestamp - * - * Return value : Internal timestamp - */ - -uint32_t WebRtcNetEQ_ScaleTimestampExternalToInternal(const MCUInst_t *MCU_inst, - uint32_t externalTS); - -/**************************************************************************** - * WebRtcNetEQ_ScaleTimestampInternalToExternal(...) - * - * Convert from external to internal timestamp using current scaling info. - * - * Input: - * - MCU_inst : MCU instance - * - externalTS : Internal timestamp - * - * Return value : External timestamp - */ - -uint32_t WebRtcNetEQ_ScaleTimestampInternalToExternal(const MCUInst_t *MCU_inst, - uint32_t internalTS); -#endif diff --git a/webrtc/modules/audio_coding/neteq/mcu_address_init.c b/webrtc/modules/audio_coding/neteq/mcu_address_init.c deleted file mode 100644 index 666ecc856..000000000 --- a/webrtc/modules/audio_coding/neteq/mcu_address_init.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 "mcu.h" - -#include /* to define NULL */ - -/* - * Initializes MCU with read address and write address - */ -int WebRtcNetEQ_McuAddressInit(MCUInst_t *inst, void * Data2McuAddress, - void * Data2DspAddress, void *main_inst) -{ - - inst->pw16_readAddress = (int16_t*) Data2McuAddress; - inst->pw16_writeAddress = (int16_t*) Data2DspAddress; - inst->main_inst = main_inst; - - inst->millisecondsPerCall = 10; - - /* Do expansions in the beginning */ - if (inst->pw16_writeAddress != NULL) inst->pw16_writeAddress[0] = DSP_INSTR_EXPAND; - - return (0); -} - diff --git a/webrtc/modules/audio_coding/neteq/mcu_dsp_common.c b/webrtc/modules/audio_coding/neteq/mcu_dsp_common.c deleted file mode 100644 index 2c48ec7dd..000000000 --- a/webrtc/modules/audio_coding/neteq/mcu_dsp_common.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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. - */ - -/* - * Communication between MCU and DSP sides. - */ - -#include "mcu_dsp_common.h" - -#include - -/* Initialize instances with read and write address */ -int WebRtcNetEQ_DSPinit(MainInst_t *inst) -{ - int res = 0; - - res |= WebRtcNetEQ_AddressInit(&inst->DSPinst, NULL, NULL, inst); - res |= WebRtcNetEQ_McuAddressInit(&inst->MCUinst, NULL, NULL, inst); - - return res; - -} - -/* The DSP side will call this function to interrupt the MCU side */ -int WebRtcNetEQ_DSP2MCUinterrupt(MainInst_t *inst, int16_t *pw16_shared_mem) -{ - inst->MCUinst.pw16_readAddress = pw16_shared_mem; - inst->MCUinst.pw16_writeAddress = pw16_shared_mem; - return WebRtcNetEQ_SignalMcu(&inst->MCUinst); -} - -int WebRtcNetEQ_IsSyncPayload(const void* payload, int payload_len_bytes) { - if (payload_len_bytes != SYNC_PAYLOAD_LEN_BYTES || - memcmp(payload, kSyncPayload, SYNC_PAYLOAD_LEN_BYTES) != 0) { - return 0; - } - return 1; -} diff --git a/webrtc/modules/audio_coding/neteq/mcu_dsp_common.h b/webrtc/modules/audio_coding/neteq/mcu_dsp_common.h deleted file mode 100644 index b4ab514bc..000000000 --- a/webrtc/modules/audio_coding/neteq/mcu_dsp_common.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ - -/* - * The main NetEQ instance, which is where the DSP and MCU sides join. - */ - -#ifndef MCU_DSP_COMMON_H -#define MCU_DSP_COMMON_H - -#include "typedefs.h" - -#include "dsp.h" -#include "mcu.h" - -/* Define size of shared memory area. */ -#if defined(NETEQ_48KHZ_WIDEBAND) - #define SHARED_MEM_SIZE (6*640) -#elif defined(NETEQ_32KHZ_WIDEBAND) - #define SHARED_MEM_SIZE (4*640) -#elif defined(NETEQ_WIDEBAND) - #define SHARED_MEM_SIZE (2*640) -#else - #define SHARED_MEM_SIZE 640 -#endif - -#define SYNC_PAYLOAD_LEN_BYTES 7 -static const uint8_t kSyncPayload[SYNC_PAYLOAD_LEN_BYTES] = { - 'a', 'v', 's', 'y', 'n', 'c', '\0' }; - -/* Struct to hold the NetEQ instance */ -typedef struct -{ - DSPInst_t DSPinst; /* DSP part of the NetEQ instance */ - MCUInst_t MCUinst; /* MCU part of the NetEQ instance */ - int16_t ErrorCode; /* Store last error code */ -#ifdef NETEQ_STEREO - int16_t masterSlave; /* 0 = not set, 1 = master, 2 = slave */ -#endif /* NETEQ_STEREO */ -} MainInst_t; - -/* Struct used for communication between DSP and MCU sides of NetEQ */ -typedef struct -{ - uint32_t playedOutTS; /* Timestamp position at end of DSP data */ - uint16_t samplesLeft; /* Number of samples stored */ - int16_t MD; /* Multiple description codec information */ - int16_t lastMode; /* Latest mode of NetEQ playout */ - int16_t frameLen; /* Frame length of previously decoded packet */ -} DSP2MCU_info_t; - -/* Initialize instances with read and write address */ -int WebRtcNetEQ_DSPinit(MainInst_t *inst); - -/* The DSP side will call this function to interrupt the MCU side */ -int WebRtcNetEQ_DSP2MCUinterrupt(MainInst_t *inst, int16_t *pw16_shared_mem); - -/* Returns 1 if the given payload matches |kSyncPayload| payload, otherwise - * 0 is returned. */ -int WebRtcNetEQ_IsSyncPayload(const void* payload, int payload_len_bytes); - -#endif diff --git a/webrtc/modules/audio_coding/neteq/mcu_reset.c b/webrtc/modules/audio_coding/neteq/mcu_reset.c deleted file mode 100644 index ddbb798af..000000000 --- a/webrtc/modules/audio_coding/neteq/mcu_reset.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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. - */ - -/* - * Reset MCU side data. - */ - -#include "mcu.h" - -#include -#include - -#include "automode.h" - -int WebRtcNetEQ_McuReset(MCUInst_t *inst) -{ - -#ifdef NETEQ_ATEVENT_DECODE - int ok; -#endif - - /* MCU/DSP Communication layer */ - inst->pw16_readAddress = NULL; - inst->pw16_writeAddress = NULL; - inst->main_inst = NULL; - inst->one_desc = 0; - inst->BufferStat_inst.Automode_inst.extraDelayMs = 0; - inst->BufferStat_inst.Automode_inst.minimum_delay_ms = 0; - inst->BufferStat_inst.Automode_inst.maximum_delay_ms = 10000; - inst->NetEqPlayoutMode = kPlayoutOn; - inst->av_sync = 0; - - WebRtcNetEQ_DbReset(&inst->codec_DB_inst); - memset(&inst->PayloadSplit_inst, 0, sizeof(SplitInfo_t)); - - /* Clear the Packet buffer and the pointer to memory storage */ - WebRtcNetEQ_PacketBufferFlush(&inst->PacketBuffer_inst); - inst->PacketBuffer_inst.memorySizeW16 = 0; - inst->PacketBuffer_inst.maxInsertPositions = 0; - - /* Clear the decision and delay history */ - memset(&inst->BufferStat_inst, 0, sizeof(BufstatsInst_t)); -#ifdef NETEQ_ATEVENT_DECODE - ok = WebRtcNetEQ_DtmfDecoderInit(&inst->DTMF_inst, 8000, 560); - if (ok != 0) - { - return ok; - } -#endif - inst->NoOfExpandCalls = 0; - inst->current_Codec = -1; - inst->current_Payload = -1; - - inst->millisecondsPerCall = 10; - inst->timestampsPerCall = inst->millisecondsPerCall * 8; - inst->fs = 8000; - inst->first_packet = 1; - - WebRtcNetEQ_ResetMcuInCallStats(inst); - - WebRtcNetEQ_ResetWaitingTimeStats(inst); - - WebRtcNetEQ_ResetMcuJitterStat(inst); - - WebRtcNetEQ_ResetAutomode(&(inst->BufferStat_inst.Automode_inst), - inst->PacketBuffer_inst.maxInsertPositions); - - return 0; -} - -/* - * Reset MCU-side statistics variables for the in-call statistics. - */ - -int WebRtcNetEQ_ResetMcuInCallStats(MCUInst_t *inst) -{ - inst->lostTS = 0; - inst->lastReportTS = 0; - inst->PacketBuffer_inst.discardedPackets = 0; - - return 0; -} - -/* - * Reset waiting-time statistics. - */ - -void WebRtcNetEQ_ResetWaitingTimeStats(MCUInst_t *inst) { - memset(inst->waiting_times, 0, - kLenWaitingTimes * sizeof(inst->waiting_times[0])); - inst->len_waiting_times = 0; - inst->next_waiting_time_index = 0; -} - -/* - * Store waiting-time in the statistics. - */ - -void WebRtcNetEQ_StoreWaitingTime(MCUInst_t *inst, int waiting_time) { - assert(inst->next_waiting_time_index < kLenWaitingTimes); - inst->waiting_times[inst->next_waiting_time_index] = waiting_time; - inst->next_waiting_time_index++; - if (inst->next_waiting_time_index >= kLenWaitingTimes) { - inst->next_waiting_time_index = 0; - } - if (inst->len_waiting_times < kLenWaitingTimes) { - inst->len_waiting_times++; - } -} - -/* - * Reset all MCU-side statistics variables for the post-call statistics. - */ - -int WebRtcNetEQ_ResetMcuJitterStat(MCUInst_t *inst) -{ - inst->BufferStat_inst.Automode_inst.countIAT500ms = 0; - inst->BufferStat_inst.Automode_inst.countIAT1000ms = 0; - inst->BufferStat_inst.Automode_inst.countIAT2000ms = 0; - inst->BufferStat_inst.Automode_inst.longestIATms = 0; - - return 0; -} - diff --git a/webrtc/modules/audio_coding/neteq/merge.c b/webrtc/modules/audio_coding/neteq/merge.c deleted file mode 100644 index 78da2c7c7..000000000 --- a/webrtc/modules/audio_coding/neteq/merge.c +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright (c) 2012 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 is the function to merge a new packet with expanded data after a packet loss. - */ - -#include "dsp.h" - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" -#include "neteq_error_codes.h" - -/**************************************************************************** - * WebRtcNetEQ_Merge(...) - * - * This function... - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector. - * - decoded : Pointer to new decoded speech. - * - len : Number of samples in pw16_decoded. - * - * - * Output: - * - inst : Updated user information - * - outData : Pointer to a memory space where the output data - * should be stored - * - pw16_len : Number of samples written to pw16_outData - * - * Return value : 0 - Ok - * <0 - Error - */ - -/* Scratch usage: - - Type Name size startpos endpos - int16_t pw16_expanded 210*fs/8000 0 209*fs/8000 - int16_t pw16_expandedLB 100 210*fs/8000 99+210*fs/8000 - int16_t pw16_decodedLB 40 100+210*fs/8000 139+210*fs/8000 - int32_t pw32_corr 2*60 140+210*fs/8000 260+210*fs/8000 - int16_t pw16_corrVec 68 210*fs/8000 67+210*fs/8000 - - [gap in scratch vector] - - func WebRtcNetEQ_Expand 40+370*fs/8000 126*fs/8000 39+496*fs/8000 - - Total: 40+496*fs/8000 - */ - -#define SCRATCH_pw16_expanded 0 -#if (defined(NETEQ_48KHZ_WIDEBAND)) -#define SCRATCH_pw16_expandedLB 1260 -#define SCRATCH_pw16_decodedLB 1360 -#define SCRATCH_pw32_corr 1400 -#define SCRATCH_pw16_corrVec 1260 -#define SCRATCH_NETEQ_EXPAND 756 -#elif (defined(NETEQ_32KHZ_WIDEBAND)) -#define SCRATCH_pw16_expandedLB 840 -#define SCRATCH_pw16_decodedLB 940 -#define SCRATCH_pw32_corr 980 -#define SCRATCH_pw16_corrVec 840 -#define SCRATCH_NETEQ_EXPAND 504 -#elif (defined(NETEQ_WIDEBAND)) -#define SCRATCH_pw16_expandedLB 420 -#define SCRATCH_pw16_decodedLB 520 -#define SCRATCH_pw32_corr 560 -#define SCRATCH_pw16_corrVec 420 -#define SCRATCH_NETEQ_EXPAND 252 -#else /* NB */ -#define SCRATCH_pw16_expandedLB 210 -#define SCRATCH_pw16_decodedLB 310 -#define SCRATCH_pw32_corr 350 -#define SCRATCH_pw16_corrVec 210 -#define SCRATCH_NETEQ_EXPAND 126 -#endif - -int WebRtcNetEQ_Merge(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_decoded, int len, int16_t *pw16_outData, - int16_t *pw16_len) -{ - - int16_t fs_mult; - int16_t fs_shift; - int32_t w32_En_new_frame, w32_En_old_frame; - int16_t w16_expmax, w16_newmax; - int16_t w16_tmp, w16_tmp2; - int32_t w32_tmp; -#ifdef SCRATCH - int16_t *pw16_expanded = pw16_scratchPtr + SCRATCH_pw16_expanded; - int16_t *pw16_expandedLB = pw16_scratchPtr + SCRATCH_pw16_expandedLB; - int16_t *pw16_decodedLB = pw16_scratchPtr + SCRATCH_pw16_decodedLB; - int32_t *pw32_corr = (int32_t*) (pw16_scratchPtr + SCRATCH_pw32_corr); - int16_t *pw16_corrVec = pw16_scratchPtr + SCRATCH_pw16_corrVec; -#else - int16_t pw16_expanded[(125+80+5)*FSMULT]; - int16_t pw16_expandedLB[100]; - int16_t pw16_decodedLB[40]; - int32_t pw32_corr[60]; - int16_t pw16_corrVec[4+60+4]; -#endif - int16_t *pw16_corr = &pw16_corrVec[4]; - int16_t w16_stopPos = 0, w16_bestIndex, w16_interpLen; - int16_t w16_bestVal; /* bestVal is dummy */ - int16_t w16_startfact, w16_inc; - int16_t w16_expandedLen; - int16_t w16_startPos; - int16_t w16_expLen, w16_newLen = 0; - int16_t *pw16_decodedOut; - int16_t w16_muted; - - int w16_decodedLen = len; - -#ifdef NETEQ_STEREO - MasterSlaveInfo *msInfo = inst->msInfo; -#endif - - fs_mult = WebRtcSpl_DivW32W16ResW16(inst->fs, 8000); - fs_shift = 30 - WebRtcSpl_NormW32(fs_mult); /* Note that this is not "exact" for 48kHz */ - - /************************************* - * Generate data to merge with - *************************************/ - /* - * Check how much data that is left since earlier - * (at least there should be the overlap)... - */ - w16_startPos = inst->endPosition - inst->curPosition; - /* Get one extra expansion to merge and overlap with */ - inst->ExpandInst.w16_stopMuting = 1; - inst->ExpandInst.w16_lagsDirection = 1; /* make sure we get the "optimal" lag */ - inst->ExpandInst.w16_lagsPosition = -1; /* out of the 3 possible ones */ - w16_expandedLen = 0; /* Does not fill any function currently */ - - if (w16_startPos >= 210 * FSMULT) - { - /* - * The number of samples available in the sync buffer is more than what fits in - * pw16_expanded.Keep the first 210*FSMULT samples, but shift them towards the end of - * the buffer. This is ok, since all of the buffer will be expand data anyway, so as - * long as the beginning is left untouched, we're fine. - */ - - w16_tmp = w16_startPos - 210 * FSMULT; /* length difference */ - - WEBRTC_SPL_MEMMOVE_W16(&inst->speechBuffer[inst->curPosition+w16_tmp] , - &inst->speechBuffer[inst->curPosition], 210*FSMULT); - - inst->curPosition += w16_tmp; /* move start position of sync buffer accordingly */ - w16_startPos = 210 * FSMULT; /* this is the truncated length */ - } - - WebRtcNetEQ_Expand(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_EXPAND, -#endif - pw16_expanded, /* let Expand write to beginning of pw16_expanded to avoid overflow */ - &w16_newLen, 0); - - /* - * Now shift the data in pw16_expanded to where it belongs. - * Truncate all that ends up outside the vector. - */ - - WEBRTC_SPL_MEMMOVE_W16(&pw16_expanded[w16_startPos], pw16_expanded, - WEBRTC_SPL_MIN(w16_newLen, - WEBRTC_SPL_MAX(210*FSMULT - w16_startPos, 0) ) ); - - inst->ExpandInst.w16_stopMuting = 0; - - /* Copy what is left since earlier into the expanded vector */ - - WEBRTC_SPL_MEMCPY_W16(pw16_expanded, &inst->speechBuffer[inst->curPosition], w16_startPos); - - /* - * Do "ugly" copy and paste from the expanded in order to generate more data - * to correlate (but not interpolate) with. - */ - w16_expandedLen = (120 + 80 + 2) * fs_mult; - w16_expLen = w16_startPos + w16_newLen; - - if (w16_expLen < w16_expandedLen) - { - while ((w16_expLen + w16_newLen) < w16_expandedLen) - { - WEBRTC_SPL_MEMCPY_W16(&pw16_expanded[w16_expLen], &pw16_expanded[w16_startPos], - w16_newLen); - w16_expLen += w16_newLen; - } - - /* Copy last part (fraction of a whole expansion) */ - - WEBRTC_SPL_MEMCPY_W16(&pw16_expanded[w16_expLen], &pw16_expanded[w16_startPos], - (w16_expandedLen-w16_expLen)); - } - w16_expLen = w16_expandedLen; - - /* Adjust muting factor (main muting factor times expand muting factor) */ - inst->w16_muteFactor - = (int16_t) WEBRTC_SPL_MUL_16_16_RSFT(inst->w16_muteFactor, - inst->ExpandInst.w16_expandMuteFactor, 14); - - /* Adjust muting factor if new vector is more or less of the BGN energy */ - len = WEBRTC_SPL_MIN(64*fs_mult, w16_decodedLen); - w16_expmax = WebRtcSpl_MaxAbsValueW16(pw16_expanded, (int16_t) len); - w16_newmax = WebRtcSpl_MaxAbsValueW16(pw16_decoded, (int16_t) len); - - /* Calculate energy of old data */ - w16_tmp = 6 + fs_shift - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_expmax, w16_expmax)); - w16_tmp = WEBRTC_SPL_MAX(w16_tmp,0); - w32_En_old_frame = WebRtcNetEQ_DotW16W16(pw16_expanded, pw16_expanded, len, w16_tmp); - - /* Calculate energy of new data */ - w16_tmp2 = 6 + fs_shift - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_newmax, w16_newmax)); - w16_tmp2 = WEBRTC_SPL_MAX(w16_tmp2,0); - w32_En_new_frame = WebRtcNetEQ_DotW16W16(pw16_decoded, pw16_decoded, len, w16_tmp2); - - /* Align to same Q-domain */ - if (w16_tmp2 > w16_tmp) - { - w32_En_old_frame = WEBRTC_SPL_RSHIFT_W32(w32_En_old_frame, (w16_tmp2-w16_tmp)); - } - else - { - w32_En_new_frame = WEBRTC_SPL_RSHIFT_W32(w32_En_new_frame, (w16_tmp-w16_tmp2)); - } - - /* Calculate muting factor to use for new frame */ - if (w32_En_new_frame > w32_En_old_frame) - { - /* Normalize w32_En_new_frame to 14 bits */ - w16_tmp = WebRtcSpl_NormW32(w32_En_new_frame) - 17; - w32_En_new_frame = WEBRTC_SPL_SHIFT_W32(w32_En_new_frame, w16_tmp); - - /* - * Put w32_En_old_frame in a domain 14 higher, so that - * w32_En_old_frame/w32_En_new_frame is in Q14 - */ - w16_tmp = w16_tmp + 14; - w32_En_old_frame = WEBRTC_SPL_SHIFT_W32(w32_En_old_frame, w16_tmp); - w16_tmp - = WebRtcSpl_DivW32W16ResW16(w32_En_old_frame, (int16_t) w32_En_new_frame); - /* Calculate sqrt(w32_En_old_frame/w32_En_new_frame) in Q14 */ - w16_muted = (int16_t) WebRtcSpl_SqrtFloor( - WEBRTC_SPL_LSHIFT_W32((int32_t)w16_tmp,14)); - } - else - { - w16_muted = 16384; /* Set = 1.0 when old frame has higher energy than new */ - } - - /* Set the raise the continued muting factor w16_muted if w16_muteFactor is lower */ - if (w16_muted > inst->w16_muteFactor) - { - inst->w16_muteFactor = WEBRTC_SPL_MIN(w16_muted, 16384); - } - -#ifdef NETEQ_STEREO - - /* Sanity for msInfo */ - if (msInfo == NULL) - { - /* this should not happen here */ - return MASTER_SLAVE_ERROR; - } - - /* do not downsample and calculate correlations for slave instance(s) */ - if ((msInfo->msMode == NETEQ_MASTER) || (msInfo->msMode == NETEQ_MONO)) - { -#endif - - /********************************************* - * Downsample to 4kHz and find best overlap - *********************************************/ - - /* Downsample to 4 kHz */ - if (inst->fs == 8000) - { - WebRtcSpl_DownsampleFast(&pw16_expanded[2], (int16_t) (w16_expandedLen - 2), - pw16_expandedLB, (int16_t) (100), - (int16_t*) WebRtcNetEQ_kDownsample8kHzTbl, (int16_t) 3, - (int16_t) 2, (int16_t) 0); - if (w16_decodedLen <= 80) - { - /* Not quite long enough, so we have to cheat a bit... */ - int16_t temp_len = w16_decodedLen - 2; - w16_tmp = temp_len / 2; - WebRtcSpl_DownsampleFast(&pw16_decoded[2], temp_len, - pw16_decodedLB, w16_tmp, - (int16_t*) WebRtcNetEQ_kDownsample8kHzTbl, - (int16_t) 3, (int16_t) 2, (int16_t) 0); - WebRtcSpl_MemSetW16(&pw16_decodedLB[w16_tmp], 0, (40 - w16_tmp)); - } - else - { - WebRtcSpl_DownsampleFast(&pw16_decoded[2], - (int16_t) (w16_decodedLen - 2), pw16_decodedLB, - (int16_t) (40), (int16_t*) WebRtcNetEQ_kDownsample8kHzTbl, - (int16_t) 3, (int16_t) 2, (int16_t) 0); - } -#ifdef NETEQ_WIDEBAND - } - else if (inst->fs==16000) - { - WebRtcSpl_DownsampleFast( - &pw16_expanded[4], (int16_t)(w16_expandedLen-4), - pw16_expandedLB, (int16_t)(100), - (int16_t*)WebRtcNetEQ_kDownsample16kHzTbl, (int16_t)5, - (int16_t)4, (int16_t)0); - if (w16_decodedLen<=160) - { - /* Not quite long enough, so we have to cheat a bit... */ - int16_t temp_len = w16_decodedLen - 4; - w16_tmp = temp_len / 4; - WebRtcSpl_DownsampleFast( - &pw16_decoded[4], temp_len, - pw16_decodedLB, w16_tmp, - (int16_t*)WebRtcNetEQ_kDownsample16kHzTbl, (int16_t)5, - (int16_t)4, (int16_t)0); - WebRtcSpl_MemSetW16(&pw16_decodedLB[w16_tmp], 0, (40-w16_tmp)); - } - else - { - WebRtcSpl_DownsampleFast( - &pw16_decoded[4], (int16_t)(w16_decodedLen-4), - pw16_decodedLB, (int16_t)(40), - (int16_t*)WebRtcNetEQ_kDownsample16kHzTbl, (int16_t)5, - (int16_t)4, (int16_t)0); - } -#endif -#ifdef NETEQ_32KHZ_WIDEBAND - } - else if (inst->fs==32000) - { - /* - * TODO(hlundin) Why is the offset into pw16_expanded 6? - */ - WebRtcSpl_DownsampleFast( - &pw16_expanded[6], (int16_t)(w16_expandedLen-6), - pw16_expandedLB, (int16_t)(100), - (int16_t*)WebRtcNetEQ_kDownsample32kHzTbl, (int16_t)7, - (int16_t)8, (int16_t)0); - if (w16_decodedLen<=320) - { - /* Not quite long enough, so we have to cheat a bit... */ - int16_t temp_len = w16_decodedLen - 6; - w16_tmp = temp_len / 8; - WebRtcSpl_DownsampleFast( - &pw16_decoded[6], temp_len, - pw16_decodedLB, w16_tmp, - (int16_t*)WebRtcNetEQ_kDownsample32kHzTbl, (int16_t)7, - (int16_t)8, (int16_t)0); - WebRtcSpl_MemSetW16(&pw16_decodedLB[w16_tmp], 0, (40-w16_tmp)); - } - else - { - WebRtcSpl_DownsampleFast( - &pw16_decoded[6], (int16_t)(w16_decodedLen-6), - pw16_decodedLB, (int16_t)(40), - (int16_t*)WebRtcNetEQ_kDownsample32kHzTbl, (int16_t)7, - (int16_t)8, (int16_t)0); - } -#endif -#ifdef NETEQ_48KHZ_WIDEBAND - } - else /* if (inst->fs==48000) */ - { - /* - * TODO(hlundin) Why is the offset into pw16_expanded 6? - */ - WebRtcSpl_DownsampleFast( - &pw16_expanded[6], (int16_t)(w16_expandedLen-6), - pw16_expandedLB, (int16_t)(100), - (int16_t*)WebRtcNetEQ_kDownsample48kHzTbl, (int16_t)7, - (int16_t)12, (int16_t)0); - if (w16_decodedLen<=320) - { - /* Not quite long enough, so we have to cheat a bit... */ - /* - * TODO(hlundin): Is this correct? Downsampling is a factor 12 - * but w16_tmp = temp_len / 8. - * (Was w16_tmp = ((w16_decodedLen-6)>>3) before re-write.) - */ - int16_t temp_len = w16_decodedLen - 6; - w16_tmp = temp_len / 8; - WebRtcSpl_DownsampleFast( - &pw16_decoded[6], temp_len, - pw16_decodedLB, w16_tmp, - (int16_t*)WebRtcNetEQ_kDownsample48kHzTbl, (int16_t)7, - (int16_t)12, (int16_t)0); - WebRtcSpl_MemSetW16(&pw16_decodedLB[w16_tmp], 0, (40-w16_tmp)); - } - else - { - WebRtcSpl_DownsampleFast( - &pw16_decoded[6], (int16_t)(w16_decodedLen-6), - pw16_decodedLB, (int16_t)(40), - (int16_t*)WebRtcNetEQ_kDownsample48kHzTbl, (int16_t)7, - (int16_t)12, (int16_t)0); - } -#endif - } - - /* Calculate correlation without any normalization (40 samples) */ - w16_tmp = WebRtcSpl_DivW32W16ResW16((int32_t) inst->ExpandInst.w16_maxLag, - (int16_t) (fs_mult * 2)) + 1; - w16_stopPos = WEBRTC_SPL_MIN(60, w16_tmp); - w32_tmp = WEBRTC_SPL_MUL_16_16(w16_expmax, w16_newmax); - if (w32_tmp > 26843546) - { - w16_tmp = 3; - } - else - { - w16_tmp = 0; - } - - WebRtcNetEQ_CrossCorr(pw32_corr, pw16_decodedLB, pw16_expandedLB, 40, - (int16_t) w16_stopPos, w16_tmp, 1); - - /* Normalize correlation to 14 bits and put in a int16_t vector */ - WebRtcSpl_MemSetW16(pw16_corrVec, 0, (4 + 60 + 4)); - w32_tmp = WebRtcSpl_MaxAbsValueW32(pw32_corr, w16_stopPos); - w16_tmp = 17 - WebRtcSpl_NormW32(w32_tmp); - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - WebRtcSpl_VectorBitShiftW32ToW16(pw16_corr, w16_stopPos, pw32_corr, w16_tmp); - - /* Calculate allowed starting point for peak finding. - The peak location bestIndex must fulfill two criteria: - (1) w16_bestIndex+w16_decodedLen < inst->timestampsPerCall+inst->ExpandInst.w16_overlap - (2) w16_bestIndex+w16_decodedLen < w16_startPos */ - w16_tmp = WEBRTC_SPL_MAX(0, WEBRTC_SPL_MAX(w16_startPos, - inst->timestampsPerCall+inst->ExpandInst.w16_overlap) - w16_decodedLen); - /* Downscale starting index to 4kHz domain */ - w16_tmp2 = WebRtcSpl_DivW32W16ResW16((int32_t) w16_tmp, - (int16_t) (fs_mult << 1)); - -#ifdef NETEQ_STEREO - } /* end if (msInfo->msMode != NETEQ_SLAVE) */ - - if ((msInfo->msMode == NETEQ_MASTER) || (msInfo->msMode == NETEQ_MONO)) - { - /* This is master or mono instance; find peak */ - WebRtcNetEQ_PeakDetection(&pw16_corr[w16_tmp2], w16_stopPos, 1, fs_mult, &w16_bestIndex, - &w16_bestVal); - w16_bestIndex += w16_tmp; /* compensate for modified starting index */ - msInfo->bestIndex = w16_bestIndex; - } - else if (msInfo->msMode == NETEQ_SLAVE) - { - /* Get peak location from master instance */ - w16_bestIndex = msInfo->bestIndex; - } - else - { - /* Invalid mode */ - return MASTER_SLAVE_ERROR; - } - -#else /* NETEQ_STEREO */ - - /* Find peak */ - WebRtcNetEQ_PeakDetection(&pw16_corr[w16_tmp2], w16_stopPos, 1, fs_mult, &w16_bestIndex, - &w16_bestVal); - w16_bestIndex += w16_tmp; /* compensate for modified starting index */ - -#endif /* NETEQ_STEREO */ - - /* - * Ensure that underrun does not occur for 10ms case => we have to get at least - * 10ms + overlap . (This should never happen thanks to the above modification of - * peak-finding starting point.) - * */ - while ((w16_bestIndex + w16_decodedLen) < (inst->timestampsPerCall - + inst->ExpandInst.w16_overlap) || w16_bestIndex + w16_decodedLen < w16_startPos) - { - w16_bestIndex += w16_newLen; /* Jump one lag ahead */ - } - pw16_decodedOut = pw16_outData + w16_bestIndex; - - /* Mute the new decoded data if needed (and unmute it linearly) */ - w16_interpLen = WEBRTC_SPL_MIN(60*fs_mult, - w16_expandedLen-w16_bestIndex); /* this is the overlapping part of pw16_expanded */ - w16_interpLen = WEBRTC_SPL_MIN(w16_interpLen, w16_decodedLen); - w16_inc = WebRtcSpl_DivW32W16ResW16(4194, - fs_mult); /* in Q20, 0.004 for NB and 0.002 for WB */ - if (inst->w16_muteFactor < 16384) - { - WebRtcNetEQ_UnmuteSignal(pw16_decoded, &inst->w16_muteFactor, pw16_decoded, w16_inc, - (int16_t) w16_interpLen); - WebRtcNetEQ_UnmuteSignal(&pw16_decoded[w16_interpLen], &inst->w16_muteFactor, - &pw16_decodedOut[w16_interpLen], w16_inc, - (int16_t) (w16_decodedLen - w16_interpLen)); - } - else - { - /* No muting needed */ - - WEBRTC_SPL_MEMMOVE_W16(&pw16_decodedOut[w16_interpLen], &pw16_decoded[w16_interpLen], - (w16_decodedLen-w16_interpLen)); - } - - /* Do overlap and interpolate linearly */ - w16_inc = WebRtcSpl_DivW32W16ResW16(16384, (int16_t) (w16_interpLen + 1)); /* Q14 */ - w16_startfact = (16384 - w16_inc); - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_expanded, w16_bestIndex); - WebRtcNetEQ_MixVoiceUnvoice(pw16_decodedOut, &pw16_expanded[w16_bestIndex], pw16_decoded, - &w16_startfact, w16_inc, w16_interpLen); - - inst->w16_mode = MODE_MERGE; - inst->ExpandInst.w16_consecExp = 0; /* Last was not expand any more */ - - /* New added length (w16_startPos samples were borrowed) */ - *pw16_len = w16_bestIndex + w16_decodedLen - w16_startPos; - - /* Update VQmon parameter */ - inst->w16_concealedTS += (*pw16_len - w16_decodedLen); - inst->w16_concealedTS = WEBRTC_SPL_MAX(0, inst->w16_concealedTS); - - /* Update in-call and post-call statistics */ - if (inst->ExpandInst.w16_expandMuteFactor == 0) - { - /* expansion generates noise only */ - inst->statInst.expandedNoiseSamples += (*pw16_len - w16_decodedLen); - /* Short-term activity statistics. */ - inst->activity_stats.merge_expand_bgn_samples += - (*pw16_len - w16_decodedLen); - } - else - { - /* expansion generates more than only noise */ - inst->statInst.expandedVoiceSamples += (*pw16_len - w16_decodedLen); - /* Short-term activity statistics. */ - inst->activity_stats.merge_expand_normal_samples += - (*pw16_len - w16_decodedLen); - } - inst->statInst.expandLength += (*pw16_len - w16_decodedLen); - - - /* Copy back the first part of the data to the speechHistory */ - - WEBRTC_SPL_MEMCPY_W16(&inst->speechBuffer[inst->curPosition], pw16_outData, w16_startPos); - - - /* Move data to within outData */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, &pw16_outData[w16_startPos], (*pw16_len)); - - return 0; -} - -#undef SCRATCH_pw16_expanded -#undef SCRATCH_pw16_expandedLB -#undef SCRATCH_pw16_decodedLB -#undef SCRATCH_pw32_corr -#undef SCRATCH_pw16_corrVec -#undef SCRATCH_NETEQ_EXPAND diff --git a/webrtc/modules/audio_coding/neteq4/merge.cc b/webrtc/modules/audio_coding/neteq/merge.cc similarity index 94% rename from webrtc/modules/audio_coding/neteq4/merge.cc rename to webrtc/modules/audio_coding/neteq/merge.cc index 4b5601657..d3d807751 100644 --- a/webrtc/modules/audio_coding/neteq4/merge.cc +++ b/webrtc/modules/audio_coding/neteq/merge.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/merge.h" +#include "webrtc/modules/audio_coding/neteq/merge.h" #include #include // memmove, memcpy, memset, size_t @@ -16,10 +16,11 @@ #include // min, max #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/dsp_helper.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/dsp_helper.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { @@ -307,9 +308,11 @@ int16_t Merge::CorrelateAndPeakSearch(int16_t expanded_max, int16_t input_max, stop_position_downsamp, correlation_shift, 1); // Normalize correlation to 14 bits and copy to a 16-bit array. - static const int kPadLength = 4; - int16_t correlation16[kPadLength + kMaxCorrelationLength + kPadLength] = {0}; - int16_t* correlation_ptr = &correlation16[kPadLength]; + const int pad_length = static_cast(expand_->overlap_length() - 1); + const int correlation_buffer_size = 2 * pad_length + kMaxCorrelationLength; + scoped_ptr correlation16(new int16_t[correlation_buffer_size]); + memset(correlation16.get(), 0, correlation_buffer_size * sizeof(int16_t)); + int16_t* correlation_ptr = &correlation16[pad_length]; int32_t max_correlation = WebRtcSpl_MaxAbsValueW32(correlation, stop_position_downsamp); int16_t norm_shift = std::max(0, 17 - WebRtcSpl_NormW32(max_correlation)); @@ -332,7 +335,7 @@ int16_t Merge::CorrelateAndPeakSearch(int16_t expanded_max, int16_t input_max, // start index |start_index_downsamp| and the effective array length. int modified_stop_pos = std::min(stop_position_downsamp, - kMaxCorrelationLength + kPadLength - start_index_downsamp); + kMaxCorrelationLength + pad_length - start_index_downsamp); int best_correlation_index; int16_t best_correlation; static const int kNumCorrelationCandidates = 1; @@ -355,4 +358,9 @@ int16_t Merge::CorrelateAndPeakSearch(int16_t expanded_max, int16_t input_max, return best_correlation_index; } +int Merge::RequiredFutureSamples() { + return static_cast(fs_hz_ / 100 * num_channels_); // 10 ms. +} + + } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/merge.h b/webrtc/modules/audio_coding/neteq/merge.h similarity index 88% rename from webrtc/modules/audio_coding/neteq4/merge.h rename to webrtc/modules/audio_coding/neteq/merge.h index f1f64e6c5..1bf0483df 100644 --- a/webrtc/modules/audio_coding/neteq4/merge.h +++ b/webrtc/modules/audio_coding/neteq/merge.h @@ -8,13 +8,13 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MERGE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MERGE_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MERGE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MERGE_H_ #include -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -35,8 +35,8 @@ class Merge { public: Merge(int fs_hz, size_t num_channels, Expand* expand, SyncBuffer* sync_buffer) : fs_hz_(fs_hz), - fs_mult_(fs_hz_ / 8000), num_channels_(num_channels), + fs_mult_(fs_hz_ / 8000), timestamps_per_call_(fs_hz_ / 100), expand_(expand), sync_buffer_(sync_buffer), @@ -44,6 +44,8 @@ class Merge { assert(num_channels_ > 0); } + virtual ~Merge() {} + // The main method to produce the audio data. The decoded data is supplied in // |input|, having |input_length| samples in total for all channels // (interleaved). The result is written to |output|. The number of channels @@ -51,9 +53,15 @@ class Merge { // de-interleaving |input|. The values in |external_mute_factor_array| (Q14) // will be used to scale the audio, and is updated in the process. The array // must have |num_channels_| elements. - int Process(int16_t* input, size_t input_length, - int16_t* external_mute_factor_array, - AudioMultiVector* output); + virtual int Process(int16_t* input, size_t input_length, + int16_t* external_mute_factor_array, + AudioMultiVector* output); + + virtual int RequiredFutureSamples(); + + protected: + const int fs_hz_; + const size_t num_channels_; private: static const int kMaxSampleRate = 48000; @@ -87,9 +95,7 @@ class Merge { int start_position, int input_length, int expand_period) const; - const int fs_hz_; const int fs_mult_; // fs_hz_ / 8000. - const size_t num_channels_; const int timestamps_per_call_; Expand* expand_; SyncBuffer* sync_buffer_; @@ -101,4 +107,4 @@ class Merge { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MERGE_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MERGE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/merge_unittest.cc b/webrtc/modules/audio_coding/neteq/merge_unittest.cc similarity index 74% rename from webrtc/modules/audio_coding/neteq4/merge_unittest.cc rename to webrtc/modules/audio_coding/neteq/merge_unittest.cc index 1d7b1f1fe..fb5f789ff 100644 --- a/webrtc/modules/audio_coding/neteq4/merge_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/merge_unittest.cc @@ -10,15 +10,15 @@ // Unit tests for Merge class. -#include "webrtc/modules/audio_coding/neteq4/merge.h" +#include "webrtc/modules/audio_coding/neteq/merge.h" #include #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/random_vector.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/random_vector.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq/min_distortion.c b/webrtc/modules/audio_coding/neteq/min_distortion.c deleted file mode 100644 index 47e2b442c..000000000 --- a/webrtc/modules/audio_coding/neteq/min_distortion.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -/* - * Calculate best overlap fit according to distortion measure. - */ - -#include "dsp_helpfunctions.h" - -#include "signal_processing_library.h" - -int16_t WebRtcNetEQ_MinDistortion(const int16_t *pw16_data, - int16_t w16_minLag, int16_t w16_maxLag, - int16_t len, int32_t *pw16_dist) -{ - int i, j; - const int16_t *pw16_data1; - const int16_t *pw16_data2; - int32_t w32_diff; - int32_t w32_sumdiff; - int16_t bestIndex = -1; - int32_t minDist = WEBRTC_SPL_WORD32_MAX; - - for (i = w16_minLag; i <= w16_maxLag; i++) - { - w32_sumdiff = 0; - pw16_data1 = pw16_data; - pw16_data2 = pw16_data - i; - - for (j = 0; j < len; j++) - { - w32_diff = pw16_data1[j] - pw16_data2[j]; - w32_sumdiff += WEBRTC_SPL_ABS_W32(w32_diff); - } - - /* Compare with previous minimum */ - if (w32_sumdiff < minDist) - { - minDist = w32_sumdiff; - bestIndex = i; - } - } - - *pw16_dist = minDist; - - return bestIndex; -} - diff --git a/webrtc/modules/audio_coding/neteq/mix_voice_unvoice.c b/webrtc/modules/audio_coding/neteq/mix_voice_unvoice.c deleted file mode 100644 index 6c70d4916..000000000 --- a/webrtc/modules/audio_coding/neteq/mix_voice_unvoice.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 function mixes a voiced signal with an unvoiced signal and - * updates the weight on a sample by sample basis. - */ - -#include "dsp_helpfunctions.h" - -#include "signal_processing_library.h" - -void WebRtcNetEQ_MixVoiceUnvoice(int16_t *pw16_outData, int16_t *pw16_voicedVec, - int16_t *pw16_unvoicedVec, - int16_t *w16_current_vfraction, - int16_t w16_vfraction_change, int16_t N) -{ - int i; - int16_t w16_tmp2; - int16_t vfraction = *w16_current_vfraction; - - w16_tmp2 = 16384 - vfraction; - for (i = 0; i < N; i++) - { - pw16_outData[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32( - WEBRTC_SPL_MUL_16_16(vfraction, pw16_voicedVec[i]) + - WEBRTC_SPL_MUL_16_16(w16_tmp2, pw16_unvoicedVec[i]) + 8192, - 14); - vfraction -= w16_vfraction_change; - w16_tmp2 += w16_vfraction_change; - } - *w16_current_vfraction = vfraction; -} - diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_audio_decoder.h b/webrtc/modules/audio_coding/neteq/mock/mock_audio_decoder.h similarity index 80% rename from webrtc/modules/audio_coding/neteq4/mock/mock_audio_decoder.h rename to webrtc/modules/audio_coding/neteq/mock/mock_audio_decoder.h index a6d587447..edf3b54e9 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_audio_decoder.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_audio_decoder.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_AUDIO_DECODER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_AUDIO_DECODER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_AUDIO_DECODER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_AUDIO_DECODER_H_ -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include "gmock/gmock.h" @@ -35,4 +35,4 @@ class MockAudioDecoder : public AudioDecoder { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_AUDIO_DECODER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_AUDIO_DECODER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_audio_vector.h b/webrtc/modules/audio_coding/neteq/mock/mock_audio_vector.h similarity index 83% rename from webrtc/modules/audio_coding/neteq4/mock/mock_audio_vector.h rename to webrtc/modules/audio_coding/neteq/mock/mock_audio_vector.h index 7a4747b0d..a5a787c7a 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_audio_vector.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_audio_vector.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_AUDIO_VECTOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_AUDIO_VECTOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_AUDIO_VECTOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_AUDIO_VECTOR_H_ -#include "webrtc/modules/audio_coding/neteq4/audio_vector.h" +#include "webrtc/modules/audio_coding/neteq/audio_vector.h" #include "gmock/gmock.h" @@ -48,4 +48,4 @@ class MockAudioVector : public AudioVector { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_AUDIO_VECTOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_AUDIO_VECTOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_buffer_level_filter.h b/webrtc/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h similarity index 75% rename from webrtc/modules/audio_coding/neteq4/mock/mock_buffer_level_filter.h rename to webrtc/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h index 872655172..d9210668d 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_buffer_level_filter.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" #include "gmock/gmock.h" @@ -34,4 +34,4 @@ class MockBufferLevelFilter : public BufferLevelFilter { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h b/webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h similarity index 87% rename from webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h rename to webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h index c4ca25a52..583fa54ba 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DECODER_DATABASE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DECODER_DATABASE_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DECODER_DATABASE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DECODER_DATABASE_H_ -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" #include "gmock/gmock.h" @@ -61,4 +61,4 @@ class MockDecoderDatabase : public DecoderDatabase { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DECODER_DATABASE_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DECODER_DATABASE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_delay_manager.h b/webrtc/modules/audio_coding/neteq/mock/mock_delay_manager.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/mock/mock_delay_manager.h rename to webrtc/modules/audio_coding/neteq/mock/mock_delay_manager.h index 1edfb8737..c21a1c28c 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_delay_manager.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_delay_manager.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DELAY_MANAGER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DELAY_MANAGER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_ -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" #include "gmock/gmock.h" @@ -60,4 +60,4 @@ class MockDelayManager : public DelayManager { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DELAY_MANAGER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_delay_peak_detector.h b/webrtc/modules/audio_coding/neteq/mock/mock_delay_peak_detector.h similarity index 76% rename from webrtc/modules/audio_coding/neteq4/mock/mock_delay_peak_detector.h rename to webrtc/modules/audio_coding/neteq/mock/mock_delay_peak_detector.h index 211b2b91e..26e09329e 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_delay_peak_detector.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_delay_peak_detector.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DELAY_PEAK_DETECTOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DELAY_PEAK_DETECTOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_PEAK_DETECTOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_PEAK_DETECTOR_H_ -#include "webrtc/modules/audio_coding/neteq4/delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/delay_peak_detector.h" #include "gmock/gmock.h" @@ -31,4 +31,4 @@ class MockDelayPeakDetector : public DelayPeakDetector { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DELAY_PEAK_DETECTOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_PEAK_DETECTOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_buffer.h b/webrtc/modules/audio_coding/neteq/mock/mock_dtmf_buffer.h similarity index 77% rename from webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_buffer.h rename to webrtc/modules/audio_coding/neteq/mock/mock_dtmf_buffer.h index 5a89db46f..0351d6b1e 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_buffer.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_dtmf_buffer.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DTMF_BUFFER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DTMF_BUFFER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DTMF_BUFFER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DTMF_BUFFER_H_ -#include "webrtc/modules/audio_coding/neteq4/dtmf_buffer.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_buffer.h" #include "gmock/gmock.h" @@ -35,4 +35,4 @@ class MockDtmfBuffer : public DtmfBuffer { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DTMF_BUFFER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DTMF_BUFFER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_tone_generator.h b/webrtc/modules/audio_coding/neteq/mock/mock_dtmf_tone_generator.h similarity index 74% rename from webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_tone_generator.h rename to webrtc/modules/audio_coding/neteq/mock/mock_dtmf_tone_generator.h index d34f7470e..3bed4d152 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_tone_generator.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_dtmf_tone_generator.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DTMF_TONE_GENERATOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DTMF_TONE_GENERATOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DTMF_TONE_GENERATOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DTMF_TONE_GENERATOR_H_ -#include "webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h" #include "gmock/gmock.h" @@ -32,4 +32,4 @@ class MockDtmfToneGenerator : public DtmfToneGenerator { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_DTMF_TONE_GENERATOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DTMF_TONE_GENERATOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_external_decoder_pcm16b.h b/webrtc/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h similarity index 89% rename from webrtc/modules/audio_coding/neteq4/mock/mock_external_decoder_pcm16b.h rename to webrtc/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h index efc0c7158..9522b537e 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_external_decoder_pcm16b.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_EXTERNAL_DECODER_PCM16B_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_EXTERNAL_DECODER_PCM16B_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_EXTERNAL_DECODER_PCM16B_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_EXTERNAL_DECODER_PCM16B_H_ -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" #include "gmock/gmock.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -96,4 +96,4 @@ class MockExternalPcm16B : public ExternalPcm16B { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_EXTERNAL_DECODER_PCM16B_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_EXTERNAL_DECODER_PCM16B_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_packet_buffer.h b/webrtc/modules/audio_coding/neteq/mock/mock_packet_buffer.h similarity index 78% rename from webrtc/modules/audio_coding/neteq4/mock/mock_packet_buffer.h rename to webrtc/modules/audio_coding/neteq/mock/mock_packet_buffer.h index 37fa90de7..2882248c1 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_packet_buffer.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_packet_buffer.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_PACKET_BUFFER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_PACKET_BUFFER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_PACKET_BUFFER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_PACKET_BUFFER_H_ -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" #include "gmock/gmock.h" @@ -19,8 +19,8 @@ namespace webrtc { class MockPacketBuffer : public PacketBuffer { public: - MockPacketBuffer(size_t max_number_of_packets, size_t max_payload_memory) - : PacketBuffer(max_number_of_packets, max_payload_memory) {} + MockPacketBuffer(size_t max_number_of_packets) + : PacketBuffer(max_number_of_packets) {} virtual ~MockPacketBuffer() { Die(); } MOCK_METHOD0(Die, void()); MOCK_METHOD0(Flush, @@ -55,4 +55,4 @@ class MockPacketBuffer : public PacketBuffer { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_PACKET_BUFFER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_PACKET_BUFFER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h b/webrtc/modules/audio_coding/neteq/mock/mock_payload_splitter.h similarity index 80% rename from webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h rename to webrtc/modules/audio_coding/neteq/mock/mock_payload_splitter.h index 369dfc43f..f1665423a 100644 --- a/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h +++ b/webrtc/modules/audio_coding/neteq/mock/mock_payload_splitter.h @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_PAYLOAD_SPLITTER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_PAYLOAD_SPLITTER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_PAYLOAD_SPLITTER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_PAYLOAD_SPLITTER_H_ -#include "webrtc/modules/audio_coding/neteq4/payload_splitter.h" +#include "webrtc/modules/audio_coding/neteq/payload_splitter.h" #include "gmock/gmock.h" @@ -36,4 +36,4 @@ class MockPayloadSplitter : public PayloadSplitter { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_MOCK_MOCK_PAYLOAD_SPLITTER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_PAYLOAD_SPLITTER_H_ diff --git a/webrtc/modules/audio_coding/neteq/mute_signal.c b/webrtc/modules/audio_coding/neteq/mute_signal.c deleted file mode 100644 index 767a71dee..000000000 --- a/webrtc/modules/audio_coding/neteq/mute_signal.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 function mutes a signal linearly on a sample by sample basis. - */ - -#include "dsp_helpfunctions.h" - -#include "signal_processing_library.h" - -void WebRtcNetEQ_MuteSignal(int16_t *pw16_inout, int16_t muteSlope, - int16_t N) -{ - int i; - int32_t w32_tmp = 1048608; /* (16384<<6 + 32) */ - - for (i = 0; i < N; i++) - { - pw16_inout[i] - = (int16_t) ((WEBRTC_SPL_MUL_16_16((int16_t)(w32_tmp>>6), pw16_inout[i]) - + 8192) >> 14); - w32_tmp -= muteSlope; - } -} - diff --git a/webrtc/modules/audio_coding/neteq4/neteq.cc b/webrtc/modules/audio_coding/neteq/neteq.cc similarity index 55% rename from webrtc/modules/audio_coding/neteq4/neteq.cc rename to webrtc/modules/audio_coding/neteq/neteq.cc index a64f01b25..7edacde76 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq.cc +++ b/webrtc/modules/audio_coding/neteq/neteq.cc @@ -8,43 +8,43 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/accelerate.h" -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" -#include "webrtc/modules/audio_coding/neteq4/delay_peak_detector.h" -#include "webrtc/modules/audio_coding/neteq4/dtmf_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/neteq_impl.h" -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/payload_splitter.h" -#include "webrtc/modules/audio_coding/neteq4/preemptive_expand.h" -#include "webrtc/modules/audio_coding/neteq4/timestamp_scaler.h" +#include "webrtc/modules/audio_coding/neteq/accelerate.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_buffer.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/neteq_impl.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/payload_splitter.h" +#include "webrtc/modules/audio_coding/neteq/preemptive_expand.h" +#include "webrtc/modules/audio_coding/neteq/timestamp_scaler.h" namespace webrtc { // Creates all classes needed and inject them into a new NetEqImpl object. // Return the new object. -NetEq* NetEq::Create(int sample_rate_hz) { +NetEq* NetEq::Create(const NetEq::Config& config) { BufferLevelFilter* buffer_level_filter = new BufferLevelFilter; DecoderDatabase* decoder_database = new DecoderDatabase; DelayPeakDetector* delay_peak_detector = new DelayPeakDetector; - DelayManager* delay_manager = new DelayManager(kMaxNumPacketsInBuffer, - delay_peak_detector); - DtmfBuffer* dtmf_buffer = new DtmfBuffer(sample_rate_hz); + DelayManager* delay_manager = + new DelayManager(config.max_packets_in_buffer, delay_peak_detector); + delay_manager->SetMaximumDelay(config.max_delay_ms); + DtmfBuffer* dtmf_buffer = new DtmfBuffer(config.sample_rate_hz); DtmfToneGenerator* dtmf_tone_generator = new DtmfToneGenerator; - PacketBuffer* packet_buffer = new PacketBuffer(kMaxNumPacketsInBuffer, - kMaxBytesInBuffer); + PacketBuffer* packet_buffer = new PacketBuffer(config.max_packets_in_buffer); PayloadSplitter* payload_splitter = new PayloadSplitter; TimestampScaler* timestamp_scaler = new TimestampScaler(*decoder_database); AccelerateFactory* accelerate_factory = new AccelerateFactory; ExpandFactory* expand_factory = new ExpandFactory; PreemptiveExpandFactory* preemptive_expand_factory = new PreemptiveExpandFactory; - return new NetEqImpl(sample_rate_hz, + return new NetEqImpl(config.sample_rate_hz, buffer_level_filter, decoder_database, delay_manager, diff --git a/webrtc/modules/audio_coding/neteq/neteq.gypi b/webrtc/modules/audio_coding/neteq/neteq.gypi index a8acfb3df..21ccee41e 100644 --- a/webrtc/modules/audio_coding/neteq/neteq.gypi +++ b/webrtc/modules/audio_coding/neteq/neteq.gypi @@ -7,243 +7,208 @@ # be found in the AUTHORS file in the root of the source tree. { + 'variables': { + 'codecs': [ + 'G711', + 'G722', + 'PCM16B', + 'iLBC', + 'iSAC', + 'iSACFix', + 'CNG', + ], + 'neteq_defines': [], + 'conditions': [ + ['include_opus==1', { + 'codecs': ['webrtc_opus',], + 'neteq_defines': ['WEBRTC_CODEC_OPUS',], + }], + ], + 'neteq_dependencies': [ + '<@(codecs)', + '<(DEPTH)/third_party/opus/opus.gyp:opus', + '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + ], + }, 'targets': [ { - 'target_name': 'NetEq', + 'target_name': 'neteq', 'type': 'static_library', 'dependencies': [ - 'CNG', - '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', + '<@(neteq_dependencies)', ], 'defines': [ - 'NETEQ_VOICEENGINE_CODECS', # TODO: Should create a Chrome define which - 'SCRATCH', # specifies a subset of codecs to support. + '<@(neteq_defines)', ], 'include_dirs': [ - 'interface', - '<(webrtc_root)', + # Need Opus header files for the audio classifier. + '<(DEPTH)/third_party/opus/src/celt', + '<(DEPTH)/third_party/opus/src/src', ], 'direct_dependent_settings': { 'include_dirs': [ - 'interface', - '<(webrtc_root)', + # Need Opus header files for the audio classifier. + '<(DEPTH)/third_party/opus/src/celt', + '<(DEPTH)/third_party/opus/src/src', ], }, + 'export_dependent_settings': [ + '<(DEPTH)/third_party/opus/opus.gyp:opus', + ], 'sources': [ - 'interface/webrtc_neteq.h', - 'interface/webrtc_neteq_help_macros.h', - 'interface/webrtc_neteq_internal.h', - 'accelerate.c', - 'automode.c', - 'automode.h', - 'bgn_update.c', - 'buffer_stats.h', - 'bufstats_decision.c', - 'cng_internal.c', - 'codec_db.c', - 'codec_db.h', - 'codec_db_defines.h', - 'correlator.c', - 'delay_logging.h', - 'dsp.c', - 'dsp.h', - 'dsp_helpfunctions.c', - 'dsp_helpfunctions.h', - 'dtmf_buffer.c', + 'interface/audio_decoder.h', + 'interface/neteq.h', + 'accelerate.cc', + 'accelerate.h', + 'audio_classifier.cc', + 'audio_classifier.h', + 'audio_decoder_impl.cc', + 'audio_decoder_impl.h', + 'audio_decoder.cc', + 'audio_multi_vector.cc', + 'audio_multi_vector.h', + 'audio_vector.cc', + 'audio_vector.h', + 'background_noise.cc', + 'background_noise.h', + 'buffer_level_filter.cc', + 'buffer_level_filter.h', + 'comfort_noise.cc', + 'comfort_noise.h', + 'decision_logic.cc', + 'decision_logic.h', + 'decision_logic_fax.cc', + 'decision_logic_fax.h', + 'decision_logic_normal.cc', + 'decision_logic_normal.h', + 'decoder_database.cc', + 'decoder_database.h', + 'defines.h', + 'delay_manager.cc', + 'delay_manager.h', + 'delay_peak_detector.cc', + 'delay_peak_detector.h', + 'dsp_helper.cc', + 'dsp_helper.h', + 'dtmf_buffer.cc', 'dtmf_buffer.h', - 'dtmf_tonegen.c', - 'dtmf_tonegen.h', - 'expand.c', - 'mcu.h', - 'mcu_address_init.c', - 'mcu_dsp_common.c', - 'mcu_dsp_common.h', - 'mcu_reset.c', - 'merge.c', - 'min_distortion.c', - 'mix_voice_unvoice.c', - 'mute_signal.c', - 'neteq_defines.h', - 'neteq_error_codes.h', - 'neteq_statistics.h', - 'normal.c', - 'packet_buffer.c', + 'dtmf_tone_generator.cc', + 'dtmf_tone_generator.h', + 'expand.cc', + 'expand.h', + 'merge.cc', + 'merge.h', + 'neteq_impl.cc', + 'neteq_impl.h', + 'neteq.cc', + 'statistics_calculator.cc', + 'statistics_calculator.h', + 'normal.cc', + 'normal.h', + 'packet_buffer.cc', 'packet_buffer.h', - 'peak_detection.c', - 'preemptive_expand.c', - 'random_vector.c', - 'recin.c', - 'recout.c', - 'rtcp.c', + 'payload_splitter.cc', + 'payload_splitter.h', + 'post_decode_vad.cc', + 'post_decode_vad.h', + 'preemptive_expand.cc', + 'preemptive_expand.h', + 'random_vector.cc', + 'random_vector.h', + 'rtcp.cc', 'rtcp.h', - 'rtp.c', - 'rtp.h', - 'set_fs.c', - 'signal_mcu.c', - 'split_and_insert.c', - 'unmute_signal.c', - 'webrtc_neteq.c', + 'sync_buffer.cc', + 'sync_buffer.h', + 'timestamp_scaler.cc', + 'timestamp_scaler.h', + 'time_stretch.cc', + 'time_stretch.h', ], }, ], # targets 'conditions': [ ['include_tests==1', { + 'includes': ['neteq_tests.gypi',], 'targets': [ { - 'target_name': 'neteq_unittests', + 'target_name': 'audio_decoder_unittests', 'type': '<(gtest_target_type)', 'dependencies': [ - 'NetEq', - 'NetEqTestTools', - 'neteq_unittest_tools', + '<@(codecs)', '<(DEPTH)/testing/gtest.gyp:gtest', + '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', '<(webrtc_root)/test/test.gyp:test_support_main', ], - 'sources': [ - 'webrtc_neteq_unittest.cc', + 'defines': [ + 'AUDIO_DECODER_UNITTEST', + 'WEBRTC_CODEC_G722', + 'WEBRTC_CODEC_ILBC', + 'WEBRTC_CODEC_ISACFX', + 'WEBRTC_CODEC_ISAC', + 'WEBRTC_CODEC_PCM16', + '<@(neteq_defines)', ], - # Disable warnings to enable Win64 build, issue 1323. - 'msvs_disabled_warnings': [ - 4267, # size_t to int truncation. + 'sources': [ + 'audio_decoder_impl.cc', + 'audio_decoder_impl.h', + 'audio_decoder_unittest.cc', + 'audio_decoder.cc', + 'interface/audio_decoder.h', ], 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], }], ], - }, # neteq_unittests - { - 'target_name': 'NetEqRTPplay', - 'type': 'executable', - 'dependencies': [ - 'NetEq', # NetEQ library defined above - 'NetEqTestTools', # Test helpers - 'G711', - 'G722', - 'PCM16B', - 'iLBC', - 'iSAC', - 'CNG', - ], - 'defines': [ - # TODO: Make codec selection conditional on definitions in target NetEq - 'CODEC_ILBC', - 'CODEC_PCM16B', - 'CODEC_G711', - 'CODEC_G722', - 'CODEC_ISAC', - 'CODEC_PCM16B_WB', - 'CODEC_ISAC_SWB', - 'CODEC_ISAC_FB', - 'CODEC_PCM16B_32KHZ', - 'CODEC_CNGCODEC8', - 'CODEC_CNGCODEC16', - 'CODEC_CNGCODEC32', - 'CODEC_ATEVENT_DECODE', - 'CODEC_RED', - ], - 'include_dirs': [ - '.', - 'test', - ], - 'sources': [ - 'test/NetEqRTPplay.cc', - ], - # Disable warnings to enable Win64 build, issue 1323. - 'msvs_disabled_warnings': [ - 4267, # size_t to int truncation. - ], - }, + }, # audio_decoder_unittests { - 'target_name': 'neteq3_speed_test', - 'type': 'executable', - 'dependencies': [ - 'NetEq', - 'PCM16B', - 'neteq_unittest_tools', - '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', - '<(webrtc_root)/test/test.gyp:test_support_main', - ], - 'sources': [ - 'test/neteq_speed_test.cc', - ], - }, - - { - 'target_name': 'NetEqTestTools', - # Collection of useful functions used in other tests + 'target_name': 'neteq_unittest_tools', 'type': 'static_library', - 'variables': { - # Expects RTP packets without payloads when enabled. - 'neteq_dummy_rtp%': 0, - }, 'dependencies': [ - 'G711', - 'G722', - 'PCM16B', - 'iLBC', - 'iSAC', - 'CNG', - '<(DEPTH)/testing/gtest.gyp:gtest', + 'rtp_rtcp', ], 'direct_dependent_settings': { 'include_dirs': [ - 'interface', - 'test', + 'tools', ], }, - 'defines': [ - # TODO: Make codec selection conditional on definitions in target NetEq - 'CODEC_ILBC', - 'CODEC_PCM16B', - 'CODEC_G711', - 'CODEC_G722', - 'CODEC_ISAC', - 'CODEC_PCM16B_WB', - 'CODEC_ISAC_SWB', - 'CODEC_ISAC_FB', - 'CODEC_PCM16B_32KHZ', - 'CODEC_CNGCODEC8', - 'CODEC_CNGCODEC16', - 'CODEC_CNGCODEC32', - 'CODEC_ATEVENT_DECODE', - 'CODEC_RED', - ], 'include_dirs': [ - 'interface', - 'test', + 'tools', ], 'sources': [ - 'test/NETEQTEST_CodecClass.cc', - 'test/NETEQTEST_CodecClass.h', - 'test/NETEQTEST_DummyRTPpacket.cc', - 'test/NETEQTEST_DummyRTPpacket.h', - 'test/NETEQTEST_NetEQClass.cc', - 'test/NETEQTEST_NetEQClass.h', - 'test/NETEQTEST_RTPpacket.cc', - 'test/NETEQTEST_RTPpacket.h', - ], - # Disable warnings to enable Win64 build, issue 1323. - 'msvs_disabled_warnings': [ - 4267, # size_t to int truncation. - ], - }, + 'tools/audio_checksum.h', + 'tools/audio_loop.cc', + 'tools/audio_loop.h', + 'tools/audio_sink.h', + 'tools/input_audio_file.cc', + 'tools/input_audio_file.h', + 'tools/output_audio_file.h', + 'tools/packet.cc', + 'tools/packet.h', + 'tools/packet_source.h', + 'tools/rtp_file_source.cc', + 'tools/rtp_file_source.h', + 'tools/rtp_generator.cc', + 'tools/rtp_generator.h', + ], + }, # neteq_unittest_tools ], # targets 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'targets': [ { - 'target_name': 'neteq_unittests_apk_target', + 'target_name': 'audio_decoder_unittests_apk_target', 'type': 'none', 'dependencies': [ - '<(apk_tests_path):neteq_unittests_apk', + '<(apk_tests_path):audio_decoder_unittests_apk', ], }, ], @@ -251,17 +216,17 @@ ['test_isolation_mode != "noop"', { 'targets': [ { - 'target_name': 'neteq_unittests_run', + 'target_name': 'audio_decoder_unittests_run', 'type': 'none', 'dependencies': [ - 'neteq_unittests', + 'audio_decoder_unittests', ], 'includes': [ '../../../build/isolate.gypi', - 'neteq_unittests.isolate', + 'audio_decoder_unittests.isolate', ], 'sources': [ - 'neteq_unittests.isolate', + 'audio_decoder_unittests.isolate', ], }, ], diff --git a/webrtc/modules/audio_coding/neteq/neteq_defines.h b/webrtc/modules/audio_coding/neteq/neteq_defines.h deleted file mode 100644 index b3b3da5b7..000000000 --- a/webrtc/modules/audio_coding/neteq/neteq_defines.h +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/***************************************************************************************** - * - * Compilation flags in NetEQ: - * - ***************************************************************************************** - * - ***** Platform flags ****** - * - * SCRATCH Run NetEQ with "Scratch memory" to save some stack memory. - * Definition can be used on all platforms - * - ***** Summary flags ****** - * - * NETEQ_ALL_SPECIAL_CODECS Add support for special codecs (CN/RED/DTMF) - * - * NETEQ_ALL_NB_CODECS Add support for all NB codecs (except CN/RED/DTMF) - * - * NETEQ_ALL_WB_CODECS Add support for all WB codecs (except CN/RED/DTMF) - * - * NETEQ_VOICEENGINE_CODECS Support for all NB, WB and SWB32 codecs and CN, RED and DTMF - * - * NETEQ_ALL_CODECS Support for all NB, WB, SWB 32kHz and SWB 48kHz as well as - * CN, RED and DTMF - * - ***** Sampling frequency ****** - * (Note: usually not needed when Summary flags are used) - * - * NETEQ_WIDEBAND Wideband enabled - * - * NETEQ_32KHZ_WIDEBAND Super wideband @ 32kHz enabled - * - * NETEQ_48KHZ_WIDEBAND Super wideband @ 48kHz enabled - * - ***** Special Codec ****** - * (Note: not needed if NETEQ_ALL_CODECS is used) - * - * NETEQ_RED_CODEC With this flag you enable NetEQ to understand redundancy in - * the RTP. NetEQ will use the redundancy if it's the same - * codec - * - * NETEQ_CNG_CODEC Enable DTX with the CN payload - * - * NETEQ_ATEVENT_DECODE Enable AVT event and play out the corresponding DTMF tone - * - ***** Speech Codecs ***** - * (Note: Not needed if Summary flags are used) - * - * NETEQ_G711_CODEC Enable G.711 u- and A-law - * - * NETEQ_PCM16B_CODEC Enable uncompressed 16-bit - * - * NETEQ_ILBC_CODEC Enable iLBC - * - * NETEQ_ISAC_CODEC Enable iSAC - * - * NETEQ_ISAC_SWB_CODEC Enable iSAC-SWB - * - * Note that the decoder of iSAC full-band operates at 32 kHz, that is the - * decoded signal is at 32 kHz. - * NETEQ_ISAC_FB_CODEC Enable iSAC-FB - * - * NETEQ_G722_CODEC Enable G.722 - * - * NETEQ_G729_CODEC Enable G.729 - * - * NETEQ_G729_1_CODEC Enable G.729.1 - * - * NETEQ_G726_CODEC Enable G.726 - * - * NETEQ_G722_1_CODEC Enable G722.1 - * - * NETEQ_G722_1C_CODEC Enable G722.1 Annex C - * - * NETEQ_OPUS_CODEC Enable Opus - * - * NETEQ_SPEEX_CODEC Enable Speex (at 8 and 16 kHz sample rate) - * - * NETEQ_CELT_CODEC Enable Celt (at 32 kHz sample rate) - * - * NETEQ_GSMFR_CODEC Enable GSM-FR - * - * NETEQ_AMR_CODEC Enable AMR (narrowband) - * - * NETEQ_AMRWB_CODEC Enable AMR-WB - * - * NETEQ_CNG_CODEC Enable DTX with the CNG payload - * - * NETEQ_ATEVENT_DECODE Enable AVT event and play out the corresponding DTMF tone - * - ***** Test flags ****** - * - * WEBRTC_NETEQ_40BITACC_TEST Run NetEQ with simulated 40-bit accumulator to run - * bit-exact to a DSP implementation where the main (splib - * and NetEQ) functions have been 40-bit optimized - * - ***************************************************************************************** - */ - -#if !defined NETEQ_DEFINES_H -#define NETEQ_DEFINES_H - -/* Data block structure for MCU to DSP communication: - * - * - * First 3 16-bit words are pre-header that contains instructions and timestamp update - * Fourth 16-bit word is length of data block 1 - * Rest is payload data - * - * 0 48 64 80 - * -------------...---------------------------------------------------------------------- - * | PreHeader ... | Length 1 | Payload data 1 ...... | Lenght 2| Data block 2.... | ... - * -------------...---------------------------------------------------------------------- - * - * - * Preheader: - * 4 MSB can be either of: - */ - -#define DSP_INSTR_NORMAL 0x1000 -/* Payload data will contain the encoded frames */ - -#define DSP_INSTR_MERGE 0x2000 -/* Payload data block 1 will contain the encoded frame */ -/* Info block will contain the number of missing samples */ - -#define DSP_INSTR_EXPAND 0x3000 -/* Payload data will be empty */ - -#define DSP_INSTR_ACCELERATE 0x4000 -/* Payload data will contain the encoded frame */ - -#define DSP_INSTR_DO_RFC3389CNG 0x5000 -/* Payload data will contain the SID frame if there is one*/ - -#define DSP_INSTR_DTMF_GENERATE 0x6000 -/* Payload data will be one int16_t with the current DTMF value and one - * int16_t with the current volume value - */ -#define DSP_INSTR_NORMAL_ONE_DESC 0x7000 -/* No encoded frames */ - -#define DSP_INSTR_DO_CODEC_INTERNAL_CNG 0x8000 -/* Codec has a built-in VAD/DTX scheme (use the above for "no transmission") */ - -#define DSP_INSTR_PREEMPTIVE_EXPAND 0x9000 -/* Payload data will contain the encoded frames, if any */ - -#define DSP_INSTR_DO_ALTERNATIVE_PLC 0xB000 -/* NetEQ switched off and packet missing... */ - -#define DSP_INSTR_DO_ALTERNATIVE_PLC_INC_TS 0xC000 -/* NetEQ switched off and packet missing... */ - -#define DSP_INSTR_DO_AUDIO_REPETITION 0xD000 -/* NetEQ switched off and packet missing... */ - -#define DSP_INSTR_DO_AUDIO_REPETITION_INC_TS 0xE000 -/* NetEQ switched off and packet missing... */ - -#define DSP_INSTR_FADE_TO_BGN 0xF000 -/* Exception handling: fade out to BGN (expand) */ - -/* - * Next 4 bits signal additional data that needs to be transmitted - */ - -#define DSP_CODEC_NO_CHANGE 0x0100 -#define DSP_CODEC_NEW_CODEC 0x0200 -#define DSP_CODEC_ADD_LATE_PKT 0x0300 -#define DSP_CODEC_RESET 0x0400 -#define DSP_DTMF_PAYLOAD 0x0010 - -/* - * The most significant bit of the payload-length - * is used to flag whether the associated payload - * is redundant payload. This currently useful only for - * iSAC, where redundant payloads have to be treated - * differently. Every time the length is read it must be - * masked by DSP_CODEC_MASK_RED_FLAG to ignore the flag. - * Use DSP_CODEC_RED_FLAG to set or retrieve the flag. - */ -#define DSP_CODEC_MASK_RED_FLAG 0x7FFF -#define DSP_CODEC_RED_FLAG 0x8000 - -/* - * The first block of payload data consist of decode function pointers, - * and then the speech blocks. - * - */ - - -/* - * The playout modes that NetEq produced (i.e. gives more info about if the - * Accelerate was successful or not) - */ - -#define MODE_NORMAL 0x0000 -#define MODE_EXPAND 0x0001 -#define MODE_MERGE 0x0002 -#define MODE_SUCCESS_ACCELERATE 0x0003 -#define MODE_UNSUCCESS_ACCELERATE 0x0004 -#define MODE_RFC3389CNG 0x0005 -#define MODE_LOWEN_ACCELERATE 0x0006 -#define MODE_DTMF 0x0007 -#define MODE_ONE_DESCRIPTOR 0x0008 -#define MODE_CODEC_INTERNAL_CNG 0x0009 -#define MODE_SUCCESS_PREEMPTIVE 0x000A -#define MODE_UNSUCCESS_PREEMPTIVE 0x000B -#define MODE_LOWEN_PREEMPTIVE 0x000C -#define MODE_FADE_TO_BGN 0x000D - -#define MODE_ERROR 0x0010 - -#define MODE_AWAITING_CODEC_PTR 0x0100 - -#define MODE_BGN_ONLY 0x0200 - -#define MODE_MASTER_DTMF_SIGNAL 0x0400 - -#define MODE_USING_STEREO 0x0800 - - - -/***********************/ -/* Group codec defines */ -/***********************/ - -#if (defined(NETEQ_ALL_SPECIAL_CODECS)) - #define NETEQ_CNG_CODEC - #define NETEQ_ATEVENT_DECODE - #define NETEQ_RED_CODEC - #define NETEQ_VAD - #define NETEQ_ARBITRARY_CODEC -#endif - -#if (defined(NETEQ_ALL_NB_CODECS)) /* Except RED, DTMF and CNG */ - #define NETEQ_PCM16B_CODEC - #define NETEQ_G711_CODEC - #define NETEQ_ILBC_CODEC - #define NETEQ_G729_CODEC - #define NETEQ_G726_CODEC - #define NETEQ_GSMFR_CODEC - #define NETEQ_OPUS_CODEC - #define NETEQ_AMR_CODEC -#endif - -#if (defined(NETEQ_ALL_WB_CODECS)) /* Except RED, DTMF and CNG */ - #define NETEQ_ISAC_CODEC - #define NETEQ_G722_CODEC - #define NETEQ_G722_1_CODEC - #define NETEQ_G729_1_CODEC - #define NETEQ_OPUS_CODEC - #define NETEQ_SPEEX_CODEC - #define NETEQ_AMRWB_CODEC - #define NETEQ_WIDEBAND -#endif - -#if (defined(NETEQ_ALL_WB32_CODECS)) /* AAC, RED, DTMF and CNG */ - #define NETEQ_ISAC_SWB_CODEC - #define NETEQ_32KHZ_WIDEBAND - #define NETEQ_G722_1C_CODEC - #define NETEQ_CELT_CODEC - #define NETEQ_OPUS_CODEC -#endif - -#if (defined(NETEQ_VOICEENGINE_CODECS)) - /* Special codecs */ - #define NETEQ_CNG_CODEC - #define NETEQ_ATEVENT_DECODE - #define NETEQ_RED_CODEC - #define NETEQ_VAD - #define NETEQ_ARBITRARY_CODEC - - /* Narrowband codecs */ - #define NETEQ_PCM16B_CODEC - #define NETEQ_G711_CODEC - #define NETEQ_ILBC_CODEC - #define NETEQ_AMR_CODEC - #define NETEQ_G729_CODEC - #define NETEQ_GSMFR_CODEC - - /* Wideband codecs */ - #define NETEQ_WIDEBAND - #define NETEQ_ISAC_CODEC - #define NETEQ_G722_CODEC - #define NETEQ_G722_1_CODEC - #define NETEQ_G729_1_CODEC - #define NETEQ_AMRWB_CODEC - #define NETEQ_SPEEX_CODEC - - /* Super wideband 32kHz codecs */ - #define NETEQ_ISAC_SWB_CODEC - #define NETEQ_32KHZ_WIDEBAND - #define NETEQ_G722_1C_CODEC - #define NETEQ_CELT_CODEC - - /* Fullband 48 kHz codecs */ - #define NETEQ_OPUS_CODEC - #define NETEQ_ISAC_FB_CODEC -#endif - -#if (defined(NETEQ_ALL_CODECS)) - /* Special codecs */ - #define NETEQ_CNG_CODEC - #define NETEQ_ATEVENT_DECODE - #define NETEQ_RED_CODEC - #define NETEQ_VAD - #define NETEQ_ARBITRARY_CODEC - - /* Narrowband codecs */ - #define NETEQ_PCM16B_CODEC - #define NETEQ_G711_CODEC - #define NETEQ_ILBC_CODEC - #define NETEQ_G729_CODEC - #define NETEQ_G726_CODEC - #define NETEQ_GSMFR_CODEC - #define NETEQ_AMR_CODEC - - /* Wideband codecs */ - #define NETEQ_WIDEBAND - #define NETEQ_ISAC_CODEC - #define NETEQ_G722_CODEC - #define NETEQ_G722_1_CODEC - #define NETEQ_G729_1_CODEC - #define NETEQ_SPEEX_CODEC - #define NETEQ_AMRWB_CODEC - - /* Super wideband 32kHz codecs */ - #define NETEQ_ISAC_SWB_CODEC - #define NETEQ_32KHZ_WIDEBAND - #define NETEQ_G722_1C_CODEC - #define NETEQ_CELT_CODEC - - /* Super wideband 48kHz codecs */ - #define NETEQ_48KHZ_WIDEBAND - #define NETEQ_OPUS_CODEC - #define NETEQ_ISAC_FB_CODEC -#endif - -/* Max output size from decoding one frame */ -#if defined(NETEQ_48KHZ_WIDEBAND) - #define NETEQ_MAX_FRAME_SIZE 5760 /* 120 ms super wideband */ - #define NETEQ_MAX_OUTPUT_SIZE 6480 /* 120+15 ms super wideband (120 ms - * decoded + 15 ms for merge overlap) */ -#elif defined(NETEQ_32KHZ_WIDEBAND) - #define NETEQ_MAX_FRAME_SIZE 3840 /* 120 ms super wideband */ - #define NETEQ_MAX_OUTPUT_SIZE 4320 /* 120+15 ms super wideband (120 ms - * decoded + 15 ms for merge overlap) */ -#elif defined(NETEQ_WIDEBAND) - #define NETEQ_MAX_FRAME_SIZE 1920 /* 120 ms wideband */ - #define NETEQ_MAX_OUTPUT_SIZE 2160 /* 120+15 ms wideband (120 ms decoded + - * 15 ms for merge overlap) */ -#else - #define NETEQ_MAX_FRAME_SIZE 960 /* 120 ms narrowband */ - #define NETEQ_MAX_OUTPUT_SIZE 1080 /* 120+15 ms narrowband (120 ms decoded - * + 15 ms for merge overlap) */ -#endif - - -/* Enable stereo */ -#define NETEQ_STEREO - -#endif /* #if !defined NETEQ_DEFINES_H */ - diff --git a/webrtc/modules/audio_coding/neteq/neteq_error_codes.h b/webrtc/modules/audio_coding/neteq/neteq_error_codes.h deleted file mode 100644 index ab639d9c3..000000000 --- a/webrtc/modules/audio_coding/neteq/neteq_error_codes.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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. - */ - -/* - * Definition of error codes. - * - * NOTE: When modifying the error codes, - * also modify the function WebRtcNetEQ_GetErrorCode! - */ - -#if !defined NETEQ_ERROR_CODES_H -#define NETEQ_ERROR_CODES_H - -/* Misc Error */ -#define NETEQ_OTHER_ERROR -1000 - -/* Misc Recout Errors */ -#define FAULTY_INSTRUCTION -1001 -#define FAULTY_NETWORK_TYPE -1002 -#define FAULTY_DELAYVALUE -1003 -#define FAULTY_PLAYOUTMODE -1004 -#define CORRUPT_INSTANCE -1005 -#define ILLEGAL_MASTER_SLAVE_SWITCH -1006 -#define MASTER_SLAVE_ERROR -1007 - -/* Misc Recout problems */ -#define UNKNOWN_BUFSTAT_DECISION -2001 -#define RECOUT_ERROR_DECODING -2002 -#define RECOUT_ERROR_SAMPLEUNDERRUN -2003 -#define RECOUT_ERROR_DECODED_TOO_MUCH -2004 - -/* Misc RecIn problems */ -#define RECIN_CNG_ERROR -3001 -#define RECIN_UNKNOWNPAYLOAD -3002 -#define RECIN_BUFFERINSERT_ERROR -3003 -#define RECIN_SYNC_RTP_CHANGED_CODEC -3004 -#define RECIN_SYNC_RTP_NOT_ACCEPTABLE -3005 - -/* PBUFFER/BUFSTAT ERRORS */ -#define PBUFFER_INIT_ERROR -4001 -#define PBUFFER_INSERT_ERROR1 -4002 -#define PBUFFER_INSERT_ERROR2 -4003 -#define PBUFFER_INSERT_ERROR3 -4004 -#define PBUFFER_INSERT_ERROR4 -4005 -#define PBUFFER_INSERT_ERROR5 -4006 -#define UNKNOWN_G723_HEADER -4007 -#define PBUFFER_NONEXISTING_PACKET -4008 -#define PBUFFER_NOT_INITIALIZED -4009 -#define AMBIGUOUS_ILBC_FRAME_SIZE -4010 - -/* CODEC DATABASE ERRORS */ -#define CODEC_DB_FULL -5001 -#define CODEC_DB_NOT_EXIST1 -5002 -#define CODEC_DB_NOT_EXIST2 -5003 -#define CODEC_DB_NOT_EXIST3 -5004 -#define CODEC_DB_NOT_EXIST4 -5005 -#define CODEC_DB_UNKNOWN_CODEC -5006 -#define CODEC_DB_PAYLOAD_TAKEN -5007 -#define CODEC_DB_UNSUPPORTED_CODEC -5008 -#define CODEC_DB_UNSUPPORTED_FS -5009 - -/* DTMF ERRORS */ -#define DTMF_DEC_PARAMETER_ERROR -6001 -#define DTMF_INSERT_ERROR -6002 -#define DTMF_GEN_UNKNOWN_SAMP_FREQ -6003 -#define DTMF_NOT_SUPPORTED -6004 - -/* RTP/PACKET ERRORS */ -#define RED_SPLIT_ERROR1 -7001 -#define RED_SPLIT_ERROR2 -7002 -#define RTP_TOO_SHORT_PACKET -7003 -#define RTP_CORRUPT_PACKET -7004 - -#endif diff --git a/webrtc/modules/audio_coding/neteq4/neteq_external_decoder_unittest.cc b/webrtc/modules/audio_coding/neteq/neteq_external_decoder_unittest.cc similarity index 93% rename from webrtc/modules/audio_coding/neteq4/neteq_external_decoder_unittest.cc rename to webrtc/modules/audio_coding/neteq/neteq_external_decoder_unittest.cc index fec25e985..a40107651 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_external_decoder_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/neteq_external_decoder_unittest.cc @@ -15,10 +15,10 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_external_decoder_pcm16b.h" -#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h" -#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/gtest_disable.h" @@ -45,13 +45,15 @@ class NetEqExternalDecoderTest : public ::testing::Test { frame_size_ms_(10), frame_size_samples_(frame_size_ms_ * samples_per_ms_), output_size_samples_(frame_size_ms_ * samples_per_ms_), - neteq_external_(NetEq::Create(sample_rate_hz_)), - neteq_(NetEq::Create(sample_rate_hz_)), external_decoder_(new MockExternalPcm16B(kDecoderPCM16Bswb32kHz)), rtp_generator_(samples_per_ms_), payload_size_bytes_(0), last_send_time_(0), last_arrival_time_(0) { + NetEq::Config config; + config.sample_rate_hz = sample_rate_hz_; + neteq_external_ = NetEq::Create(config); + neteq_ = NetEq::Create(config); input_ = new int16_t[frame_size_samples_]; encoded_ = new uint8_t[2 * frame_size_samples_]; } @@ -78,7 +80,6 @@ class NetEqExternalDecoderTest : public ::testing::Test { ASSERT_EQ(NetEq::kOK, neteq_external_->RegisterExternalDecoder(external_decoder_, decoder, - sample_rate_hz_, kPayloadType)); ASSERT_EQ(NetEq::kOK, neteq_->RegisterPayloadType(decoder, kPayloadType)); @@ -202,7 +203,7 @@ class NetEqExternalDecoderTest : public ::testing::Test { scoped_ptr input_file_; }; -TEST_F(NetEqExternalDecoderTest, DISABLED_ON_ANDROID(RunTest)) { +TEST_F(NetEqExternalDecoderTest, RunTest) { RunTest(100); // Run 100 laps @ 10 ms each in the test loop. } diff --git a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc b/webrtc/modules/audio_coding/neteq/neteq_impl.cc similarity index 95% rename from webrtc/modules/audio_coding/neteq4/neteq_impl.cc rename to webrtc/modules/audio_coding/neteq/neteq_impl.cc index aed1dbb41..64a866039 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc +++ b/webrtc/modules/audio_coding/neteq/neteq_impl.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/neteq_impl.h" +#include "webrtc/modules/audio_coding/neteq/neteq_impl.h" #include #include // memset @@ -16,28 +16,28 @@ #include #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" -#include "webrtc/modules/audio_coding/neteq4/accelerate.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" -#include "webrtc/modules/audio_coding/neteq4/buffer_level_filter.h" -#include "webrtc/modules/audio_coding/neteq4/comfort_noise.h" -#include "webrtc/modules/audio_coding/neteq4/decision_logic.h" -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/defines.h" -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" -#include "webrtc/modules/audio_coding/neteq4/delay_peak_detector.h" -#include "webrtc/modules/audio_coding/neteq4/dtmf_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/dtmf_tone_generator.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/modules/audio_coding/neteq4/merge.h" -#include "webrtc/modules/audio_coding/neteq4/normal.h" -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/packet.h" -#include "webrtc/modules/audio_coding/neteq4/payload_splitter.h" -#include "webrtc/modules/audio_coding/neteq4/post_decode_vad.h" -#include "webrtc/modules/audio_coding/neteq4/preemptive_expand.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/timestamp_scaler.h" +#include "webrtc/modules/audio_coding/neteq/accelerate.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/comfort_noise.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/defines.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_buffer.h" +#include "webrtc/modules/audio_coding/neteq/dtmf_tone_generator.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/merge.h" +#include "webrtc/modules/audio_coding/neteq/normal.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" +#include "webrtc/modules/audio_coding/neteq/payload_splitter.h" +#include "webrtc/modules/audio_coding/neteq/post_decode_vad.h" +#include "webrtc/modules/audio_coding/neteq/preemptive_expand.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/timestamp_scaler.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/logging.h" @@ -61,8 +61,10 @@ NetEqImpl::NetEqImpl(int fs, TimestampScaler* timestamp_scaler, AccelerateFactory* accelerate_factory, ExpandFactory* expand_factory, - PreemptiveExpandFactory* preemptive_expand_factory) - : buffer_level_filter_(buffer_level_filter), + PreemptiveExpandFactory* preemptive_expand_factory, + bool create_components) + : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + buffer_level_filter_(buffer_level_filter), decoder_database_(decoder_database), delay_manager_(delay_manager), delay_peak_detector_(delay_peak_detector), @@ -76,7 +78,6 @@ NetEqImpl::NetEqImpl(int fs, accelerate_factory_(accelerate_factory), preemptive_expand_factory_(preemptive_expand_factory), last_mode_(kModeNormal), - mute_factor_array_(NULL), decoded_buffer_length_(kMaxFrameSize), decoded_buffer_(new int16_t[decoded_buffer_length_]), playout_timestamp_(0), @@ -89,7 +90,6 @@ NetEqImpl::NetEqImpl(int fs, first_packet_(true), error_code_(0), decoder_error_code_(0), - crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), decoded_packet_sequence_number_(-1), decoded_packet_timestamp_(0) { if (fs != 8000 && fs != 16000 && fs != 32000 && fs != 48000) { @@ -97,19 +97,15 @@ NetEqImpl::NetEqImpl(int fs, "Changing to 8000 Hz."; fs = 8000; } - LOG(LS_INFO) << "Create NetEqImpl object with fs = " << fs << "."; + LOG(LS_VERBOSE) << "Create NetEqImpl object with fs = " << fs << "."; fs_hz_ = fs; fs_mult_ = fs / 8000; output_size_samples_ = kOutputSizeMs * 8 * fs_mult_; decoder_frame_length_ = 3 * output_size_samples_; WebRtcSpl_Init(); - decision_logic_.reset(DecisionLogic::Create(fs_hz_, output_size_samples_, - kPlayoutOn, - decoder_database_.get(), - *packet_buffer_.get(), - delay_manager_.get(), - buffer_level_filter_.get())); - SetSampleRateAndChannels(fs, 1); // Default is 1 channel. + if (create_components) { + SetSampleRateAndChannels(fs, 1); // Default is 1 channel. + } } NetEqImpl::~NetEqImpl() { @@ -204,7 +200,6 @@ int NetEqImpl::RegisterPayloadType(enum NetEqDecoder codec, int NetEqImpl::RegisterExternalDecoder(AudioDecoder* decoder, enum NetEqDecoder codec, - int sample_rate_hz, uint8_t rtp_payload_type) { CriticalSectionScoped lock(crit_sect_.get()); LOG_API2(static_cast(rtp_payload_type), codec); @@ -213,6 +208,7 @@ int NetEqImpl::RegisterExternalDecoder(AudioDecoder* decoder, assert(false); return kFail; } + const int sample_rate_hz = AudioDecoder::CodecSampleRateHz(codec); int ret = decoder_database_->InsertExternal(rtp_payload_type, codec, sample_rate_hz, decoder); if (ret != DecoderDatabase::kOK) { @@ -284,12 +280,7 @@ void NetEqImpl::SetPlayoutMode(NetEqPlayoutMode mode) { CriticalSectionScoped lock(crit_sect_.get()); if (!decision_logic_.get() || mode != decision_logic_->playout_mode()) { // The reset() method calls delete for the old object. - decision_logic_.reset(DecisionLogic::Create(fs_hz_, output_size_samples_, - mode, - decoder_database_.get(), - *packet_buffer_.get(), - delay_manager_.get(), - buffer_level_filter_.get())); + CreateDecisionLogic(mode); } } @@ -344,9 +335,15 @@ void NetEqImpl::DisableVad() { vad_->Disable(); } -uint32_t NetEqImpl::PlayoutTimestamp() { +bool NetEqImpl::GetPlayoutTimestamp(uint32_t* timestamp) { CriticalSectionScoped lock(crit_sect_.get()); - return timestamp_scaler_->ToExternal(playout_timestamp_); + if (first_packet_) { + // We don't have a valid RTP timestamp until we have decoded our first + // RTP packet. + return false; + } + *timestamp = timestamp_scaler_->ToExternal(playout_timestamp_); + return true; } int NetEqImpl::LastError() { @@ -373,12 +370,9 @@ void NetEqImpl::FlushBuffers() { } void NetEqImpl::PacketBufferStatistics(int* current_num_packets, - int* max_num_packets, - int* current_memory_size_bytes, - int* max_memory_size_bytes) const { + int* max_num_packets) const { CriticalSectionScoped lock(crit_sect_.get()); - packet_buffer_->BufferStat(current_num_packets, max_num_packets, - current_memory_size_bytes, max_memory_size_bytes); + packet_buffer_->BufferStat(current_num_packets, max_num_packets); } int NetEqImpl::DecodedRtpInfo(int* sequence_number, uint32_t* timestamp) const { @@ -402,6 +396,11 @@ NetEqBackgroundNoiseMode NetEqImpl::BackgroundNoiseMode() const { return background_noise_->mode(); } +const SyncBuffer* NetEqImpl::sync_buffer_for_test() const { + CriticalSectionScoped lock(crit_sect_.get()); + return sync_buffer_.get(); +} + // Methods below this line are private. int NetEqImpl::InsertPacketInternal(const WebRtcRTPHeader& rtp_header, @@ -614,9 +613,6 @@ int NetEqImpl::InsertPacketInternal(const WebRtcRTPHeader& rtp_header, new_codec_ = true; update_sample_rate_and_channels = true; LOG_F(LS_WARNING) << "Packet buffer flushed"; - } else if (ret == PacketBuffer::kOversizePacket) { - LOG_F(LS_WARNING) << "Packet larger than packet buffer"; - return kOversizePacket; } else if (ret != PacketBuffer::kOK) { LOG_FERR1(LS_WARNING, InsertPacketList, packet_list.size()); PacketBuffer::DeleteAllPackets(&packet_list); @@ -943,7 +939,7 @@ int NetEqImpl::GetDecision(Operations* operation, return 0; } - decision_logic_->ExpandDecision(*operation == kExpand); + decision_logic_->ExpandDecision(*operation); // Check conditions for reset. if (new_codec_ || *operation == kUndefined) { @@ -1062,6 +1058,11 @@ int NetEqImpl::GetDecision(Operations* operation, // Move on with the preemptive expand decision. break; } + case kMerge: { + required_samples = + std::max(merge_->RequiredFutureSamples(), required_samples); + break; + } default: { // Do nothing. } @@ -1829,6 +1830,14 @@ int NetEqImpl::ExtractPackets(int required_samples, PacketList* packet_list) { return extracted_samples; } +void NetEqImpl::UpdatePlcComponents(int fs_hz, size_t channels) { + // Delete objects and create new ones. + expand_.reset(expand_factory_->Create(background_noise_.get(), + sync_buffer_.get(), &random_vector_, + fs_hz, channels)); + merge_.reset(new Merge(fs_hz, channels, expand_.get(), sync_buffer_.get())); +} + void NetEqImpl::SetSampleRateAndChannels(int fs_hz, size_t channels) { LOG_API2(fs_hz, channels); // TODO(hlundin): Change to an enumerator and skip assert. @@ -1876,21 +1885,20 @@ void NetEqImpl::SetSampleRateAndChannels(int fs_hz, size_t channels) { // Reset random vector. random_vector_.Reset(); - // Delete Expand object and create a new one. - expand_.reset(expand_factory_->Create(background_noise_.get(), - sync_buffer_.get(), &random_vector_, - fs_hz, channels)); + UpdatePlcComponents(fs_hz, channels); + // Move index so that we create a small set of future samples (all 0). sync_buffer_->set_next_index(sync_buffer_->next_index() - - expand_->overlap_length()); + expand_->overlap_length()); normal_.reset(new Normal(fs_hz, decoder_database_.get(), *background_noise_, expand_.get())); - merge_.reset(new Merge(fs_hz, channels, expand_.get(), sync_buffer_.get())); accelerate_.reset( accelerate_factory_->Create(fs_hz, channels, *background_noise_)); - preemptive_expand_.reset( - preemptive_expand_factory_->Create(fs_hz, channels, *background_noise_)); + preemptive_expand_.reset(preemptive_expand_factory_->Create( + fs_hz, channels, + *background_noise_, + static_cast(expand_->overlap_length()))); // Delete ComfortNoise object and create a new one. comfort_noise_.reset(new ComfortNoise(fs_hz, decoder_database_.get(), @@ -1903,8 +1911,11 @@ void NetEqImpl::SetSampleRateAndChannels(int fs_hz, size_t channels) { decoded_buffer_.reset(new int16_t[decoded_buffer_length_]); } - // Communicate new sample rate and output size to DecisionLogic object. - assert(decision_logic_.get()); + // Create DecisionLogic if it is not created yet, then communicate new sample + // rate and output size to DecisionLogic object. + if (!decision_logic_.get()) { + CreateDecisionLogic(kPlayoutOn); + } decision_logic_->SetSampleRate(fs_hz_, output_size_samples_); } @@ -1925,4 +1936,12 @@ NetEqOutputType NetEqImpl::LastOutputType() { } } +void NetEqImpl::CreateDecisionLogic(NetEqPlayoutMode mode) { + decision_logic_.reset(DecisionLogic::Create(fs_hz_, output_size_samples_, + mode, + decoder_database_.get(), + *packet_buffer_.get(), + delay_manager_.get(), + buffer_level_filter_.get())); +} } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/neteq_impl.h b/webrtc/modules/audio_coding/neteq/neteq_impl.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/neteq_impl.h rename to webrtc/modules/audio_coding/neteq/neteq_impl.h index 511452e95..e92babd8e 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_impl.h +++ b/webrtc/modules/audio_coding/neteq/neteq_impl.h @@ -8,19 +8,19 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_NETEQ_IMPL_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_NETEQ_IMPL_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_NETEQ_IMPL_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_NETEQ_IMPL_H_ #include -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/defines.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/packet.h" // Declare PacketList. -#include "webrtc/modules/audio_coding/neteq4/random_vector.h" -#include "webrtc/modules/audio_coding/neteq4/rtcp.h" -#include "webrtc/modules/audio_coding/neteq4/statistics_calculator.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/defines.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" // Declare PacketList. +#include "webrtc/modules/audio_coding/neteq/random_vector.h" +#include "webrtc/modules/audio_coding/neteq/rtcp.h" +#include "webrtc/modules/audio_coding/neteq/statistics_calculator.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/system_wrappers/interface/thread_annotations.h" #include "webrtc/typedefs.h" @@ -70,7 +70,8 @@ class NetEqImpl : public webrtc::NetEq { TimestampScaler* timestamp_scaler, AccelerateFactory* accelerate_factory, ExpandFactory* expand_factory, - PreemptiveExpandFactory* preemptive_expand_factory); + PreemptiveExpandFactory* preemptive_expand_factory, + bool create_components = true); virtual ~NetEqImpl(); @@ -114,11 +115,10 @@ class NetEqImpl : public webrtc::NetEq { // Provides an externally created decoder object |decoder| to insert in the // decoder database. The decoder implements a decoder of type |codec| and - // associates it with |rtp_payload_type|. The decoder operates at the - // frequency |sample_rate_hz|. Returns kOK on success, kFail on failure. + // associates it with |rtp_payload_type|. Returns kOK on success, kFail on + // failure. virtual int RegisterExternalDecoder(AudioDecoder* decoder, enum NetEqDecoder codec, - int sample_rate_hz, uint8_t rtp_payload_type); // Removes |rtp_payload_type| from the codec database. Returns 0 on success, @@ -166,8 +166,7 @@ class NetEqImpl : public webrtc::NetEq { // Disables post-decode VAD. virtual void DisableVad(); - // Returns the RTP timestamp for the last sample delivered by GetAudio(). - virtual uint32_t PlayoutTimestamp(); + virtual bool GetPlayoutTimestamp(uint32_t* timestamp); virtual int SetTargetNumberOfChannels() { return kNotImplemented; } @@ -186,9 +185,7 @@ class NetEqImpl : public webrtc::NetEq { virtual void FlushBuffers(); virtual void PacketBufferStatistics(int* current_num_packets, - int* max_num_packets, - int* current_memory_size_bytes, - int* max_memory_size_bytes) const; + int* max_num_packets) const; // Get sequence number and timestamp of the latest RTP. // This method is to facilitate NACK. @@ -200,7 +197,10 @@ class NetEqImpl : public webrtc::NetEq { // Gets background noise mode. virtual NetEqBackgroundNoiseMode BackgroundNoiseMode() const; - private: + // This accessor method is only intended for testing purposes. + virtual const SyncBuffer* sync_buffer_for_test() const; + + protected: static const int kOutputSizeMs = 10; static const int kMaxFrameSize = 2880; // 60 ms @ 48 kHz. // TODO(hlundin): Provide a better value for kSyncBufferSize. @@ -328,19 +328,33 @@ class NetEqImpl : public webrtc::NetEq { // GetAudio(). NetEqOutputType LastOutputType() EXCLUSIVE_LOCKS_REQUIRED(crit_sect_); - const scoped_ptr buffer_level_filter_; - const scoped_ptr decoder_database_; - const scoped_ptr delay_manager_; - const scoped_ptr delay_peak_detector_; - const scoped_ptr dtmf_buffer_; - const scoped_ptr dtmf_tone_generator_; - const scoped_ptr packet_buffer_; - const scoped_ptr payload_splitter_; - const scoped_ptr timestamp_scaler_; - const scoped_ptr vad_; - const scoped_ptr expand_factory_; - const scoped_ptr accelerate_factory_; - const scoped_ptr preemptive_expand_factory_; + // Updates Expand and Merge. + virtual void UpdatePlcComponents(int fs_hz, size_t channels) + EXCLUSIVE_LOCKS_REQUIRED(crit_sect_); + + // Creates DecisionLogic object for the given mode. + virtual void CreateDecisionLogic(NetEqPlayoutMode mode) + EXCLUSIVE_LOCKS_REQUIRED(crit_sect_); + + const scoped_ptr crit_sect_; + const scoped_ptr buffer_level_filter_ + GUARDED_BY(crit_sect_); + const scoped_ptr decoder_database_ GUARDED_BY(crit_sect_); + const scoped_ptr delay_manager_ GUARDED_BY(crit_sect_); + const scoped_ptr delay_peak_detector_ + GUARDED_BY(crit_sect_); + const scoped_ptr dtmf_buffer_ GUARDED_BY(crit_sect_); + const scoped_ptr dtmf_tone_generator_ + GUARDED_BY(crit_sect_); + const scoped_ptr packet_buffer_ GUARDED_BY(crit_sect_); + const scoped_ptr payload_splitter_ GUARDED_BY(crit_sect_); + const scoped_ptr timestamp_scaler_ GUARDED_BY(crit_sect_); + const scoped_ptr vad_ GUARDED_BY(crit_sect_); + const scoped_ptr expand_factory_ GUARDED_BY(crit_sect_); + const scoped_ptr accelerate_factory_ + GUARDED_BY(crit_sect_); + const scoped_ptr preemptive_expand_factory_ + GUARDED_BY(crit_sect_); scoped_ptr background_noise_ GUARDED_BY(crit_sect_); scoped_ptr decision_logic_ GUARDED_BY(crit_sect_); @@ -360,9 +374,9 @@ class NetEqImpl : public webrtc::NetEq { int output_size_samples_ GUARDED_BY(crit_sect_); int decoder_frame_length_ GUARDED_BY(crit_sect_); Modes last_mode_ GUARDED_BY(crit_sect_); - scoped_array mute_factor_array_ GUARDED_BY(crit_sect_); + scoped_ptr mute_factor_array_ GUARDED_BY(crit_sect_); size_t decoded_buffer_length_ GUARDED_BY(crit_sect_); - scoped_array decoded_buffer_ GUARDED_BY(crit_sect_); + scoped_ptr decoded_buffer_ GUARDED_BY(crit_sect_); uint32_t playout_timestamp_ GUARDED_BY(crit_sect_); bool new_codec_ GUARDED_BY(crit_sect_); uint32_t timestamp_ GUARDED_BY(crit_sect_); @@ -373,7 +387,6 @@ class NetEqImpl : public webrtc::NetEq { bool first_packet_ GUARDED_BY(crit_sect_); int error_code_ GUARDED_BY(crit_sect_); // Store last error code. int decoder_error_code_ GUARDED_BY(crit_sect_); - const scoped_ptr crit_sect_; // These values are used by NACK module to estimate time-to-play of // a missing packet. Occasionally, NetEq might decide to decode more @@ -385,8 +398,9 @@ class NetEqImpl : public webrtc::NetEq { int decoded_packet_sequence_number_ GUARDED_BY(crit_sect_); uint32_t decoded_packet_timestamp_ GUARDED_BY(crit_sect_); + private: DISALLOW_COPY_AND_ASSIGN(NetEqImpl); }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_NETEQ_IMPL_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_NETEQ_IMPL_H_ diff --git a/webrtc/modules/audio_coding/neteq4/neteq_impl_unittest.cc b/webrtc/modules/audio_coding/neteq/neteq_impl_unittest.cc similarity index 72% rename from webrtc/modules/audio_coding/neteq4/neteq_impl_unittest.cc rename to webrtc/modules/audio_coding/neteq/neteq_impl_unittest.cc index 295dc037f..2e66487fa 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_impl_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/neteq_impl_unittest.cc @@ -8,24 +8,25 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/neteq_impl.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/neteq_impl.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/accelerate.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_audio_decoder.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_buffer_level_filter.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_delay_manager.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_delay_peak_detector.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_dtmf_tone_generator.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_packet_buffer.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h" -#include "webrtc/modules/audio_coding/neteq4/preemptive_expand.h" -#include "webrtc/modules/audio_coding/neteq4/timestamp_scaler.h" +#include "webrtc/modules/audio_coding/neteq/accelerate.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_delay_peak_detector.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_dtmf_buffer.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_dtmf_tone_generator.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_payload_splitter.h" +#include "webrtc/modules/audio_coding/neteq/preemptive_expand.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/timestamp_scaler.h" using ::testing::Return; using ::testing::ReturnNull; @@ -47,9 +48,9 @@ int DeletePacketsAndReturnOk(PacketList* packet_list) { class NetEqImplTest : public ::testing::Test { protected: - static const int kInitSampleRateHz = 8000; NetEqImplTest() : neteq_(NULL), + config_(), mock_buffer_level_filter_(NULL), buffer_level_filter_(NULL), use_mock_buffer_level_filter_(true), @@ -74,7 +75,9 @@ class NetEqImplTest : public ::testing::Test { mock_payload_splitter_(NULL), payload_splitter_(NULL), use_mock_payload_splitter_(true), - timestamp_scaler_(NULL) {} + timestamp_scaler_(NULL) { + config_.sample_rate_hz = 8000; + } void CreateInstance() { if (use_mock_buffer_level_filter_) { @@ -99,19 +102,19 @@ class NetEqImplTest : public ::testing::Test { delay_peak_detector_ = new DelayPeakDetector; } if (use_mock_delay_manager_) { - mock_delay_manager_ = new MockDelayManager(NetEq::kMaxNumPacketsInBuffer, + mock_delay_manager_ = new MockDelayManager(config_.max_packets_in_buffer, delay_peak_detector_); EXPECT_CALL(*mock_delay_manager_, set_streaming_mode(false)).Times(1); delay_manager_ = mock_delay_manager_; } else { delay_manager_ = - new DelayManager(NetEq::kMaxNumPacketsInBuffer, delay_peak_detector_); + new DelayManager(config_.max_packets_in_buffer, delay_peak_detector_); } if (use_mock_dtmf_buffer_) { - mock_dtmf_buffer_ = new MockDtmfBuffer(kInitSampleRateHz); + mock_dtmf_buffer_ = new MockDtmfBuffer(config_.sample_rate_hz); dtmf_buffer_ = mock_dtmf_buffer_; } else { - dtmf_buffer_ = new DtmfBuffer(kInitSampleRateHz); + dtmf_buffer_ = new DtmfBuffer(config_.sample_rate_hz); } if (use_mock_dtmf_tone_generator_) { mock_dtmf_tone_generator_ = new MockDtmfToneGenerator; @@ -120,12 +123,10 @@ class NetEqImplTest : public ::testing::Test { dtmf_tone_generator_ = new DtmfToneGenerator; } if (use_mock_packet_buffer_) { - mock_packet_buffer_ = new MockPacketBuffer(NetEq::kMaxNumPacketsInBuffer, - NetEq::kMaxBytesInBuffer); + mock_packet_buffer_ = new MockPacketBuffer(config_.max_packets_in_buffer); packet_buffer_ = mock_packet_buffer_; } else { - packet_buffer_ = new PacketBuffer(NetEq::kMaxNumPacketsInBuffer, - NetEq::kMaxBytesInBuffer); + packet_buffer_ = new PacketBuffer(config_.max_packets_in_buffer); } if (use_mock_payload_splitter_) { mock_payload_splitter_ = new MockPayloadSplitter; @@ -139,7 +140,7 @@ class NetEqImplTest : public ::testing::Test { PreemptiveExpandFactory* preemptive_expand_factory = new PreemptiveExpandFactory; - neteq_ = new NetEqImpl(kInitSampleRateHz, + neteq_ = new NetEqImpl(config_.sample_rate_hz, buffer_level_filter_, decoder_database_, delay_manager_, @@ -193,6 +194,7 @@ class NetEqImplTest : public ::testing::Test { } NetEqImpl* neteq_; + NetEq::Config config_; MockBufferLevelFilter* mock_buffer_level_filter_; BufferLevelFilter* buffer_level_filter_; bool use_mock_buffer_level_filter_; @@ -224,7 +226,8 @@ class NetEqImplTest : public ::testing::Test { // This tests the interface class NetEq. // TODO(hlundin): Move to separate file? TEST(NetEq, CreateAndDestroy) { - NetEq* neteq = NetEq::Create(8000); + NetEq::Config config; + NetEq* neteq = NetEq::Create(config); delete neteq; } @@ -376,7 +379,7 @@ TEST_F(NetEqImplTest, InsertPacketsUntilBufferIsFull) { neteq_->RegisterPayloadType(kDecoderPCM16B, kPayloadType)); // Insert packets. The buffer should not flush. - for (int i = 1; i <= NetEq::kMaxNumPacketsInBuffer; ++i) { + for (int i = 1; i <= config_.max_packets_in_buffer; ++i) { EXPECT_EQ(NetEq::kOK, neteq_->InsertPacket( rtp_header, payload, kPayloadLengthBytes, kReceiveTime)); @@ -396,4 +399,100 @@ TEST_F(NetEqImplTest, InsertPacketsUntilBufferIsFull) { EXPECT_EQ(rtp_header.header.sequenceNumber, test_header->sequenceNumber); } +// This test verifies that timestamps propagate from the incoming packets +// through to the sync buffer and to the playout timestamp. +TEST_F(NetEqImplTest, VerifyTimestampPropagation) { + UseNoMocks(); + CreateInstance(); + + const uint8_t kPayloadType = 17; // Just an arbitrary number. + const uint32_t kReceiveTime = 17; // Value doesn't matter for this test. + const int kSampleRateHz = 8000; + const int kPayloadLengthSamples = 10 * kSampleRateHz / 1000; // 10 ms. + const size_t kPayloadLengthBytes = kPayloadLengthSamples; + uint8_t payload[kPayloadLengthBytes] = {0}; + WebRtcRTPHeader rtp_header; + rtp_header.header.payloadType = kPayloadType; + rtp_header.header.sequenceNumber = 0x1234; + rtp_header.header.timestamp = 0x12345678; + rtp_header.header.ssrc = 0x87654321; + + // This is a dummy decoder that produces as many output samples as the input + // has bytes. The output is an increasing series, starting at 1 for the first + // sample, and then increasing by 1 for each sample. + class CountingSamplesDecoder : public AudioDecoder { + public: + explicit CountingSamplesDecoder(enum NetEqDecoder type) + : AudioDecoder(type), next_value_(1) {} + + // Produce as many samples as input bytes (|encoded_len|). + virtual int Decode(const uint8_t* encoded, + size_t encoded_len, + int16_t* decoded, + SpeechType* speech_type) { + for (size_t i = 0; i < encoded_len; ++i) { + decoded[i] = next_value_++; + } + *speech_type = kSpeech; + return encoded_len; + } + + virtual int Init() { + next_value_ = 1; + return 0; + } + + uint16_t next_value() const { return next_value_; } + + private: + int16_t next_value_; + } decoder_(kDecoderPCM16B); + + EXPECT_EQ(NetEq::kOK, + neteq_->RegisterExternalDecoder( + &decoder_, kDecoderPCM16B, kPayloadType)); + + // Insert one packet. + EXPECT_EQ(NetEq::kOK, + neteq_->InsertPacket( + rtp_header, payload, kPayloadLengthBytes, kReceiveTime)); + + // Pull audio once. + const int kMaxOutputSize = 10 * kSampleRateHz / 1000; + int16_t output[kMaxOutputSize]; + int samples_per_channel; + int num_channels; + NetEqOutputType type; + EXPECT_EQ( + NetEq::kOK, + neteq_->GetAudio( + kMaxOutputSize, output, &samples_per_channel, &num_channels, &type)); + ASSERT_EQ(kMaxOutputSize, samples_per_channel); + EXPECT_EQ(1, num_channels); + EXPECT_EQ(kOutputNormal, type); + + // Start with a simple check that the fake decoder is behaving as expected. + EXPECT_EQ(kPayloadLengthSamples, decoder_.next_value() - 1); + + // The value of the last of the output samples is the same as the number of + // samples played from the decoded packet. Thus, this number + the RTP + // timestamp should match the playout timestamp. + uint32_t timestamp = 0; + EXPECT_TRUE(neteq_->GetPlayoutTimestamp(×tamp)); + EXPECT_EQ(rtp_header.header.timestamp + output[samples_per_channel - 1], + timestamp); + + // Check the timestamp for the last value in the sync buffer. This should + // be one full frame length ahead of the RTP timestamp. + const SyncBuffer* sync_buffer = neteq_->sync_buffer_for_test(); + ASSERT_TRUE(sync_buffer != NULL); + EXPECT_EQ(rtp_header.header.timestamp + kPayloadLengthSamples, + sync_buffer->end_timestamp()); + + // Check that the number of samples still to play from the sync buffer add + // up with what was already played out. + EXPECT_EQ(kPayloadLengthSamples - output[samples_per_channel - 1], + static_cast(sync_buffer->FutureLength())); +} + } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/neteq_statistics.h b/webrtc/modules/audio_coding/neteq/neteq_statistics.h deleted file mode 100644 index bba5b06b9..000000000 --- a/webrtc/modules/audio_coding/neteq/neteq_statistics.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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. - */ - -/* - * Definitions of statistics data structures for MCU and DSP sides. - */ - -#include "typedefs.h" - -#ifndef NETEQ_STATISTICS_H -#define NETEQ_STATISTICS_H - -/* - * Statistics struct on DSP side - */ -typedef struct -{ - - /* variables for in-call statistics; queried through WebRtcNetEQ_GetNetworkStatistics */ - uint32_t expandLength; /* number of samples produced through expand */ - uint32_t preemptiveLength; /* number of samples produced through pre-emptive - expand */ - uint32_t accelerateLength; /* number of samples removed through accelerate */ - int addedSamples; /* number of samples inserted in off mode */ - - /* variables for post-call statistics; queried through WebRtcNetEQ_GetJitterStatistics */ - uint32_t expandedVoiceSamples; /* number of voice samples produced through expand */ - uint32_t expandedNoiseSamples; /* number of noise (background) samples produced - through expand */ - -} DSPStats_t; - -typedef struct { - int preemptive_expand_bgn_samples; - int preemptive_expand_normal_samples; - - int expand_bgn_samples; - int expand_normal_samples; - - int merge_expand_bgn_samples; - int merge_expand_normal_samples; - - int accelerate_bgn_samples; - int accelarate_normal_samples; -} ActivityStats; - - -#endif - diff --git a/webrtc/modules/audio_coding/neteq4/neteq_stereo_unittest.cc b/webrtc/modules/audio_coding/neteq/neteq_stereo_unittest.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/neteq_stereo_unittest.cc rename to webrtc/modules/audio_coding/neteq/neteq_stereo_unittest.cc index df212db2d..3c695c81d 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_stereo_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/neteq_stereo_unittest.cc @@ -16,9 +16,9 @@ #include "gtest/gtest.h" #include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h" -#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/gtest_disable.h" @@ -54,14 +54,16 @@ class NetEqStereoTest : public ::testing::TestWithParam { frame_size_ms_(GetParam().frame_size), frame_size_samples_(frame_size_ms_ * samples_per_ms_), output_size_samples_(10 * samples_per_ms_), - neteq_mono_(NetEq::Create(sample_rate_hz_)), - neteq_(NetEq::Create(sample_rate_hz_)), rtp_generator_mono_(samples_per_ms_), rtp_generator_(samples_per_ms_), payload_size_bytes_(0), multi_payload_size_bytes_(0), last_send_time_(0), last_arrival_time_(0) { + NetEq::Config config; + config.sample_rate_hz = sample_rate_hz_; + neteq_mono_ = NetEq::Create(config); + neteq_ = NetEq::Create(config); input_ = new int16_t[frame_size_samples_]; encoded_ = new uint8_t[2 * frame_size_samples_]; input_multi_channel_ = new int16_t[frame_size_samples_ * num_channels_]; diff --git a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi similarity index 77% rename from webrtc/modules/audio_coding/neteq4/neteq_tests.gypi rename to webrtc/modules/audio_coding/neteq/neteq_tests.gypi index 9d0aa4233..97d835f18 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi +++ b/webrtc/modules/audio_coding/neteq/neteq_tests.gypi @@ -12,8 +12,8 @@ 'target_name': 'neteq_rtpplay', 'type': 'executable', 'dependencies': [ - 'NetEq4', - 'NetEq4TestTools', + 'neteq', + 'neteq_test_tools', 'neteq_unittest_tools', 'PCM16B', '<(webrtc_root)/test/test.gyp:test_support_main', @@ -31,7 +31,7 @@ 'type': 'executable', 'dependencies': [ # TODO(hlundin): Make RTPencode use ACM to encode files. - 'NetEq4TestTools',# Test helpers + 'neteq_test_tools',# Test helpers 'G711', 'G722', 'PCM16B', @@ -49,6 +49,7 @@ 'CODEC_PCM16B_WB', 'CODEC_ISAC_SWB', 'CODEC_PCM16B_32KHZ', + 'CODEC_PCM16B_48KHZ', 'CODEC_CNGCODEC8', 'CODEC_CNGCODEC16', 'CODEC_CNGCODEC32', @@ -84,9 +85,10 @@ 'target_name': 'rtp_analyze', 'type': 'executable', 'dependencies': [ - 'NetEq4TestTools', + 'neteq_unittest_tools', '<(DEPTH)/testing/gtest.gyp:gtest', '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', ], 'sources': [ 'tools/rtp_analyze.cc', @@ -97,7 +99,7 @@ 'target_name': 'RTPchange', 'type': 'executable', 'dependencies': [ - 'NetEq4TestTools', + 'neteq_test_tools', '<(DEPTH)/testing/gtest.gyp:gtest', ], 'sources': [ @@ -109,7 +111,7 @@ 'target_name': 'RTPtimeshift', 'type': 'executable', 'dependencies': [ - 'NetEq4TestTools', + 'neteq_test_tools', '<(DEPTH)/testing/gtest.gyp:gtest', ], 'sources': [ @@ -121,7 +123,7 @@ 'target_name': 'RTPcat', 'type': 'executable', 'dependencies': [ - 'NetEq4TestTools', + 'neteq_test_tools', '<(DEPTH)/testing/gtest.gyp:gtest', ], 'sources': [ @@ -133,7 +135,7 @@ 'target_name': 'rtp_to_text', 'type': 'executable', 'dependencies': [ - 'NetEq4TestTools', + 'neteq_test_tools', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', ], 'sources': [ @@ -145,7 +147,7 @@ 'target_name': 'audio_classifier_test', 'type': 'executable', 'dependencies': [ - 'NetEq4', + 'neteq', ], 'sources': [ 'test/audio_classifier_test.cc', @@ -153,12 +155,29 @@ }, { - 'target_name': 'neteq4_speed_test', - 'type': 'executable', + 'target_name': 'neteq_test_support', + 'type': 'static_library', 'dependencies': [ - 'NetEq4', - 'neteq_unittest_tools', + 'neteq', 'PCM16B', + 'neteq_unittest_tools', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + ], + 'sources': [ + 'tools/neteq_performance_test.cc', + 'tools/neteq_performance_test.h', + 'tools/neteq_quality_test.cc', + 'tools/neteq_quality_test.h', + ], + }, # neteq_test_support + + { + 'target_name': 'neteq_speed_test', + 'type': 'executable', + 'dependencies': [ + 'neteq', + 'neteq_test_support', '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', '<(webrtc_root)/test/test.gyp:test_support_main', ], @@ -168,11 +187,11 @@ }, { - 'target_name': 'neteq4_opus_fec_quality_test', + 'target_name': 'neteq_opus_fec_quality_test', 'type': 'executable', 'dependencies': [ - 'NetEq4', - 'neteq_unittest_tools', + 'neteq', + 'neteq_test_support', 'webrtc_opus', '<(DEPTH)/testing/gtest.gyp:gtest', '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', @@ -184,7 +203,23 @@ }, { - 'target_name': 'NetEq4TestTools', + 'target_name': 'neteq_isac_quality_test', + 'type': 'executable', + 'dependencies': [ + 'neteq', + 'neteq_test_support', + 'iSACFix', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + '<(webrtc_root)/test/test.gyp:test_support_main', + ], + 'sources': [ + 'test/neteq_isac_quality_test.cc', + ], + }, + + { + 'target_name': 'neteq_test_tools', # Collection of useful functions used in other tests. 'type': 'static_library', 'variables': { diff --git a/webrtc/modules/audio_coding/neteq4/neteq_unittest.cc b/webrtc/modules/audio_coding/neteq/neteq_unittest.cc similarity index 91% rename from webrtc/modules/audio_coding/neteq4/neteq_unittest.cc rename to webrtc/modules/audio_coding/neteq/neteq_unittest.cc index 365e233e6..0233e1950 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/neteq_unittest.cc @@ -12,19 +12,20 @@ * This file includes unit tests for NetEQ. */ -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" #include #include #include // memset +#include #include #include #include #include "gflags/gflags.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h" +#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" #include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/gtest_disable.h" @@ -140,7 +141,7 @@ void RefFiles::ReadFromFileAndCompare( NetEqNetworkStatistics ref_stats; ASSERT_EQ(1u, fread(&ref_stats, stat_size, 1, input_fp_)); // Compare - EXPECT_EQ(0, memcmp(&stats, &ref_stats, stat_size)); + ASSERT_EQ(0, memcmp(&stats, &ref_stats, stat_size)); } } @@ -173,11 +174,11 @@ void RefFiles::ReadFromFileAndCompare( ASSERT_EQ(1u, fread(&(ref_stats.jitter), sizeof(ref_stats.jitter), 1, input_fp_)); // Compare - EXPECT_EQ(ref_stats.fraction_lost, stats.fraction_lost); - EXPECT_EQ(ref_stats.cumulative_lost, stats.cumulative_lost); - EXPECT_EQ(ref_stats.extended_max_sequence_number, + ASSERT_EQ(ref_stats.fraction_lost, stats.fraction_lost); + ASSERT_EQ(ref_stats.cumulative_lost, stats.cumulative_lost); + ASSERT_EQ(ref_stats.extended_max_sequence_number, stats.extended_max_sequence_number); - EXPECT_EQ(ref_stats.jitter, stats.jitter); + ASSERT_EQ(ref_stats.jitter, stats.jitter); } } @@ -227,11 +228,14 @@ class NetEqDecodingTest : public ::testing::Test { void DuplicateCng(); + uint32_t PlayoutTimestamp(); + NetEq* neteq_; FILE* rtp_fp_; unsigned int sim_clock_; int16_t out_data_[kMaxBlockSize]; int output_sample_rate_; + int algorithmic_delay_ms_; }; // Allocating the static const so that it can be passed by reference. @@ -246,12 +250,18 @@ NetEqDecodingTest::NetEqDecodingTest() : neteq_(NULL), rtp_fp_(NULL), sim_clock_(0), - output_sample_rate_(kInitSampleRateHz) { + output_sample_rate_(kInitSampleRateHz), + algorithmic_delay_ms_(0) { memset(out_data_, 0, sizeof(out_data_)); } void NetEqDecodingTest::SetUp() { - neteq_ = NetEq::Create(kInitSampleRateHz); + NetEq::Config config; + config.sample_rate_hz = kInitSampleRateHz; + neteq_ = NetEq::Create(config); + NetEqNetworkStatistics stat; + ASSERT_EQ(0, neteq_->NetworkStatistics(&stat)); + algorithmic_delay_ms_ = stat.current_buffer_size_ms; ASSERT_TRUE(neteq_); LoadDecoders(); } @@ -373,19 +383,20 @@ void NetEqDecodingTest::DecodeAndCheckStats(const std::string &rtp_file, ASSERT_GT(rtp.readFromFile(rtp_fp_), 0); while (rtp.dataLen() >= 0) { int out_len; - Process(&rtp, &out_len); + ASSERT_NO_FATAL_FAILURE(Process(&rtp, &out_len)); // Query the network statistics API once per second if (sim_clock_ % 1000 == 0) { // Process NetworkStatistics. NetEqNetworkStatistics network_stats; ASSERT_EQ(0, neteq_->NetworkStatistics(&network_stats)); - network_stat_files.ProcessReference(network_stats); + ASSERT_NO_FATAL_FAILURE( + network_stat_files.ProcessReference(network_stats)); // Process RTCPstat. RtcpStatistics rtcp_stats; neteq_->GetRtcpStatistics(&rtcp_stats); - rtcp_stat_files.ProcessReference(rtcp_stats); + ASSERT_NO_FATAL_FAILURE(rtcp_stat_files.ProcessReference(rtcp_stats)); } } } @@ -483,8 +494,8 @@ void NetEqDecodingTest::CheckBgnOff(int sampling_rate_hz, ASSERT_EQ(expected_samples_per_channel, samples_per_channel); // To be able to test the fading of background noise we need at lease to pull - // 610 frames. - const int kFadingThreshold = 610; + // 611 frames. + const int kFadingThreshold = 611; // Test several CNG-to-PLC packet for the expected behavior. The number 20 is // arbitrary, but sufficiently large to test enough number of frames. @@ -515,25 +526,14 @@ void NetEqDecodingTest::CheckBgnOff(int sampling_rate_hz, EXPECT_TRUE(plc_to_cng); // Just to be sure that PLC-to-CNG has occurred. } -#if defined(_WIN32) && defined(WEBRTC_ARCH_64_BITS) -// Disabled for Windows 64-bit until webrtc:1458 is fixed. -#define MAYBE_TestBitExactness DISABLED_TestBitExactness -#else -#define MAYBE_TestBitExactness TestBitExactness -#endif - -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(MAYBE_TestBitExactness)) { +TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(TestBitExactness)) { const std::string input_rtp_file = webrtc::test::ProjectRootPath() + "resources/audio_coding/neteq_universal_new.rtp"; -#if defined(_MSC_VER) && (_MSC_VER >= 1700) - // For Visual Studio 2012 and later, we will have to use the generic reference - // file, rather than the windows-specific one. - const std::string input_ref_file = webrtc::test::ProjectRootPath() + - "resources/audio_coding/neteq4_universal_ref.pcm"; -#else + // Note that neteq4_universal_ref.pcm and neteq4_universal_ref_win_32.pcm + // are identical. The latter could have been removed, but if clients still + // have a copy of the file, the test will fail. const std::string input_ref_file = webrtc::test::ResourcePath("audio_coding/neteq4_universal_ref", "pcm"); -#endif if (FLAGS_gen_ref) { DecodeAndCompare(input_rtp_file, ""); @@ -565,7 +565,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(TestNetworkStatistics)) { } // TODO(hlundin): Re-enable test once the statistics interface is up and again. -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(TestFrameWaitingTimeStatistics)) { +TEST_F(NetEqDecodingTest, TestFrameWaitingTimeStatistics) { // Use fax mode to avoid time-scaling. This is to simplify the testing of // packet waiting times in the packet buffer. neteq_->SetPlayoutMode(kPlayoutFax); @@ -639,8 +639,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(TestFrameWaitingTimeStatistics)) { EXPECT_EQ(100u, waiting_times.size()); } -TEST_F(NetEqDecodingTest, - DISABLED_ON_ANDROID(TestAverageInterArrivalTimeNegative)) { +TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimeNegative) { const int kNumFrames = 3000; // Needed for convergence. int frame_index = 0; const int kSamples = 10 * 16; @@ -671,8 +670,7 @@ TEST_F(NetEqDecodingTest, EXPECT_EQ(-103196, network_stats.clockdrift_ppm); } -TEST_F(NetEqDecodingTest, - DISABLED_ON_ANDROID(TestAverageInterArrivalTimePositive)) { +TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimePositive) { const int kNumFrames = 5000; // Needed for convergence. int frame_index = 0; const int kSamples = 10 * 16; @@ -740,7 +738,7 @@ void NetEqDecodingTest::LongCngWithClockDrift(double drift_factor, } EXPECT_EQ(kOutputNormal, type); - int32_t delay_before = timestamp - neteq_->PlayoutTimestamp(); + int32_t delay_before = timestamp - PlayoutTimestamp(); // Insert CNG for 1 minute (= 60000 ms). const int kCngPeriodMs = 100; @@ -833,13 +831,13 @@ void NetEqDecodingTest::LongCngWithClockDrift(double drift_factor, // Check that the speech starts again within reasonable time. double time_until_speech_returns_ms = t_ms - speech_restart_time_ms; EXPECT_LT(time_until_speech_returns_ms, max_time_to_speech_ms); - int32_t delay_after = timestamp - neteq_->PlayoutTimestamp(); + int32_t delay_after = timestamp - PlayoutTimestamp(); // Compare delay before and after, and make sure it differs less than 20 ms. EXPECT_LE(delay_after, delay_before + delay_tolerance_ms * 16); EXPECT_GE(delay_after, delay_before - delay_tolerance_ms * 16); } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(LongCngWithNegativeClockDrift)) { +TEST_F(NetEqDecodingTest, LongCngWithNegativeClockDrift) { // Apply a clock drift of -25 ms / s (sender faster than receiver). const double kDriftFactor = 1000.0 / (1000.0 + 25.0); const double kNetworkFreezeTimeMs = 0.0; @@ -853,7 +851,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(LongCngWithNegativeClockDrift)) { kMaxTimeToSpeechMs); } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(LongCngWithPositiveClockDrift)) { +TEST_F(NetEqDecodingTest, LongCngWithPositiveClockDrift) { // Apply a clock drift of +25 ms / s (sender slower than receiver). const double kDriftFactor = 1000.0 / (1000.0 - 25.0); const double kNetworkFreezeTimeMs = 0.0; @@ -867,8 +865,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(LongCngWithPositiveClockDrift)) { kMaxTimeToSpeechMs); } -TEST_F(NetEqDecodingTest, - DISABLED_ON_ANDROID(LongCngWithNegativeClockDriftNetworkFreeze)) { +TEST_F(NetEqDecodingTest, LongCngWithNegativeClockDriftNetworkFreeze) { // Apply a clock drift of -25 ms / s (sender faster than receiver). const double kDriftFactor = 1000.0 / (1000.0 + 25.0); const double kNetworkFreezeTimeMs = 5000.0; @@ -882,8 +879,7 @@ TEST_F(NetEqDecodingTest, kMaxTimeToSpeechMs); } -TEST_F(NetEqDecodingTest, - DISABLED_ON_ANDROID(LongCngWithPositiveClockDriftNetworkFreeze)) { +TEST_F(NetEqDecodingTest, LongCngWithPositiveClockDriftNetworkFreeze) { // Apply a clock drift of +25 ms / s (sender slower than receiver). const double kDriftFactor = 1000.0 / (1000.0 - 25.0); const double kNetworkFreezeTimeMs = 5000.0; @@ -897,9 +893,7 @@ TEST_F(NetEqDecodingTest, kMaxTimeToSpeechMs); } -TEST_F( - NetEqDecodingTest, - DISABLED_ON_ANDROID(LongCngWithPositiveClockDriftNetworkFreezeExtraPull)) { +TEST_F(NetEqDecodingTest, LongCngWithPositiveClockDriftNetworkFreezeExtraPull) { // Apply a clock drift of +25 ms / s (sender slower than receiver). const double kDriftFactor = 1000.0 / (1000.0 - 25.0); const double kNetworkFreezeTimeMs = 5000.0; @@ -913,7 +907,7 @@ TEST_F( kMaxTimeToSpeechMs); } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(LongCngWithoutClockDrift)) { +TEST_F(NetEqDecodingTest, LongCngWithoutClockDrift) { const double kDriftFactor = 1.0; // No drift. const double kNetworkFreezeTimeMs = 0.0; const bool kGetAudioDuringFreezeRecovery = false; @@ -926,7 +920,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(LongCngWithoutClockDrift)) { kMaxTimeToSpeechMs); } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(UnknownPayloadType)) { +TEST_F(NetEqDecodingTest, UnknownPayloadType) { const int kPayloadBytes = 100; uint8_t payload[kPayloadBytes] = {0}; WebRtcRTPHeader rtp_info; @@ -937,18 +931,6 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(UnknownPayloadType)) { EXPECT_EQ(NetEq::kUnknownRtpPayloadType, neteq_->LastError()); } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(OversizePacket)) { - // Payload size is greater than packet buffer size - const int kPayloadBytes = NetEq::kMaxBytesInBuffer + 1; - uint8_t payload[kPayloadBytes] = {0}; - WebRtcRTPHeader rtp_info; - PopulateRtpInfo(0, 0, &rtp_info); - rtp_info.header.payloadType = 103; // iSAC, no packet splitting. - EXPECT_EQ(NetEq::kFail, - neteq_->InsertPacket(rtp_info, payload, kPayloadBytes, 0)); - EXPECT_EQ(NetEq::kOversizePacket, neteq_->LastError()); -} - TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(DecoderError)) { const int kPayloadBytes = 100; uint8_t payload[kPayloadBytes] = {0}; @@ -988,7 +970,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(DecoderError)) { } } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(GetAudioBeforeInsertPacket)) { +TEST_F(NetEqDecodingTest, GetAudioBeforeInsertPacket) { NetEqOutputType type; // Set all of |out_data_| to 1, and verify that it was set to 0 by the call // to GetAudio. @@ -1011,7 +993,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(GetAudioBeforeInsertPacket)) { } } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(BackgroundNoise)) { +TEST_F(NetEqDecodingTest, BackgroundNoise) { neteq_->SetBackgroundNoiseMode(kBgnOn); CheckBgnOff(8000, kBgnOn); CheckBgnOff(16000, kBgnOn); @@ -1031,7 +1013,7 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(BackgroundNoise)) { EXPECT_EQ(kBgnFade, neteq_->BackgroundNoiseMode()); } -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(SyncPacketInsert)) { +TEST_F(NetEqDecodingTest, SyncPacketInsert) { WebRtcRTPHeader rtp_info; uint32_t receive_timestamp = 0; // For the readability use the following payloads instead of the defaults of @@ -1110,12 +1092,16 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(SyncPacketInsert)) { // First insert several noise like packets, then sync-packets. Decoding all // packets should not produce error, statistics should not show any packet loss // and sync-packets should decode to zero. -TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(SyncPacketDecode)) { +// TODO(turajs) we will have a better test if we have a referece NetEq, and +// when Sync packets are inserted in "test" NetEq we insert all-zero payload +// in reference NetEq and compare the output of those two. +TEST_F(NetEqDecodingTest, SyncPacketDecode) { WebRtcRTPHeader rtp_info; PopulateRtpInfo(0, 0, &rtp_info); const int kPayloadBytes = kBlockSize16kHz * sizeof(int16_t); uint8_t payload[kPayloadBytes]; int16_t decoded[kBlockSize16kHz]; + int algorithmic_frame_delay = algorithmic_delay_ms_ / 10 + 1; for (int n = 0; n < kPayloadBytes; ++n) { payload[n] = (rand() & 0xF0) + 1; // Non-zero random sequence. } @@ -1125,7 +1111,6 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(SyncPacketDecode)) { int num_channels; int samples_per_channel; uint32_t receive_timestamp = 0; - int delay_samples = 0; for (int n = 0; n < 100; ++n) { ASSERT_EQ(0, neteq_->InsertPacket(rtp_info, payload, kPayloadBytes, receive_timestamp)); @@ -1135,16 +1120,15 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(SyncPacketDecode)) { ASSERT_EQ(kBlockSize16kHz, samples_per_channel); ASSERT_EQ(1, num_channels); - // Even if there is RTP packet in NetEq's buffer, the first frame pulled - // from NetEq starts with few zero samples. Here we measure this delay. - if (n == 0) { - while (decoded[delay_samples] == 0) delay_samples++; - } rtp_info.header.sequenceNumber++; rtp_info.header.timestamp += kBlockSize16kHz; receive_timestamp += kBlockSize16kHz; } const int kNumSyncPackets = 10; + + // Make sure sufficient number of sync packets are inserted that we can + // conduct a test. + ASSERT_GT(kNumSyncPackets, algorithmic_frame_delay); // Insert sync-packets, the decoded sequence should be all-zero. for (int n = 0; n < kNumSyncPackets; ++n) { ASSERT_EQ(0, neteq_->InsertSyncPacket(rtp_info, receive_timestamp)); @@ -1153,38 +1137,44 @@ TEST_F(NetEqDecodingTest, DISABLED_ON_ANDROID(SyncPacketDecode)) { &output_type)); ASSERT_EQ(kBlockSize16kHz, samples_per_channel); ASSERT_EQ(1, num_channels); - EXPECT_TRUE(IsAllZero(&decoded[delay_samples], - samples_per_channel * num_channels - delay_samples)); - delay_samples = 0; // Delay only matters in the first frame. + if (n > algorithmic_frame_delay) { + EXPECT_TRUE(IsAllZero(decoded, samples_per_channel * num_channels)); + } rtp_info.header.sequenceNumber++; rtp_info.header.timestamp += kBlockSize16kHz; receive_timestamp += kBlockSize16kHz; } - // We insert a regular packet, if sync packet are not correctly buffered then + + // We insert regular packets, if sync packet are not correctly buffered then // network statistics would show some packet loss. - ASSERT_EQ(0, neteq_->InsertPacket(rtp_info, payload, kPayloadBytes, - receive_timestamp)); - ASSERT_EQ(0, neteq_->GetAudio(kBlockSize16kHz, decoded, - &samples_per_channel, &num_channels, - &output_type)); - // Make sure the last inserted packet is decoded and there are non-zero - // samples. - EXPECT_FALSE(IsAllZero(decoded, samples_per_channel * num_channels)); + for (int n = 0; n <= algorithmic_frame_delay + 10; ++n) { + ASSERT_EQ(0, neteq_->InsertPacket(rtp_info, payload, kPayloadBytes, + receive_timestamp)); + ASSERT_EQ(0, neteq_->GetAudio(kBlockSize16kHz, decoded, + &samples_per_channel, &num_channels, + &output_type)); + if (n >= algorithmic_frame_delay + 1) { + // Expect that this frame contain samples from regular RTP. + EXPECT_TRUE(IsAllNonZero(decoded, samples_per_channel * num_channels)); + } + rtp_info.header.sequenceNumber++; + rtp_info.header.timestamp += kBlockSize16kHz; + receive_timestamp += kBlockSize16kHz; + } NetEqNetworkStatistics network_stats; ASSERT_EQ(0, neteq_->NetworkStatistics(&network_stats)); // Expecting a "clean" network. EXPECT_EQ(0, network_stats.packet_loss_rate); EXPECT_EQ(0, network_stats.expand_rate); EXPECT_EQ(0, network_stats.accelerate_rate); - EXPECT_EQ(0, network_stats.preemptive_rate); + EXPECT_LE(network_stats.preemptive_rate, 150); } // Test if the size of the packet buffer reported correctly when containing // sync packets. Also, test if network packets override sync packets. That is to // prefer decoding a network packet to a sync packet, if both have same sequence // number and timestamp. -TEST_F(NetEqDecodingTest, - DISABLED_ON_ANDROID(SyncPacketBufferSizeAndOverridenByNetworkPackets)) { +TEST_F(NetEqDecodingTest, SyncPacketBufferSizeAndOverridenByNetworkPackets) { WebRtcRTPHeader rtp_info; PopulateRtpInfo(0, 0, &rtp_info); const int kPayloadBytes = kBlockSize16kHz * sizeof(int16_t); @@ -1199,7 +1189,8 @@ TEST_F(NetEqDecodingTest, int num_channels; int samples_per_channel; uint32_t receive_timestamp = 0; - for (int n = 0; n < 1; ++n) { + int algorithmic_frame_delay = algorithmic_delay_ms_ / 10 + 1; + for (int n = 0; n < algorithmic_frame_delay; ++n) { ASSERT_EQ(0, neteq_->InsertPacket(rtp_info, payload, kPayloadBytes, receive_timestamp)); ASSERT_EQ(0, neteq_->GetAudio(kBlockSize16kHz, decoded, @@ -1225,7 +1216,8 @@ TEST_F(NetEqDecodingTest, } NetEqNetworkStatistics network_stats; ASSERT_EQ(0, neteq_->NetworkStatistics(&network_stats)); - EXPECT_EQ(kNumSyncPackets * 10, network_stats.current_buffer_size_ms); + EXPECT_EQ(kNumSyncPackets * 10 + algorithmic_delay_ms_, + network_stats.current_buffer_size_ms); // Rewind |rtp_info| to that of the first sync packet. memcpy(&rtp_info, &first_sync_packet_rtp_info, sizeof(rtp_info)); @@ -1298,7 +1290,8 @@ void NetEqDecodingTest::WrapTest(uint16_t start_seq_no, if (packets_inserted > 4) { // Expect preferred and actual buffer size to be no more than 2 frames. EXPECT_LE(network_stats.preferred_buffer_size_ms, kFrameSizeMs * 2); - EXPECT_LE(network_stats.current_buffer_size_ms, kFrameSizeMs * 2); + EXPECT_LE(network_stats.current_buffer_size_ms, kFrameSizeMs * 2 + + algorithmic_delay_ms_); } last_seq_no = seq_no; last_timestamp = timestamp; @@ -1319,7 +1312,7 @@ void NetEqDecodingTest::WrapTest(uint16_t start_seq_no, ASSERT_EQ(1, num_channels); // Expect delay (in samples) to be less than 2 packets. - EXPECT_LE(timestamp - neteq_->PlayoutTimestamp(), + EXPECT_LE(timestamp - PlayoutTimestamp(), static_cast(kSamples * 2)); } // Make sure we have actually tested wrap-around. @@ -1362,6 +1355,8 @@ void NetEqDecodingTest::DuplicateCng() { const int kSamples = kFrameSizeMs * kSampleRateKhz; const int kPayloadBytes = kSamples * 2; + const int algorithmic_delay_samples = std::max( + algorithmic_delay_ms_ * kSampleRateKhz, 5 * kSampleRateKhz / 8); // Insert three speech packet. Three are needed to get the frame length // correct. int out_len; @@ -1398,7 +1393,7 @@ void NetEqDecodingTest::DuplicateCng() { kMaxBlockSize, out_data_, &out_len, &num_channels, &type)); ASSERT_EQ(kBlockSize16kHz, out_len); EXPECT_EQ(kOutputCNG, type); - EXPECT_EQ(timestamp - 10, neteq_->PlayoutTimestamp()); + EXPECT_EQ(timestamp - algorithmic_delay_samples, PlayoutTimestamp()); // Insert the same CNG packet again. Note that at this point it is old, since // we have already decoded the first copy of it. @@ -1412,7 +1407,8 @@ void NetEqDecodingTest::DuplicateCng() { kMaxBlockSize, out_data_, &out_len, &num_channels, &type)); ASSERT_EQ(kBlockSize16kHz, out_len); EXPECT_EQ(kOutputCNG, type); - EXPECT_EQ(timestamp - 10, neteq_->PlayoutTimestamp()); + EXPECT_EQ(timestamp - algorithmic_delay_samples, + PlayoutTimestamp()); } // Insert speech again. @@ -1427,7 +1423,14 @@ void NetEqDecodingTest::DuplicateCng() { kMaxBlockSize, out_data_, &out_len, &num_channels, &type)); ASSERT_EQ(kBlockSize16kHz, out_len); EXPECT_EQ(kOutputNormal, type); - EXPECT_EQ(timestamp + kSamples - 10, neteq_->PlayoutTimestamp()); + EXPECT_EQ(timestamp + kSamples - algorithmic_delay_samples, + PlayoutTimestamp()); +} + +uint32_t NetEqDecodingTest::PlayoutTimestamp() { + uint32_t playout_timestamp = 0; + EXPECT_TRUE(neteq_->GetPlayoutTimestamp(&playout_timestamp)); + return playout_timestamp; } TEST_F(NetEqDecodingTest, DiscardDuplicateCng) { DuplicateCng(); } diff --git a/webrtc/modules/audio_coding/neteq/neteq_unittests.isolate b/webrtc/modules/audio_coding/neteq/neteq_unittests.isolate deleted file mode 100644 index e8f4e482a..000000000 --- a/webrtc/modules/audio_coding/neteq/neteq_unittests.isolate +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2013 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. -{ - 'conditions': [ - ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. - 'variables': { - 'isolate_dependency_untracked': [ - '../../../../../data/', - '../../../../../resources/', - ], - }, - }], - ['OS=="linux" or OS=="mac" or OS=="win"', { - 'variables': { - 'command': [ - '../../../../testing/test_env.py', - '<(PRODUCT_DIR)/neteq_unittests<(EXECUTABLE_SUFFIX)', - ], - 'isolate_dependency_touched': [ - '../../../../DEPS', - ], - 'isolate_dependency_tracked': [ - '../../../../resources/audio_coding/neteq_network_stats.dat', - '../../../../resources/audio_coding/neteq_rtcp_stats.dat', - '../../../../resources/audio_coding/neteq_universal.rtp', - '../../../../resources/audio_coding/neteq_universal_ref.pcm', - '../../../../resources/audio_coding/testfile32kHz.pcm', - '../../../../testing/test_env.py', - '<(PRODUCT_DIR)/neteq_unittests<(EXECUTABLE_SUFFIX)', - ], - 'isolate_dependency_untracked': [ - '../../../../tools/swarming_client/', - ], - }, - }], - ], -} diff --git a/webrtc/modules/audio_coding/neteq/normal.c b/webrtc/modules/audio_coding/neteq/normal.c deleted file mode 100644 index 8cbda5215..000000000 --- a/webrtc/modules/audio_coding/neteq/normal.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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 file contains the function for handling "normal" speech operation. - */ -#include "dsp.h" - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" - -/* Scratch usage: - - Type Name size startpos endpos - int16_t pw16_expanded 125*fs/8000 0 125*fs/8000-1 - - func WebRtcNetEQ_Expand 40+370*fs/8000 125*fs/8000 39+495*fs/8000 - - Total: 40+495*fs/8000 - */ - -#define SCRATCH_PW16_EXPANDED 0 -#if (defined(NETEQ_48KHZ_WIDEBAND)) -#define SCRATCH_NETEQ_EXPAND 756 -#elif (defined(NETEQ_32KHZ_WIDEBAND)) -#define SCRATCH_NETEQ_EXPAND 504 -#elif (defined(NETEQ_WIDEBAND)) -#define SCRATCH_NETEQ_EXPAND 252 -#else /* NB */ -#define SCRATCH_NETEQ_EXPAND 126 -#endif - -/**************************************************************************** - * WebRtcNetEQ_Normal(...) - * - * This function has the possibility to modify data that is played out in Normal - * mode, for example adjust the gain of the signal. The length of the signal - * can not be changed. - * - * Input: - * - inst : NetEq instance, i.e. the user that requests more - * speech/audio data - * - scratchPtr : Pointer to scratch vector - * - decoded : Pointer to vector of new data from decoder - * (Vector contents may be altered by the function) - * - len : Number of input samples - * - * Output: - * - inst : Updated user information - * - outData : Pointer to a memory space where the output data - * should be stored - * - pw16_len : Pointer to variable where the number of samples - * produced will be written - * - * Return value : >=0 - Number of samples written to outData - * -1 - Error - */ - -int WebRtcNetEQ_Normal(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - int16_t *pw16_decoded, int16_t len, - int16_t *pw16_outData, int16_t *pw16_len) -{ - - int i; - int16_t fs_mult; - int16_t fs_shift; - int32_t w32_En_speech; - int16_t enLen; - int16_t w16_muted; - int16_t w16_inc, w16_frac; - int16_t w16_tmp; - int32_t w32_tmp; - - /* Sanity check */ - if (len < 0) - { - /* Cannot have negative length of input vector */ - return (-1); - } - - if (len == 0) - { - /* Still got some data to play => continue with the same mode */ - *pw16_len = len; - return (len); - } - - fs_mult = WebRtcSpl_DivW32W16ResW16(inst->fs, 8000); - fs_shift = 30 - WebRtcSpl_NormW32(fs_mult); /* Note that this is not "exact" for 48kHz */ - - /* - * Check if last RecOut call resulted in an Expand or a FadeToBGN. If so, we have to take - * care of some cross-fading and unmuting. - */ - if (inst->w16_mode == MODE_EXPAND || inst->w16_mode == MODE_FADE_TO_BGN) - { - - /* Define memory where temporary result from Expand algorithm can be stored. */ -#ifdef SCRATCH - int16_t *pw16_expanded = pw16_scratchPtr + SCRATCH_PW16_EXPANDED; -#else - int16_t pw16_expanded[FSMULT * 125]; -#endif - int16_t expandedLen = 0; - int16_t w16_decodedMax; - - /* Find largest value in new data */ - w16_decodedMax = WebRtcSpl_MaxAbsValueW16(pw16_decoded, (int16_t) len); - - /* Generate interpolation data using Expand */ - /* First, set Expand parameters to appropriate values. */ - inst->ExpandInst.w16_lagsPosition = 0; - inst->ExpandInst.w16_lagsDirection = 0; - inst->ExpandInst.w16_stopMuting = 1; /* Do not mute signal any more */ - - /* Call Expand */ - WebRtcNetEQ_Expand(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_EXPAND, -#endif - pw16_expanded, &expandedLen, (int16_t) (inst->w16_mode == MODE_FADE_TO_BGN)); - - inst->ExpandInst.w16_stopMuting = 0; /* Restore value */ - inst->ExpandInst.w16_consecExp = 0; /* Last was not Expand any more */ - - /* Adjust muting factor (main muting factor times expand muting factor) */ - if (inst->w16_mode == MODE_FADE_TO_BGN) - { - /* If last mode was FadeToBGN, the mute factor should be zero. */ - inst->w16_muteFactor = 0; - } - else - { - /* w16_muteFactor * w16_expandMuteFactor */ - inst->w16_muteFactor - = (int16_t) WEBRTC_SPL_MUL_16_16_RSFT(inst->w16_muteFactor, - inst->ExpandInst.w16_expandMuteFactor, 14); - } - - /* Adjust muting factor if needed (to BGN level) */ - enLen = WEBRTC_SPL_MIN(fs_mult<<6, len); /* min( fs_mult * 64, len ) */ - w16_tmp = 6 + fs_shift - WebRtcSpl_NormW32( - WEBRTC_SPL_MUL_16_16(w16_decodedMax, w16_decodedMax)); - w16_tmp = WEBRTC_SPL_MAX(w16_tmp, 0); - w32_En_speech = WebRtcNetEQ_DotW16W16(pw16_decoded, pw16_decoded, enLen, w16_tmp); - w32_En_speech = WebRtcSpl_DivW32W16(w32_En_speech, (int16_t) (enLen >> w16_tmp)); - - if ((w32_En_speech != 0) && (w32_En_speech > inst->BGNInst.w32_energy)) - { - /* Normalize new frame energy to 15 bits */ - w16_tmp = WebRtcSpl_NormW32(w32_En_speech) - 16; - /* we want inst->BGNInst.energy/En_speech in Q14 */ - w32_tmp = WEBRTC_SPL_SHIFT_W32(inst->BGNInst.w32_energy, (w16_tmp+14)); - w16_tmp = (int16_t) WEBRTC_SPL_SHIFT_W32(w32_En_speech, w16_tmp); - w16_tmp = (int16_t) WebRtcSpl_DivW32W16(w32_tmp, w16_tmp); - w16_muted = (int16_t) WebRtcSpl_SqrtFloor( - WEBRTC_SPL_LSHIFT_W32((int32_t) w16_tmp, - 14)); /* w16_muted in Q14 (sqrt(Q28)) */ - } - else - { - w16_muted = 16384; /* 1.0 in Q14 */ - } - if (w16_muted > inst->w16_muteFactor) - { - inst->w16_muteFactor = WEBRTC_SPL_MIN(w16_muted, 16384); - } - - /* If muted increase by 0.64 for every 20 ms (NB/WB 0.0040/0.0020 in Q14) */ - w16_inc = WebRtcSpl_DivW32W16ResW16(64, fs_mult); - for (i = 0; i < len; i++) - { - /* scale with mute factor */ - w32_tmp = WEBRTC_SPL_MUL_16_16(pw16_decoded[i], inst->w16_muteFactor); - /* shift 14 with proper rounding */ - pw16_decoded[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32((w32_tmp + 8192), 14); - /* increase mute_factor towards 16384 */ - inst->w16_muteFactor = WEBRTC_SPL_MIN(16384, (inst->w16_muteFactor+w16_inc)); - } - - /* - * Interpolate the expanded data into the new vector - * (NB/WB/SWB32/SWB40 8/16/32/32 samples) - */ - fs_shift = WEBRTC_SPL_MIN(3, fs_shift); /* Set to 3 for >32kHz */ - w16_inc = 4 >> fs_shift; - w16_frac = w16_inc; - for (i = 0; i < 8 * fs_mult; i++) - { - pw16_decoded[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32( - (WEBRTC_SPL_MUL_16_16(w16_frac, pw16_decoded[i]) + - WEBRTC_SPL_MUL_16_16((32 - w16_frac), pw16_expanded[i]) + 8), - 5); - w16_frac += w16_inc; - } - -#ifdef NETEQ_CNG_CODEC - } - else if (inst->w16_mode==MODE_RFC3389CNG) - { /* previous was RFC 3389 CNG...*/ - int16_t pw16_CngInterp[32]; - /* Reset mute factor and start up fresh */ - inst->w16_muteFactor = 16384; - if (inst->CNG_Codec_inst != NULL) - { - /* Generate long enough for 32kHz */ - if(WebRtcCng_Generate(inst->CNG_Codec_inst,pw16_CngInterp, 32, 0)<0) - { - /* error returned; set return vector to all zeros */ - WebRtcSpl_MemSetW16(pw16_CngInterp, 0, 32); - } - } - else - { - /* - * If no CNG instance is defined, just copy from the decoded data. - * (This will result in interpolating the decoded with itself.) - */ - WEBRTC_SPL_MEMCPY_W16(pw16_CngInterp, pw16_decoded, fs_mult * 8); - } - /* - * Interpolate the CNG into the new vector - * (NB/WB/SWB32kHz/SWB48kHz 8/16/32/32 samples) - */ - fs_shift = WEBRTC_SPL_MIN(3, fs_shift); /* Set to 3 for >32kHz */ - w16_inc = 4>>fs_shift; - w16_frac = w16_inc; - for (i = 0; i < 8 * fs_mult; i++) - { - pw16_decoded[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32( - (WEBRTC_SPL_MUL_16_16(w16_frac, pw16_decoded[i]) + - WEBRTC_SPL_MUL_16_16((32-w16_frac), pw16_CngInterp[i]) + 8), - 5); - w16_frac += w16_inc; - } -#endif - - } - else if (inst->w16_muteFactor < 16384) - { - /* - * Previous was neither of Expand, FadeToBGN or RFC3389_CNG, but we are still - * ramping up from previous muting. - * If muted increase by 0.64 for every 20 ms (NB/WB 0.0040/0.0020 in Q14) - */ - w16_inc = WebRtcSpl_DivW32W16ResW16(64, fs_mult); - for (i = 0; i < len; i++) - { - /* scale with mute factor */ - w32_tmp = WEBRTC_SPL_MUL_16_16(pw16_decoded[i], inst->w16_muteFactor); - /* shift 14 with proper rounding */ - pw16_decoded[i] = (int16_t) WEBRTC_SPL_RSHIFT_W32((w32_tmp + 8192), 14); - /* increase mute_factor towards 16384 */ - inst->w16_muteFactor = WEBRTC_SPL_MIN(16384, (inst->w16_muteFactor+w16_inc)); - } - } - - /* Copy data to other buffer */WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, len); - - inst->w16_mode = MODE_NORMAL; - *pw16_len = len; - return (len); - -} - -#undef SCRATCH_PW16_EXPANDED -#undef SCRATCH_NETEQ_EXPAND - diff --git a/webrtc/modules/audio_coding/neteq4/normal.cc b/webrtc/modules/audio_coding/neteq/normal.cc similarity index 95% rename from webrtc/modules/audio_coding/neteq4/normal.cc rename to webrtc/modules/audio_coding/neteq/normal.cc index 8d9c020f9..bfde179bd 100644 --- a/webrtc/modules/audio_coding/neteq4/normal.cc +++ b/webrtc/modules/audio_coding/neteq/normal.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/normal.h" +#include "webrtc/modules/audio_coding/neteq/normal.h" #include // memset, memcpy @@ -16,11 +16,11 @@ #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" #include "webrtc/modules/audio_coding/codecs/cng/include/webrtc_cng.h" -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/normal.h b/webrtc/modules/audio_coding/neteq/normal.h similarity index 85% rename from webrtc/modules/audio_coding/neteq4/normal.h rename to webrtc/modules/audio_coding/neteq/normal.h index df283198f..aa24b528a 100644 --- a/webrtc/modules/audio_coding/neteq4/normal.h +++ b/webrtc/modules/audio_coding/neteq/normal.h @@ -8,16 +8,16 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_NORMAL_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_NORMAL_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_NORMAL_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_NORMAL_H_ #include // Access to size_t. #include -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/defines.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/defines.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -65,4 +65,4 @@ class Normal { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_NORMAL_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_NORMAL_H_ diff --git a/webrtc/modules/audio_coding/neteq4/normal_unittest.cc b/webrtc/modules/audio_coding/neteq/normal_unittest.cc similarity index 71% rename from webrtc/modules/audio_coding/neteq4/normal_unittest.cc rename to webrtc/modules/audio_coding/neteq/normal_unittest.cc index 2bd7b894f..c855865cf 100644 --- a/webrtc/modules/audio_coding/neteq4/normal_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/normal_unittest.cc @@ -10,16 +10,16 @@ // Unit tests for Normal class. -#include "webrtc/modules/audio_coding/neteq4/normal.h" +#include "webrtc/modules/audio_coding/neteq/normal.h" #include #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" -#include "webrtc/modules/audio_coding/neteq4/expand.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/random_vector.h" -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/expand.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/random_vector.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/packet.h b/webrtc/modules/audio_coding/neteq/packet.h similarity index 95% rename from webrtc/modules/audio_coding/neteq4/packet.h rename to webrtc/modules/audio_coding/neteq/packet.h index 4518f9138..89ddda782 100644 --- a/webrtc/modules/audio_coding/neteq4/packet.h +++ b/webrtc/modules/audio_coding/neteq/packet.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PACKET_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PACKET_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_PACKET_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_PACKET_H_ #include @@ -85,4 +85,4 @@ struct Packet { typedef std::list PacketList; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PACKET_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_PACKET_H_ diff --git a/webrtc/modules/audio_coding/neteq/packet_buffer.c b/webrtc/modules/audio_coding/neteq/packet_buffer.c deleted file mode 100644 index a542333cf..000000000 --- a/webrtc/modules/audio_coding/neteq/packet_buffer.c +++ /dev/null @@ -1,851 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* - * Implementation of the actual packet buffer data structure. - */ - -#include -#include "packet_buffer.h" - -#include /* to define NULL */ - -#include "signal_processing_library.h" - -#include "mcu_dsp_common.h" - -#include "neteq_error_codes.h" - -#ifdef NETEQ_DELAY_LOGGING -/* special code for offline delay logging */ -#include "delay_logging.h" -#include - -extern FILE *delay_fid2; /* file pointer to delay log file */ -extern uint32_t tot_received_packets; -#endif /* NETEQ_DELAY_LOGGING */ - - -int WebRtcNetEQ_PacketBufferInit(PacketBuf_t *bufferInst, int maxNoOfPackets, - int16_t *pw16_memory, int memorySize) -{ - int i; - int pos = 0; - - /* Sanity check */ - if ((memorySize < PBUFFER_MIN_MEMORY_SIZE) || (pw16_memory == NULL) - || (maxNoOfPackets < 2) || (maxNoOfPackets > 600)) - { - /* Invalid parameters */ - return (PBUFFER_INIT_ERROR); - } - - /* Clear the buffer instance */ - WebRtcSpl_MemSetW16((int16_t*) bufferInst, 0, - sizeof(PacketBuf_t) / sizeof(int16_t)); - - /* Clear the buffer memory */ - WebRtcSpl_MemSetW16((int16_t*) pw16_memory, 0, memorySize); - - /* Set maximum number of packets */ - bufferInst->maxInsertPositions = maxNoOfPackets; - - /* Initialize array pointers */ - /* After each pointer has been set, the index pos is advanced to point immediately - * after the the recently allocated vector. Note that one step for the pos index - * corresponds to a int16_t. - */ - - bufferInst->timeStamp = (uint32_t*) &pw16_memory[pos]; - pos += maxNoOfPackets << 1; /* advance maxNoOfPackets * uint32_t */ - - bufferInst->payloadLocation = (int16_t**) &pw16_memory[pos]; - pos += maxNoOfPackets * (sizeof(int16_t*) / sizeof(int16_t)); /* advance */ - - bufferInst->seqNumber = (uint16_t*) &pw16_memory[pos]; - pos += maxNoOfPackets; /* advance maxNoOfPackets * uint16_t */ - - bufferInst->payloadType = &pw16_memory[pos]; - pos += maxNoOfPackets; /* advance maxNoOfPackets * int16_t */ - - bufferInst->payloadLengthBytes = &pw16_memory[pos]; - pos += maxNoOfPackets; /* advance maxNoOfPackets * int16_t */ - - bufferInst->rcuPlCntr = &pw16_memory[pos]; - pos += maxNoOfPackets; /* advance maxNoOfPackets * int16_t */ - - bufferInst->waitingTime = (int*) (&pw16_memory[pos]); - /* Advance maxNoOfPackets * sizeof(waitingTime element). */ - pos += maxNoOfPackets * - sizeof(*bufferInst->waitingTime) / sizeof(*pw16_memory); - - /* The payload memory starts after the slot arrays */ - bufferInst->startPayloadMemory = &pw16_memory[pos]; - bufferInst->currentMemoryPos = bufferInst->startPayloadMemory; - bufferInst->memorySizeW16 = (memorySize - pos); /* Remaining memory */ - - /* Initialize each payload slot as empty with infinite delay */ - for (i = 0; i < bufferInst->maxInsertPositions; i++) - { - bufferInst->payloadType[i] = -1; - } - - /* Reset buffer parameters */ - bufferInst->numPacketsInBuffer = 0; - bufferInst->packSizeSamples = 0; - bufferInst->insertPosition = 0; - - /* Reset buffer statistics */ - bufferInst->discardedPackets = 0; - - return (0); -} - - -int WebRtcNetEQ_PacketBufferFlush(PacketBuf_t *bufferInst) -{ - int i; - - /* Sanity check */ - if (bufferInst->startPayloadMemory == NULL) - { - /* Packet buffer has not been initialized */ - /* Don't do the flushing operation, since we do not - know the state of the struct variables */ - return (0); - } - - /* Set all payload lengths to zero */ - WebRtcSpl_MemSetW16(bufferInst->payloadLengthBytes, 0, bufferInst->maxInsertPositions); - - /* Reset buffer variables */ - bufferInst->numPacketsInBuffer = 0; - bufferInst->currentMemoryPos = bufferInst->startPayloadMemory; - bufferInst->insertPosition = 0; - - /* Clear all slots, starting with the last one */ - for (i = (bufferInst->maxInsertPositions - 1); i >= 0; i--) - { - bufferInst->payloadType[i] = -1; - bufferInst->timeStamp[i] = 0; - bufferInst->seqNumber[i] = 0; - } - - return (0); -} - - -int WebRtcNetEQ_PacketBufferInsert(PacketBuf_t *bufferInst, const RTPPacket_t *RTPpacket, - int16_t *flushed, int av_sync) -{ - int nextPos; - int i; - -#ifdef NETEQ_DELAY_LOGGING - /* special code for offline delay logging */ - int temp_var; -#endif /* NETEQ_DELAY_LOGGING */ - - /* Initialize to "no flush" */ - *flushed = 0; - - /* Sanity check */ - if (bufferInst->startPayloadMemory == NULL) - { - /* packet buffer has not been initialized */ - return (-1); - } - - /* Sanity check for payload length - (payloadLen in bytes and memory size in int16_t) */ - if ((RTPpacket->payloadLen > (bufferInst->memorySizeW16 << 1)) || (RTPpacket->payloadLen - <= 0)) - { - /* faulty or too long payload length */ - return (-1); - } - - /* If we are in AV-sync mode, there is a risk that we have inserted a sync - * packet but now received the real version of it. Or because of some timing - * we might be overwriting a true payload with sync (I'm not sure why this - * should happen in regular case, but in some FEC enabled case happens). - * Go through packets and delete the sync version of the packet in hand. Or - * if this is sync packet and the regular version of it exists in the buffer - * refrain from inserting. - * - * TODO(turajs): Could we get this for free if we had set the RCU-counter of - * the sync packet to a number larger than 2? - */ - if (av_sync) { - for (i = 0; i < bufferInst->maxInsertPositions; ++i) { - /* Check if sequence numbers match and the payload actually exists. */ - if (bufferInst->seqNumber[i] == RTPpacket->seqNumber && - bufferInst->payloadLengthBytes[i] > 0) { - if (WebRtcNetEQ_IsSyncPayload(RTPpacket->payload, - RTPpacket->payloadLen)) { - return 0; - } - - if (WebRtcNetEQ_IsSyncPayload(bufferInst->payloadLocation[i], - bufferInst->payloadLengthBytes[i])) { - /* Clear the position in the buffer. */ - bufferInst->payloadType[i] = -1; - bufferInst->payloadLengthBytes[i] = 0; - - /* Reduce packet counter by one. */ - bufferInst->numPacketsInBuffer--; - /* TODO(turajs) if this is the latest packet better we rewind - * insertPosition and related variables. */ - break; /* There should be only one match. */ - } - } - } - } - - /* Find a position in the buffer for this packet */ - if (bufferInst->numPacketsInBuffer != 0) - { - /* Get the next slot */ - bufferInst->insertPosition++; - if (bufferInst->insertPosition >= bufferInst->maxInsertPositions) - { - /* "Wrap around" and start from the beginning */ - bufferInst->insertPosition = 0; - } - - /* Check if there is enough space for the new packet */ - if (bufferInst->currentMemoryPos + ((RTPpacket->payloadLen + 1) >> 1) - >= &bufferInst->startPayloadMemory[bufferInst->memorySizeW16]) - { - int16_t *tempMemAddress; - - /* - * Payload does not fit at the end of the memory, put it in the beginning - * instead - */ - bufferInst->currentMemoryPos = bufferInst->startPayloadMemory; - - /* - * Now, we must search for the next non-empty payload, - * finding the one with the lowest start address for the payload - */ - tempMemAddress = &bufferInst->startPayloadMemory[bufferInst->memorySizeW16]; - nextPos = -1; - - /* Loop through all slots again */ - for (i = 0; i < bufferInst->maxInsertPositions; i++) - { - /* Look for the non-empty slot with the lowest - payload location address */ - if (bufferInst->payloadLengthBytes[i] != 0 && bufferInst->payloadLocation[i] - < tempMemAddress) - { - tempMemAddress = bufferInst->payloadLocation[i]; - nextPos = i; - } - } - - /* Check that we did find a previous payload */ - if (nextPos == -1) - { - /* The buffer is corrupt => flush and return error */ - WebRtcNetEQ_PacketBufferFlush(bufferInst); - *flushed = 1; - return (-1); - } - } - else - { - /* Payload fits at the end of memory. */ - - /* Find the next non-empty slot. */ - nextPos = bufferInst->insertPosition + 1; - - /* Increase nextPos until a non-empty slot is found or end of array is encountered*/ - while ((bufferInst->payloadLengthBytes[nextPos] == 0) && (nextPos - < bufferInst->maxInsertPositions)) - { - nextPos++; - } - - if (nextPos == bufferInst->maxInsertPositions) - { - /* - * Reached the end of the array, so there must be a packet in the first - * position instead - */ - nextPos = 0; - - /* Increase nextPos until a non-empty slot is found */ - while (bufferInst->payloadLengthBytes[nextPos] == 0) - { - nextPos++; - } - } - } /* end if-else */ - - /* - * Check if the new payload will extend into a payload later in memory. - * If so, the buffer is full. - */ - if ((bufferInst->currentMemoryPos <= bufferInst->payloadLocation[nextPos]) - && ((&bufferInst->currentMemoryPos[(RTPpacket->payloadLen + 1) >> 1]) - > bufferInst->payloadLocation[nextPos])) - { - /* Buffer is full, so the buffer must be flushed */ - WebRtcNetEQ_PacketBufferFlush(bufferInst); - *flushed = 1; - } - - if (bufferInst->payloadLengthBytes[bufferInst->insertPosition] != 0) - { - /* All positions are already taken and entire buffer should be flushed */ - WebRtcNetEQ_PacketBufferFlush(bufferInst); - *flushed = 1; - } - - } - else - { - /* Buffer is empty, just insert the packet at the beginning */ - bufferInst->currentMemoryPos = bufferInst->startPayloadMemory; - bufferInst->insertPosition = 0; - } - - /* Insert packet in the found position */ - if (RTPpacket->starts_byte1 == 0) - { - /* Payload is 16-bit aligned => just copy it */ - - WEBRTC_SPL_MEMCPY_W8(bufferInst->currentMemoryPos, - RTPpacket->payload, RTPpacket->payloadLen); - } - else - { - /* Payload is not 16-bit aligned => align it during copy operation */ - for (i = 0; i < RTPpacket->payloadLen; i++) - { - /* copy the (i+1)-th byte to the i-th byte */ - - WEBRTC_SPL_SET_BYTE(bufferInst->currentMemoryPos, - (WEBRTC_SPL_GET_BYTE(RTPpacket->payload, (i + 1))), i); - } - } - - /* Copy the packet information */ - bufferInst->payloadLocation[bufferInst->insertPosition] = bufferInst->currentMemoryPos; - bufferInst->payloadLengthBytes[bufferInst->insertPosition] = RTPpacket->payloadLen; - bufferInst->payloadType[bufferInst->insertPosition] = RTPpacket->payloadType; - bufferInst->seqNumber[bufferInst->insertPosition] = RTPpacket->seqNumber; - bufferInst->timeStamp[bufferInst->insertPosition] = RTPpacket->timeStamp; - bufferInst->rcuPlCntr[bufferInst->insertPosition] = RTPpacket->rcuPlCntr; - bufferInst->waitingTime[bufferInst->insertPosition] = 0; - /* Update buffer parameters */ - bufferInst->numPacketsInBuffer++; - bufferInst->currentMemoryPos += (RTPpacket->payloadLen + 1) >> 1; - -#ifdef NETEQ_DELAY_LOGGING - /* special code for offline delay logging */ - if (*flushed) - { - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_FLUSH; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - } - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_RECIN; - if ((fwrite(&temp_var, sizeof(int), - 1, delay_fid2) != 1) || - (fwrite(&RTPpacket->timeStamp, sizeof(uint32_t), - 1, delay_fid2) != 1) || - (fwrite(&RTPpacket->seqNumber, sizeof(uint16_t), - 1, delay_fid2) != 1) || - (fwrite(&RTPpacket->payloadType, sizeof(int), - 1, delay_fid2) != 1) || - (fwrite(&RTPpacket->payloadLen, sizeof(int16_t), - 1, delay_fid2) != 1)) { - return -1; - } - tot_received_packets++; -#endif /* NETEQ_DELAY_LOGGING */ - - return (0); -} - - -int WebRtcNetEQ_PacketBufferExtract(PacketBuf_t *bufferInst, RTPPacket_t *RTPpacket, - int bufferPosition, int *waitingTime) -{ - - /* Sanity check */ - if (bufferInst->startPayloadMemory == NULL) - { - /* packet buffer has not been initialized */ - return (PBUFFER_NOT_INITIALIZED); - } - - if (bufferPosition < 0 || bufferPosition >= bufferInst->maxInsertPositions) - { - /* buffer position is outside valid range */ - return (NETEQ_OTHER_ERROR); - } - - /* Check that there is a valid payload in the specified position */ - if (bufferInst->payloadLengthBytes[bufferPosition] <= 0) - { - /* The position does not contain a valid payload */ - RTPpacket->payloadLen = 0; /* Set zero length */ - return (PBUFFER_NONEXISTING_PACKET); /* Return error */ - } - - /* Payload exists => extract payload data */ - - /* Copy the actual data payload to RTP packet struct */ - - WEBRTC_SPL_MEMCPY_W16((int16_t*) RTPpacket->payload, - bufferInst->payloadLocation[bufferPosition], - (bufferInst->payloadLengthBytes[bufferPosition] + 1) >> 1); /*length in int16_t*/ - - /* Copy payload parameters */ - RTPpacket->payloadLen = bufferInst->payloadLengthBytes[bufferPosition]; - RTPpacket->payloadType = bufferInst->payloadType[bufferPosition]; - RTPpacket->seqNumber = bufferInst->seqNumber[bufferPosition]; - RTPpacket->timeStamp = bufferInst->timeStamp[bufferPosition]; - RTPpacket->rcuPlCntr = bufferInst->rcuPlCntr[bufferPosition]; - *waitingTime = bufferInst->waitingTime[bufferPosition]; - RTPpacket->starts_byte1 = 0; /* payload is 16-bit aligned */ - - /* Clear the position in the packet buffer */ - bufferInst->payloadType[bufferPosition] = -1; - bufferInst->payloadLengthBytes[bufferPosition] = 0; - bufferInst->seqNumber[bufferPosition] = 0; - bufferInst->timeStamp[bufferPosition] = 0; - bufferInst->waitingTime[bufferPosition] = 0; - bufferInst->payloadLocation[bufferPosition] = bufferInst->startPayloadMemory; - - /* Reduce packet counter with one */ - bufferInst->numPacketsInBuffer--; - - return (0); -} - -int WebRtcNetEQ_PacketBufferFindLowestTimestamp(PacketBuf_t* buffer_inst, - uint32_t current_time_stamp, - uint32_t* time_stamp, - int* buffer_position, - int erase_old_packets, - int16_t* payload_type) { - int32_t time_stamp_diff = WEBRTC_SPL_WORD32_MAX; /* Smallest diff found. */ - int32_t new_diff; - int i; - int16_t rcu_payload_cntr; - if (buffer_inst->startPayloadMemory == NULL) { - /* Packet buffer has not been initialized. */ - return PBUFFER_NOT_INITIALIZED; - } - - /* Initialize all return values. */ - *time_stamp = 0; - *payload_type = -1; /* Indicates that no packet was found. */ - *buffer_position = -1; /* Indicates that no packet was found. */ - rcu_payload_cntr = WEBRTC_SPL_WORD16_MAX; /* Indicates no packet found. */ - - /* Check if buffer is empty. */ - if (buffer_inst->numPacketsInBuffer <= 0) { - return 0; - } - - /* Loop through all slots in buffer. */ - if (erase_old_packets) { /* If old payloads should be discarded. */ - for (i = 0; i < buffer_inst->maxInsertPositions; ++i) { - /* Calculate difference between this slot and current_time_stamp. */ - new_diff = (int32_t)(buffer_inst->timeStamp[i] - current_time_stamp); - - /* Check if payload should be discarded. */ - if ((new_diff < 0) /* Payload is too old */ - && (new_diff > -30000) /* Account for TS wrap-around. */ - && (buffer_inst->payloadLengthBytes[i] > 0)) { /* Payload exists. */ - /* Throw away old packet. */ - - /* Clear the position in the buffer. */ - buffer_inst->payloadType[i] = -1; - buffer_inst->payloadLengthBytes[i] = 0; - - /* Reduce packet counter by one. */ - buffer_inst->numPacketsInBuffer--; - /* Increase discard counter for in-call statistics. */ - buffer_inst->discardedPackets++; - } else if (((new_diff < time_stamp_diff) - || ((new_diff == time_stamp_diff) - && (buffer_inst->rcuPlCntr[i] < rcu_payload_cntr))) - && (buffer_inst->payloadLengthBytes[i] > 0)) { - /* New diff is smaller than previous diffs or we have a candidate with a - * time stamp as previous candidate but better RCU-counter; - * and the payload exists. - */ - /* Save this position as the best candidate. */ - *buffer_position = i; - time_stamp_diff = new_diff; - *payload_type = buffer_inst->payloadType[i]; - rcu_payload_cntr = buffer_inst->rcuPlCntr[i]; - } - } - } else { - for (i = 0; i < buffer_inst->maxInsertPositions; ++i) { - /* Calculate difference between this slot and current_time_stamp. */ - new_diff = (int32_t)(buffer_inst->timeStamp[i] - current_time_stamp); - - /* Check if this is the oldest packet. */ - if (((new_diff < time_stamp_diff) - || ((new_diff == time_stamp_diff) - && (buffer_inst->rcuPlCntr[i] < rcu_payload_cntr))) - && (buffer_inst->payloadLengthBytes[i] > 0)) { - /* New diff is smaller than previous diffs or we have a candidate with a - * time_stamp as previous candidate but better RCU-counter; - * and the payload exists. - */ - /* Save this position as the best candidate. */ - *buffer_position = i; - time_stamp_diff = new_diff; - *payload_type = buffer_inst->payloadType[i]; - rcu_payload_cntr = buffer_inst->rcuPlCntr[i]; - } - } - } - - /* Check that we did find a real position. */ - if (*buffer_position >= 0) { - /* Get the time_stamp for the best position. */ - *time_stamp = buffer_inst->timeStamp[*buffer_position]; - } - - return 0; -} - -int WebRtcNetEQ_PacketBufferGetPacketSize(const PacketBuf_t* buffer_inst, - int buffer_pos, - const CodecDbInst_t* codec_database, - int codec_pos, int last_duration, - int av_sync) { - if (codec_database->funcDurationEst[codec_pos] == NULL) { - return last_duration; - } - - if (av_sync != 0 && - WebRtcNetEQ_IsSyncPayload(buffer_inst->payloadLocation[buffer_pos], - buffer_inst->payloadLengthBytes[buffer_pos])) { - // In AV-sync and sync payload, report |last_duration| as current duration. - return last_duration; - } - - return (*codec_database->funcDurationEst[codec_pos])( - codec_database->codec_state[codec_pos], - (const uint8_t *)buffer_inst->payloadLocation[buffer_pos], - buffer_inst->payloadLengthBytes[buffer_pos]); -} - -int32_t WebRtcNetEQ_PacketBufferGetSize(const PacketBuf_t* buffer_inst, - const CodecDbInst_t* codec_database, - int av_sync) { - int i, count; - int last_duration; - int last_codec_pos; - int last_payload_type; - int32_t size_samples; - - count = 0; - last_duration = buffer_inst->packSizeSamples; - last_codec_pos = -1; - last_payload_type = -1; - size_samples = 0; - - /* Loop through all slots in the buffer */ - for (i = 0; i < buffer_inst->maxInsertPositions; i++) { - /* Only count the packets with non-zero size */ - if (buffer_inst->payloadLengthBytes[i] != 0) { - int payload_type; - int codec_pos; - /* Figure out the codec database entry for this payload_type. */ - payload_type = buffer_inst->payloadType[i]; - /* Remember the last one, to avoid the database search. */ - if(payload_type == last_payload_type) { - codec_pos = last_codec_pos; - } - else { - codec_pos = WebRtcNetEQ_DbGetCodec(codec_database, - payload_type); - if (codec_pos >= 0) { - codec_pos = codec_database->position[codec_pos]; - } - } - last_codec_pos = codec_pos; - last_payload_type = payload_type; - if (codec_pos >= 0) { - /* - * Right now WebRtcNetEQ_PacketBufferGetPacketSize either always - * returns last_duration or always computes the real duration without - * looking at last_duration. If an implementation really wanted to use - * last_duration to compute a changing duration, we would have to - * iterate through the packets in chronological order by timestamp. - */ - /* Check for error before setting. */ - int temp_last_duration = WebRtcNetEQ_PacketBufferGetPacketSize( - buffer_inst, i, codec_database, codec_pos, - last_duration, av_sync); - if (temp_last_duration >= 0) - last_duration = temp_last_duration; - } - /* Add in the size of this packet. */ - size_samples += last_duration; - count++; - } - } - - /* Sanity check; size cannot be negative */ - if (size_samples < 0) { - size_samples = 0; - } - return size_samples; -} - -void WebRtcNetEQ_IncrementWaitingTimes(PacketBuf_t *buffer_inst) { - int i; - /* Loop through all slots in the buffer. */ - for (i = 0; i < buffer_inst->maxInsertPositions; ++i) { - /* Only increment waiting time for the packets with non-zero size. */ - if (buffer_inst->payloadLengthBytes[i] != 0) { - buffer_inst->waitingTime[i]++; - } - } -} - -int WebRtcNetEQ_GetDefaultCodecSettings(const enum WebRtcNetEQDecoder *codecID, - int noOfCodecs, int *maxBytes, - int *maxSlots, - int* per_slot_overhead_bytes) -{ - int i; - int ok = 0; - int16_t w16_tmp; - int16_t codecBytes; - int16_t codecBuffers; - - /* Initialize return variables to zero */ - *maxBytes = 0; - *maxSlots = 0; - - /* Loop through all codecs supplied to function */ - for (i = 0; i < noOfCodecs; i++) - { - /* Find current codec and set parameters accordingly */ - - if ((codecID[i] == kDecoderPCMu) || (codecID[i] == kDecoderPCMu_2ch)) - { - codecBytes = 1680; /* Up to 210ms @ 64kbps */ - codecBuffers = 30; /* Down to 5ms frames */ - } - else if ((codecID[i] == kDecoderPCMa) || - (codecID[i] == kDecoderPCMa_2ch)) - { - codecBytes = 1680; /* Up to 210ms @ 64kbps */ - codecBuffers = 30; /* Down to 5ms frames */ - } - else if (codecID[i] == kDecoderILBC) - { - codecBytes = 380; /* 200ms @ 15.2kbps (20ms frames) */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderISAC) - { - codecBytes = 960; /* 240ms @ 32kbps (60ms frames) */ - codecBuffers = 8; - } - else if ((codecID[i] == kDecoderISACswb) || - (codecID[i] == kDecoderISACfb)) - { - codecBytes = 1560; /* 240ms @ 52kbps (30ms frames) */ - codecBuffers = 8; - } - else if (codecID[i] == kDecoderOpus) - { - codecBytes = 15300; /* 240ms @ 510kbps (60ms frames) */ - codecBuffers = 30; /* Replicating the value for PCMu/a */ - } - else if ((codecID[i] == kDecoderPCM16B) || - (codecID[i] == kDecoderPCM16B_2ch)) - { - codecBytes = 3360; /* 210ms */ - codecBuffers = 15; - } - else if ((codecID[i] == kDecoderPCM16Bwb) || - (codecID[i] == kDecoderPCM16Bwb_2ch)) - { - codecBytes = 6720; /* 210ms */ - codecBuffers = 15; - } - else if ((codecID[i] == kDecoderPCM16Bswb32kHz) || - (codecID[i] == kDecoderPCM16Bswb32kHz_2ch)) - { - codecBytes = 13440; /* 210ms */ - codecBuffers = 15; - } - else if (codecID[i] == kDecoderPCM16Bswb48kHz) - { - codecBytes = 20160; /* 210ms */ - codecBuffers = 15; - } - else if ((codecID[i] == kDecoderG722) || - (codecID[i] == kDecoderG722_2ch)) - { - codecBytes = 1680; /* 210ms @ 64kbps */ - codecBuffers = 15; - } - else if (codecID[i] == kDecoderRED) - { - codecBytes = 0; /* Should not be max... */ - codecBuffers = 0; - } - else if (codecID[i] == kDecoderAVT) - { - codecBytes = 0; /* Should not be max... */ - codecBuffers = 0; - } - else if (codecID[i] == kDecoderCNG) - { - codecBytes = 0; /* Should not be max... */ - codecBuffers = 0; - } - else if (codecID[i] == kDecoderG729) - { - codecBytes = 210; /* 210ms @ 8kbps */ - codecBuffers = 20; /* max 200ms supported for 10ms frames */ - } - else if (codecID[i] == kDecoderG729_1) - { - codecBytes = 840; /* 210ms @ 32kbps */ - codecBuffers = 10; /* max 200ms supported for 20ms frames */ - } - else if (codecID[i] == kDecoderG726_16) - { - codecBytes = 400; /* 200ms @ 16kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG726_24) - { - codecBytes = 600; /* 200ms @ 24kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG726_32) - { - codecBytes = 800; /* 200ms @ 32kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG726_40) - { - codecBytes = 1000; /* 200ms @ 40kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG722_1_16) - { - codecBytes = 420; /* 210ms @ 16kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG722_1_24) - { - codecBytes = 630; /* 210ms @ 24kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG722_1_32) - { - codecBytes = 840; /* 210ms @ 32kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG722_1C_24) - { - codecBytes = 630; /* 210ms @ 24kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG722_1C_32) - { - codecBytes = 840; /* 210ms @ 32kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderG722_1C_48) - { - codecBytes = 1260; /* 210ms @ 48kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderSPEEX_8) - { - codecBytes = 1250; /* 210ms @ 50kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderSPEEX_16) - { - codecBytes = 1250; /* 210ms @ 50kbps */ - codecBuffers = 10; - } - else if ((codecID[i] == kDecoderCELT_32) || - (codecID[i] == kDecoderCELT_32_2ch)) - { - codecBytes = 1250; /* 210ms @ 50kbps */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderGSMFR) - { - codecBytes = 340; /* 200ms */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderAMR) - { - codecBytes = 384; /* 240ms @ 12.2kbps+headers (60ms frames) */ - codecBuffers = 10; - } - else if (codecID[i] == kDecoderAMRWB) - { - codecBytes = 744; - codecBuffers = 10; - } - else if (codecID[i] == kDecoderArbitrary) - { - codecBytes = 6720; /* Assume worst case uncompressed WB 210ms */ - codecBuffers = 15; - } - else - { - /*Unknow codec */ - codecBytes = 0; - codecBuffers = 0; - ok = CODEC_DB_UNKNOWN_CODEC; - } - - /* Update max variables */ - *maxBytes = WEBRTC_SPL_MAX((*maxBytes), codecBytes); - *maxSlots = WEBRTC_SPL_MAX((*maxSlots), codecBuffers); - - } /* end of for loop */ - - /* - * Add size needed by the additional pointers for each slot inside struct, - * as indicated on each line below. - */ - w16_tmp = (sizeof(uint32_t) /* timeStamp */ - + sizeof(int16_t*) /* payloadLocation */ - + sizeof(uint16_t) /* seqNumber */ - + sizeof(int16_t) /* payloadType */ - + sizeof(int16_t) /* payloadLengthBytes */ - + sizeof(int16_t) /* rcuPlCntr */ - + sizeof(int)); /* waitingTime */ - /* Add the extra size per slot to the memory count */ - *maxBytes += w16_tmp * (*maxSlots); - - *per_slot_overhead_bytes = w16_tmp; - return ok; -} diff --git a/webrtc/modules/audio_coding/neteq4/packet_buffer.cc b/webrtc/modules/audio_coding/neteq/packet_buffer.cc similarity index 77% rename from webrtc/modules/audio_coding/neteq4/packet_buffer.cc rename to webrtc/modules/audio_coding/neteq/packet_buffer.cc index 0cc0854fc..8a81c2598 100644 --- a/webrtc/modules/audio_coding/neteq4/packet_buffer.cc +++ b/webrtc/modules/audio_coding/neteq/packet_buffer.cc @@ -12,12 +12,12 @@ // an STL list. The list is kept sorted at all times so that the next packet to // decode is at the beginning of the list. -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" #include // find_if() -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" namespace webrtc { @@ -36,14 +36,8 @@ class NewTimestampIsLarger { const Packet* new_packet_; }; -// Constructor. The arguments define the maximum number of slots and maximum -// payload memory (excluding RTP headers) that the buffer will accept. -PacketBuffer::PacketBuffer(size_t max_number_of_packets, - size_t max_memory_bytes) - : max_number_of_packets_(max_number_of_packets), - max_memory_bytes_(max_memory_bytes), - current_memory_bytes_(0) { -} +PacketBuffer::PacketBuffer(size_t max_number_of_packets) + : max_number_of_packets_(max_number_of_packets) {} // Destructor. All packets in the buffer will be destroyed. PacketBuffer::~PacketBuffer() { @@ -53,7 +47,6 @@ PacketBuffer::~PacketBuffer() { // Flush the buffer. All packets in the buffer will be destroyed. void PacketBuffer::Flush() { DeleteAllPackets(&buffer_); - current_memory_bytes_ = 0; } int PacketBuffer::InsertPacket(Packet* packet) { @@ -66,22 +59,10 @@ int PacketBuffer::InsertPacket(Packet* packet) { int return_val = kOK; - if ((buffer_.size() >= max_number_of_packets_) || - (current_memory_bytes_ + packet->payload_length - > static_cast(max_memory_bytes_))) { + if (buffer_.size() >= max_number_of_packets_) { // Buffer is full. Flush it. Flush(); return_val = kFlushed; - if ((buffer_.size() >= max_number_of_packets_) || - (current_memory_bytes_ + packet->payload_length - > static_cast(max_memory_bytes_))) { - // Buffer is still too small for the packet. Either the buffer limits are - // really small, or the packet is really large. Delete the packet and - // return an error. - delete [] packet->payload; - delete packet; - return kOversizePacket; - } } // Get an iterator pointing to the place in the buffer where the new packet @@ -91,7 +72,6 @@ int PacketBuffer::InsertPacket(Packet* packet) { buffer_.rbegin(), buffer_.rend(), NewTimestampIsLarger(packet)); buffer_.insert(rit.base(), packet); // Insert the packet at that position. - current_memory_bytes_ += packet->payload_length; return return_val; } @@ -183,8 +163,6 @@ Packet* PacketBuffer::GetNextPacket(int* discard_count) { // Assert that the packet sanity checks in InsertPacket method works. assert(packet && packet->payload); buffer_.pop_front(); - current_memory_bytes_ -= packet->payload_length; - assert(current_memory_bytes_ >= 0); // Assert bookkeeping is correct. // Discard other packets with the same timestamp. These are duplicates or // redundant payloads that should not be used. if (discard_count) { @@ -206,11 +184,9 @@ int PacketBuffer::DiscardNextPacket() { if (Empty()) { return kBufferEmpty; } - Packet* temp_packet = buffer_.front(); // Assert that the packet sanity checks in InsertPacket method works. - assert(temp_packet && temp_packet->payload); - current_memory_bytes_ -= temp_packet->payload_length; - assert(current_memory_bytes_ >= 0); // Assert bookkeeping is correct. + assert(buffer_.front()); + assert(buffer_.front()->payload); DeleteFirstPacket(&buffer_); return kOK; } @@ -241,11 +217,11 @@ int PacketBuffer::NumSamplesInBuffer(DecoderDatabase* decoder_database, int duration; if (packet->sync_packet) { duration = last_duration; + } else if (packet->primary) { + duration = + decoder->PacketDuration(packet->payload, packet->payload_length); } else { - duration = packet->primary ? - decoder->PacketDuration(packet->payload, packet->payload_length) : - decoder->PacketDurationRedundant(packet->payload, - packet->payload_length); + continue; } if (duration >= 0) { last_duration = duration; // Save the most up-to-date (valid) duration. @@ -280,14 +256,9 @@ void PacketBuffer::DeleteAllPackets(PacketList* packet_list) { } } -void PacketBuffer::BufferStat(int* num_packets, - int* max_num_packets, - int* current_memory_bytes, - int* max_memory_bytes) const { +void PacketBuffer::BufferStat(int* num_packets, int* max_num_packets) const { *num_packets = static_cast(buffer_.size()); *max_num_packets = static_cast(max_number_of_packets_); - *current_memory_bytes = current_memory_bytes_; - *max_memory_bytes = static_cast(max_memory_bytes_); } } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/packet_buffer.h b/webrtc/modules/audio_coding/neteq/packet_buffer.h index 61ff2b970..76c4ddd16 100644 --- a/webrtc/modules/audio_coding/neteq/packet_buffer.h +++ b/webrtc/modules/audio_coding/neteq/packet_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * Copyright (c) 2012 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 @@ -8,247 +8,128 @@ * be found in the AUTHORS file in the root of the source tree. */ -/* - * Interface for the actual packet buffer data structure. - */ - -#ifndef PACKET_BUFFER_H -#define PACKET_BUFFER_H - -#include "typedefs.h" - -#include "webrtc_neteq.h" -#include "rtp.h" - -/* Define minimum allowed buffer memory, in 16-bit words */ -#define PBUFFER_MIN_MEMORY_SIZE 150 - -/****************************/ -/* The packet buffer struct */ -/****************************/ - -typedef struct -{ - - /* Variables common to the entire buffer */ - uint16_t packSizeSamples; /* packet size in samples of last decoded packet */ - int16_t *startPayloadMemory; /* pointer to the payload memory */ - int memorySizeW16; /* the size (in int16_t) of the payload memory */ - int16_t *currentMemoryPos; /* The memory position to insert next payload */ - int numPacketsInBuffer; /* The number of packets in the buffer */ - int insertPosition; /* The position to insert next packet */ - int maxInsertPositions; /* Maximum number of packets allowed */ - - /* Arrays with one entry per packet slot */ - /* NOTE: If these are changed, the changes must be accounted for at the end of - the function WebRtcNetEQ_GetDefaultCodecSettings(). */ - uint32_t *timeStamp; /* Timestamp in slot n */ - int16_t **payloadLocation; /* Memory location of payload in slot n */ - uint16_t *seqNumber; /* Sequence number in slot n */ - int16_t *payloadType; /* Payload type of packet in slot n */ - int16_t *payloadLengthBytes; /* Payload length of packet in slot n */ - int16_t *rcuPlCntr; /* zero for non-RCU payload, 1 for main payload - 2 for redundant payload */ - int *waitingTime; - - /* Statistics counter */ - uint16_t discardedPackets; /* Number of discarded packets */ - -} PacketBuf_t; - -/*************************/ -/* Function declarations */ -/*************************/ - -/**************************************************************************** - * WebRtcNetEQ_PacketBufferInit(...) - * - * This function initializes the packet buffer. - * - * Input: - * - bufferInst : Buffer instance to be initialized - * - noOfPackets : Maximum number of packets that buffer should hold - * - memory : Pointer to the storage memory for the payloads - * - memorySize : The size of the payload memory (in int16_t) - * - * Output: - * - bufferInst : Updated buffer instance - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_PacketBufferInit(PacketBuf_t *bufferInst, int maxNoOfPackets, - int16_t *pw16_memory, int memorySize); - -/**************************************************************************** - * WebRtcNetEQ_PacketBufferFlush(...) - * - * This function flushes all the packets in the buffer. - * - * Input: - * - bufferInst : Buffer instance to be flushed - * - * Output: - * - bufferInst : Flushed buffer instance - * - * Return value : 0 - Ok - */ - -int WebRtcNetEQ_PacketBufferFlush(PacketBuf_t *bufferInst); - -/**************************************************************************** - * WebRtcNetEQ_PacketBufferInsert(...) - * - * This function inserts an RTP packet into the packet buffer. - * - * Input: - * - bufferInst : Buffer instance - * - RTPpacket : An RTP packet struct (with payload, sequence - * number, etc.) - * - av_sync : 1 indicates AV-sync enabled, 0 disabled. - * - * Output: - * - bufferInst : Updated buffer instance - * - flushed : 1 if buffer was flushed, 0 otherwise - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_PacketBufferInsert(PacketBuf_t *bufferInst, const RTPPacket_t *RTPpacket, - int16_t *flushed, int av_sync); - -/**************************************************************************** - * WebRtcNetEQ_PacketBufferExtract(...) - * - * This function extracts a payload from the buffer. - * - * Input: - * - bufferInst : Buffer instance - * - bufferPosition: Position of the packet that should be extracted - * - * Output: - * - RTPpacket : An RTP packet struct (with payload, sequence - * number, etc) - * - bufferInst : Updated buffer instance - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_PacketBufferExtract(PacketBuf_t *bufferInst, RTPPacket_t *RTPpacket, - int bufferPosition, int *waitingTime); - -/**************************************************************************** - * WebRtcNetEQ_PacketBufferFindLowestTimestamp(...) - * - * This function finds the next packet with the lowest timestamp. - * - * Input: - * - buffer_inst : Buffer instance. - * - current_time_stamp : The timestamp to compare packet timestamps with. - * - erase_old_packets : If non-zero, erase packets older than currentTS. - * - * Output: - * - time_stamp : Lowest timestamp that was found. - * - buffer_position : Position of this packet (-1 if there are no - * packets in the buffer). - * - payload_type : Payload type of the found payload. - * - * Return value : 0 - Ok; - * < 0 - Error. - */ - -int WebRtcNetEQ_PacketBufferFindLowestTimestamp(PacketBuf_t* buffer_inst, - uint32_t current_time_stamp, - uint32_t* time_stamp, - int* buffer_position, - int erase_old_packets, - int16_t* payload_type); - -/**************************************************************************** - * WebRtcNetEQ_PacketBufferGetPacketSize(...) - * - * Calculate and return an estimate of the data length (in samples) of the - * given packet. If no estimate is available (because we do not know how to - * compute packet durations for the associated payload type), last_duration - * will be returned instead. - * - * Input: - * - buffer_inst : Buffer instance - * - buffer_pos : The index of the buffer of which to estimate the - * duration - * - codec_database : Codec database instance - * - codec_pos : The codec database entry associated with the payload - * type of the specified buffer. - * - last_duration : The duration of the previous frame. - * - av_sync : 1 indicates AV-sync enabled, 0 disabled. - * - * Return value : The buffer size in samples - */ - -int WebRtcNetEQ_PacketBufferGetPacketSize(const PacketBuf_t* buffer_inst, - int buffer_pos, - const CodecDbInst_t* codec_database, - int codec_pos, int last_duration, - int av_sync); - -/**************************************************************************** - * WebRtcNetEQ_PacketBufferGetSize(...) - * - * Calculate and return an estimate of the total data length (in samples) - * currently in the buffer. The estimate is calculated as the number of - * packets currently in the buffer (which does not have any remaining waiting - * time), multiplied with the number of samples obtained from the last - * decoded packet. - * - * Input: - * - buffer_inst : Buffer instance - * - codec_database : Codec database instance - * - av_sync : 1 indicates AV-sync enabled, 0 disabled. - * - * Return value : The buffer size in samples - */ - -int32_t WebRtcNetEQ_PacketBufferGetSize(const PacketBuf_t* buffer_inst, - const CodecDbInst_t* codec_database, - int av_sync); - -/**************************************************************************** - * WebRtcNetEQ_IncrementWaitingTimes(...) - * - * Increment the waiting time for all packets in the buffer by one. - * - * Input: - * - bufferInst : Buffer instance - * - * Return value : n/a - */ - -void WebRtcNetEQ_IncrementWaitingTimes(PacketBuf_t *buffer_inst); - -/**************************************************************************** - * WebRtcNetEQ_GetDefaultCodecSettings(...) - * - * Calculates a recommended buffer size for a specific set of codecs. - * - * Input: - * - codecID : An array of codec types that will be used - * - noOfCodecs : Number of codecs in array codecID - * - * Output: - * - maxBytes : Recommended buffer memory size in bytes - * - maxSlots : Recommended number of slots in buffer - * - per_slot_overhead_bytes : overhead in bytes for each slot in buffer. - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_GetDefaultCodecSettings(const enum WebRtcNetEQDecoder *codecID, - int noOfCodecs, int *maxBytes, - int *maxSlots, - int* per_slot_overhead_bytes); - -#endif /* PACKET_BUFFER_H */ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_PACKET_BUFFER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_PACKET_BUFFER_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +// Forward declaration. +class DecoderDatabase; + +// This is the actual buffer holding the packets before decoding. +class PacketBuffer { + public: + enum BufferReturnCodes { + kOK = 0, + kFlushed, + kNotFound, + kBufferEmpty, + kInvalidPacket, + kInvalidPointer + }; + + // Constructor creates a buffer which can hold a maximum of + // |max_number_of_packets| packets. + PacketBuffer(size_t max_number_of_packets); + + // Deletes all packets in the buffer before destroying the buffer. + virtual ~PacketBuffer(); + + // Flushes the buffer and deletes all packets in it. + virtual void Flush(); + + // Returns true for an empty buffer. + virtual bool Empty() const { return buffer_.empty(); } + + // Inserts |packet| into the buffer. The buffer will take over ownership of + // the packet object. + // Returns PacketBuffer::kOK on success, PacketBuffer::kFlushed if the buffer + // was flushed due to overfilling. + virtual int InsertPacket(Packet* packet); + + // Inserts a list of packets into the buffer. The buffer will take over + // ownership of the packet objects. + // Returns PacketBuffer::kOK if all packets were inserted successfully. + // If the buffer was flushed due to overfilling, only a subset of the list is + // inserted, and PacketBuffer::kFlushed is returned. + // The last three parameters are included for legacy compatibility. + // TODO(hlundin): Redesign to not use current_*_payload_type and + // decoder_database. + virtual int InsertPacketList(PacketList* packet_list, + const DecoderDatabase& decoder_database, + uint8_t* current_rtp_payload_type, + uint8_t* current_cng_rtp_payload_type); + + // Gets the timestamp for the first packet in the buffer and writes it to the + // output variable |next_timestamp|. + // Returns PacketBuffer::kBufferEmpty if the buffer is empty, + // PacketBuffer::kOK otherwise. + virtual int NextTimestamp(uint32_t* next_timestamp) const; + + // Gets the timestamp for the first packet in the buffer with a timestamp no + // lower than the input limit |timestamp|. The result is written to the output + // variable |next_timestamp|. + // Returns PacketBuffer::kBufferEmpty if the buffer is empty, + // PacketBuffer::kOK otherwise. + virtual int NextHigherTimestamp(uint32_t timestamp, + uint32_t* next_timestamp) const; + + // Returns a (constant) pointer the RTP header of the first packet in the + // buffer. Returns NULL if the buffer is empty. + virtual const RTPHeader* NextRtpHeader() const; + + // Extracts the first packet in the buffer and returns a pointer to it. + // Returns NULL if the buffer is empty. The caller is responsible for deleting + // the packet. + // Subsequent packets with the same timestamp as the one extracted will be + // discarded and properly deleted. The number of discarded packets will be + // written to the output variable |discard_count|. + virtual Packet* GetNextPacket(int* discard_count); + + // Discards the first packet in the buffer. The packet is deleted. + // Returns PacketBuffer::kBufferEmpty if the buffer is empty, + // PacketBuffer::kOK otherwise. + virtual int DiscardNextPacket(); + + // Discards all packets that are (strictly) older than |timestamp_limit|. + // Returns number of packets discarded. + virtual int DiscardOldPackets(uint32_t timestamp_limit); + + // Returns the number of packets in the buffer, including duplicates and + // redundant packets. + virtual int NumPacketsInBuffer() const { + return static_cast(buffer_.size()); + } + + // Returns the number of samples in the buffer, including samples carried in + // duplicate and redundant packets. + virtual int NumSamplesInBuffer(DecoderDatabase* decoder_database, + int last_decoded_length) const; + + // Increase the waiting time counter for every packet in the buffer by |inc|. + // The default value for |inc| is 1. + virtual void IncrementWaitingTimes(int inc = 1); + + virtual void BufferStat(int* num_packets, int* max_num_packets) const; + + // Static method that properly deletes the first packet, and its payload + // array, in |packet_list|. Returns false if |packet_list| already was empty, + // otherwise true. + static bool DeleteFirstPacket(PacketList* packet_list); + + // Static method that properly deletes all packets, and their payload arrays, + // in |packet_list|. + static void DeleteAllPackets(PacketList* packet_list); + + private: + size_t max_number_of_packets_; + PacketList buffer_; + DISALLOW_COPY_AND_ASSIGN(PacketBuffer); +}; + +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_PACKET_BUFFER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/packet_buffer_unittest.cc b/webrtc/modules/audio_coding/neteq/packet_buffer_unittest.cc similarity index 87% rename from webrtc/modules/audio_coding/neteq4/packet_buffer_unittest.cc rename to webrtc/modules/audio_coding/neteq/packet_buffer_unittest.cc index c8109dc6d..5e6b89fdc 100644 --- a/webrtc/modules/audio_coding/neteq4/packet_buffer_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/packet_buffer_unittest.cc @@ -10,12 +10,12 @@ // Unit tests for PacketBuffer class. -#include "webrtc/modules/audio_coding/neteq4/packet_buffer.h" +#include "webrtc/modules/audio_coding/neteq/packet_buffer.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/packet.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" using ::testing::Return; using ::testing::_; @@ -70,13 +70,13 @@ void PacketGenerator::SkipPacket() { // Start of test definitions. TEST(PacketBuffer, CreateAndDestroy) { - PacketBuffer* buffer = new PacketBuffer(10, 1000); // 10 packets, 1000 bytes. + PacketBuffer* buffer = new PacketBuffer(10); // 10 packets. EXPECT_TRUE(buffer->Empty()); delete buffer; } TEST(PacketBuffer, InsertPacket) { - PacketBuffer buffer(10, 1000); // 10 packets, 1000 bytes. + PacketBuffer buffer(10); // 10 packets. PacketGenerator gen(17u, 4711u, 0, 10); const int payload_len = 100; @@ -88,7 +88,6 @@ TEST(PacketBuffer, InsertPacket) { EXPECT_EQ(4711u, next_ts); EXPECT_FALSE(buffer.Empty()); EXPECT_EQ(1, buffer.NumPacketsInBuffer()); - EXPECT_EQ(payload_len, buffer.current_memory_bytes()); const RTPHeader* hdr = buffer.NextRtpHeader(); EXPECT_EQ(&(packet->header), hdr); // Compare pointer addresses. @@ -98,7 +97,7 @@ TEST(PacketBuffer, InsertPacket) { // Test to flush buffer. TEST(PacketBuffer, FlushBuffer) { - PacketBuffer buffer(10, 1000); // 10 packets, 1000 bytes. + PacketBuffer buffer(10); // 10 packets. PacketGenerator gen(0, 0, 0, 10); const int payload_len = 10; @@ -109,18 +108,16 @@ TEST(PacketBuffer, FlushBuffer) { } EXPECT_EQ(10, buffer.NumPacketsInBuffer()); EXPECT_FALSE(buffer.Empty()); - EXPECT_EQ(10 * payload_len, buffer.current_memory_bytes()); buffer.Flush(); // Buffer should delete the payloads itself. EXPECT_EQ(0, buffer.NumPacketsInBuffer()); EXPECT_TRUE(buffer.Empty()); - EXPECT_EQ(0, buffer.current_memory_bytes()); } // Test to fill the buffer over the limits, and verify that it flushes. TEST(PacketBuffer, OverfillBuffer) { - PacketBuffer buffer(10, 1000); // 10 packets, 1000 bytes. + PacketBuffer buffer(10); // 10 packets. PacketGenerator gen(0, 0, 0, 10); // Insert 10 small packets; should be ok. @@ -131,7 +128,6 @@ TEST(PacketBuffer, OverfillBuffer) { EXPECT_EQ(PacketBuffer::kOK, buffer.InsertPacket(packet)); } EXPECT_EQ(10, buffer.NumPacketsInBuffer()); - EXPECT_EQ(10 * payload_len, buffer.current_memory_bytes()); uint32_t next_ts; EXPECT_EQ(PacketBuffer::kOK, buffer.NextTimestamp(&next_ts)); EXPECT_EQ(0u, next_ts); // Expect first inserted packet to be first in line. @@ -140,30 +136,17 @@ TEST(PacketBuffer, OverfillBuffer) { Packet* packet = gen.NextPacket(payload_len); EXPECT_EQ(PacketBuffer::kFlushed, buffer.InsertPacket(packet)); EXPECT_EQ(1, buffer.NumPacketsInBuffer()); - EXPECT_EQ(payload_len, buffer.current_memory_bytes()); EXPECT_EQ(PacketBuffer::kOK, buffer.NextTimestamp(&next_ts)); // Expect last inserted packet to be first in line. EXPECT_EQ(packet->header.timestamp, next_ts); - // Insert 2 large packets; expect to flush when inserting the second one. - const int large_payload_len = 500; - packet = gen.NextPacket(large_payload_len); - EXPECT_EQ(PacketBuffer::kOK, buffer.InsertPacket(packet)); - EXPECT_EQ(2, buffer.NumPacketsInBuffer()); - EXPECT_EQ(payload_len + large_payload_len, buffer.current_memory_bytes()); - - packet = gen.NextPacket(large_payload_len); - EXPECT_EQ(PacketBuffer::kFlushed, buffer.InsertPacket(packet)); - EXPECT_EQ(1, buffer.NumPacketsInBuffer()); - EXPECT_EQ(large_payload_len, buffer.current_memory_bytes()); - - // Flush buffer to delete remaining packets. + // Flush buffer to delete all packets. buffer.Flush(); } // Test inserting a list of packets. TEST(PacketBuffer, InsertPacketList) { - PacketBuffer buffer(10, 1000); // 10 packets, 1000 bytes. + PacketBuffer buffer(10); // 10 packets. PacketGenerator gen(0, 0, 0, 10); PacketList list; const int payload_len = 10; @@ -187,7 +170,6 @@ TEST(PacketBuffer, InsertPacketList) { ¤t_cng_pt)); EXPECT_TRUE(list.empty()); // The PacketBuffer should have depleted the list. EXPECT_EQ(10, buffer.NumPacketsInBuffer()); - EXPECT_EQ(10 * payload_len, buffer.current_memory_bytes()); EXPECT_EQ(0, current_pt); // Current payload type changed to 0. EXPECT_EQ(0xFF, current_cng_pt); // CNG payload type not changed. @@ -200,7 +182,7 @@ TEST(PacketBuffer, InsertPacketList) { // Expecting the buffer to flush. // TODO(hlundin): Remove this test when legacy operation is no longer needed. TEST(PacketBuffer, InsertPacketListChangePayloadType) { - PacketBuffer buffer(10, 1000); // 10 packets, 1000 bytes. + PacketBuffer buffer(10); // 10 packets. PacketGenerator gen(0, 0, 0, 10); PacketList list; const int payload_len = 10; @@ -229,7 +211,6 @@ TEST(PacketBuffer, InsertPacketListChangePayloadType) { ¤t_cng_pt)); EXPECT_TRUE(list.empty()); // The PacketBuffer should have depleted the list. EXPECT_EQ(1, buffer.NumPacketsInBuffer()); // Only the last packet. - EXPECT_EQ(1 * payload_len, buffer.current_memory_bytes()); EXPECT_EQ(1, current_pt); // Current payload type changed to 0. EXPECT_EQ(0xFF, current_cng_pt); // CNG payload type not changed. @@ -252,7 +233,7 @@ TEST(PacketBuffer, InsertPacketListChangePayloadType) { // 8 0x0005 0x00000028 0x0000001E // 9 0x0006 0x00000032 0x00000028 TEST(PacketBuffer, ExtractOrderRedundancy) { - PacketBuffer buffer(100, 1000); // 100 packets, 1000 bytes. + PacketBuffer buffer(100); // 100 packets. const uint32_t ts_increment = 10; // Samples per packet. const uint16_t start_seq_no = 0xFFFF - 2; // Wraps after 3 packets. const uint32_t start_ts = 0xFFFFFFFF - @@ -321,7 +302,7 @@ TEST(PacketBuffer, ExtractOrderRedundancy) { } TEST(PacketBuffer, DiscardPackets) { - PacketBuffer buffer(100, 1000); // 100 packets, 1000 bytes. + PacketBuffer buffer(100); // 100 packets. const uint16_t start_seq_no = 17; const uint32_t start_ts = 4711; const uint32_t ts_increment = 10; @@ -335,7 +316,6 @@ TEST(PacketBuffer, DiscardPackets) { buffer.InsertPacket(packet); } EXPECT_EQ(10, buffer.NumPacketsInBuffer()); - EXPECT_EQ(10 * payload_len, buffer.current_memory_bytes()); // Discard them one by one and make sure that the right packets are at the // front of the buffer. @@ -351,7 +331,7 @@ TEST(PacketBuffer, DiscardPackets) { } TEST(PacketBuffer, Reordering) { - PacketBuffer buffer(100, 1000); // 100 packets, 1000 bytes. + PacketBuffer buffer(100); // 100 packets. const uint16_t start_seq_no = 17; const uint32_t start_ts = 4711; const uint32_t ts_increment = 10; @@ -384,7 +364,6 @@ TEST(PacketBuffer, Reordering) { ¤t_pt, ¤t_cng_pt)); EXPECT_EQ(10, buffer.NumPacketsInBuffer()); - EXPECT_EQ(10 * payload_len, buffer.current_memory_bytes()); // Extract them and make sure that come out in the right order. uint32_t current_ts = start_ts; @@ -408,18 +387,8 @@ TEST(PacketBuffer, Failures) { int payload_len = 100; PacketGenerator gen(start_seq_no, start_ts, 0, ts_increment); - PacketBuffer* buffer = new PacketBuffer(0, 1000); // 0 packets, 1000 bytes. - Packet* packet = gen.NextPacket(payload_len); - EXPECT_EQ(PacketBuffer::kOversizePacket, buffer->InsertPacket(packet)); - delete buffer; - - buffer = new PacketBuffer(100, 10); // 100 packets, 10 bytes. - packet = gen.NextPacket(payload_len); - EXPECT_EQ(PacketBuffer::kOversizePacket, buffer->InsertPacket(packet)); - delete buffer; - - buffer = new PacketBuffer(100, 10000); // 100 packets, 10000 bytes. - packet = NULL; + PacketBuffer* buffer = new PacketBuffer(100); // 100 packets. + Packet* packet = NULL; EXPECT_EQ(PacketBuffer::kInvalidPacket, buffer->InsertPacket(packet)); packet = gen.NextPacket(payload_len); delete [] packet->payload; @@ -448,7 +417,7 @@ TEST(PacketBuffer, Failures) { // Insert packet list of three packets, where the second packet has an invalid // payload. Expect first packet to be inserted, and the remaining two to be // discarded. - buffer = new PacketBuffer(100, 1000); // 100 packets, 1000 bytes. + buffer = new PacketBuffer(100); // 100 packets. PacketList list; list.push_back(gen.NextPacket(payload_len)); // Valid packet. packet = gen.NextPacket(payload_len); diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter.cc b/webrtc/modules/audio_coding/neteq/payload_splitter.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/payload_splitter.cc rename to webrtc/modules/audio_coding/neteq/payload_splitter.cc index 0e97b7d8f..1d61ef0cf 100644 --- a/webrtc/modules/audio_coding/neteq4/payload_splitter.cc +++ b/webrtc/modules/audio_coding/neteq/payload_splitter.cc @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/payload_splitter.h" +#include "webrtc/modules/audio_coding/neteq/payload_splitter.h" #include -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter.h b/webrtc/modules/audio_coding/neteq/payload_splitter.h similarity index 91% rename from webrtc/modules/audio_coding/neteq4/payload_splitter.h rename to webrtc/modules/audio_coding/neteq/payload_splitter.h index 0f6caed8a..a3dd77e5a 100644 --- a/webrtc/modules/audio_coding/neteq4/payload_splitter.h +++ b/webrtc/modules/audio_coding/neteq/payload_splitter.h @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PAYLOAD_SPLITTER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PAYLOAD_SPLITTER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_PAYLOAD_SPLITTER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_PAYLOAD_SPLITTER_H_ -#include "webrtc/modules/audio_coding/neteq4/packet.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" namespace webrtc { @@ -87,4 +87,4 @@ class PayloadSplitter { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PAYLOAD_SPLITTER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_PAYLOAD_SPLITTER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc b/webrtc/modules/audio_coding/neteq/payload_splitter_unittest.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc rename to webrtc/modules/audio_coding/neteq/payload_splitter_unittest.cc index c83a9b582..5cde1bda5 100644 --- a/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/payload_splitter_unittest.cc @@ -10,15 +10,15 @@ // Unit tests for PayloadSplitter class. -#include "webrtc/modules/audio_coding/neteq4/payload_splitter.h" +#include "webrtc/modules/audio_coding/neteq/payload_splitter.h" #include #include // pair #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/packet.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" using ::testing::Return; diff --git a/webrtc/modules/audio_coding/neteq/peak_detection.c b/webrtc/modules/audio_coding/neteq/peak_detection.c deleted file mode 100644 index 8c85d2a83..000000000 --- a/webrtc/modules/audio_coding/neteq/peak_detection.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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. - */ - -/* - * Implementation of the peak detection used for finding correlation peaks. - */ - -#include "dsp_helpfunctions.h" - -#include "signal_processing_library.h" - -/* Table of constants used in parabolic fit function WebRtcNetEQ_PrblFit */ -const int16_t WebRtcNetEQ_kPrblCf[17][3] = { { 120, 32, 64 }, { 140, 44, 75 }, - { 150, 50, 80 }, { 160, 57, 85 }, - { 180, 72, 96 }, { 200, 89, 107 }, - { 210, 98, 112 }, { 220, 108, 117 }, - { 240, 128, 128 }, { 260, 150, 139 }, - { 270, 162, 144 }, { 280, 174, 149 }, - { 300, 200, 160 }, { 320, 228, 171 }, - { 330, 242, 176 }, { 340, 257, 181 }, - { 360, 288, 192 } }; - -int16_t WebRtcNetEQ_PeakDetection(int16_t *pw16_data, int16_t w16_dataLen, - int16_t w16_nmbPeaks, int16_t fs_mult, - int16_t *pw16_winIndex, - int16_t *pw16_winValue) -{ - /* Local variables */ - int i; - int16_t w16_tmp; - int16_t w16_tmp2; - int16_t indMin = 0; - int16_t indMax = 0; - - /* Peak detection */ - - for (i = 0; i <= (w16_nmbPeaks - 1); i++) - { - if (w16_nmbPeaks == 1) - { - /* - * Single peak - * The parabola fit assumes that an extra point is available; worst case it gets - * a zero on the high end of the signal. - */ - w16_dataLen++; - } - - pw16_winIndex[i] = WebRtcSpl_MaxIndexW16(pw16_data, (int16_t) (w16_dataLen - 1)); - - if (i != w16_nmbPeaks - 1) - { - w16_tmp = pw16_winIndex[i] - 2; /* *fs_mult; */ - indMin = WEBRTC_SPL_MAX(0, w16_tmp); - w16_tmp = pw16_winIndex[i] + 2; /* *fs_mult; */ - w16_tmp2 = w16_dataLen - 1; - indMax = WEBRTC_SPL_MIN(w16_tmp2, w16_tmp); - } - - if ((pw16_winIndex[i] != 0) && (pw16_winIndex[i] != (w16_dataLen - 2))) - { - /* Parabola fit*/ - WebRtcNetEQ_PrblFit(&(pw16_data[pw16_winIndex[i] - 1]), &(pw16_winIndex[i]), - &(pw16_winValue[i]), fs_mult); - } - else - { - if (pw16_winIndex[i] == (w16_dataLen - 2)) - { - if (pw16_data[pw16_winIndex[i]] > pw16_data[pw16_winIndex[i] + 1]) - { - WebRtcNetEQ_PrblFit(&(pw16_data[pw16_winIndex[i] - 1]), - &(pw16_winIndex[i]), &(pw16_winValue[i]), fs_mult); - } - else if (pw16_data[pw16_winIndex[i]] <= pw16_data[pw16_winIndex[i] + 1]) - { - pw16_winValue[i] = (pw16_data[pw16_winIndex[i]] - + pw16_data[pw16_winIndex[i] + 1]) >> 1; /* lin approx */ - pw16_winIndex[i] = (pw16_winIndex[i] * 2 + 1) * fs_mult; - } - } - else - { - pw16_winValue[i] = pw16_data[pw16_winIndex[i]]; - pw16_winIndex[i] = pw16_winIndex[i] * 2 * fs_mult; - } - } - - if (i != w16_nmbPeaks - 1) - { - WebRtcSpl_MemSetW16(&(pw16_data[indMin]), 0, (indMax - indMin + 1)); - /* for (j=indMin; j<=indMax; j++) pw16_data[j] = 0; */ - } - } - - return 0; -} - -int16_t WebRtcNetEQ_PrblFit(int16_t *pw16_3pts, int16_t *pw16_Ind, - int16_t *pw16_outVal, int16_t fs_mult) -{ - /* Variables */ - int32_t Num, Den; - int32_t temp; - int16_t flag, stp, strt, lmt; - uint16_t PFind[13]; - - if (fs_mult == 1) - { - PFind[0] = 0; - PFind[1] = 8; - PFind[2] = 16; - } - else if (fs_mult == 2) - { - PFind[0] = 0; - PFind[1] = 4; - PFind[2] = 8; - PFind[3] = 12; - PFind[4] = 16; - } - else if (fs_mult == 4) - { - PFind[0] = 0; - PFind[1] = 2; - PFind[2] = 4; - PFind[3] = 6; - PFind[4] = 8; - PFind[5] = 10; - PFind[6] = 12; - PFind[7] = 14; - PFind[8] = 16; - } - else - { - PFind[0] = 0; - PFind[1] = 1; - PFind[2] = 3; - PFind[3] = 4; - PFind[4] = 5; - PFind[5] = 7; - PFind[6] = 8; - PFind[7] = 9; - PFind[8] = 11; - PFind[9] = 12; - PFind[10] = 13; - PFind[11] = 15; - PFind[12] = 16; - } - - /* Num = -3*pw16_3pts[0] + 4*pw16_3pts[1] - pw16_3pts[2]; */ - /* Den = pw16_3pts[0] - 2*pw16_3pts[1] + pw16_3pts[2]; */ - Num = WEBRTC_SPL_MUL_16_16(pw16_3pts[0],-3) + WEBRTC_SPL_MUL_16_16(pw16_3pts[1],4) - - pw16_3pts[2]; - - Den = pw16_3pts[0] + WEBRTC_SPL_MUL_16_16(pw16_3pts[1],-2) + pw16_3pts[2]; - - temp = (int32_t) WEBRTC_SPL_MUL(Num, (int32_t)120); /* need 32_16 really */ - flag = 1; - stp = WebRtcNetEQ_kPrblCf[PFind[fs_mult]][0] - WebRtcNetEQ_kPrblCf[PFind[fs_mult - 1]][0]; - strt = (WebRtcNetEQ_kPrblCf[PFind[fs_mult]][0] - + WebRtcNetEQ_kPrblCf[PFind[fs_mult - 1]][0]) >> 1; - - if (temp < (int32_t) WEBRTC_SPL_MUL(-Den,(int32_t)strt)) - { - lmt = strt - stp; - while (flag) - { - if ((flag == fs_mult) || (temp - > (int32_t) WEBRTC_SPL_MUL(-Den,(int32_t)lmt))) - { - *pw16_outVal - = (int16_t) - (((int32_t) ((int32_t) WEBRTC_SPL_MUL(Den,(int32_t)WebRtcNetEQ_kPrblCf[PFind[fs_mult-flag]][1]) - + (int32_t) WEBRTC_SPL_MUL(Num,(int32_t)WebRtcNetEQ_kPrblCf[PFind[fs_mult-flag]][2]) - + WEBRTC_SPL_MUL_16_16(pw16_3pts[0],256))) >> 8); - *pw16_Ind = (*pw16_Ind) * (fs_mult << 1) - flag; - flag = 0; - } - else - { - flag++; - lmt -= stp; - } - } - } - else if (temp > (int32_t) WEBRTC_SPL_MUL(-Den,(int32_t)(strt+stp))) - { - lmt = strt + (stp << 1); - while (flag) - { - if ((flag == fs_mult) || (temp - < (int32_t) WEBRTC_SPL_MUL(-Den,(int32_t)lmt))) - { - int32_t temp_term_1, temp_term_2, temp_term_3; - - temp_term_1 = WEBRTC_SPL_MUL(Den, - (int32_t) WebRtcNetEQ_kPrblCf[PFind[fs_mult+flag]][1]); - temp_term_2 = WEBRTC_SPL_MUL(Num, - (int32_t) WebRtcNetEQ_kPrblCf[PFind[fs_mult+flag]][2]); - temp_term_3 = WEBRTC_SPL_MUL_16_16(pw16_3pts[0],256); - - *pw16_outVal - = (int16_t) ((temp_term_1 + temp_term_2 + temp_term_3) >> 8); - - *pw16_Ind = (*pw16_Ind) * (fs_mult << 1) + flag; - flag = 0; - } - else - { - flag++; - lmt += stp; - } - } - - } - else - { - *pw16_outVal = pw16_3pts[1]; - *pw16_Ind = (*pw16_Ind) * 2 * fs_mult; - } - - return 0; -} - diff --git a/webrtc/modules/audio_coding/neteq4/post_decode_vad.cc b/webrtc/modules/audio_coding/neteq/post_decode_vad.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/post_decode_vad.cc rename to webrtc/modules/audio_coding/neteq/post_decode_vad.cc index c3d5c7edd..7ae7f97ab 100644 --- a/webrtc/modules/audio_coding/neteq4/post_decode_vad.cc +++ b/webrtc/modules/audio_coding/neteq/post_decode_vad.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/post_decode_vad.h" +#include "webrtc/modules/audio_coding/neteq/post_decode_vad.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/post_decode_vad.h b/webrtc/modules/audio_coding/neteq/post_decode_vad.h similarity index 80% rename from webrtc/modules/audio_coding/neteq4/post_decode_vad.h rename to webrtc/modules/audio_coding/neteq/post_decode_vad.h index eb197d9ef..e713009c8 100644 --- a/webrtc/modules/audio_coding/neteq4/post_decode_vad.h +++ b/webrtc/modules/audio_coding/neteq/post_decode_vad.h @@ -8,17 +8,17 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_POST_DECODE_VAD_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_POST_DECODE_VAD_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_POST_DECODE_VAD_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_POST_DECODE_VAD_H_ #include // size_t +#include "webrtc/base/constructormagic.h" #include "webrtc/common_audio/vad/include/webrtc_vad.h" #include "webrtc/common_types.h" // NULL -#include "webrtc/modules/audio_coding/neteq4/defines.h" -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/modules/audio_coding/neteq4/packet.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/modules/audio_coding/neteq/defines.h" +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -69,4 +69,4 @@ class PostDecodeVad { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_POST_DECODE_VAD_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_POST_DECODE_VAD_H_ diff --git a/webrtc/modules/audio_coding/neteq4/post_decode_vad_unittest.cc b/webrtc/modules/audio_coding/neteq/post_decode_vad_unittest.cc similarity index 90% rename from webrtc/modules/audio_coding/neteq4/post_decode_vad_unittest.cc rename to webrtc/modules/audio_coding/neteq/post_decode_vad_unittest.cc index a4d9da8e1..ed48db858 100644 --- a/webrtc/modules/audio_coding/neteq4/post_decode_vad_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/post_decode_vad_unittest.cc @@ -10,7 +10,7 @@ // Unit tests for PostDecodeVad class. -#include "webrtc/modules/audio_coding/neteq4/post_decode_vad.h" +#include "webrtc/modules/audio_coding/neteq/post_decode_vad.h" #include "gtest/gtest.h" diff --git a/webrtc/modules/audio_coding/neteq/preemptive_expand.c b/webrtc/modules/audio_coding/neteq/preemptive_expand.c deleted file mode 100644 index e56c06284..000000000 --- a/webrtc/modules/audio_coding/neteq/preemptive_expand.c +++ /dev/null @@ -1,527 +0,0 @@ -/* - * Copyright (c) 2012 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 file contains the Pre-emptive Expand algorithm that is used to increase - * the delay by repeating a part of the audio stream. - */ - -#include "dsp.h" - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" -#include "neteq_error_codes.h" - -#define PREEMPTIVE_CORR_LEN 50 -#define PREEMPTIVE_MIN_LAG 10 -#define PREEMPTIVE_MAX_LAG 60 -#define PREEMPTIVE_DOWNSAMPLED_LEN (PREEMPTIVE_CORR_LEN + PREEMPTIVE_MAX_LAG) - -/* Scratch usage: - - Type Name size startpos endpos - int16_t pw16_downSampSpeech 110 0 109 - int32_t pw32_corr 2*50 110 209 - int16_t pw16_corr 50 0 49 - - Total: 110+2*50 - */ - -#define SCRATCH_PW16_DS_SPEECH 0 -#define SCRATCH_PW32_CORR PREEMPTIVE_DOWNSAMPLED_LEN -#define SCRATCH_PW16_CORR 0 - -/**************************************************************************** - * WebRtcNetEQ_PreEmptiveExpand(...) - * - * This function tries to extend the audio data by repeating one or several - * pitch periods. The operation is only carried out if the correlation is - * strong or if the signal energy is very low. The algorithm is the - * reciprocal of the Accelerate algorithm. - * - * Input: - * - inst : NetEQ DSP instance - * - scratchPtr : Pointer to scratch vector. - * - decoded : Pointer to newly decoded speech. - * - len : Length of decoded speech. - * - oldDataLen : Length of the part of decoded that has already been played out. - * - BGNonly : If non-zero, Pre-emptive Expand will only copy - * the first DEFAULT_TIME_ADJUST seconds of the - * input and append to the end. No signal matching is - * done. - * - * Output: - * - inst : Updated instance - * - outData : Pointer to a memory space where the output data - * should be stored. The vector must be at least - * min(len + 120*fs/8000, NETEQ_MAX_OUTPUT_SIZE) - * elements long. - * - pw16_len : Number of samples written to outData. - * - * Return value : 0 - Ok - * <0 - Error - */ - -int WebRtcNetEQ_PreEmptiveExpand(DSPInst_t *inst, -#ifdef SCRATCH - int16_t *pw16_scratchPtr, -#endif - const int16_t *pw16_decoded, int len, int oldDataLen, - int16_t *pw16_outData, int16_t *pw16_len, - int16_t BGNonly) -{ - -#ifdef SCRATCH - /* Use scratch memory for internal temporary vectors */ - int16_t *pw16_downSampSpeech = pw16_scratchPtr + SCRATCH_PW16_DS_SPEECH; - int32_t *pw32_corr = (int32_t*) (pw16_scratchPtr + SCRATCH_PW32_CORR); - int16_t *pw16_corr = pw16_scratchPtr + SCRATCH_PW16_CORR; -#else - /* Allocate memory for temporary vectors */ - int16_t pw16_downSampSpeech[PREEMPTIVE_DOWNSAMPLED_LEN]; - int32_t pw32_corr[PREEMPTIVE_CORR_LEN]; - int16_t pw16_corr[PREEMPTIVE_CORR_LEN]; -#endif - int16_t w16_decodedMax = 0; - int16_t w16_tmp = 0; - int16_t w16_tmp2; - int32_t w32_tmp; - int32_t w32_tmp2; - - const int16_t w16_startLag = PREEMPTIVE_MIN_LAG; - const int16_t w16_endLag = PREEMPTIVE_MAX_LAG; - const int16_t w16_corrLen = PREEMPTIVE_CORR_LEN; - const int16_t *pw16_vec1, *pw16_vec2; - int16_t *pw16_vectmp; - int16_t w16_inc, w16_startfact; - int16_t w16_bestIndex, w16_bestVal; - int16_t w16_VAD = 1; - int16_t fsMult; - int16_t fsMult120; - int32_t w32_en1, w32_en2, w32_cc; - int16_t w16_en1, w16_en2; - int16_t w16_en1Scale, w16_en2Scale; - int16_t w16_sqrtEn1En2; - int16_t w16_bestCorr = 0; - int ok; - -#ifdef NETEQ_STEREO - MasterSlaveInfo *msInfo = inst->msInfo; -#endif - - fsMult = WebRtcNetEQ_CalcFsMult(inst->fs); /* Calculate fs/8000 */ - - /* Pre-calculate common multiplication with fsMult */ - fsMult120 = (int16_t) WEBRTC_SPL_MUL_16_16(fsMult, 120); /* 15 ms */ - - inst->ExpandInst.w16_consecExp = 0; /* Last was not expand any more */ - - /* - * Sanity check for len variable; must be (almost) 30 ms (120*fsMult + max(bestIndex)). - * Also, the new part must be at least .625 ms (w16_overlap). - */ - if (len < (int16_t) WEBRTC_SPL_MUL_16_16((120 + 119), fsMult) || oldDataLen >= len - - inst->ExpandInst.w16_overlap) - { - /* Length of decoded data too short */ - inst->w16_mode = MODE_UNSUCCESS_PREEMPTIVE; - *pw16_len = len; - - - /* simply move all data from decoded to outData */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - - return NETEQ_OTHER_ERROR; - } - - /***********************************/ - /* Special operations for BGN only */ - /***********************************/ - - /* Check if "background noise only" flag is set */ - if (BGNonly) - { - /* special operation for BGN only; simply insert a chunk of data */ - w16_bestIndex = DEFAULT_TIME_ADJUST * (fsMult << 3); /* X*fs/1000 */ - - /* Sanity check for bestIndex */ - if (w16_bestIndex > len) - { /* not good, do nothing instead */ - inst->w16_mode = MODE_UNSUCCESS_PREEMPTIVE; - *pw16_len = len; - - - /* simply move all data from decoded to outData */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - - return NETEQ_OTHER_ERROR; - } - - /* set length parameter */ - *pw16_len = len + w16_bestIndex; - - - /* copy to output */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, len); - WEBRTC_SPL_MEMCPY_W16(&pw16_outData[len], pw16_decoded, w16_bestIndex); - - /* set mode */ - inst->w16_mode = MODE_LOWEN_PREEMPTIVE; - - /* update statistics */ - inst->statInst.preemptiveLength += w16_bestIndex; - /* Short-term activity statistics. */ - inst->activity_stats.preemptive_expand_bgn_samples += w16_bestIndex; - - return 0; - } /* end of special code for BGN mode */ - -#ifdef NETEQ_STEREO - - /* Sanity for msInfo */ - if (msInfo == NULL) - { - /* this should not happen here */ - return MASTER_SLAVE_ERROR; - } - - if ((msInfo->msMode == NETEQ_MASTER) || (msInfo->msMode == NETEQ_MONO)) - { - /* Find correlation lag only for non-slave instances */ - -#endif - - /****************************************************************/ - /* Find the strongest correlation lag by downsampling to 4 kHz, */ - /* calculating correlation for downsampled signal and finding */ - /* the strongest correlation peak. */ - /****************************************************************/ - - /* find maximum absolute value */ - w16_decodedMax = WebRtcSpl_MaxAbsValueW16(pw16_decoded, (int16_t) len); - - /* downsample the decoded speech to 4 kHz */ - ok = WebRtcNetEQ_DownSampleTo4kHz(pw16_decoded, len, inst->fs, pw16_downSampSpeech, - PREEMPTIVE_DOWNSAMPLED_LEN, 1 /* compensate delay*/); - if (ok != 0) - { - /* error */ - inst->w16_mode = MODE_UNSUCCESS_PREEMPTIVE; - *pw16_len = len; - - - /* simply move all data from decoded to outData */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - - return NETEQ_OTHER_ERROR; - } - - /* - * Set scaling factor for cross correlation to protect against - * overflow (log2(50) => 6) - */ - w16_tmp = 6 - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_decodedMax, w16_decodedMax)); - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - /* Perform correlation from lag 10 to lag 60 in 4 kHz domain */WebRtcNetEQ_CrossCorr( - pw32_corr, &pw16_downSampSpeech[w16_endLag], - &pw16_downSampSpeech[w16_endLag - w16_startLag], w16_corrLen, - (int16_t) (w16_endLag - w16_startLag), w16_tmp, -1); - - /* Normalize correlation to 14 bits and put in a int16_t vector */ - w32_tmp = WebRtcSpl_MaxAbsValueW32(pw32_corr, w16_corrLen); - w16_tmp = 17 - WebRtcSpl_NormW32(w32_tmp); - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - WebRtcSpl_VectorBitShiftW32ToW16(pw16_corr, w16_corrLen, pw32_corr, w16_tmp); - - /* Find limits for peak finding, in order to avoid overful NetEQ algorithm buffer. */ - /* Calculate difference between MAX_OUTPUT_SIZE and len in 4 kHz domain. */ - w16_tmp = WebRtcSpl_DivW32W16ResW16((int32_t) (NETEQ_MAX_OUTPUT_SIZE - len), - (int16_t) (fsMult << 1)) - w16_startLag; - w16_tmp = WEBRTC_SPL_MIN(w16_corrLen, w16_tmp); /* no more than corrLen = 50 */ - -#ifdef NETEQ_STEREO - } /* end if (msInfo->msMode != NETEQ_SLAVE) */ - - if ((msInfo->msMode == NETEQ_MASTER) || (msInfo->msMode == NETEQ_MONO)) - { - /* Find the strongest correlation peak by using the parabolic fit method */ - WebRtcNetEQ_PeakDetection(pw16_corr, w16_tmp, 1, fsMult, &w16_bestIndex, &w16_bestVal); - /* 0 <= bestIndex <= (2*w16_tmp - 1)*fsMult <= (2*corrLen - 1)*fsMult = 99*fsMult */ - - /* Compensate bestIndex for displaced starting position */ - w16_bestIndex = w16_bestIndex + w16_startLag * WEBRTC_SPL_LSHIFT_W16(fsMult, 1); - /* 20*fsMult <= bestIndex <= 119*fsMult */ - - msInfo->bestIndex = w16_bestIndex; - } - else if (msInfo->msMode == NETEQ_SLAVE) - { - if (msInfo->extraInfo == PE_EXP_FAIL) - { - /* Master has signaled an unsuccessful preemptive expand */ - w16_bestIndex = 0; - } - else - { - /* Get best index from master */ - w16_bestIndex = msInfo->bestIndex; - } - } - else - { - /* Invalid mode */ - return (MASTER_SLAVE_ERROR); - } - -#else /* NETEQ_STEREO */ - - /* Find the strongest correlation peak by using the parabolic fit method */ - WebRtcNetEQ_PeakDetection(pw16_corr, w16_tmp, 1, fsMult, &w16_bestIndex, &w16_bestVal); - /* 0 <= bestIndex <= (2*w16_tmp - 1)*fsMult <= (2*corrLen - 1)*fsMult = 99*fsMult */ - - /* Compensate bestIndex for displaced starting position */ - w16_bestIndex = w16_bestIndex + w16_startLag * WEBRTC_SPL_LSHIFT_W16(fsMult, 1); - /* 20*fsMult <= bestIndex <= 119*fsMult */ - -#endif /* NETEQ_STEREO */ - -#ifdef NETEQ_STEREO - - if ((msInfo->msMode == NETEQ_MASTER) || (msInfo->msMode == NETEQ_MONO)) - { - /* Calculate correlation only for non-slave instances */ - -#endif /* NETEQ_STEREO */ - - /*****************************************************/ - /* Calculate correlation bestCorr for the found lag. */ - /* Also do a simple VAD decision. */ - /*****************************************************/ - - /* - * Calculate scaling to ensure that bestIndex samples can be square-summed - * without overflowing - */ - w16_tmp = (31 - - WebRtcSpl_NormW32(WEBRTC_SPL_MUL_16_16(w16_decodedMax, w16_decodedMax))); - w16_tmp += (31 - WebRtcSpl_NormW32(w16_bestIndex)); - w16_tmp -= 31; - w16_tmp = WEBRTC_SPL_MAX(0, w16_tmp); - - /* vec1 starts at 15 ms minus one pitch period */ - pw16_vec1 = &pw16_decoded[fsMult120 - w16_bestIndex]; - /* vec2 start at 15 ms */ - pw16_vec2 = &pw16_decoded[fsMult120]; - - /* Calculate energies for vec1 and vec2 */ - w32_en1 = WebRtcNetEQ_DotW16W16((int16_t*) pw16_vec1, - (int16_t*) pw16_vec1, w16_bestIndex, w16_tmp); - w32_en2 = WebRtcNetEQ_DotW16W16((int16_t*) pw16_vec2, - (int16_t*) pw16_vec2, w16_bestIndex, w16_tmp); - - /* Calculate cross-correlation at the found lag */ - w32_cc = WebRtcNetEQ_DotW16W16((int16_t*) pw16_vec1, (int16_t*) pw16_vec2, - w16_bestIndex, w16_tmp); - - /* Check VAD constraint - ((en1+en2)/(2*bestIndex)) <= 8*inst->BGNInst.energy */ - w32_tmp = WEBRTC_SPL_RSHIFT_W32(w32_en1 + w32_en2, 4); /* (en1+en2)/(2*8) */ - if (inst->BGNInst.w16_initialized == 1) - { - w32_tmp2 = inst->BGNInst.w32_energy; - } - else - { - /* if BGN parameters have not been estimated, use a fixed threshold */ - w32_tmp2 = 75000; - } - w16_tmp2 = 16 - WebRtcSpl_NormW32(w32_tmp2); - w16_tmp2 = WEBRTC_SPL_MAX(0, w16_tmp2); - w32_tmp = WEBRTC_SPL_RSHIFT_W32(w32_tmp, w16_tmp2); - w16_tmp2 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_tmp2, w16_tmp2); - w32_tmp2 = WEBRTC_SPL_MUL_16_16(w16_bestIndex, w16_tmp2); - - /* Scale w32_tmp properly before comparing with w32_tmp2 */ - /* (w16_tmp is scaling before energy calculation, thus 2*w16_tmp) */ - if (WebRtcSpl_NormW32(w32_tmp) < WEBRTC_SPL_LSHIFT_W32(w16_tmp,1)) - { - /* Cannot scale only w32_tmp, must scale w32_temp2 too */ - int16_t tempshift = WebRtcSpl_NormW32(w32_tmp); - w32_tmp = WEBRTC_SPL_LSHIFT_W32(w32_tmp, tempshift); - w32_tmp2 = WEBRTC_SPL_RSHIFT_W32(w32_tmp2, - WEBRTC_SPL_LSHIFT_W32(w16_tmp,1) - tempshift); - } - else - { - w32_tmp = WEBRTC_SPL_LSHIFT_W32(w32_tmp, - WEBRTC_SPL_LSHIFT_W32(w16_tmp,1)); - } - - if (w32_tmp <= w32_tmp2) /*((en1+en2)/(2*bestIndex)) <= 8*inst->BGNInst.energy */ - { - /* The signal seems to be passive speech */ - w16_VAD = 0; - w16_bestCorr = 0; /* Correlation does not matter */ - - /* For low energy expansion, the new data can be less than 15 ms, - but we must ensure that bestIndex is not larger than the new data. */ - w16_bestIndex = WEBRTC_SPL_MIN( w16_bestIndex, len - oldDataLen ); - } - else - { - /* The signal is active speech */ - w16_VAD = 1; - - /* Calculate correlation (cc/sqrt(en1*en2)) */ - - /* Start with calculating scale values */ - w16_en1Scale = 16 - WebRtcSpl_NormW32(w32_en1); - w16_en1Scale = WEBRTC_SPL_MAX(0, w16_en1Scale); - w16_en2Scale = 16 - WebRtcSpl_NormW32(w32_en2); - w16_en2Scale = WEBRTC_SPL_MAX(0, w16_en2Scale); - - /* Make sure total scaling is even (to simplify scale factor after sqrt) */ - if ((w16_en1Scale + w16_en2Scale) & 1) - { - w16_en1Scale += 1; - } - - /* Convert energies to int16_t */ - w16_en1 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_en1, w16_en1Scale); - w16_en2 = (int16_t) WEBRTC_SPL_RSHIFT_W32(w32_en2, w16_en2Scale); - - /* Calculate energy product */ - w32_tmp = WEBRTC_SPL_MUL_16_16(w16_en1, w16_en2); - - /* Calculate square-root of energy product */ - w16_sqrtEn1En2 = (int16_t) WebRtcSpl_SqrtFloor(w32_tmp); - - /* Calculate cc/sqrt(en1*en2) in Q14 */ - w16_tmp = 14 - ((w16_en1Scale + w16_en2Scale) >> 1); - w32_cc = WEBRTC_SPL_SHIFT_W32(w32_cc, w16_tmp); - w32_cc = WEBRTC_SPL_MAX(0, w32_cc); /* Don't divide with negative number */ - w16_bestCorr = (int16_t) WebRtcSpl_DivW32W16(w32_cc, w16_sqrtEn1En2); - w16_bestCorr = WEBRTC_SPL_MIN(16384, w16_bestCorr); /* set maximum to 1.0 */ - } - -#ifdef NETEQ_STEREO - - } /* end if (msInfo->msMode != NETEQ_SLAVE) */ - -#endif /* NETEQ_STEREO */ - - /*******************************************************/ - /* Check preemptive expand criteria and insert samples */ - /*******************************************************/ - - /* Check for strong correlation (>0.9) and at least 15 ms new data, - or passive speech */ -#ifdef NETEQ_STEREO - if (((((w16_bestCorr > 14746) && (oldDataLen <= fsMult120)) || (w16_VAD == 0)) - && (msInfo->msMode != NETEQ_SLAVE)) || ((msInfo->msMode == NETEQ_SLAVE) - && (msInfo->extraInfo != PE_EXP_FAIL))) -#else - if (((w16_bestCorr > 14746) && (oldDataLen <= fsMult120)) - || (w16_VAD == 0)) -#endif - { - /* Do expand operation by overlap add */ - - /* Set length of the first part, not to be modified */ - int16_t w16_startIndex = WEBRTC_SPL_MAX(oldDataLen, fsMult120); - - /* - * Calculate cross-fading slope so that the fading factor goes from - * 1 (16384 in Q14) to 0 in one pitch period (bestIndex). - */ - w16_inc = (int16_t) WebRtcSpl_DivW32W16((int32_t) 16384, - (int16_t) (w16_bestIndex + 1)); /* in Q14 */ - - /* Initiate fading factor */ - w16_startfact = 16384 - w16_inc; - - /* vec1 starts at 15 ms minus one pitch period */ - pw16_vec1 = &pw16_decoded[w16_startIndex - w16_bestIndex]; - /* vec2 start at 15 ms */ - pw16_vec2 = &pw16_decoded[w16_startIndex]; - - - /* Copy unmodified part [0 to 15 ms] */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, w16_startIndex); - - /* Generate interpolated part of length bestIndex (1 pitch period) */ - pw16_vectmp = pw16_outData + w16_startIndex; - /* Reuse mixing function from Expand */ - WebRtcNetEQ_MixVoiceUnvoice(pw16_vectmp, (int16_t*) pw16_vec2, - (int16_t*) pw16_vec1, &w16_startfact, w16_inc, w16_bestIndex); - - /* Move the last part (also unmodified) */ - /* Take from decoded at 15 ms */ - pw16_vec2 = &pw16_decoded[w16_startIndex]; - WEBRTC_SPL_MEMMOVE_W16(&pw16_outData[w16_startIndex + w16_bestIndex], pw16_vec2, - (int16_t) (len - w16_startIndex)); - - /* Set the mode flag */ - if (w16_VAD) - { - inst->w16_mode = MODE_SUCCESS_PREEMPTIVE; - } - else - { - inst->w16_mode = MODE_LOWEN_PREEMPTIVE; - } - - /* Calculate resulting length = original length + pitch period */ - *pw16_len = len + w16_bestIndex; - - /* Update in-call statistics */ - inst->statInst.preemptiveLength += w16_bestIndex; - /* Short-term activity statistics. */ - inst->activity_stats.preemptive_expand_normal_samples += w16_bestIndex; - return 0; - } - else - { - /* Preemptive Expand not allowed */ - -#ifdef NETEQ_STEREO - /* Signal to slave(s) that this was unsuccessful */ - if (msInfo->msMode == NETEQ_MASTER) - { - msInfo->extraInfo = PE_EXP_FAIL; - } -#endif - - /* Set mode flag to unsuccessful preemptive expand */ - inst->w16_mode = MODE_UNSUCCESS_PREEMPTIVE; - - /* Length is unmodified */ - *pw16_len = len; - - - /* Simply move all data from decoded to outData */ - - WEBRTC_SPL_MEMMOVE_W16(pw16_outData, pw16_decoded, (int16_t) len); - - return 0; - } -} - -#undef SCRATCH_PW16_DS_SPEECH -#undef SCRATCH_PW32_CORR -#undef SCRATCH_PW16_CORR diff --git a/webrtc/modules/audio_coding/neteq4/preemptive_expand.cc b/webrtc/modules/audio_coding/neteq/preemptive_expand.cc similarity index 94% rename from webrtc/modules/audio_coding/neteq4/preemptive_expand.cc rename to webrtc/modules/audio_coding/neteq/preemptive_expand.cc index c7ce31040..b2dc3e60c 100644 --- a/webrtc/modules/audio_coding/neteq4/preemptive_expand.cc +++ b/webrtc/modules/audio_coding/neteq/preemptive_expand.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/preemptive_expand.h" +#include "webrtc/modules/audio_coding/neteq/preemptive_expand.h" #include // min, max @@ -101,8 +101,10 @@ PreemptiveExpand::ReturnCodes PreemptiveExpand::CheckCriteriaAndStretch( PreemptiveExpand* PreemptiveExpandFactory::Create( int sample_rate_hz, size_t num_channels, - const BackgroundNoise& background_noise) const { - return new PreemptiveExpand(sample_rate_hz, num_channels, background_noise); + const BackgroundNoise& background_noise, + int overlap_samples) const { + return new PreemptiveExpand( + sample_rate_hz, num_channels, background_noise, overlap_samples); } } // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/preemptive_expand.h b/webrtc/modules/audio_coding/neteq/preemptive_expand.h similarity index 79% rename from webrtc/modules/audio_coding/neteq4/preemptive_expand.h rename to webrtc/modules/audio_coding/neteq/preemptive_expand.h index 241425e81..1aa613301 100644 --- a/webrtc/modules/audio_coding/neteq4/preemptive_expand.h +++ b/webrtc/modules/audio_coding/neteq/preemptive_expand.h @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PREEMPTIVE_EXPAND_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PREEMPTIVE_EXPAND_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_PREEMPTIVE_EXPAND_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_PREEMPTIVE_EXPAND_H_ #include -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/modules/audio_coding/neteq4/time_stretch.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" +#include "webrtc/modules/audio_coding/neteq/time_stretch.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -29,11 +29,13 @@ class BackgroundNoise; // PreemptiveExpand are implemented. class PreemptiveExpand : public TimeStretch { public: - PreemptiveExpand(int sample_rate_hz, size_t num_channels, - const BackgroundNoise& background_noise) + PreemptiveExpand(int sample_rate_hz, + size_t num_channels, + const BackgroundNoise& background_noise, + int overlap_samples) : TimeStretch(sample_rate_hz, num_channels, background_noise), old_data_length_per_channel_(-1), - overlap_samples_(5 * sample_rate_hz / 8000) { + overlap_samples_(overlap_samples) { } virtual ~PreemptiveExpand() {} @@ -77,8 +79,9 @@ struct PreemptiveExpandFactory { virtual PreemptiveExpand* Create( int sample_rate_hz, size_t num_channels, - const BackgroundNoise& background_noise) const; + const BackgroundNoise& background_noise, + int overlap_samples) const; }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PREEMPTIVE_EXPAND_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_PREEMPTIVE_EXPAND_H_ diff --git a/webrtc/modules/audio_coding/neteq/random_vector.c b/webrtc/modules/audio_coding/neteq/random_vector.c deleted file mode 100644 index c168ab543..000000000 --- a/webrtc/modules/audio_coding/neteq/random_vector.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 function generates a pseudo-random vector. - */ - -#include "dsp_helpfunctions.h" - -/* - * Values are normalized so that - * sqrt(dot(pw16_NETEQFIX_RANDN_TBL,pw16_NETEQFIX_RANDN_TBL)/256)=2^13 - */ -const int16_t WebRtcNetEQ_kRandnTbl[RANDVEC_NO_OF_SAMPLES] = -{ - 2680, 5532, 441, 5520, 16170, -5146, -1024, -8733, 3115, 9598, -10380, -4959, -1280, -21716, 7133, -1522, - 13458, -3902, 2789, -675, 3441, 5016, -13599, -4003, -2739, 3922, -7209, 13352, -11617, -7241, 12905, -2314, - 5426, 10121, -9702, 11207, -13542, 1373, 816, -5934, -12504, 4798, 1811, 4112, -613, 201, -10367, -2960, - -2419, 3442, 4299, -6116, -6092, 1552, -1650, -480, -1237, 18720, -11858, -8303, -8212, 865, -2890, -16968, - 12052, -5845, -5912, 9777, -5665, -6294, 5426, -4737, -6335, 1652, 761, 3832, 641, -8552, -9084, -5753, - 8146, 12156, -4915, 15086, -1231, -1869, 11749, -9319, -6403, 11407, 6232, -1683, 24340, -11166, 4017, -10448, - 3153, -2936, 6212, 2891, -866, -404, -4807, -2324, -1917, -2388, -6470, -3895, -10300, 5323, -5403, 2205, - 4640, 7022, -21186, -6244, -882, -10031, -3395, -12885, 7155, -5339, 5079, -2645, -9515, 6622, 14651, 15852, - 359, 122, 8246, -3502, -6696, -3679, -13535, -1409, -704, -7403, -4007, 1798, 279, -420, -12796, -14219, - 1141, 3359, 11434, 7049, -6684, -7473, 14283, -4115, -9123, -8969, 4152, 4117, 13792, 5742, 16168, 8661, - -1609, -6095, 1881, 14380, -5588, 6758, -6425, -22969, -7269, 7031, 1119, -1611, -5850, -11281, 3559, -8952, - -10146, -4667, -16251, -1538, 2062, -1012, -13073, 227, -3142, -5265, 20, 5770, -7559, 4740, -4819, 992, - -8208, -7130, -4652, 6725, 7369, -1036, 13144, -1588, -5304, -2344, -449, -5705, -8894, 5205, -17904, -11188, - -1022, 4852, 10101, -5255, -4200, -752, 7941, -1543, 5959, 14719, 13346, 17045, -15605, -1678, -1600, -9230, - 68, 23348, 1172, 7750, 11212, -18227, 9956, 4161, 883, 3947, 4341, 1014, -4889, -2603, 1246, -5630, - -3596, -870, -1298, 2784, -3317, -6612, -20541, 4166, 4181, -8625, 3562, 12890, 4761, 3205, -12259, -8579 -}; - - -void WebRtcNetEQ_RandomVec(uint32_t *w32_seed, int16_t *pw16_randVec, - int16_t w16_len, int16_t w16_incval) -{ - int i; - int16_t w16_pos; - for (i = 0; i < w16_len; i++) - { - *w32_seed = (*w32_seed) + w16_incval; - w16_pos = (int16_t) ((*w32_seed) & (RANDVEC_NO_OF_SAMPLES - 1)); - pw16_randVec[i] = WebRtcNetEQ_kRandnTbl[w16_pos]; - } -} - diff --git a/webrtc/modules/audio_coding/neteq4/random_vector.cc b/webrtc/modules/audio_coding/neteq/random_vector.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/random_vector.cc rename to webrtc/modules/audio_coding/neteq/random_vector.cc index e7a5a1d1b..b12f21715 100644 --- a/webrtc/modules/audio_coding/neteq4/random_vector.cc +++ b/webrtc/modules/audio_coding/neteq/random_vector.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/random_vector.h" +#include "webrtc/modules/audio_coding/neteq/random_vector.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/random_vector.h b/webrtc/modules/audio_coding/neteq/random_vector.h similarity index 82% rename from webrtc/modules/audio_coding/neteq4/random_vector.h rename to webrtc/modules/audio_coding/neteq/random_vector.h index 64cfe0d9d..767dc48ee 100644 --- a/webrtc/modules/audio_coding/neteq4/random_vector.h +++ b/webrtc/modules/audio_coding/neteq/random_vector.h @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_RANDOM_VECTOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_RANDOM_VECTOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_RANDOM_VECTOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_RANDOM_VECTOR_H_ #include // size_t -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -47,4 +47,4 @@ class RandomVector { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_RANDOM_VECTOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_RANDOM_VECTOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/random_vector_unittest.cc b/webrtc/modules/audio_coding/neteq/random_vector_unittest.cc similarity index 91% rename from webrtc/modules/audio_coding/neteq4/random_vector_unittest.cc rename to webrtc/modules/audio_coding/neteq/random_vector_unittest.cc index 83193e2a7..cbdcdf7c8 100644 --- a/webrtc/modules/audio_coding/neteq4/random_vector_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/random_vector_unittest.cc @@ -10,7 +10,7 @@ // Unit tests for RandomVector class. -#include "webrtc/modules/audio_coding/neteq4/random_vector.h" +#include "webrtc/modules/audio_coding/neteq/random_vector.h" #include "gtest/gtest.h" diff --git a/webrtc/modules/audio_coding/neteq/recin.c b/webrtc/modules/audio_coding/neteq/recin.c deleted file mode 100644 index 17bea5f5b..000000000 --- a/webrtc/modules/audio_coding/neteq/recin.c +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* - * Implementation of the RecIn function, which is the main function for inserting RTP - * packets into NetEQ. - */ - -#include "mcu.h" - -#include - -#include "automode.h" -#include "dtmf_buffer.h" -#include "mcu_dsp_common.h" -#include "neteq_defines.h" -#include "neteq_error_codes.h" -#include "signal_processing_library.h" - -int WebRtcNetEQ_RecInInternal(MCUInst_t *MCU_inst, RTPPacket_t *RTPpacketInput, - uint32_t uw32_timeRec) -{ - RTPPacket_t RTPpacket[2]; - int i_k; - int i_ok = 0, i_No_Of_Payloads = 1; - int16_t flushed = 0; - int16_t codecPos; - int curr_Codec; - int16_t isREDPayload = 0; - int32_t temp_bufsize; - int is_sync_rtp = MCU_inst->av_sync && WebRtcNetEQ_IsSyncPayload( - RTPpacketInput->payload, RTPpacketInput->payloadLen); -#ifdef NETEQ_RED_CODEC - RTPPacket_t* RTPpacketPtr[2]; /* Support for redundancy up to 2 payloads */ - RTPpacketPtr[0] = &RTPpacket[0]; - RTPpacketPtr[1] = &RTPpacket[1]; -#endif - - temp_bufsize = WebRtcNetEQ_PacketBufferGetSize(&MCU_inst->PacketBuffer_inst, - &MCU_inst->codec_DB_inst, - MCU_inst->av_sync); - /* - * Copy from input RTP packet to local copy - * (mainly to enable multiple payloads using RED) - */ - - WEBRTC_SPL_MEMCPY_W8(&RTPpacket[0], RTPpacketInput, sizeof(RTPPacket_t)); - - /* Reinitialize NetEq if it's needed (changed SSRC or first call) */ - - if ((RTPpacket[0].ssrc != MCU_inst->ssrc) || (MCU_inst->first_packet == 1)) - { - WebRtcNetEQ_RTCPInit(&MCU_inst->RTCP_inst, RTPpacket[0].seqNumber); - MCU_inst->first_packet = 0; - - /* Flush the buffer */ - WebRtcNetEQ_PacketBufferFlush(&MCU_inst->PacketBuffer_inst); - - /* Store new SSRC */ - MCU_inst->ssrc = RTPpacket[0].ssrc; - - /* Update codecs */ - MCU_inst->timeStamp = RTPpacket[0].timeStamp; - MCU_inst->current_Payload = RTPpacket[0].payloadType; - - /*Set MCU to update codec on next SignalMCU call */ - MCU_inst->new_codec = 1; - - /* Reset timestamp scaling */ - MCU_inst->TSscalingInitialized = 0; - - } - - if (!is_sync_rtp) { /* Update only if it not sync packet. */ - /* Call RTCP statistics if it is not sync packet. */ - i_ok |= WebRtcNetEQ_RTCPUpdate(&(MCU_inst->RTCP_inst), - RTPpacket[0].seqNumber, - RTPpacket[0].timeStamp, uw32_timeRec); - } - - /* If Redundancy is supported and this is the redundancy payload, separate the payloads */ -#ifdef NETEQ_RED_CODEC - if (RTPpacket[0].payloadType == WebRtcNetEQ_DbGetPayload(&MCU_inst->codec_DB_inst, - kDecoderRED)) - { - if (is_sync_rtp) - { - /* Sync packet should not have RED payload type. */ - return RECIN_SYNC_RTP_NOT_ACCEPTABLE; - } - - /* Split the payload into a main and a redundancy payloads */ - i_ok = WebRtcNetEQ_RedundancySplit(RTPpacketPtr, 2, &i_No_Of_Payloads); - if (i_ok < 0) - { - /* error returned */ - return i_ok; - } - - /* - * Only accept a few redundancies of the same type as the main data, - * AVT events and CNG. - */ - if ((i_No_Of_Payloads > 1) && (RTPpacket[0].payloadType != RTPpacket[1].payloadType) - && (RTPpacket[0].payloadType != WebRtcNetEQ_DbGetPayload(&MCU_inst->codec_DB_inst, - kDecoderAVT)) && (RTPpacket[1].payloadType != WebRtcNetEQ_DbGetPayload( - &MCU_inst->codec_DB_inst, kDecoderAVT)) && (!WebRtcNetEQ_DbIsCNGPayload( - &MCU_inst->codec_DB_inst, RTPpacket[0].payloadType)) - && (!WebRtcNetEQ_DbIsCNGPayload(&MCU_inst->codec_DB_inst, RTPpacket[1].payloadType))) - { - i_No_Of_Payloads = 1; - } - isREDPayload = 1; - } -#endif - - /* loop over the number of payloads */ - for (i_k = 0; i_k < i_No_Of_Payloads; i_k++) - { - - if (isREDPayload == 1) - { - RTPpacket[i_k].rcuPlCntr = i_k; - } - else - { - RTPpacket[i_k].rcuPlCntr = 0; - } - - /* Force update of SplitInfo if it's iLBC because of potential change between 20/30ms */ - if (RTPpacket[i_k].payloadType == WebRtcNetEQ_DbGetPayload(&MCU_inst->codec_DB_inst, - kDecoderILBC) && !is_sync_rtp) /* Don't update if sync RTP. */ - { - i_ok = WebRtcNetEQ_DbGetSplitInfo( - &MCU_inst->PayloadSplit_inst, - (enum WebRtcNetEQDecoder) WebRtcNetEQ_DbGetCodec(&MCU_inst->codec_DB_inst, - RTPpacket[i_k].payloadType), RTPpacket[i_k].payloadLen); - if (i_ok < 0) - { - /* error returned */ - return i_ok; - } - } - - /* Get information about timestamp scaling for this payload type */ - i_ok = WebRtcNetEQ_GetTimestampScaling(MCU_inst, RTPpacket[i_k].payloadType); - if (i_ok < 0) - { - /* error returned */ - return i_ok; - } - - if (MCU_inst->TSscalingInitialized == 0 && MCU_inst->scalingFactor != kTSnoScaling) - { - /* Must initialize scaling with current timestamps */ - MCU_inst->externalTS = RTPpacket[i_k].timeStamp; - MCU_inst->internalTS = RTPpacket[i_k].timeStamp; - MCU_inst->TSscalingInitialized = 1; - } - - /* Adjust timestamp if timestamp scaling is needed (e.g. SILK or G.722) */ - if (MCU_inst->TSscalingInitialized == 1) - { - uint32_t newTS = WebRtcNetEQ_ScaleTimestampExternalToInternal(MCU_inst, - RTPpacket[i_k].timeStamp); - - /* save the incoming timestamp for next time */ - MCU_inst->externalTS = RTPpacket[i_k].timeStamp; - - /* add the scaled difference to last scaled timestamp and save ... */ - MCU_inst->internalTS = newTS; - - RTPpacket[i_k].timeStamp = newTS; - } - - /* Is this a DTMF packet?*/ - if (RTPpacket[i_k].payloadType == WebRtcNetEQ_DbGetPayload(&MCU_inst->codec_DB_inst, - kDecoderAVT)) - { - if (is_sync_rtp) - { - /* Sync RTP should not have AVT payload type. */ - return RECIN_SYNC_RTP_NOT_ACCEPTABLE; - } - -#ifdef NETEQ_ATEVENT_DECODE - if (MCU_inst->AVT_PlayoutOn) - { - i_ok = WebRtcNetEQ_DtmfInsertEvent(&MCU_inst->DTMF_inst, - RTPpacket[i_k].payload, RTPpacket[i_k].payloadLen, - RTPpacket[i_k].timeStamp); - if (i_ok != 0) - { - return i_ok; - } - } -#endif -#ifdef NETEQ_STEREO - if (MCU_inst->usingStereo == 0) - { - /* do not set this for DTMF packets when using stereo mode */ - MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF = 1; - } -#else - MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF = 1; -#endif - } - else if (WebRtcNetEQ_DbIsCNGPayload(&MCU_inst->codec_DB_inst, - RTPpacket[i_k].payloadType)) - { - /* Is this a CNG packet? how should we handle this?*/ -#ifdef NETEQ_CNG_CODEC - /* Get CNG sample rate */ - uint16_t fsCng = WebRtcNetEQ_DbGetSampleRate(&MCU_inst->codec_DB_inst, - RTPpacket[i_k].payloadType); - if (is_sync_rtp) - { - /* Sync RTP should not have CNG payload type. */ - return RECIN_SYNC_RTP_NOT_ACCEPTABLE; - } - - /* Force sampling frequency to 32000 Hz CNG 48000 Hz. */ - /* TODO(tlegrand): remove limitation once ACM has full 48 kHz - * support. */ - if (fsCng > 32000) { - fsCng = 32000; - } - if ((fsCng != MCU_inst->fs) && (fsCng > 8000)) - { - /* - * We have received CNG with a different sample rate from what we are using - * now (must be > 8000, since we may use only one CNG type (default) for all - * frequencies). Flush buffer and signal new codec. - */ - WebRtcNetEQ_PacketBufferFlush(&MCU_inst->PacketBuffer_inst); - MCU_inst->new_codec = 1; - MCU_inst->current_Codec = -1; - } - i_ok = WebRtcNetEQ_PacketBufferInsert(&MCU_inst->PacketBuffer_inst, - &RTPpacket[i_k], &flushed, MCU_inst->av_sync); - if (i_ok < 0) - { - return RECIN_CNG_ERROR; - } - MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF = 1; -#else /* NETEQ_CNG_CODEC not defined */ - return RECIN_UNKNOWNPAYLOAD; -#endif /* NETEQ_CNG_CODEC */ - } - else - { - /* Reinitialize the splitting if the payload and/or the payload length has changed */ - curr_Codec = WebRtcNetEQ_DbGetCodec(&MCU_inst->codec_DB_inst, - RTPpacket[i_k].payloadType); - if (curr_Codec != MCU_inst->current_Codec) - { - if (curr_Codec < 0) - { - return RECIN_UNKNOWNPAYLOAD; - } - if (is_sync_rtp) - { - /* Sync RTP should not cause codec change. */ - return RECIN_SYNC_RTP_CHANGED_CODEC; - } - MCU_inst->current_Codec = curr_Codec; - MCU_inst->current_Payload = RTPpacket[i_k].payloadType; - i_ok = WebRtcNetEQ_DbGetSplitInfo(&MCU_inst->PayloadSplit_inst, - (enum WebRtcNetEQDecoder) MCU_inst->current_Codec, - RTPpacket[i_k].payloadLen); - if (i_ok < 0) - { /* error returned */ - return i_ok; - } - WebRtcNetEQ_PacketBufferFlush(&MCU_inst->PacketBuffer_inst); - MCU_inst->new_codec = 1; - } - - /* Parse the payload and insert it into the buffer */ - i_ok = WebRtcNetEQ_SplitAndInsertPayload(&RTPpacket[i_k], - &MCU_inst->PacketBuffer_inst, &MCU_inst->PayloadSplit_inst, - &flushed, MCU_inst->av_sync); - if (i_ok < 0) - { - return i_ok; - } - if (MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF != 0) - { - /* first normal packet after CNG or DTMF */ - MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF = -1; - } - } - /* Reset DSP timestamp etc. if packet buffer flushed */ - if (flushed) - { - MCU_inst->new_codec = 1; - } - } - - /* - * If not sync RTP, update Bandwidth Estimate. - * Only send the main payload to BWE. - */ - if (!is_sync_rtp && - (curr_Codec = WebRtcNetEQ_DbGetCodec(&MCU_inst->codec_DB_inst, - RTPpacket[0].payloadType)) >= 0) - { - codecPos = MCU_inst->codec_DB_inst.position[curr_Codec]; - if (MCU_inst->codec_DB_inst.funcUpdBWEst[codecPos] != NULL) /* codec has BWE function */ - { - if (RTPpacket[0].starts_byte1) /* check for shifted byte alignment */ - { - /* re-align to 16-bit alignment */ - for (i_k = 0; i_k < RTPpacket[0].payloadLen; i_k++) - { - WEBRTC_SPL_SET_BYTE(RTPpacket[0].payload, - WEBRTC_SPL_GET_BYTE(RTPpacket[0].payload, i_k+1), - i_k); - } - RTPpacket[0].starts_byte1 = 0; - } - - MCU_inst->codec_DB_inst.funcUpdBWEst[codecPos]( - MCU_inst->codec_DB_inst.codec_state[codecPos], - (const uint16_t *) RTPpacket[0].payload, - (int32_t) RTPpacket[0].payloadLen, RTPpacket[0].seqNumber, - (uint32_t) RTPpacket[0].timeStamp, (uint32_t) uw32_timeRec); - } - } - - if (MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF == 0) - { - /* Calculate the total speech length carried in each packet */ - temp_bufsize = WebRtcNetEQ_PacketBufferGetSize( - &MCU_inst->PacketBuffer_inst, &MCU_inst->codec_DB_inst, - MCU_inst->av_sync) - temp_bufsize; - - if ((temp_bufsize > 0) && (MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF - == 0) && (temp_bufsize - != MCU_inst->BufferStat_inst.Automode_inst.packetSpeechLenSamp)) - { - /* Change the auto-mode parameters if packet length has changed */ - WebRtcNetEQ_SetPacketSpeechLen(&(MCU_inst->BufferStat_inst.Automode_inst), - (int16_t) temp_bufsize, MCU_inst->fs); - } - - /* update statistics */ - if ((int32_t) (RTPpacket[0].timeStamp - MCU_inst->timeStamp) >= 0 - && !MCU_inst->new_codec) - { - /* - * Only update statistics if incoming packet is not older than last played out - * packet, and if new codec flag is not set. - */ - WebRtcNetEQ_UpdateIatStatistics(&MCU_inst->BufferStat_inst.Automode_inst, - MCU_inst->PacketBuffer_inst.maxInsertPositions, RTPpacket[0].seqNumber, - RTPpacket[0].timeStamp, MCU_inst->fs, - WebRtcNetEQ_DbIsMDCodec((enum WebRtcNetEQDecoder) MCU_inst->current_Codec), - (MCU_inst->NetEqPlayoutMode == kPlayoutStreaming)); - } - } - else if (MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF == -1) - { - /* - * This is first "normal" packet after CNG or DTMF. - * Reset packet time counter and measure time until next packet, - * but don't update statistics. - */ - MCU_inst->BufferStat_inst.Automode_inst.lastPackCNGorDTMF = 0; - MCU_inst->BufferStat_inst.Automode_inst.packetIatCountSamp = 0; - } - return 0; - -} - -int WebRtcNetEQ_GetTimestampScaling(MCUInst_t *MCU_inst, int rtpPayloadType) -{ - enum WebRtcNetEQDecoder codec; - int codecNumber; - - codecNumber = WebRtcNetEQ_DbGetCodec(&MCU_inst->codec_DB_inst, rtpPayloadType); - if (codecNumber < 0) - { - /* error */ - return codecNumber; - } - - /* cast to enumerator */ - codec = (enum WebRtcNetEQDecoder) codecNumber; - - /* - * The factor obtained below is the number with which the RTP timestamp must be - * multiplied to get the true sample count. - */ - switch (codec) - { - case kDecoderG722: - case kDecoderG722_2ch: - { - /* Use timestamp scaling with factor 2 (two output samples per RTP timestamp) */ - MCU_inst->scalingFactor = kTSscalingTwo; - break; - } - case kDecoderISACfb: - case kDecoderOpus: - { - /* We resample Opus internally to 32 kHz, and isac-fb decodes at - * 32 kHz, but timestamps are counted at 48 kHz. So there are two - * output samples per three RTP timestamp ticks. */ - MCU_inst->scalingFactor = kTSscalingTwoThirds; - break; - } - - case kDecoderAVT: - case kDecoderCNG: - { - /* TODO(tlegrand): remove scaling once ACM has full 48 kHz - * support. */ - uint16_t sample_freq = - WebRtcNetEQ_DbGetSampleRate(&MCU_inst->codec_DB_inst, - rtpPayloadType); - if (sample_freq == 48000) { - MCU_inst->scalingFactor = kTSscalingTwoThirds; - } - - /* For sample_freq <= 32 kHz, do not change the timestamp scaling - * settings. */ - break; - } - default: - { - /* do not use timestamp scaling */ - MCU_inst->scalingFactor = kTSnoScaling; - break; - } - } - return 0; -} - -uint32_t WebRtcNetEQ_ScaleTimestampExternalToInternal(const MCUInst_t *MCU_inst, - uint32_t externalTS) -{ - int32_t timestampDiff; - uint32_t internalTS; - - /* difference between this and last incoming timestamp */ - timestampDiff = externalTS - MCU_inst->externalTS; - - switch (MCU_inst->scalingFactor) - { - case kTSscalingTwo: - { - /* multiply with 2 */ - timestampDiff = WEBRTC_SPL_LSHIFT_W32(timestampDiff, 1); - break; - } - case kTSscalingTwoThirds: - { - /* multiply with 2/3 */ - timestampDiff = WEBRTC_SPL_LSHIFT_W32(timestampDiff, 1); - timestampDiff = WebRtcSpl_DivW32W16(timestampDiff, 3); - break; - } - case kTSscalingFourThirds: - { - /* multiply with 4/3 */ - timestampDiff = WEBRTC_SPL_LSHIFT_W32(timestampDiff, 2); - timestampDiff = WebRtcSpl_DivW32W16(timestampDiff, 3); - break; - } - default: - { - /* no scaling */ - } - } - - /* add the scaled difference to last scaled timestamp and save ... */ - internalTS = MCU_inst->internalTS + timestampDiff; - - return internalTS; -} - -uint32_t WebRtcNetEQ_ScaleTimestampInternalToExternal(const MCUInst_t *MCU_inst, - uint32_t internalTS) -{ - int32_t timestampDiff; - uint32_t externalTS; - - /* difference between this and last incoming timestamp */ - timestampDiff = (int32_t) internalTS - MCU_inst->internalTS; - - switch (MCU_inst->scalingFactor) - { - case kTSscalingTwo: - { - /* divide by 2 */ - timestampDiff = WEBRTC_SPL_RSHIFT_W32(timestampDiff, 1); - break; - } - case kTSscalingTwoThirds: - { - /* multiply with 3/2 */ - timestampDiff = WEBRTC_SPL_MUL_32_16(timestampDiff, 3); - timestampDiff = WEBRTC_SPL_RSHIFT_W32(timestampDiff, 1); - break; - } - case kTSscalingFourThirds: - { - /* multiply with 3/4 */ - timestampDiff = WEBRTC_SPL_MUL_32_16(timestampDiff, 3); - timestampDiff = WEBRTC_SPL_RSHIFT_W32(timestampDiff, 2); - break; - } - default: - { - /* no scaling */ - } - } - - /* add the scaled difference to last scaled timestamp and save ... */ - externalTS = MCU_inst->externalTS + timestampDiff; - - return externalTS; -} diff --git a/webrtc/modules/audio_coding/neteq/recout.c b/webrtc/modules/audio_coding/neteq/recout.c deleted file mode 100644 index 8f6200731..000000000 --- a/webrtc/modules/audio_coding/neteq/recout.c +++ /dev/null @@ -1,1502 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* - * Implementation of RecOut function, which is the main function for the audio output - * process. This function must be called (through the NetEQ API) once every 10 ms. - */ - -#include "dsp.h" - -#include -#include /* to define NULL */ - -#include "signal_processing_library.h" - -#include "dsp_helpfunctions.h" -#include "neteq_error_codes.h" -#include "neteq_defines.h" -#include "mcu_dsp_common.h" - -/* Audio types */ -#define TYPE_SPEECH 1 -#define TYPE_CNG 2 - -#ifdef NETEQ_DELAY_LOGGING -#include "delay_logging.h" -#include -#pragma message("*******************************************************************") -#pragma message("You have specified to use NETEQ_DELAY_LOGGING in the NetEQ library.") -#pragma message("Make sure that your test application supports this.") -#pragma message("*******************************************************************") -#endif - -/* Scratch usage: - - Type Name size startpos endpos - int16_t pw16_NetEqAlgorithm_buffer 1080*fs/8000 0 1080*fs/8000-1 - struct dspInfo 6 1080*fs/8000 1085*fs/8000 - - func WebRtcNetEQ_Normal 40+495*fs/8000 0 39+495*fs/8000 - func WebRtcNetEQ_Merge 40+496*fs/8000 0 39+496*fs/8000 - func WebRtcNetEQ_Expand 40+370*fs/8000 126*fs/800 39+496*fs/8000 - func WebRtcNetEQ_Accelerate 210 240*fs/8000 209+240*fs/8000 - func WebRtcNetEQ_BGNUpdate 69 480*fs/8000 68+480*fs/8000 - - Total: 1086*fs/8000 - */ - -#define SCRATCH_ALGORITHM_BUFFER 0 -#define SCRATCH_NETEQ_NORMAL 0 -#define SCRATCH_NETEQ_MERGE 0 - -#if (defined(NETEQ_48KHZ_WIDEBAND)) -#define SCRATCH_DSP_INFO 6480 -#define SCRATCH_NETEQ_ACCELERATE 1440 -#define SCRATCH_NETEQ_BGN_UPDATE 2880 -#define SCRATCH_NETEQ_EXPAND 756 -#elif (defined(NETEQ_32KHZ_WIDEBAND)) -#define SCRATCH_DSP_INFO 4320 -#define SCRATCH_NETEQ_ACCELERATE 960 -#define SCRATCH_NETEQ_BGN_UPDATE 1920 -#define SCRATCH_NETEQ_EXPAND 504 -#elif (defined(NETEQ_WIDEBAND)) -#define SCRATCH_DSP_INFO 2160 -#define SCRATCH_NETEQ_ACCELERATE 480 -#define SCRATCH_NETEQ_BGN_UPDATE 960 -#define SCRATCH_NETEQ_EXPAND 252 -#else /* NB */ -#define SCRATCH_DSP_INFO 1080 -#define SCRATCH_NETEQ_ACCELERATE 240 -#define SCRATCH_NETEQ_BGN_UPDATE 480 -#define SCRATCH_NETEQ_EXPAND 126 -#endif - -#if (defined(NETEQ_48KHZ_WIDEBAND)) -#define SIZE_SCRATCH_BUFFER 6516 -#elif (defined(NETEQ_32KHZ_WIDEBAND)) -#define SIZE_SCRATCH_BUFFER 4344 -#elif (defined(NETEQ_WIDEBAND)) -#define SIZE_SCRATCH_BUFFER 2172 -#else /* NB */ -#define SIZE_SCRATCH_BUFFER 1086 -#endif - -#ifdef NETEQ_DELAY_LOGGING -extern FILE *delay_fid2; /* file pointer to delay log file */ -extern uint32_t tot_received_packets; -#endif - - -int WebRtcNetEQ_RecOutInternal(DSPInst_t *inst, int16_t *pw16_outData, - int16_t *pw16_len, int16_t BGNonly, - int av_sync) -{ - - int16_t blockLen, payloadLen, len = 0, pos; - int16_t w16_tmp1, w16_tmp2, w16_tmp3, DataEnough; - int16_t *blockPtr; - int16_t MD = 0; - - int16_t speechType = TYPE_SPEECH; - uint16_t instr; - uint16_t uw16_tmp; -#ifdef SCRATCH - char pw8_ScratchBuffer[((SIZE_SCRATCH_BUFFER + 1) * 2)]; - int16_t *pw16_scratchPtr = (int16_t*) pw8_ScratchBuffer; - /* pad with 240*fs_mult to match the overflow guard below */ - int16_t pw16_decoded_buffer[NETEQ_MAX_FRAME_SIZE+240*6]; - int16_t *pw16_NetEqAlgorithm_buffer = pw16_scratchPtr - + SCRATCH_ALGORITHM_BUFFER; - DSP2MCU_info_t *dspInfo = (DSP2MCU_info_t*) (pw16_scratchPtr + SCRATCH_DSP_INFO); -#else - /* pad with 240*fs_mult to match the overflow guard below */ - int16_t pw16_decoded_buffer[NETEQ_MAX_FRAME_SIZE+240*6]; - int16_t pw16_NetEqAlgorithm_buffer[NETEQ_MAX_OUTPUT_SIZE+240*6]; - DSP2MCU_info_t dspInfoStruct; - DSP2MCU_info_t *dspInfo = &dspInfoStruct; -#endif - int16_t fs_mult; - int borrowedSamples; - int oldBorrowedSamples; - int return_value = 0; - int16_t lastModeBGNonly = (inst->w16_mode & MODE_BGN_ONLY) != 0; /* check BGN flag */ - void *mainInstBackup = inst->main_inst; - -#ifdef NETEQ_DELAY_LOGGING - int temp_var; -#endif - int16_t dtmfValue = -1; - int16_t dtmfVolume = -1; - int playDtmf = 0; -#ifdef NETEQ_ATEVENT_DECODE - int dtmfSwitch = 0; -#endif -#ifdef NETEQ_STEREO - MasterSlaveInfo *msInfo = inst->msInfo; -#endif - int16_t *sharedMem = pw16_NetEqAlgorithm_buffer; /* Reuse memory SHARED_MEM_SIZE size */ - inst->pw16_readAddress = sharedMem; - inst->pw16_writeAddress = sharedMem; - - /* Get information about if there is one descriptor left */ - if (inst->codec_ptr_inst.funcGetMDinfo != NULL) - { - MD = inst->codec_ptr_inst.funcGetMDinfo(inst->codec_ptr_inst.codec_state); - if (MD > 0) - MD = 1; - else - MD = 0; - } - -#ifdef NETEQ_STEREO - if ((msInfo->msMode == NETEQ_SLAVE) && (inst->codec_ptr_inst.funcDecode != NULL)) - { - /* - * Valid function pointers indicate that we have decoded something, - * and that the timestamp information is correct. - */ - - /* Get the information from master to correct synchronization */ - uint32_t currentMasterTimestamp; - uint32_t currentSlaveTimestamp; - - currentMasterTimestamp = msInfo->endTimestamp - msInfo->samplesLeftWithOverlap; - currentSlaveTimestamp = inst->endTimestamp - (inst->endPosition - inst->curPosition); - - /* Partition the uint32_t space in three: [0 0.25) [0.25 0.75] (0.75 1] - * We consider a wrap to have occurred if the timestamps are in - * different edge partitions. - */ - if (currentSlaveTimestamp < 0x40000000 && - currentMasterTimestamp > 0xc0000000) { - // Slave has wrapped. - currentSlaveTimestamp += (0xffffffff - currentMasterTimestamp) + 1; - currentMasterTimestamp = 0; - } else if (currentMasterTimestamp < 0x40000000 && - currentSlaveTimestamp > 0xc0000000) { - // Master has wrapped. - currentMasterTimestamp += (0xffffffff - currentSlaveTimestamp) + 1; - currentSlaveTimestamp = 0; - } - - if (currentSlaveTimestamp < currentMasterTimestamp) - { - /* brute-force discard a number of samples to catch up */ - inst->curPosition += currentMasterTimestamp - currentSlaveTimestamp; - - } - else if (currentSlaveTimestamp > currentMasterTimestamp) - { - /* back off current position to slow down */ - inst->curPosition -= currentSlaveTimestamp - currentMasterTimestamp; - } - - /* make sure we have at least "overlap" samples left */ - inst->curPosition = WEBRTC_SPL_MIN(inst->curPosition, - inst->endPosition - inst->ExpandInst.w16_overlap); - - /* make sure we do not end up outside the speech history */ - inst->curPosition = WEBRTC_SPL_MAX(inst->curPosition, 0); - } -#endif - - /* Write status data to shared memory */ - dspInfo->playedOutTS = inst->endTimestamp; - dspInfo->samplesLeft = inst->endPosition - inst->curPosition - - inst->ExpandInst.w16_overlap; - dspInfo->MD = MD; - dspInfo->lastMode = inst->w16_mode; - dspInfo->frameLen = inst->w16_frameLen; - - /* Force update of codec if codec function is NULL */ - if (inst->codec_ptr_inst.funcDecode == NULL) - { - dspInfo->lastMode |= MODE_AWAITING_CODEC_PTR; - } - -#ifdef NETEQ_STEREO - if (msInfo->msMode == NETEQ_SLAVE && (msInfo->extraInfo == DTMF_OVERDUB - || msInfo->extraInfo == DTMF_ONLY)) - { - /* Signal that the master instance generated DTMF tones */ - dspInfo->lastMode |= MODE_MASTER_DTMF_SIGNAL; - } - - if (msInfo->msMode != NETEQ_MONO) - { - /* We are using stereo mode; signal this to MCU side */ - dspInfo->lastMode |= MODE_USING_STEREO; - } -#endif - - WEBRTC_SPL_MEMCPY_W8(inst->pw16_writeAddress,dspInfo,sizeof(DSP2MCU_info_t)); - - /* Signal MCU with "interrupt" call to main inst*/ -#ifdef NETEQ_STEREO - assert(msInfo != NULL); - if (msInfo->msMode == NETEQ_MASTER) - { - /* clear info to slave */ - WebRtcSpl_MemSetW16((int16_t *) msInfo, 0, - sizeof(MasterSlaveInfo) / sizeof(int16_t)); - /* re-set mode */ - msInfo->msMode = NETEQ_MASTER; - - /* Store some information to slave */ - msInfo->endTimestamp = inst->endTimestamp; - msInfo->samplesLeftWithOverlap = inst->endPosition - inst->curPosition; - } -#endif - - /* - * This call will trigger the MCU side to make a decision based on buffer contents and - * decision history. Instructions, encoded data and function pointers will be written - * to the shared memory. - */ - return_value = WebRtcNetEQ_DSP2MCUinterrupt((MainInst_t *) inst->main_inst, sharedMem); - - /* Read MCU data and instructions */ - instr = (uint16_t) (inst->pw16_readAddress[0] & 0xf000); - -#ifdef NETEQ_STEREO - if (msInfo->msMode == NETEQ_MASTER) - { - msInfo->instruction = instr; - } - else if (msInfo->msMode == NETEQ_SLAVE) - { - /* Nothing to do */ - } -#endif - - /* check for error returned from MCU side, if so, return error */ - if (return_value < 0) - { - inst->w16_mode = MODE_ERROR; - dspInfo->lastMode = MODE_ERROR; - return return_value; - } - - blockPtr = &((inst->pw16_readAddress)[3]); - - /* Check for DTMF payload flag */ - if ((inst->pw16_readAddress[0] & DSP_DTMF_PAYLOAD) != 0) - { - playDtmf = 1; - dtmfValue = blockPtr[1]; - dtmfVolume = blockPtr[2]; - blockPtr += 3; - -#ifdef NETEQ_STEREO - if (msInfo->msMode == NETEQ_MASTER) - { - /* signal to slave that master is using DTMF */ - msInfo->extraInfo = DTMF_OVERDUB; - } -#endif - } - - blockLen = (((*blockPtr) & DSP_CODEC_MASK_RED_FLAG) + 1) >> 1; /* In # of int16_t */ - payloadLen = ((*blockPtr) & DSP_CODEC_MASK_RED_FLAG); - blockPtr++; - - /* Do we have to change our decoder? */ - if ((inst->pw16_readAddress[0] & 0x0f00) == DSP_CODEC_NEW_CODEC) - { - WEBRTC_SPL_MEMCPY_W16(&inst->codec_ptr_inst,blockPtr,(payloadLen+1)>>1); - if (inst->codec_ptr_inst.codec_fs != 0) - { - return_value = WebRtcNetEQ_DSPInit(inst, inst->codec_ptr_inst.codec_fs); - if (return_value != 0) - { /* error returned */ - instr = DSP_INSTR_FADE_TO_BGN; /* emergency instruction */ - } -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_CHANGE_FS; - if ((fwrite(&temp_var, sizeof(int), - 1, delay_fid2) != 1) || - (fwrite(&inst->fs, sizeof(uint16_t), - 1, delay_fid2) != 1)) { - return -1; - } -#endif - } - - /* Copy it again since the init destroys this part */ - - WEBRTC_SPL_MEMCPY_W16(&inst->codec_ptr_inst,blockPtr,(payloadLen+1)>>1); - inst->endTimestamp = inst->codec_ptr_inst.timeStamp; - inst->videoSyncTimestamp = inst->codec_ptr_inst.timeStamp; - blockPtr += blockLen; - blockLen = (((*blockPtr) & DSP_CODEC_MASK_RED_FLAG) + 1) >> 1; - payloadLen = ((*blockPtr) & DSP_CODEC_MASK_RED_FLAG); - blockPtr++; - if (inst->codec_ptr_inst.funcDecodeInit != NULL) - { - inst->codec_ptr_inst.funcDecodeInit(inst->codec_ptr_inst.codec_state); - } - -#ifdef NETEQ_CNG_CODEC - - /* Also update the CNG state as this might be uninitialized */ - - WEBRTC_SPL_MEMCPY_W16(&inst->CNG_Codec_inst,blockPtr,(payloadLen+1)>>1); - blockPtr += blockLen; - blockLen = (((*blockPtr) & DSP_CODEC_MASK_RED_FLAG) + 1) >> 1; - payloadLen = ((*blockPtr) & DSP_CODEC_MASK_RED_FLAG); - blockPtr++; - if (inst->CNG_Codec_inst != NULL) - { - WebRtcCng_InitDec(inst->CNG_Codec_inst); - } -#endif - } - else if ((inst->pw16_readAddress[0] & 0x0f00) == DSP_CODEC_RESET) - { - /* Reset the current codec (but not DSP struct) */ - if (inst->codec_ptr_inst.funcDecodeInit != NULL) - { - inst->codec_ptr_inst.funcDecodeInit(inst->codec_ptr_inst.codec_state); - } - -#ifdef NETEQ_CNG_CODEC - /* And reset CNG */ - if (inst->CNG_Codec_inst != NULL) - { - WebRtcCng_InitDec(inst->CNG_Codec_inst); - } -#endif /*NETEQ_CNG_CODEC*/ - } - - fs_mult = WebRtcNetEQ_CalcFsMult(inst->fs); - - /* Add late packet? */ - if ((inst->pw16_readAddress[0] & 0x0f00) == DSP_CODEC_ADD_LATE_PKT) - { - if (inst->codec_ptr_inst.funcAddLatePkt != NULL) - { - /* Only do this if the codec has support for Add Late Pkt */ - inst->codec_ptr_inst.funcAddLatePkt(inst->codec_ptr_inst.codec_state, blockPtr, - payloadLen); - } - blockPtr += blockLen; - blockLen = (((*blockPtr) & DSP_CODEC_MASK_RED_FLAG) + 1) >> 1; /* In # of Word16 */ - payloadLen = ((*blockPtr) & DSP_CODEC_MASK_RED_FLAG); - blockPtr++; - } - - /* Do we have to decode data? */ - if ((instr == DSP_INSTR_NORMAL) || (instr == DSP_INSTR_ACCELERATE) || (instr - == DSP_INSTR_MERGE) || (instr == DSP_INSTR_PREEMPTIVE_EXPAND)) - { - /* Do we need to update codec-internal PLC state? */ - if ((instr == DSP_INSTR_MERGE) && (inst->codec_ptr_inst.funcDecodePLC != NULL)) - { - len = 0; - len = inst->codec_ptr_inst.funcDecodePLC(inst->codec_ptr_inst.codec_state, - &pw16_decoded_buffer[len], 1); - } - len = 0; - - /* Do decoding */ - while ((blockLen > 0) && (len < (240 * fs_mult))) /* Guard somewhat against overflow */ - { - if (inst->codec_ptr_inst.funcDecode != NULL) - { - int16_t dec_Len; - if (!BGNonly) - { - /* Check if this is a sync payload. */ - if (av_sync && WebRtcNetEQ_IsSyncPayload(blockPtr, - payloadLen)) { - /* Zero-stuffing with same size as the last frame. */ - dec_Len = inst->w16_frameLen; - memset(&pw16_decoded_buffer[len], 0, dec_Len * - sizeof(pw16_decoded_buffer[len])); - } else { - /* Do decoding as normal - * - * blockPtr is pointing to payload, at this point, - * the most significant bit of *(blockPtr - 1) is a flag if - * set to 1 indicates that the following payload is the - * redundant payload. - */ - if (((*(blockPtr - 1) & DSP_CODEC_RED_FLAG) != 0) - && (inst->codec_ptr_inst.funcDecodeRCU != NULL)) - { - dec_Len = inst->codec_ptr_inst.funcDecodeRCU( - inst->codec_ptr_inst.codec_state, blockPtr, - payloadLen, &pw16_decoded_buffer[len], &speechType); - } - else - { - /* Regular decoding. */ - dec_Len = inst->codec_ptr_inst.funcDecode( - inst->codec_ptr_inst.codec_state, blockPtr, - payloadLen, &pw16_decoded_buffer[len], &speechType); - } - } - } - else - { - /* - * Background noise mode: don't decode, just produce the same length BGN. - * Don't call Expand for BGN here, since Expand uses the memory where the - * bitstreams are stored (sharemem). - */ - dec_Len = inst->w16_frameLen; - } - - if (dec_Len > 0) - { - len += dec_Len; - /* Update frameLen */ - inst->w16_frameLen = dec_Len; - } - else if (dec_Len < 0) - { - /* Error */ - len = -1; - break; - } - /* - * Sanity check (although we might still write outside memory when this - * happens...) - */ - if (len > NETEQ_MAX_FRAME_SIZE) - { - WebRtcSpl_MemSetW16(pw16_outData, 0, inst->timestampsPerCall); - *pw16_len = inst->timestampsPerCall; - inst->w16_mode = MODE_ERROR; - dspInfo->lastMode = MODE_ERROR; - return RECOUT_ERROR_DECODED_TOO_MUCH; - } - - /* Verify that instance was not corrupted by decoder */ - if (mainInstBackup != inst->main_inst) - { - /* Instance is corrupt */ - return CORRUPT_INSTANCE; - } - - } - blockPtr += blockLen; - blockLen = (((*blockPtr) & DSP_CODEC_MASK_RED_FLAG) + 1) >> 1; /* In # of Word16 */ - payloadLen = ((*blockPtr) & DSP_CODEC_MASK_RED_FLAG); - blockPtr++; - } - - if (len < 0) - { - len = 0; - inst->endTimestamp += inst->w16_frameLen; /* advance one frame */ - if (inst->codec_ptr_inst.funcGetErrorCode != NULL) - { - return_value = -inst->codec_ptr_inst.funcGetErrorCode( - inst->codec_ptr_inst.codec_state); - } - else - { - return_value = RECOUT_ERROR_DECODING; - } - instr = DSP_INSTR_FADE_TO_BGN; - } - if (speechType != TYPE_CNG) - { - /* - * Don't increment timestamp if codec returned CNG speech type - * since in this case, the MCU side will increment the CNGplayedTS counter. - */ - inst->endTimestamp += len; - } - } - else if (instr == DSP_INSTR_NORMAL_ONE_DESC) - { - if (inst->codec_ptr_inst.funcDecode != NULL) - { - len = inst->codec_ptr_inst.funcDecode(inst->codec_ptr_inst.codec_state, NULL, 0, - pw16_decoded_buffer, &speechType); -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_DECODE_ONE_DESC; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - if (fwrite(&inst->endTimestamp, sizeof(uint32_t), - 1, delay_fid2) != 1) { - return -1; - } - if (fwrite(&dspInfo->samplesLeft, sizeof(uint16_t), - 1, delay_fid2) != 1) { - return -1; - } - tot_received_packets++; -#endif - } - if (speechType != TYPE_CNG) - { - /* - * Don't increment timestamp if codec returned CNG speech type - * since in this case, the MCU side will increment the CNGplayedTS counter. - */ - inst->endTimestamp += len; - } - - /* Verify that instance was not corrupted by decoder */ - if (mainInstBackup != inst->main_inst) - { - /* Instance is corrupt */ - return CORRUPT_INSTANCE; - } - - if (len <= 0) - { - len = 0; - if (inst->codec_ptr_inst.funcGetErrorCode != NULL) - { - return_value = -inst->codec_ptr_inst.funcGetErrorCode( - inst->codec_ptr_inst.codec_state); - } - else - { - return_value = RECOUT_ERROR_DECODING; - } - if ((inst->codec_ptr_inst.funcDecodeInit != NULL) - && (inst->codec_ptr_inst.codec_state != NULL)) - { - /* Reinitialize codec state as something is obviously wrong */ - inst->codec_ptr_inst.funcDecodeInit(inst->codec_ptr_inst.codec_state); - } - inst->endTimestamp += inst->w16_frameLen; /* advance one frame */ - instr = DSP_INSTR_FADE_TO_BGN; - } - } - - if (len == 0 && lastModeBGNonly) /* no new data */ - { - BGNonly = 1; /* force BGN this time too */ - } - -#ifdef NETEQ_VAD - if ((speechType == TYPE_CNG) /* decoder responded with codec-internal CNG */ - || ((instr == DSP_INSTR_DO_RFC3389CNG) && (blockLen > 0)) /* ... or, SID frame */ - || (inst->fs > 16000)) /* ... or, if not NB or WB */ - { - /* disable post-decode VAD upon first sign of send-side DTX/VAD active, or if SWB */ - inst->VADInst.VADEnabled = 0; - inst->VADInst.VADDecision = 1; /* set to always active, just to be on the safe side */ - inst->VADInst.SIDintervalCounter = 0; /* reset SID interval counter */ - } - else if (!inst->VADInst.VADEnabled) /* VAD disabled and no SID/CNG data observed this time */ - { - inst->VADInst.SIDintervalCounter++; /* increase counter */ - } - - /* check for re-enabling the VAD */ - if (inst->VADInst.SIDintervalCounter >= POST_DECODE_VAD_AUTO_ENABLE) - { - /* - * It's been a while since the last CNG/SID frame was observed => re-enable VAD. - * (Do not care to look for a VAD instance, since this is done inside the init - * function) - */ - WebRtcNetEQ_InitVAD(&inst->VADInst, inst->fs); - } - - if (len > 0 /* if we decoded any data */ - && inst->VADInst.VADEnabled /* and VAD enabled */ - && inst->fs <= 16000) /* can only do VAD for NB and WB */ - { - int VADframeSize; /* VAD frame size in ms */ - int VADSamplePtr = 0; - - inst->VADInst.VADDecision = 0; - - if (inst->VADInst.VADFunction != NULL) /* make sure that VAD function is provided */ - { - /* divide the data into groups, as large as possible */ - for (VADframeSize = 30; VADframeSize >= 10; VADframeSize -= 10) - { - /* loop through 30, 20, 10 */ - - while (inst->VADInst.VADDecision == 0 - && len - VADSamplePtr >= VADframeSize * fs_mult * 8) - { - /* - * Only continue until first active speech found, and as long as there is - * one VADframeSize left. - */ - - /* call VAD with new decoded data */ - inst->VADInst.VADDecision |= inst->VADInst.VADFunction( - inst->VADInst.VADState, (int) inst->fs, - (int16_t *) &pw16_decoded_buffer[VADSamplePtr], - (VADframeSize * fs_mult * 8)); - - VADSamplePtr += VADframeSize * fs_mult * 8; /* increment sample counter */ - } - } - } - else - { /* VAD function is NULL */ - inst->VADInst.VADDecision = 1; /* set decision to active */ - inst->VADInst.VADEnabled = 0; /* disable VAD since we have no VAD function */ - } - - } -#endif /* NETEQ_VAD */ - - /* Adjust timestamp if needed */ - uw16_tmp = (uint16_t) inst->pw16_readAddress[1]; - inst->endTimestamp += (((uint32_t) uw16_tmp) << 16); - uw16_tmp = (uint16_t) inst->pw16_readAddress[2]; - inst->endTimestamp += uw16_tmp; - - if (BGNonly && len > 0) - { - /* - * If BGN mode, we did not produce any data at decoding. - * Do it now instead. - */ - - WebRtcNetEQ_GenerateBGN(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_EXPAND, -#endif - pw16_decoded_buffer, len); - } - - /* Switch on the instruction received from the MCU side. */ - switch (instr) - { - case DSP_INSTR_NORMAL: - - /* Allow for signal processing to apply gain-back etc */ - WebRtcNetEQ_Normal(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_NORMAL, -#endif - pw16_decoded_buffer, len, pw16_NetEqAlgorithm_buffer, &len); - - /* If last packet was decoded as a inband CNG set mode to CNG instead */ - if ((speechType == TYPE_CNG) || ((inst->w16_mode == MODE_CODEC_INTERNAL_CNG) - && (len == 0))) - { - inst->w16_mode = MODE_CODEC_INTERNAL_CNG; - } - -#ifdef NETEQ_ATEVENT_DECODE - if (playDtmf == 0) - { - inst->DTMFInst.reinit = 1; - } -#endif - break; - case DSP_INSTR_NORMAL_ONE_DESC: - - /* Allow for signal processing to apply gain-back etc */ - WebRtcNetEQ_Normal(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_NORMAL, -#endif - pw16_decoded_buffer, len, pw16_NetEqAlgorithm_buffer, &len); -#ifdef NETEQ_ATEVENT_DECODE - if (playDtmf == 0) - { - inst->DTMFInst.reinit = 1; - } -#endif - inst->w16_mode = MODE_ONE_DESCRIPTOR; - break; - case DSP_INSTR_MERGE: -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_MERGE_INFO; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - temp_var = -len; -#endif - /* Call Merge with history*/ - return_value = WebRtcNetEQ_Merge(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_MERGE, -#endif - pw16_decoded_buffer, len, pw16_NetEqAlgorithm_buffer, &len); - - if (return_value < 0) - { - /* error */ - return return_value; - } - -#ifdef NETEQ_DELAY_LOGGING - temp_var += len; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } -#endif - /* If last packet was decoded as a inband CNG set mode to CNG instead */ - if (speechType == TYPE_CNG) inst->w16_mode = MODE_CODEC_INTERNAL_CNG; -#ifdef NETEQ_ATEVENT_DECODE - if (playDtmf == 0) - { - inst->DTMFInst.reinit = 1; - } -#endif - break; - - case DSP_INSTR_EXPAND: - len = 0; - pos = 0; - while ((inst->endPosition - inst->curPosition - inst->ExpandInst.w16_overlap + pos) - < (inst->timestampsPerCall)) - { - return_value = WebRtcNetEQ_Expand(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_EXPAND, -#endif - pw16_NetEqAlgorithm_buffer, &len, BGNonly); - if (return_value < 0) - { - /* error */ - return return_value; - } - - /* - * Update buffer, but only end part (otherwise expand state is destroyed - * since it reuses speechBuffer[] memory - */ - - WEBRTC_SPL_MEMMOVE_W16(inst->pw16_speechHistory, - inst->pw16_speechHistory + len, - (inst->w16_speechHistoryLen-len)); - WEBRTC_SPL_MEMCPY_W16(&inst->pw16_speechHistory[inst->w16_speechHistoryLen-len], - pw16_NetEqAlgorithm_buffer, len); - - inst->curPosition -= len; - - /* Update variables for VQmon */ - inst->w16_concealedTS += len; -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_EXPAND_INFO; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - temp_var = len; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } -#endif - len = 0; /* already written the data, so do not write it again further down. */ - } -#ifdef NETEQ_ATEVENT_DECODE - if (playDtmf == 0) - { - inst->DTMFInst.reinit = 1; - } -#endif - break; - - case DSP_INSTR_ACCELERATE: - if (len < 3 * 80 * fs_mult) - { - /* We need to move data from the speechBuffer[] in order to get 30 ms */ - borrowedSamples = 3 * 80 * fs_mult - len; - - WEBRTC_SPL_MEMMOVE_W16(&pw16_decoded_buffer[borrowedSamples], - pw16_decoded_buffer, len); - WEBRTC_SPL_MEMCPY_W16(pw16_decoded_buffer, - &(inst->speechBuffer[inst->endPosition-borrowedSamples]), - borrowedSamples); - - return_value = WebRtcNetEQ_Accelerate(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_ACCELERATE, -#endif - pw16_decoded_buffer, 3 * inst->timestampsPerCall, - pw16_NetEqAlgorithm_buffer, &len, BGNonly); - - if (return_value < 0) - { - /* error */ - return return_value; - } - - /* Copy back samples to the buffer */ - if (len < borrowedSamples) - { - /* - * This destroys the beginning of the buffer, but will not cause any - * problems - */ - - WEBRTC_SPL_MEMCPY_W16(&inst->speechBuffer[inst->endPosition-borrowedSamples], - pw16_NetEqAlgorithm_buffer, len); - WEBRTC_SPL_MEMMOVE_W16(&inst->speechBuffer[borrowedSamples-len], - inst->speechBuffer, - (inst->endPosition-(borrowedSamples-len))); - - inst->curPosition += (borrowedSamples - len); -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_ACCELERATE_INFO; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - temp_var = 3 * inst->timestampsPerCall - len; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } -#endif - len = 0; - } - else - { - WEBRTC_SPL_MEMCPY_W16(&inst->speechBuffer[inst->endPosition-borrowedSamples], - pw16_NetEqAlgorithm_buffer, borrowedSamples); - WEBRTC_SPL_MEMMOVE_W16(pw16_NetEqAlgorithm_buffer, - &pw16_NetEqAlgorithm_buffer[borrowedSamples], - (len-borrowedSamples)); -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_ACCELERATE_INFO; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - temp_var = 3 * inst->timestampsPerCall - len; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } -#endif - len = len - borrowedSamples; - } - - } - else - { -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_ACCELERATE_INFO; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - temp_var = len; -#endif - return_value = WebRtcNetEQ_Accelerate(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_ACCELERATE, -#endif - pw16_decoded_buffer, len, pw16_NetEqAlgorithm_buffer, &len, BGNonly); - - if (return_value < 0) - { - /* error */ - return return_value; - } - -#ifdef NETEQ_DELAY_LOGGING - temp_var -= len; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } -#endif - } - /* If last packet was decoded as a inband CNG set mode to CNG instead */ - if (speechType == TYPE_CNG) inst->w16_mode = MODE_CODEC_INTERNAL_CNG; -#ifdef NETEQ_ATEVENT_DECODE - if (playDtmf == 0) - { - inst->DTMFInst.reinit = 1; - } -#endif - break; - - case DSP_INSTR_DO_RFC3389CNG: -#ifdef NETEQ_CNG_CODEC - if (blockLen > 0) - { - if (WebRtcCng_UpdateSid(inst->CNG_Codec_inst, (uint8_t*) blockPtr, - payloadLen) < 0) - { - /* error returned from CNG function */ - return_value = -WebRtcCng_GetErrorCodeDec(inst->CNG_Codec_inst); - len = inst->timestampsPerCall; - WebRtcSpl_MemSetW16(pw16_NetEqAlgorithm_buffer, 0, len); - break; - } - } - - if (BGNonly) - { - /* Get data from BGN function instead of CNG */ - len = WebRtcNetEQ_GenerateBGN(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_EXPAND, -#endif - pw16_NetEqAlgorithm_buffer, inst->timestampsPerCall); - if (len != inst->timestampsPerCall) - { - /* this is not good, treat this as an error */ - return_value = -1; - } - } - else - { - return_value = WebRtcNetEQ_Cng(inst, pw16_NetEqAlgorithm_buffer, - inst->timestampsPerCall); - } - len = inst->timestampsPerCall; - inst->ExpandInst.w16_consecExp = 0; - inst->w16_mode = MODE_RFC3389CNG; -#ifdef NETEQ_ATEVENT_DECODE - if (playDtmf == 0) - { - inst->DTMFInst.reinit = 1; - } -#endif - - if (return_value < 0) - { - /* error returned */ - WebRtcSpl_MemSetW16(pw16_NetEqAlgorithm_buffer, 0, len); - } - - break; -#else - return FAULTY_INSTRUCTION; -#endif - case DSP_INSTR_DO_CODEC_INTERNAL_CNG: - /* - * This represents the case when there is no transmission and the decoder should - * do internal CNG. - */ - len = 0; - if (inst->codec_ptr_inst.funcDecode != NULL && !BGNonly) - { - len = inst->codec_ptr_inst.funcDecode(inst->codec_ptr_inst.codec_state, - blockPtr, 0, pw16_decoded_buffer, &speechType); - } - else - { - /* get BGN data */ - len = WebRtcNetEQ_GenerateBGN(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_EXPAND, -#endif - pw16_decoded_buffer, inst->timestampsPerCall); - } - WebRtcNetEQ_Normal(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_NORMAL, -#endif - pw16_decoded_buffer, len, pw16_NetEqAlgorithm_buffer, &len); - inst->w16_mode = MODE_CODEC_INTERNAL_CNG; - inst->ExpandInst.w16_consecExp = 0; - break; - - case DSP_INSTR_DTMF_GENERATE: -#ifdef NETEQ_ATEVENT_DECODE - dtmfSwitch = 0; - if ((inst->w16_mode != MODE_DTMF) && (inst->DTMFInst.reinit == 0)) - { - /* Special case; see below. - * We must catch this before calling DTMFGenerate, - * since reinit is set to 0 in that call. - */ - dtmfSwitch = 1; - } - - len = WebRtcNetEQ_DTMFGenerate(&inst->DTMFInst, dtmfValue, dtmfVolume, - pw16_NetEqAlgorithm_buffer, inst->fs, -1); - if (len < 0) - { - /* error occurred */ - return_value = len; - len = inst->timestampsPerCall; - WebRtcSpl_MemSetW16(pw16_NetEqAlgorithm_buffer, 0, len); - } - - if (dtmfSwitch == 1) - { - /* - * This is the special case where the previous operation was DTMF overdub. - * but the current instruction is "regular" DTMF. We must make sure that the - * DTMF does not have any discontinuities. The first DTMF sample that we - * generate now must be played out immediately, wherefore it must be copied to - * the speech buffer. - */ - - /* - * Generate extra DTMF data to fill the space between - * curPosition and endPosition - */ - int16_t tempLen; - - tempLen = WebRtcNetEQ_DTMFGenerate(&inst->DTMFInst, dtmfValue, dtmfVolume, - &pw16_NetEqAlgorithm_buffer[len], inst->fs, - inst->endPosition - inst->curPosition); - if (tempLen < 0) - { - /* error occurred */ - return_value = tempLen; - len = inst->endPosition - inst->curPosition; - WebRtcSpl_MemSetW16(pw16_NetEqAlgorithm_buffer, 0, - inst->endPosition - inst->curPosition); - } - - /* Add to total length */ - len += tempLen; - - /* Overwrite the "future" part of the speech buffer with the new DTMF data */ - - WEBRTC_SPL_MEMCPY_W16(&inst->speechBuffer[inst->curPosition], - pw16_NetEqAlgorithm_buffer, - inst->endPosition - inst->curPosition); - - /* Shuffle the remaining data to the beginning of algorithm buffer */ - len -= (inst->endPosition - inst->curPosition); - WEBRTC_SPL_MEMMOVE_W16(pw16_NetEqAlgorithm_buffer, - &pw16_NetEqAlgorithm_buffer[inst->endPosition - inst->curPosition], - len); - } - - inst->endTimestamp += inst->timestampsPerCall; - inst->DTMFInst.reinit = 0; - inst->ExpandInst.w16_consecExp = 0; - inst->w16_mode = MODE_DTMF; - BGNonly = 0; /* override BGN only and let DTMF through */ - - playDtmf = 0; /* set to zero because the DTMF is already in the Algorithm buffer */ - /* - * If playDtmf is 1, an extra DTMF vector will be generated and overdubbed - * on the output. - */ - -#ifdef NETEQ_STEREO - if (msInfo->msMode == NETEQ_MASTER) - { - /* signal to slave that master is using DTMF only */ - msInfo->extraInfo = DTMF_ONLY; - } -#endif - - break; -#else - inst->w16_mode = MODE_ERROR; - dspInfo->lastMode = MODE_ERROR; - return FAULTY_INSTRUCTION; -#endif - - case DSP_INSTR_DO_ALTERNATIVE_PLC: - if (inst->codec_ptr_inst.funcDecodePLC != 0) - { - len = inst->codec_ptr_inst.funcDecodePLC(inst->codec_ptr_inst.codec_state, - pw16_NetEqAlgorithm_buffer, 1); - } - else - { - len = inst->timestampsPerCall; - /* ZeroStuffing... */ - WebRtcSpl_MemSetW16(pw16_NetEqAlgorithm_buffer, 0, len); - /* By not advancing the timestamp, NetEq inserts samples. */ - inst->statInst.addedSamples += len; - } - inst->ExpandInst.w16_consecExp = 0; - break; - case DSP_INSTR_DO_ALTERNATIVE_PLC_INC_TS: - if (inst->codec_ptr_inst.funcDecodePLC != 0) - { - len = inst->codec_ptr_inst.funcDecodePLC(inst->codec_ptr_inst.codec_state, - pw16_NetEqAlgorithm_buffer, 1); - } - else - { - len = inst->timestampsPerCall; - /* ZeroStuffing... */ - WebRtcSpl_MemSetW16(pw16_NetEqAlgorithm_buffer, 0, len); - } - inst->ExpandInst.w16_consecExp = 0; - inst->endTimestamp += len; - break; - case DSP_INSTR_DO_AUDIO_REPETITION: - len = inst->timestampsPerCall; - /* copy->paste... */ - WEBRTC_SPL_MEMCPY_W16(pw16_NetEqAlgorithm_buffer, - &inst->speechBuffer[inst->endPosition-len], len); - inst->ExpandInst.w16_consecExp = 0; - break; - case DSP_INSTR_DO_AUDIO_REPETITION_INC_TS: - len = inst->timestampsPerCall; - /* copy->paste... */ - WEBRTC_SPL_MEMCPY_W16(pw16_NetEqAlgorithm_buffer, - &inst->speechBuffer[inst->endPosition-len], len); - inst->ExpandInst.w16_consecExp = 0; - inst->endTimestamp += len; - break; - - case DSP_INSTR_PREEMPTIVE_EXPAND: - if (len < 3 * inst->timestampsPerCall) - { - /* borrow samples from sync buffer if necessary */ - borrowedSamples = 3 * inst->timestampsPerCall - len; /* borrow this many samples */ - /* calculate how many of these are already played out */ - oldBorrowedSamples = WEBRTC_SPL_MAX(0, - borrowedSamples - (inst->endPosition - inst->curPosition)); - WEBRTC_SPL_MEMMOVE_W16(&pw16_decoded_buffer[borrowedSamples], - pw16_decoded_buffer, len); - WEBRTC_SPL_MEMCPY_W16(pw16_decoded_buffer, - &(inst->speechBuffer[inst->endPosition-borrowedSamples]), - borrowedSamples); - } - else - { - borrowedSamples = 0; - oldBorrowedSamples = 0; - } - -#ifdef NETEQ_DELAY_LOGGING - w16_tmp1 = len; -#endif - /* do the expand */ - return_value = WebRtcNetEQ_PreEmptiveExpand(inst, -#ifdef SCRATCH - /* use same scratch memory as Accelerate */ - pw16_scratchPtr + SCRATCH_NETEQ_ACCELERATE, -#endif - pw16_decoded_buffer, len + borrowedSamples, oldBorrowedSamples, - pw16_NetEqAlgorithm_buffer, &len, BGNonly); - - if (return_value < 0) - { - /* error */ - return return_value; - } - - if (borrowedSamples > 0) - { - /* return borrowed samples */ - - /* Copy back to last part of speechBuffer from beginning of output buffer */ - WEBRTC_SPL_MEMCPY_W16( &(inst->speechBuffer[inst->endPosition-borrowedSamples]), - pw16_NetEqAlgorithm_buffer, - borrowedSamples); - - len -= borrowedSamples; /* remove the borrowed samples from new total length */ - - /* Move to beginning of output buffer from end of output buffer */ - WEBRTC_SPL_MEMMOVE_W16( pw16_NetEqAlgorithm_buffer, - &pw16_NetEqAlgorithm_buffer[borrowedSamples], - len); - } - -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_PREEMPTIVE_INFO; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - temp_var = len - w16_tmp1; /* number of samples added */ - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } -#endif - /* If last packet was decoded as inband CNG, set mode to CNG instead */ - if (speechType == TYPE_CNG) inst->w16_mode = MODE_CODEC_INTERNAL_CNG; -#ifdef NETEQ_ATEVENT_DECODE - if (playDtmf == 0) - { - inst->DTMFInst.reinit = 1; - } -#endif - break; - - case DSP_INSTR_FADE_TO_BGN: - { - int tempReturnValue; - /* do not overwrite return_value, since it likely contains an error code */ - - /* calculate interpolation length */ - w16_tmp3 = WEBRTC_SPL_MIN(inst->endPosition - inst->curPosition, - inst->timestampsPerCall); - /* check that it will fit in pw16_NetEqAlgorithm_buffer */ - if (w16_tmp3 + inst->w16_frameLen > NETEQ_MAX_OUTPUT_SIZE) - { - w16_tmp3 = NETEQ_MAX_OUTPUT_SIZE - inst->w16_frameLen; - } - - /* call Expand */ - len = inst->timestampsPerCall + inst->ExpandInst.w16_overlap; - pos = 0; - - tempReturnValue = WebRtcNetEQ_Expand(inst, -#ifdef SCRATCH - pw16_scratchPtr + SCRATCH_NETEQ_EXPAND, -#endif - pw16_NetEqAlgorithm_buffer, &len, 1); - - if (tempReturnValue < 0) - { - /* error */ - /* this error value will override return_value */ - return tempReturnValue; - } - - pos += len; /* got len samples from expand */ - - /* copy to fill the demand */ - while (pos + len <= inst->w16_frameLen + w16_tmp3) - { - WEBRTC_SPL_MEMCPY_W16(&pw16_NetEqAlgorithm_buffer[pos], - pw16_NetEqAlgorithm_buffer, len); - pos += len; - } - - /* fill with fraction of the expand vector if needed */ - if (pos < inst->w16_frameLen + w16_tmp3) - { - WEBRTC_SPL_MEMCPY_W16(&pw16_NetEqAlgorithm_buffer[pos], pw16_NetEqAlgorithm_buffer, - inst->w16_frameLen + w16_tmp3 - pos); - } - - len = inst->w16_frameLen + w16_tmp3; /* truncate any surplus samples since we don't want these */ - - /* - * Mix with contents in sync buffer. Find largest power of two that is less than - * interpolate length divide 16384 with this number; result is in w16_tmp2. - */ - w16_tmp1 = 2; - w16_tmp2 = 16384; - while (w16_tmp1 <= w16_tmp3) - { - w16_tmp2 >>= 1; /* divide with 2 */ - w16_tmp1 <<= 1; /* increase with a factor of 2 */ - } - - w16_tmp1 = 0; - pos = 0; - while (w16_tmp1 < 16384) - { - inst->speechBuffer[inst->curPosition + pos] - = - (int16_t) WEBRTC_SPL_RSHIFT_W32( - WEBRTC_SPL_MUL_16_16( inst->speechBuffer[inst->endPosition - w16_tmp3 + pos], - 16384-w16_tmp1 ) + - WEBRTC_SPL_MUL_16_16( pw16_NetEqAlgorithm_buffer[pos], w16_tmp1 ), - 14 ); - w16_tmp1 += w16_tmp2; - pos++; - } - - /* overwrite remainder of speech buffer */ - - WEBRTC_SPL_MEMCPY_W16( &inst->speechBuffer[inst->endPosition - w16_tmp3 + pos], - &pw16_NetEqAlgorithm_buffer[pos], w16_tmp3 - pos); - - len -= w16_tmp3; - /* shift algorithm buffer */ - - WEBRTC_SPL_MEMMOVE_W16( pw16_NetEqAlgorithm_buffer, - &pw16_NetEqAlgorithm_buffer[w16_tmp3], - len ); - - /* Update variables for VQmon */ - inst->w16_concealedTS += len; - - inst->w16_mode = MODE_FADE_TO_BGN; -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_EXPAND_INFO; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - temp_var = len; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } -#endif - - break; - } - - default: - inst->w16_mode = MODE_ERROR; - dspInfo->lastMode = MODE_ERROR; - return FAULTY_INSTRUCTION; - } /* end of grand switch */ - - /* Copy data directly to output buffer */ - - w16_tmp2 = 0; - if ((inst->endPosition + len - inst->curPosition - inst->ExpandInst.w16_overlap) - >= inst->timestampsPerCall) - { - w16_tmp2 = inst->endPosition - inst->curPosition; - w16_tmp2 = WEBRTC_SPL_MAX(w16_tmp2, 0); /* Additional error protection, just in case */ - w16_tmp1 = WEBRTC_SPL_MIN(w16_tmp2, inst->timestampsPerCall); - w16_tmp2 = inst->timestampsPerCall - w16_tmp1; - WEBRTC_SPL_MEMCPY_W16(pw16_outData, &inst->speechBuffer[inst->curPosition], w16_tmp1); - WEBRTC_SPL_MEMCPY_W16(&pw16_outData[w16_tmp1], pw16_NetEqAlgorithm_buffer, w16_tmp2); - DataEnough = 1; - } - else - { - DataEnough = 0; - } - - if (playDtmf != 0) - { -#ifdef NETEQ_ATEVENT_DECODE - int16_t outDataIndex = 0; - int16_t overdubLen = -1; /* default len */ - int16_t dtmfLen; - - /* - * Overdub the output with DTMF. Note that this is not executed if the - * DSP_INSTR_DTMF_GENERATE operation is performed above. - */ - if (inst->DTMFInst.lastDtmfSample - inst->curPosition > 0) - { - /* special operation for transition from "DTMF only" to "DTMF overdub" */ - outDataIndex - = WEBRTC_SPL_MIN(inst->DTMFInst.lastDtmfSample - inst->curPosition, - inst->timestampsPerCall); - overdubLen = inst->timestampsPerCall - outDataIndex; - } - - dtmfLen = WebRtcNetEQ_DTMFGenerate(&inst->DTMFInst, dtmfValue, dtmfVolume, - &pw16_outData[outDataIndex], inst->fs, overdubLen); - if (dtmfLen < 0) - { - /* error occurred */ - return_value = dtmfLen; - } - inst->DTMFInst.reinit = 0; -#else - inst->w16_mode = MODE_ERROR; - dspInfo->lastMode = MODE_ERROR; - return FAULTY_INSTRUCTION; -#endif - } - - /* - * Shuffle speech buffer to allow more data. Move data from pw16_NetEqAlgorithm_buffer - * to speechBuffer. - */ - if (instr != DSP_INSTR_EXPAND) - { - w16_tmp1 = WEBRTC_SPL_MIN(inst->endPosition, len); - WEBRTC_SPL_MEMMOVE_W16(inst->speechBuffer, inst->speechBuffer + w16_tmp1, - (inst->endPosition-w16_tmp1)); - WEBRTC_SPL_MEMCPY_W16(&inst->speechBuffer[inst->endPosition-w16_tmp1], - &pw16_NetEqAlgorithm_buffer[len-w16_tmp1], w16_tmp1); -#ifdef NETEQ_ATEVENT_DECODE - /* Update index to end of DTMF data in speech buffer */ - if (instr == DSP_INSTR_DTMF_GENERATE) - { - /* We have written DTMF data to the end of speech buffer */ - inst->DTMFInst.lastDtmfSample = inst->endPosition; - } - else if (inst->DTMFInst.lastDtmfSample > 0) - { - /* The end of DTMF data in speech buffer has been shuffled */ - inst->DTMFInst.lastDtmfSample -= w16_tmp1; - } -#endif - /* - * Update the BGN history if last operation was not expand (nor Merge, Accelerate - * or Pre-emptive expand, to save complexity). - */ - if ((inst->w16_mode != MODE_EXPAND) && (inst->w16_mode != MODE_MERGE) - && (inst->w16_mode != MODE_SUCCESS_ACCELERATE) && (inst->w16_mode - != MODE_LOWEN_ACCELERATE) && (inst->w16_mode != MODE_SUCCESS_PREEMPTIVE) - && (inst->w16_mode != MODE_LOWEN_PREEMPTIVE) && (inst->w16_mode - != MODE_FADE_TO_BGN) && (inst->w16_mode != MODE_DTMF) && (!BGNonly)) - { - WebRtcNetEQ_BGNUpdate(inst -#ifdef SCRATCH - , pw16_scratchPtr + SCRATCH_NETEQ_BGN_UPDATE -#endif - ); - } - } - else /* instr == DSP_INSTR_EXPAND */ - { - /* Nothing should be done since data is already copied to output. */ - } - - inst->curPosition -= len; - - /* - * Extra protection in case something should go totally wrong in terms of sizes... - * If everything is ok this should NEVER happen. - */ - if (inst->curPosition < -inst->timestampsPerCall) - { - inst->curPosition = -inst->timestampsPerCall; - } - - if ((instr != DSP_INSTR_EXPAND) && (instr != DSP_INSTR_MERGE) && (instr - != DSP_INSTR_FADE_TO_BGN)) - { - /* Reset concealed TS parameter if it does not seem to have been flushed */ - if (inst->w16_concealedTS > inst->timestampsPerCall) - { - inst->w16_concealedTS = 0; - } - } - - /* - * Double-check that we actually have 10 ms to play. If we haven't, there has been a - * serious error.The decoder might have returned way too few samples - */ - if (!DataEnough) - { - /* This should not happen. Set outdata to zeros, and return error. */ - WebRtcSpl_MemSetW16(pw16_outData, 0, inst->timestampsPerCall); - *pw16_len = inst->timestampsPerCall; - inst->w16_mode = MODE_ERROR; - dspInfo->lastMode = MODE_ERROR; - return RECOUT_ERROR_SAMPLEUNDERRUN; - } - - /* - * Update Videosync timestamp (this special timestamp is needed since the endTimestamp - * stops during CNG and Expand periods. - */ - if ((inst->w16_mode != MODE_EXPAND) && (inst->w16_mode != MODE_RFC3389CNG)) - { - uint32_t uw32_tmpTS; - uw32_tmpTS = inst->endTimestamp - (inst->endPosition - inst->curPosition); - if ((int32_t) (uw32_tmpTS - inst->videoSyncTimestamp) > 0) - { - inst->videoSyncTimestamp = uw32_tmpTS; - } - } - else - { - inst->videoSyncTimestamp += inst->timestampsPerCall; - } - - /* After this, regardless of what has happened, deliver 10 ms of future data */ - inst->curPosition += inst->timestampsPerCall; - *pw16_len = inst->timestampsPerCall; - - /* Remember if BGNonly was used */ - if (BGNonly) - { - inst->w16_mode |= MODE_BGN_ONLY; - } - - return return_value; -} - -#undef SCRATCH_ALGORITHM_BUFFER -#undef SCRATCH_NETEQ_NORMAL -#undef SCRATCH_NETEQ_MERGE -#undef SCRATCH_NETEQ_BGN_UPDATE -#undef SCRATCH_NETEQ_EXPAND -#undef SCRATCH_DSP_INFO -#undef SCRATCH_NETEQ_ACCELERATE -#undef SIZE_SCRATCH_BUFFER diff --git a/webrtc/modules/audio_coding/neteq/rtcp.c b/webrtc/modules/audio_coding/neteq/rtcp.c deleted file mode 100644 index d1ce934bc..000000000 --- a/webrtc/modules/audio_coding/neteq/rtcp.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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. - */ - -/* - * Implementation of RTCP statistics reporting. - */ - -#include "rtcp.h" - -#include - -#include "signal_processing_library.h" - -int WebRtcNetEQ_RTCPInit(WebRtcNetEQ_RTCP_t *RTCP_inst, uint16_t uw16_seqNo) -{ - /* - * Initialize everything to zero and then set the start values for the RTP packet stream. - */ - WebRtcSpl_MemSetW16((int16_t*) RTCP_inst, 0, - sizeof(WebRtcNetEQ_RTCP_t) / sizeof(int16_t)); - RTCP_inst->base_seq = uw16_seqNo; - RTCP_inst->max_seq = uw16_seqNo; - return 0; -} - -int WebRtcNetEQ_RTCPUpdate(WebRtcNetEQ_RTCP_t *RTCP_inst, uint16_t uw16_seqNo, - uint32_t uw32_timeStamp, uint32_t uw32_recTime) -{ - int16_t w16_SeqDiff; - int32_t w32_TimeDiff; - int32_t w32_JitterDiff; - - /* - * Update number of received packets, and largest packet number received. - */ - RTCP_inst->received++; - w16_SeqDiff = uw16_seqNo - RTCP_inst->max_seq; - if (w16_SeqDiff >= 0) - { - if (uw16_seqNo < RTCP_inst->max_seq) - { - /* Wrap around detected */ - RTCP_inst->cycles++; - } - RTCP_inst->max_seq = uw16_seqNo; - } - - /* Calculate Jitter, and update previous timestamps */ - /* Note that the value in RTCP_inst->jitter is in Q4. */ - if (RTCP_inst->received > 1) - { - w32_TimeDiff = (uw32_recTime - (uw32_timeStamp - RTCP_inst->transit)); - w32_TimeDiff = WEBRTC_SPL_ABS_W32(w32_TimeDiff); - w32_JitterDiff = WEBRTC_SPL_LSHIFT_W16(w32_TimeDiff, 4) - RTCP_inst->jitter; - RTCP_inst->jitter = RTCP_inst->jitter + WEBRTC_SPL_RSHIFT_W32((w32_JitterDiff + 8), 4); - } - RTCP_inst->transit = (uw32_timeStamp - uw32_recTime); - return 0; -} - -int WebRtcNetEQ_RTCPGetStats(WebRtcNetEQ_RTCP_t *RTCP_inst, - uint16_t *puw16_fraction_lost, - uint32_t *puw32_cum_lost, uint32_t *puw32_ext_max, - uint32_t *puw32_jitter, int16_t doNotReset) -{ - uint32_t uw32_exp_nr, uw32_exp_interval, uw32_rec_interval; - int32_t w32_lost; - - /* Extended highest sequence number received */ - *puw32_ext_max - = (uint32_t) WEBRTC_SPL_LSHIFT_W32((uint32_t)RTCP_inst->cycles, 16) - + RTCP_inst->max_seq; - - /* - * Calculate expected number of packets and compare it to the number of packets that - * were actually received => the cumulative number of packets lost can be extracted. - */ - uw32_exp_nr = *puw32_ext_max - RTCP_inst->base_seq + 1; - if (RTCP_inst->received == 0) - { - /* no packets received, assume none lost */ - *puw32_cum_lost = 0; - } - else if (uw32_exp_nr > RTCP_inst->received) - { - *puw32_cum_lost = uw32_exp_nr - RTCP_inst->received; - if (*puw32_cum_lost > (uint32_t) 0xFFFFFF) - { - *puw32_cum_lost = 0xFFFFFF; - } - } - else - { - *puw32_cum_lost = 0; - } - - /* Fraction lost (Since last report) */ - uw32_exp_interval = uw32_exp_nr - RTCP_inst->exp_prior; - if (!doNotReset) - { - RTCP_inst->exp_prior = uw32_exp_nr; - } - uw32_rec_interval = RTCP_inst->received - RTCP_inst->rec_prior; - if (!doNotReset) - { - RTCP_inst->rec_prior = RTCP_inst->received; - } - w32_lost = (int32_t) (uw32_exp_interval - uw32_rec_interval); - if (uw32_exp_interval == 0 || w32_lost <= 0 || RTCP_inst->received == 0) - { - *puw16_fraction_lost = 0; - } - else - { - *puw16_fraction_lost = (uint16_t) (WEBRTC_SPL_LSHIFT_W32(w32_lost, 8) - / uw32_exp_interval); - } - if (*puw16_fraction_lost > 0xFF) - { - *puw16_fraction_lost = 0xFF; - } - - /* Inter-arrival jitter */ - *puw32_jitter = (RTCP_inst->jitter) >> 4; /* scaling from Q4 */ - return 0; -} - diff --git a/webrtc/modules/audio_coding/neteq4/rtcp.cc b/webrtc/modules/audio_coding/neteq/rtcp.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/rtcp.cc rename to webrtc/modules/audio_coding/neteq/rtcp.cc index bc178fc3a..cf8e0280b 100644 --- a/webrtc/modules/audio_coding/neteq4/rtcp.cc +++ b/webrtc/modules/audio_coding/neteq/rtcp.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/rtcp.h" +#include "webrtc/modules/audio_coding/neteq/rtcp.h" #include diff --git a/webrtc/modules/audio_coding/neteq/rtcp.h b/webrtc/modules/audio_coding/neteq/rtcp.h index 5e066eb38..2a765efa5 100644 --- a/webrtc/modules/audio_coding/neteq/rtcp.h +++ b/webrtc/modules/audio_coding/neteq/rtcp.h @@ -8,95 +8,51 @@ * be found in the AUTHORS file in the root of the source tree. */ -/* - * RTCP statistics reporting. - */ - -#ifndef RTCP_H -#define RTCP_H - -#include "typedefs.h" - -typedef struct -{ - uint16_t cycles; /* The number of wrap-arounds for the sequence number */ - uint16_t max_seq; /* The maximum sequence number received - (starts from 0 again after wrap around) */ - uint16_t base_seq; /* The sequence number of the first packet that arrived */ - uint32_t received; /* The number of packets that has been received */ - uint32_t rec_prior; /* Number of packets received when last report was generated */ - uint32_t exp_prior; /* Number of packets that should have been received if no - packets were lost. Stored value from last report. */ - uint32_t jitter; /* Jitter statistics at this instance (calculated according to RFC) */ - int32_t transit; /* Clock difference for previous packet (RTPtimestamp - LOCALtime_rec) */ -} WebRtcNetEQ_RTCP_t; - -/**************************************************************************** - * WebRtcNetEQ_RTCPInit(...) - * - * This function calculates the parameters that are needed for the RTCP - * report. - * - * Input: - * - RTCP_inst : RTCP instance, that contains information about the - * packets that have been received etc. - * - seqNo : Packet number of the first received frame. - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RTCPInit(WebRtcNetEQ_RTCP_t *RTCP_inst, uint16_t uw16_seqNo); - -/**************************************************************************** - * WebRtcNetEQ_RTCPUpdate(...) - * - * This function calculates the parameters that are needed for the RTCP - * report. - * - * Input: - * - RTCP_inst : RTCP instance, that contains information about the - * packets that have been received etc. - * - seqNo : Packet number of the first received frame. - * - timeStamp : Time stamp from the RTP header. - * - recTime : Time (in RTP timestamps) when this packet was received. - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RTCPUpdate(WebRtcNetEQ_RTCP_t *RTCP_inst, uint16_t uw16_seqNo, - uint32_t uw32_timeStamp, uint32_t uw32_recTime); - -/**************************************************************************** - * WebRtcNetEQ_RTCPGetStats(...) - * - * This function calculates the parameters that are needed for the RTCP - * report. - * - * Input: - * - RTCP_inst : RTCP instance, that contains information about the - * packets that have been received etc. - * - doNotReset : If non-zero, the fraction lost statistics will not - * be reset. - * - * Output: - * - RTCP_inst : Updated RTCP information (some statistics are - * reset when generating this report) - * - fraction_lost : Number of lost RTP packets divided by the number of - * expected packets, since the last RTCP Report. - * - cum_lost : Cumulative number of lost packets during this - * session. - * - ext_max : Extended highest sequence number received. - * - jitter : Inter-arrival jitter. - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RTCPGetStats(WebRtcNetEQ_RTCP_t *RTCP_inst, - uint16_t *puw16_fraction_lost, - uint32_t *puw32_cum_lost, uint32_t *puw32_ext_max, - uint32_t *puw32_jitter, int16_t doNotReset); - -#endif +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_RTCP_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_RTCP_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +// Forward declaration. +struct RTPHeader; + +class Rtcp { + public: + Rtcp() { + Init(0); + } + + ~Rtcp() {} + + // Resets the RTCP statistics, and sets the first received sequence number. + void Init(uint16_t start_sequence_number); + + // Updates the RTCP statistics with a new received packet. + void Update(const RTPHeader& rtp_header, uint32_t receive_timestamp); + + // Returns the current RTCP statistics. If |no_reset| is true, the statistics + // are not reset, otherwise they are. + void GetStatistics(bool no_reset, RtcpStatistics* stats); + + private: + uint16_t cycles_; // The number of wrap-arounds for the sequence number. + uint16_t max_seq_no_; // The maximum sequence number received. Starts over + // from 0 after wrap-around. + uint16_t base_seq_no_; // The sequence number of the first received packet. + uint32_t received_packets_; // The number of packets that have been received. + uint32_t received_packets_prior_; // Number of packets received when last + // report was generated. + uint32_t expected_prior_; // Expected number of packets, at the time of the + // last report. + uint32_t jitter_; // Current jitter value. + int32_t transit_; // Clock difference for previous packet. + + DISALLOW_COPY_AND_ASSIGN(Rtcp); +}; + +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_RTCP_H_ diff --git a/webrtc/modules/audio_coding/neteq/rtp.c b/webrtc/modules/audio_coding/neteq/rtp.c deleted file mode 100644 index 6ab5944b5..000000000 --- a/webrtc/modules/audio_coding/neteq/rtp.c +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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. - */ - -/* - * RTP related functions. - */ - -#include "rtp.h" - -#include "typedefs.h" /* to define endianness */ - -#include "neteq_error_codes.h" - -int WebRtcNetEQ_RTPPayloadInfo(int16_t* pw16_Datagram, int i_DatagramLen, - RTPPacket_t* RTPheader) -{ - int i_P, i_X, i_CC, i_startPosition; - int i_IPver; - int i_extlength = -1; /* Default value is there is no extension */ - int i_padlength = 0; /* Default value if there is no padding */ - - if (i_DatagramLen < 12) - { - return RTP_TOO_SHORT_PACKET; - } - -#ifdef WEBRTC_ARCH_BIG_ENDIAN - i_IPver = (((uint16_t) (pw16_Datagram[0] & 0xC000)) >> 14); /* Extract the version */ - i_P = (((uint16_t) (pw16_Datagram[0] & 0x2000)) >> 13); /* Extract the P bit */ - i_X = (((uint16_t) (pw16_Datagram[0] & 0x1000)) >> 12); /* Extract the X bit */ - i_CC = ((uint16_t) (pw16_Datagram[0] >> 8) & 0xF); /* Get the CC number */ - RTPheader->payloadType = pw16_Datagram[0] & 0x7F; /* Get the coder type */ - RTPheader->seqNumber = pw16_Datagram[1]; /* Get the sequence number */ - RTPheader->timeStamp = ((((uint32_t) ((uint16_t) pw16_Datagram[2])) << 16) - | (uint16_t) (pw16_Datagram[3])); /* Get timestamp */ - RTPheader->ssrc = (((uint32_t) pw16_Datagram[4]) << 16) - + (((uint32_t) pw16_Datagram[5])); /* Get the SSRC */ - - if (i_X == 1) - { - /* Extension header exists. Find out how many int32_t it consists of. */ - i_extlength = pw16_Datagram[7 + 2 * i_CC]; - } - if (i_P == 1) - { - /* Padding exists. Find out how many bytes the padding consists of. */ - if (i_DatagramLen & 0x1) - { - /* odd number of bytes => last byte in higher byte */ - i_padlength = (((uint16_t) pw16_Datagram[i_DatagramLen >> 1]) >> 8); - } - else - { - /* even number of bytes => last byte in lower byte */ - i_padlength = ((pw16_Datagram[(i_DatagramLen >> 1) - 1]) & 0xFF); - } - } -#else /* WEBRTC_ARCH_LITTLE_ENDIAN */ - i_IPver = (((uint16_t) (pw16_Datagram[0] & 0xC0)) >> 6); /* Extract the IP version */ - i_P = (((uint16_t) (pw16_Datagram[0] & 0x20)) >> 5); /* Extract the P bit */ - i_X = (((uint16_t) (pw16_Datagram[0] & 0x10)) >> 4); /* Extract the X bit */ - i_CC = (uint16_t) (pw16_Datagram[0] & 0xF); /* Get the CC number */ - RTPheader->payloadType = (pw16_Datagram[0] >> 8) & 0x7F; /* Get the coder type */ - RTPheader->seqNumber = (((((uint16_t) pw16_Datagram[1]) >> 8) & 0xFF) - | (((uint16_t) (pw16_Datagram[1] & 0xFF)) << 8)); /* Get the packet number */ - RTPheader->timeStamp = ((((uint16_t) pw16_Datagram[2]) & 0xFF) << 24) - | ((((uint16_t) pw16_Datagram[2]) & 0xFF00) << 8) - | ((((uint16_t) pw16_Datagram[3]) >> 8) & 0xFF) - | ((((uint16_t) pw16_Datagram[3]) & 0xFF) << 8); /* Get timestamp */ - RTPheader->ssrc = ((((uint16_t) pw16_Datagram[4]) & 0xFF) << 24) - | ((((uint16_t) pw16_Datagram[4]) & 0xFF00) << 8) - | ((((uint16_t) pw16_Datagram[5]) >> 8) & 0xFF) - | ((((uint16_t) pw16_Datagram[5]) & 0xFF) << 8); /* Get the SSRC */ - - if (i_X == 1) - { - /* Extension header exists. Find out how many int32_t it consists of. */ - i_extlength = (((((uint16_t) pw16_Datagram[7 + 2 * i_CC]) >> 8) & 0xFF) - | (((uint16_t) (pw16_Datagram[7 + 2 * i_CC] & 0xFF)) << 8)); - } - if (i_P == 1) - { - /* Padding exists. Find out how many bytes the padding consists of. */ - if (i_DatagramLen & 0x1) - { - /* odd number of bytes => last byte in higher byte */ - i_padlength = (pw16_Datagram[i_DatagramLen >> 1] & 0xFF); - } - else - { - /* even number of bytes => last byte in lower byte */ - i_padlength = (((uint16_t) pw16_Datagram[(i_DatagramLen >> 1) - 1]) >> 8); - } - } -#endif - - i_startPosition = 12 + 4 * (i_extlength + 1) + 4 * i_CC; - RTPheader->payload = &pw16_Datagram[i_startPosition >> 1]; - RTPheader->payloadLen = i_DatagramLen - i_startPosition - i_padlength; - RTPheader->starts_byte1 = 0; - - if ((i_IPver != 2) || (RTPheader->payloadLen <= 0) || (RTPheader->payloadLen >= 16000) - || (i_startPosition < 12) || (i_startPosition > i_DatagramLen)) - { - return RTP_CORRUPT_PACKET; - } - - return 0; -} - -#ifdef NETEQ_RED_CODEC - -int WebRtcNetEQ_RedundancySplit(RTPPacket_t* RTPheader[], int i_MaximumPayloads, - int *i_No_Of_Payloads) -{ - const int16_t *pw16_data = RTPheader[0]->payload; /* Pointer to the data */ - uint16_t uw16_offsetTimeStamp = 65535, uw16_secondPayload = 65535; - int i_blockLength, i_k; - int i_discardedBlockLength = 0; - int singlePayload = 0; - -#ifdef WEBRTC_ARCH_BIG_ENDIAN - if ((pw16_data[0] & 0x8000) == 0) - { - /* Only one payload in this packet*/ - singlePayload = 1; - /* set the blocklength to -4 to deduce the non-existent 4-byte RED header */ - i_blockLength = -4; - RTPheader[0]->payloadType = ((((uint16_t)pw16_data[0]) & 0x7F00) >> 8); - } - else - { - /* Discard all but the two last payloads. */ - while (((pw16_data[2] & 0x8000) != 0) && - (pw16_data<((RTPheader[0]->payload)+((RTPheader[0]->payloadLen+1)>>1)))) - { - i_discardedBlockLength += (4+(((uint16_t)pw16_data[1]) & 0x3FF)); - pw16_data+=2; - } - if (pw16_data>=(RTPheader[0]->payload+((RTPheader[0]->payloadLen+1)>>1))) - { - return RED_SPLIT_ERROR2; /* Error, we are outside the packet */ - } - singlePayload = 0; /* the packet contains more than one payload */ - uw16_secondPayload = ((((uint16_t)pw16_data[0]) & 0x7F00) >> 8); - RTPheader[0]->payloadType = ((((uint16_t)pw16_data[2]) & 0x7F00) >> 8); - uw16_offsetTimeStamp = ((((uint16_t)pw16_data[0]) & 0xFF) << 6) + - ((((uint16_t)pw16_data[1]) & 0xFC00) >> 10); - i_blockLength = (((uint16_t)pw16_data[1]) & 0x3FF); - } -#else /* WEBRTC_ARCH_LITTLE_ENDIAN */ - if ((pw16_data[0] & 0x80) == 0) - { - /* Only one payload in this packet */ - singlePayload = 1; - /* set the blocklength to -4 to deduce the non-existent 4-byte RED header */ - i_blockLength = -4; - RTPheader[0]->payloadType = (((uint16_t) pw16_data[0]) & 0x7F); - } - else - { - /* Discard all but the two last payloads. */ - while (((pw16_data[2] & 0x80) != 0) && (pw16_data < ((RTPheader[0]->payload) - + ((RTPheader[0]->payloadLen + 1) >> 1)))) - { - i_discardedBlockLength += (4 + ((((uint16_t) pw16_data[1]) & 0x3) << 8) - + ((((uint16_t) pw16_data[1]) & 0xFF00) >> 8)); - pw16_data += 2; - } - if (pw16_data >= (RTPheader[0]->payload + ((RTPheader[0]->payloadLen + 1) >> 1))) - { - return RED_SPLIT_ERROR2; /* Error, we are outside the packet */; - } - singlePayload = 0; /* the packet contains more than one payload */ - uw16_secondPayload = (((uint16_t) pw16_data[0]) & 0x7F); - RTPheader[0]->payloadType = (((uint16_t) pw16_data[2]) & 0x7F); - uw16_offsetTimeStamp = ((((uint16_t) pw16_data[0]) & 0xFF00) >> 2) - + ((((uint16_t) pw16_data[1]) & 0xFC) >> 2); - i_blockLength = ((((uint16_t) pw16_data[1]) & 0x3) << 8) - + ((((uint16_t) pw16_data[1]) & 0xFF00) >> 8); - } -#endif - - if (i_MaximumPayloads < 2 || singlePayload == 1) - { - /* Reject the redundancy; or no redundant payload present. */ - for (i_k = 1; i_k < i_MaximumPayloads; i_k++) - { - RTPheader[i_k]->payloadType = -1; - RTPheader[i_k]->payloadLen = 0; - } - - /* update the pointer for the main data */ - pw16_data = &pw16_data[(5 + i_blockLength) >> 1]; - RTPheader[0]->starts_byte1 = (5 + i_blockLength) & 0x1; - RTPheader[0]->payloadLen = RTPheader[0]->payloadLen - (i_blockLength + 5) - - i_discardedBlockLength; - RTPheader[0]->payload = pw16_data; - - *i_No_Of_Payloads = 1; - - } - else - { - /* Redundancy accepted, put the redundancy in second RTPheader. */ - RTPheader[1]->payloadType = uw16_secondPayload; - RTPheader[1]->payload = &pw16_data[5 >> 1]; - RTPheader[1]->starts_byte1 = 5 & 0x1; - RTPheader[1]->seqNumber = RTPheader[0]->seqNumber; - RTPheader[1]->timeStamp = RTPheader[0]->timeStamp - uw16_offsetTimeStamp; - RTPheader[1]->ssrc = RTPheader[0]->ssrc; - RTPheader[1]->payloadLen = i_blockLength; - - /* Modify first RTP packet, so that it contains the main data. */ - RTPheader[0]->payload = &pw16_data[(5 + i_blockLength) >> 1]; - RTPheader[0]->starts_byte1 = (5 + i_blockLength) & 0x1; - RTPheader[0]->payloadLen = RTPheader[0]->payloadLen - (i_blockLength + 5) - - i_discardedBlockLength; - - /* Clear the following payloads. */ - for (i_k = 2; i_k < i_MaximumPayloads; i_k++) - { - RTPheader[i_k]->payloadType = -1; - RTPheader[i_k]->payloadLen = 0; - } - - *i_No_Of_Payloads = 2; - } - return 0; -} - -#endif - diff --git a/webrtc/modules/audio_coding/neteq/rtp.h b/webrtc/modules/audio_coding/neteq/rtp.h deleted file mode 100644 index 4642eaef7..000000000 --- a/webrtc/modules/audio_coding/neteq/rtp.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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. - */ - -/* - * RTP data struct and related functions. - */ - -#ifndef RTP_H -#define RTP_H - -#include "typedefs.h" - -#include "codec_db.h" - -typedef struct -{ - uint16_t seqNumber; - uint32_t timeStamp; - uint32_t ssrc; - int payloadType; - const int16_t *payload; - int16_t payloadLen; - int16_t starts_byte1; - int16_t rcuPlCntr; -} RTPPacket_t; - -/**************************************************************************** - * WebRtcNetEQ_RTPPayloadInfo(...) - * - * Converts a datagram into an RTP header struct. - * - * Input: - * - Datagram : UDP datagram from the network - * - DatagramLen : Length in bytes of the datagram - * - * Output: - * - RTPheader : Structure with the datagram info - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RTPPayloadInfo(int16_t* pw16_Datagram, int i_DatagramLen, - RTPPacket_t* RTPheader); - -/**************************************************************************** - * WebRtcNetEQ_RedundancySplit(...) - * - * Splits a Redundancy RTP struct into two RTP structs. User has to check - * that it's really the redundancy payload. No such check is done inside this - * function. - * - * Input: - * - RTPheader : First header holds the whole RTP packet (with the redundancy payload) - * - MaximumPayloads: - * The maximum number of RTP payloads that should be - * extracted (1+maximum_no_of_Redundancies). - * - * Output: - * - RTPheader : First header holds the main RTP data, while 2..N - * holds the redundancy data. - * - No_Of - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RedundancySplit(RTPPacket_t* RTPheader[], int i_MaximumPayloads, - int *i_No_Of_Payloads); - -#endif diff --git a/webrtc/modules/audio_coding/neteq/set_fs.c b/webrtc/modules/audio_coding/neteq/set_fs.c deleted file mode 100644 index ac974548e..000000000 --- a/webrtc/modules/audio_coding/neteq/set_fs.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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. - */ - -/* - * Function were the sample rate is set. - */ - -#include "mcu.h" - -#include "dtmf_buffer.h" -#include "neteq_error_codes.h" - -int WebRtcNetEQ_McuSetFs(MCUInst_t *inst, uint16_t fs) -{ - int16_t ok = 0; - - switch (fs) - { - case 8000: - { -#ifdef NETEQ_ATEVENT_DECODE - ok = WebRtcNetEQ_DtmfDecoderInit(&inst->DTMF_inst, 8000, 560); -#endif - inst->timestampsPerCall = inst->millisecondsPerCall * 8; - break; - } - -#ifdef NETEQ_WIDEBAND - case 16000: - { -#ifdef NETEQ_ATEVENT_DECODE - ok = WebRtcNetEQ_DtmfDecoderInit(&inst->DTMF_inst, 16000, 1120); -#endif - inst->timestampsPerCall = inst->millisecondsPerCall * 16; - break; - } -#endif - -#ifdef NETEQ_32KHZ_WIDEBAND - case 32000: - { -#ifdef NETEQ_ATEVENT_DECODE - ok = WebRtcNetEQ_DtmfDecoderInit(&inst->DTMF_inst, 32000, 2240); -#endif - inst->timestampsPerCall = inst->millisecondsPerCall * 32; - break; - } -#endif - -#ifdef NETEQ_48KHZ_WIDEBAND - case 48000: - { -#ifdef NETEQ_ATEVENT_DECODE - ok = WebRtcNetEQ_DtmfDecoderInit(&inst->DTMF_inst, 48000, 3360); -#endif - inst->timestampsPerCall = inst->millisecondsPerCall * 48; - break; - } -#endif - - default: - { - /* Not supported yet */ - return CODEC_DB_UNSUPPORTED_FS; - } - } /* end switch */ - - inst->fs = fs; - - return ok; -} diff --git a/webrtc/modules/audio_coding/neteq/signal_mcu.c b/webrtc/modules/audio_coding/neteq/signal_mcu.c deleted file mode 100644 index b795ec30e..000000000 --- a/webrtc/modules/audio_coding/neteq/signal_mcu.c +++ /dev/null @@ -1,820 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* - * Signal the MCU that data is available and ask for a RecOut decision. - */ - -#include "mcu.h" - -#include - -#include "signal_processing_library.h" - -#include "automode.h" -#include "dtmf_buffer.h" -#include "mcu_dsp_common.h" -#include "neteq_error_codes.h" - -#ifdef NETEQ_DELAY_LOGGING -#include "delay_logging.h" -#include - -extern FILE *delay_fid2; /* file pointer to delay log file */ -#endif - - -/* - * Update the frame size, if we can. - */ -static int WebRtcNetEQ_UpdatePackSizeSamples(MCUInst_t* inst, int buffer_pos, - int payload_type, - int pack_size_samples) { - if (buffer_pos >= 0) { - int codec_pos; - codec_pos = WebRtcNetEQ_DbGetCodec(&inst->codec_DB_inst, payload_type); - if (codec_pos >= 0) { - codec_pos = inst->codec_DB_inst.position[codec_pos]; - if (codec_pos >= 0) { - int temp_packet_size_samples = WebRtcNetEQ_PacketBufferGetPacketSize( - &inst->PacketBuffer_inst, buffer_pos, &inst->codec_DB_inst, - codec_pos, pack_size_samples, inst->av_sync); - if (temp_packet_size_samples > 0) - return temp_packet_size_samples; - return pack_size_samples; - } - } - } - return pack_size_samples; -} - -/* - * Signals the MCU that DSP status data is available. - */ -int WebRtcNetEQ_SignalMcu(MCUInst_t *inst) -{ - - int i_bufferpos, i_res; - uint16_t uw16_instr; - DSP2MCU_info_t dspInfo; - int16_t *blockPtr, blockLen; - uint32_t uw32_availableTS; - RTPPacket_t temp_pkt; - int32_t w32_bufsize, w32_tmp; - int16_t payloadType = -1; - int16_t wantedNoOfTimeStamps; - int32_t totalTS; - int16_t oldPT, latePacketExist = 0; - uint32_t oldTS, prevTS, uw32_tmp; - uint16_t prevSeqNo; - int16_t nextSeqNoAvail; - int16_t fs_mult, w16_tmp; - int16_t lastModeBGNonly = 0; -#ifdef NETEQ_DELAY_LOGGING - int temp_var; -#endif - int playDtmf = 0; - - fs_mult = WebRtcSpl_DivW32W16ResW16(inst->fs, 8000); - - /* Increment counter since last statistics report */ - inst->lastReportTS += inst->timestampsPerCall; - - /* Increment waiting time for all packets. */ - WebRtcNetEQ_IncrementWaitingTimes(&inst->PacketBuffer_inst); - - /* Read info from DSP so we now current status */ - - WEBRTC_SPL_MEMCPY_W8(&dspInfo,inst->pw16_readAddress,sizeof(DSP2MCU_info_t)); - - /* Set blockPtr to first payload block */ - blockPtr = &inst->pw16_writeAddress[3]; - - /* Clear instruction word and number of lost samples (2*int16_t) */ - inst->pw16_writeAddress[0] = 0; - inst->pw16_writeAddress[1] = 0; - inst->pw16_writeAddress[2] = 0; - - if ((dspInfo.lastMode & MODE_AWAITING_CODEC_PTR) != 0) - { - /* - * Make sure state is adjusted so that a codec update is - * performed when first packet arrives. - */ - if (inst->new_codec != 1) - { - inst->current_Codec = -1; - } - dspInfo.lastMode = (dspInfo.lastMode ^ MODE_AWAITING_CODEC_PTR); - } - -#ifdef NETEQ_STEREO - if ((dspInfo.lastMode & MODE_MASTER_DTMF_SIGNAL) != 0) - { - playDtmf = 1; /* force DTMF decision */ - dspInfo.lastMode = (dspInfo.lastMode ^ MODE_MASTER_DTMF_SIGNAL); - } - - if ((dspInfo.lastMode & MODE_USING_STEREO) != 0) - { - if (inst->usingStereo == 0) - { - /* stereo mode changed; reset automode instance to re-synchronize statistics */ - WebRtcNetEQ_ResetAutomode(&(inst->BufferStat_inst.Automode_inst), - inst->PacketBuffer_inst.maxInsertPositions); - } - inst->usingStereo = 1; - dspInfo.lastMode = (dspInfo.lastMode ^ MODE_USING_STEREO); - } - else - { - inst->usingStereo = 0; - } -#endif - - /* detect if BGN_ONLY flag is set in lastMode */ - if ((dspInfo.lastMode & MODE_BGN_ONLY) != 0) - { - lastModeBGNonly = 1; /* remember flag */ - dspInfo.lastMode ^= MODE_BGN_ONLY; /* clear the flag */ - } - - if ((dspInfo.lastMode == MODE_RFC3389CNG) || (dspInfo.lastMode == MODE_CODEC_INTERNAL_CNG) - || (dspInfo.lastMode == MODE_EXPAND)) - { - /* - * If last mode was CNG (or Expand, since this could be covering up for a lost CNG - * packet), increase the CNGplayedTS counter. - */ - inst->BufferStat_inst.uw32_CNGplayedTS += inst->timestampsPerCall; - - if (dspInfo.lastMode == MODE_RFC3389CNG) - { - /* remember that RFC3389CNG is on (needed if CNG is interrupted by DTMF) */ - inst->BufferStat_inst.w16_cngOn = CNG_RFC3389_ON; - } - else if (dspInfo.lastMode == MODE_CODEC_INTERNAL_CNG) - { - /* remember that internal CNG is on (needed if CNG is interrupted by DTMF) */ - inst->BufferStat_inst.w16_cngOn = CNG_INTERNAL_ON; - } - - } - - /* Update packet size from previously decoded packet */ - if (dspInfo.frameLen > 0) - { - inst->PacketBuffer_inst.packSizeSamples = dspInfo.frameLen; - } - - /* Look for late packet (unless codec has changed) */ - if (inst->new_codec != 1) - { - if (WebRtcNetEQ_DbIsMDCodec((enum WebRtcNetEQDecoder) inst->current_Codec)) - { - WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst, - inst->timeStamp, &uw32_availableTS, &i_bufferpos, 1, &payloadType); - if ((inst->new_codec != 1) && (inst->timeStamp == uw32_availableTS) - && (inst->timeStamp < dspInfo.playedOutTS) && (i_bufferpos != -1) - && (WebRtcNetEQ_DbGetPayload(&(inst->codec_DB_inst), - (enum WebRtcNetEQDecoder) inst->current_Codec) == payloadType)) - { - int waitingTime; - temp_pkt.payload = blockPtr + 1; - i_res = WebRtcNetEQ_PacketBufferExtract(&inst->PacketBuffer_inst, &temp_pkt, - i_bufferpos, &waitingTime); - if (i_res < 0) - { /* error returned */ - return i_res; - } - WebRtcNetEQ_StoreWaitingTime(inst, waitingTime); - *blockPtr = temp_pkt.payloadLen; - /* set the flag if this is a redundant payload */ - if (temp_pkt.rcuPlCntr > 0) - { - *blockPtr = (*blockPtr) | (DSP_CODEC_RED_FLAG); - } - blockPtr += ((temp_pkt.payloadLen + 1) >> 1) + 1; - - /* - * Close the data with a zero size block, in case we will not write any - * more data. - */ - *blockPtr = 0; - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0xf0ff) - | DSP_CODEC_ADD_LATE_PKT; - latePacketExist = 1; - } - } - } - - i_res = WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst, - dspInfo.playedOutTS, &uw32_availableTS, &i_bufferpos, (inst->new_codec == 0), - &payloadType); - if (i_res < 0) - { /* error returned */ - return i_res; - } - - if (inst->BufferStat_inst.w16_cngOn == CNG_RFC3389_ON) - { - /* - * Because of timestamp peculiarities, we have to "manually" disallow using a CNG - * packet with the same timestamp as the one that was last played. This can happen - * when using redundancy and will cause the timing to shift. - */ - while (i_bufferpos != -1 && WebRtcNetEQ_DbIsCNGPayload(&inst->codec_DB_inst, - payloadType) && dspInfo.playedOutTS >= uw32_availableTS) - { - - /* Don't use this packet, discard it */ - inst->PacketBuffer_inst.payloadType[i_bufferpos] = -1; - inst->PacketBuffer_inst.payloadLengthBytes[i_bufferpos] = 0; - inst->PacketBuffer_inst.numPacketsInBuffer--; - - /* Check buffer again */ - WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst, - dspInfo.playedOutTS, &uw32_availableTS, &i_bufferpos, (inst->new_codec == 0), - &payloadType); - } - } - - /* Check packet buffer */ - w32_bufsize = WebRtcNetEQ_PacketBufferGetSize(&inst->PacketBuffer_inst, - &inst->codec_DB_inst, inst->av_sync); - - if (dspInfo.lastMode == MODE_SUCCESS_ACCELERATE || dspInfo.lastMode - == MODE_LOWEN_ACCELERATE || dspInfo.lastMode == MODE_SUCCESS_PREEMPTIVE - || dspInfo.lastMode == MODE_LOWEN_PREEMPTIVE) - { - /* Subtract (dspInfo.samplesLeft + inst->timestampsPerCall) from sampleMemory */ - inst->BufferStat_inst.Automode_inst.sampleMemory -= dspInfo.samplesLeft - + inst->timestampsPerCall; - } - - /* calculate total current buffer size (in ms*8), including sync buffer */ - w32_bufsize = WebRtcSpl_DivW32W16((w32_bufsize + dspInfo.samplesLeft), fs_mult); - -#ifdef NETEQ_ATEVENT_DECODE - /* DTMF data will affect the decision */ - if (WebRtcNetEQ_DtmfDecode(&inst->DTMF_inst, blockPtr + 1, blockPtr + 2, - dspInfo.playedOutTS + inst->BufferStat_inst.uw32_CNGplayedTS) > 0) - { - playDtmf = 1; - - /* Flag DTMF payload */ - inst->pw16_writeAddress[0] = inst->pw16_writeAddress[0] | DSP_DTMF_PAYLOAD; - - /* Block Length in bytes */ - blockPtr[0] = 4; - /* Advance to next payload position */ - blockPtr += 3; - } -#endif - - /* Update the frame size, if we can. */ - inst->PacketBuffer_inst.packSizeSamples = - WebRtcNetEQ_UpdatePackSizeSamples(inst, i_bufferpos, payloadType, - inst->PacketBuffer_inst.packSizeSamples); - /* Update statistics and make decision */ - uw16_instr = WebRtcNetEQ_BufstatsDecision(&inst->BufferStat_inst, - inst->PacketBuffer_inst.packSizeSamples, w32_bufsize, dspInfo.playedOutTS, - uw32_availableTS, i_bufferpos == -1, - WebRtcNetEQ_DbIsCNGPayload(&inst->codec_DB_inst, payloadType), dspInfo.lastMode, - inst->NetEqPlayoutMode, inst->timestampsPerCall, inst->NoOfExpandCalls, fs_mult, - lastModeBGNonly, playDtmf); - - /* Check if time to reset loss counter */ - if (inst->lastReportTS > WEBRTC_SPL_UMUL(inst->fs, MAX_LOSS_REPORT_PERIOD)) - { - /* reset loss counter */ - WebRtcNetEQ_ResetMcuInCallStats(inst); - } - - /* Check sync buffer size */ - if ((dspInfo.samplesLeft >= inst->timestampsPerCall) && (uw16_instr - != BUFSTATS_DO_ACCELERATE) && (uw16_instr != BUFSTATS_DO_MERGE) && (uw16_instr - != BUFSTATS_DO_PREEMPTIVE_EXPAND)) - { - *blockPtr = 0; - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) | DSP_INSTR_NORMAL; - return 0; - } - - if (uw16_instr == BUFSTATS_DO_EXPAND) - { - inst->NoOfExpandCalls++; - } - else - { - /* reset counter */ - inst->NoOfExpandCalls = 0; - } - - /* New codec or big change in packet number? */ - if ((inst->new_codec) || (uw16_instr == BUFSTAT_REINIT)) - { - CodecFuncInst_t cinst; - - /* Clear other instructions */ - blockPtr = &inst->pw16_writeAddress[3]; - /* Clear instruction word */ - inst->pw16_writeAddress[0] = 0; - - inst->timeStamp = uw32_availableTS; - dspInfo.playedOutTS = uw32_availableTS; - if (inst->current_Codec != -1) - { - i_res = WebRtcNetEQ_DbGetPtrs(&inst->codec_DB_inst, - (enum WebRtcNetEQDecoder) inst->current_Codec, &cinst); - if (i_res < 0) - { /* error returned */ - return i_res; - } - } - else - { - /* The main codec has not been initialized yet (first packets are DTMF or CNG). */ - if (WebRtcNetEQ_DbIsCNGPayload(&inst->codec_DB_inst, payloadType)) - { - /* The currently extracted packet is CNG; get CNG fs */ - uint16_t tempFs; - - tempFs = WebRtcNetEQ_DbGetSampleRate(&inst->codec_DB_inst, payloadType); - /* TODO(tlegrand): Remove this limitation once ACM has full - * 48 kHz support. */ - if (tempFs > 32000) - { - inst->fs = 32000; - } - else if (tempFs > 0) - { - inst->fs = tempFs; - } - } - WebRtcSpl_MemSetW16((int16_t*) &cinst, 0, - sizeof(CodecFuncInst_t) / sizeof(int16_t)); - cinst.codec_fs = inst->fs; - } - cinst.timeStamp = inst->timeStamp; - blockLen = (sizeof(CodecFuncInst_t)) >> (sizeof(int16_t) - 1); /* in Word16 */ - *blockPtr = blockLen * 2; - blockPtr++; - WEBRTC_SPL_MEMCPY_W8(blockPtr,&cinst,sizeof(CodecFuncInst_t)); - blockPtr += blockLen; - inst->new_codec = 0; - - /* Reinitialize the MCU fs */ - i_res = WebRtcNetEQ_McuSetFs(inst, cinst.codec_fs); - if (i_res < 0) - { /* error returned */ - return i_res; - } - - /* Set the packet size by guessing */ - inst->PacketBuffer_inst.packSizeSamples = - WebRtcNetEQ_UpdatePackSizeSamples(inst, i_bufferpos, payloadType, - inst->timestampsPerCall * 3); - - WebRtcNetEQ_ResetAutomode(&(inst->BufferStat_inst.Automode_inst), - inst->PacketBuffer_inst.maxInsertPositions); - -#ifdef NETEQ_CNG_CODEC - /* Also insert CNG state as this might be needed by DSP */ - i_res = WebRtcNetEQ_DbGetPtrs(&inst->codec_DB_inst, kDecoderCNG, &cinst); - if ((i_res < 0) && (i_res != CODEC_DB_NOT_EXIST1)) - { - /* other error returned */ - /* (CODEC_DB_NOT_EXIST1 simply indicates that CNG is not used */ - return i_res; - } - else - { - /* CNG exists */ - blockLen = (sizeof(cinst.codec_state)) >> (sizeof(int16_t) - 1); - *blockPtr = blockLen * 2; - blockPtr++; - WEBRTC_SPL_MEMCPY_W8(blockPtr,&cinst.codec_state,sizeof(cinst.codec_state)); - blockPtr += blockLen; - } -#endif - - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0xf0ff) - | DSP_CODEC_NEW_CODEC; - - if (uw16_instr == BUFSTATS_DO_RFC3389CNG_NOPACKET) - { - /* - * Change decision to CNG packet, since we do have a CNG packet, but it was - * considered too early to use. Now, use it anyway. - */ - uw16_instr = BUFSTATS_DO_RFC3389CNG_PACKET; - } - else if (uw16_instr != BUFSTATS_DO_RFC3389CNG_PACKET) - { - uw16_instr = BUFSTATS_DO_NORMAL; - } - - /* reset loss counter */ - WebRtcNetEQ_ResetMcuInCallStats(inst); - } - - /* Should we just reset the decoder? */ - if (uw16_instr == BUFSTAT_REINIT_DECODER) - { - /* Change decision to normal and flag decoder reset */ - uw16_instr = BUFSTATS_DO_NORMAL; - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0xf0ff) | DSP_CODEC_RESET; - } - - /* Expand requires no new packet */ - if (uw16_instr == BUFSTATS_DO_EXPAND) - { - - inst->timeStamp = dspInfo.playedOutTS; - - /* Have we got one descriptor left? */ - if (WebRtcNetEQ_DbIsMDCodec((enum WebRtcNetEQDecoder) inst->current_Codec) - && (dspInfo.MD || latePacketExist)) - { - - if (dspInfo.lastMode != MODE_ONE_DESCRIPTOR) - { - /* this is the first "consecutive" one-descriptor decoding; reset counter */ - inst->one_desc = 0; - } - if (inst->one_desc < MAX_ONE_DESC) - { - /* use that one descriptor */ - inst->one_desc++; /* increase counter */ - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_NORMAL_ONE_DESC; - - /* decrease counter since we did no Expand */ - inst->NoOfExpandCalls = WEBRTC_SPL_MAX(inst->NoOfExpandCalls - 1, 0); - return 0; - } - else - { - /* too many consecutive one-descriptor decodings; do expand instead */ - inst->one_desc = 0; /* reset counter */ - } - - } - - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) | DSP_INSTR_EXPAND; - return 0; - } - - /* Merge is not needed if we still have a descriptor */ - if ((uw16_instr == BUFSTATS_DO_MERGE) && (dspInfo.MD != 0)) - { - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_NORMAL_ONE_DESC; - *blockPtr = 0; - return 0; - } - - /* Do CNG without trying to extract any packets from buffer */ - if (uw16_instr == BUFSTATS_DO_RFC3389CNG_NOPACKET) - { - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DO_RFC3389CNG; - *blockPtr = 0; - return 0; - } - - /* Do built-in CNG without extracting any new packets from buffer */ - if (uw16_instr == BUFSTATS_DO_INTERNAL_CNG_NOPACKET) - { - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DO_CODEC_INTERNAL_CNG; - *blockPtr = 0; - return 0; - } - - /* Do DTMF without extracting any new packets from buffer */ - if (uw16_instr == BUFSTATS_DO_DTMF_ONLY) - { - uint32_t timeStampJump = 0; - - /* Update timestamp */ - if ((inst->BufferStat_inst.uw32_CNGplayedTS > 0) && (dspInfo.lastMode != MODE_DTMF)) - { - /* Jump in timestamps if needed */ - timeStampJump = inst->BufferStat_inst.uw32_CNGplayedTS; - inst->pw16_writeAddress[1] = (uint16_t) (timeStampJump >> 16); - inst->pw16_writeAddress[2] = (uint16_t) (timeStampJump & 0xFFFF); - } - - inst->timeStamp = dspInfo.playedOutTS + timeStampJump; - - inst->BufferStat_inst.uw32_CNGplayedTS = 0; - inst->NoOfExpandCalls = 0; - - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DTMF_GENERATE; - *blockPtr = 0; - return 0; - } - - if (uw16_instr == BUFSTATS_DO_ACCELERATE) - { - /* In order to do a Accelerate we need at least 30 ms of data */ - if (dspInfo.samplesLeft >= (3 * 80 * fs_mult)) - { - /* Already have enough data, so we do not need to extract any more */ - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_ACCELERATE; - *blockPtr = 0; - inst->BufferStat_inst.Automode_inst.sampleMemory - = (int32_t) dspInfo.samplesLeft; - inst->BufferStat_inst.Automode_inst.prevTimeScale = 1; - return 0; - } - else if ((dspInfo.samplesLeft >= (1 * 80 * fs_mult)) - && (inst->PacketBuffer_inst.packSizeSamples >= (240 * fs_mult))) - { - /* Avoid decoding more data as it might overflow playout buffer */ - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_NORMAL; - *blockPtr = 0; - return 0; - } - else if ((dspInfo.samplesLeft < (1 * 80 * fs_mult)) - && (inst->PacketBuffer_inst.packSizeSamples >= (240 * fs_mult))) - { - /* For >= 30ms allow Accelerate with a decoding to avoid overflow in playout buffer */ - wantedNoOfTimeStamps = inst->timestampsPerCall; - } - else if (dspInfo.samplesLeft >= (2 * 80 * fs_mult)) - { - /* We need to decode another 10 ms in order to do an Accelerate */ - wantedNoOfTimeStamps = inst->timestampsPerCall; - } - else - { - /* - * Build up decoded data by decoding at least 20 ms of data. - * Do not perform Accelerate yet, but wait until we only need to do one decoding. - */ - wantedNoOfTimeStamps = 2 * inst->timestampsPerCall; - uw16_instr = BUFSTATS_DO_NORMAL; - } - } - else if (uw16_instr == BUFSTATS_DO_PREEMPTIVE_EXPAND) - { - /* In order to do a Preemptive Expand we need at least 30 ms of data */ - if (dspInfo.samplesLeft >= (3 * 80 * fs_mult)) - { - /* Already have enough data, so we do not need to extract any more */ - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_PREEMPTIVE_EXPAND; - *blockPtr = 0; - inst->BufferStat_inst.Automode_inst.sampleMemory - = (int32_t) dspInfo.samplesLeft; - inst->BufferStat_inst.Automode_inst.prevTimeScale = 1; - return 0; - } - else if ((dspInfo.samplesLeft >= (1 * 80 * fs_mult)) - && (inst->PacketBuffer_inst.packSizeSamples >= (240 * fs_mult))) - { - /* - * Avoid decoding more data as it might overflow playout buffer; - * still try Preemptive Expand though. - */ - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_PREEMPTIVE_EXPAND; - *blockPtr = 0; - inst->BufferStat_inst.Automode_inst.sampleMemory - = (int32_t) dspInfo.samplesLeft; - inst->BufferStat_inst.Automode_inst.prevTimeScale = 1; - return 0; - } - else if ((dspInfo.samplesLeft < (1 * 80 * fs_mult)) - && (inst->PacketBuffer_inst.packSizeSamples >= (240 * fs_mult))) - { - /* - * For >= 30ms allow Preemptive Expand with a decoding to avoid overflow in - * playout buffer - */ - wantedNoOfTimeStamps = inst->timestampsPerCall; - } - else if (dspInfo.samplesLeft >= (2 * 80 * fs_mult)) - { - /* We need to decode another 10 ms in order to do an Preemptive Expand */ - wantedNoOfTimeStamps = inst->timestampsPerCall; - } - else - { - /* - * Build up decoded data by decoding at least 20 ms of data, - * Still try to perform Preemptive Expand. - */ - wantedNoOfTimeStamps = 2 * inst->timestampsPerCall; - } - } - else - { - wantedNoOfTimeStamps = inst->timestampsPerCall; - } - - /* Otherwise get data from buffer, try to get at least 10ms */ - totalTS = 0; - oldTS = uw32_availableTS; - if ((i_bufferpos > -1) && (uw16_instr != BUFSTATS_DO_ALTERNATIVE_PLC) && (uw16_instr - != BUFSTATS_DO_ALTERNATIVE_PLC_INC_TS) && (uw16_instr != BUFSTATS_DO_AUDIO_REPETITION) - && (uw16_instr != BUFSTATS_DO_AUDIO_REPETITION_INC_TS)) - { - uw32_tmp = (uw32_availableTS - dspInfo.playedOutTS); - inst->pw16_writeAddress[1] = (uint16_t) (uw32_tmp >> 16); - inst->pw16_writeAddress[2] = (uint16_t) (uw32_tmp & 0xFFFF); - if (inst->BufferStat_inst.w16_cngOn == CNG_OFF) - { - /* - * Adjustment of TS only corresponds to an actual packet loss - * if comfort noise is not played. If comfort noise was just played, - * this adjustment of TS is only done to get back in sync with the - * stream TS; no loss to report. - */ - inst->lostTS += uw32_tmp; - } - - if (uw16_instr != BUFSTATS_DO_RFC3389CNG_PACKET) - { - /* We are about to decode and use a non-CNG packet => CNG period is ended */ - inst->BufferStat_inst.w16_cngOn = CNG_OFF; - } - - /* - * Reset CNG timestamp as a new packet will be delivered. - * (Also if CNG packet, since playedOutTS is updated.) - */ - inst->BufferStat_inst.uw32_CNGplayedTS = 0; - - prevSeqNo = inst->PacketBuffer_inst.seqNumber[i_bufferpos]; - prevTS = inst->PacketBuffer_inst.timeStamp[i_bufferpos]; - oldPT = inst->PacketBuffer_inst.payloadType[i_bufferpos]; - - /* These values are used by NACK module to estimate time-to-play of - * a missing packet. Occasionally, NetEq might decide to decode more - * than one packet. Therefore, these values store sequence number and - * timestamp of the first packet pulled from the packet buffer. In - * such cases, these values do not exactly represent the sequence number - * or timestamp associated with a 10ms audio pulled from NetEq. NACK - * module is designed to compensate for this. - */ - inst->decoded_packet_sequence_number = prevSeqNo; - inst->decoded_packet_timestamp = prevTS; - - /* clear flag bits */ - inst->pw16_writeAddress[0] = inst->pw16_writeAddress[0] & 0xFF3F; - do - { - int waitingTime; - inst->timeStamp = uw32_availableTS; - /* Write directly to shared memory */ - temp_pkt.payload = blockPtr + 1; - i_res = WebRtcNetEQ_PacketBufferExtract(&inst->PacketBuffer_inst, &temp_pkt, - i_bufferpos, &waitingTime); - - if (i_res < 0) - { - /* error returned */ - return i_res; - } - WebRtcNetEQ_StoreWaitingTime(inst, waitingTime); - -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_DECODE; - if ((fwrite(&temp_var, sizeof(int), - 1, delay_fid2) != 1) || - (fwrite(&temp_pkt.timeStamp, sizeof(uint32_t), - 1, delay_fid2) != 1) || - (fwrite(&dspInfo.samplesLeft, sizeof(uint16_t), - 1, delay_fid2) != 1)) { - return -1; - } -#endif - - *blockPtr = temp_pkt.payloadLen; - /* set the flag if this is a redundant payload */ - if (temp_pkt.rcuPlCntr > 0) - { - *blockPtr = (*blockPtr) | (DSP_CODEC_RED_FLAG); - } - blockPtr += ((temp_pkt.payloadLen + 1) >> 1) + 1; - - if (i_bufferpos > -1) - { - /* - * Store number of TS extracted (last extracted is assumed to be of - * packSizeSamples). - */ - totalTS = uw32_availableTS - oldTS + inst->PacketBuffer_inst.packSizeSamples; - } - /* Check what next packet is available */ - WebRtcNetEQ_PacketBufferFindLowestTimestamp(&inst->PacketBuffer_inst, - inst->timeStamp, &uw32_availableTS, &i_bufferpos, 0, &payloadType); - - nextSeqNoAvail = 0; - if ((i_bufferpos > -1) && (oldPT - == inst->PacketBuffer_inst.payloadType[i_bufferpos])) - { - w16_tmp = inst->PacketBuffer_inst.seqNumber[i_bufferpos] - prevSeqNo; - w32_tmp = inst->PacketBuffer_inst.timeStamp[i_bufferpos] - prevTS; - if ((w16_tmp == 1) || /* Next packet */ - ((w16_tmp == 0) && (w32_tmp == inst->PacketBuffer_inst.packSizeSamples))) - { /* or packet split into frames */ - nextSeqNoAvail = 1; - } - prevSeqNo = inst->PacketBuffer_inst.seqNumber[i_bufferpos]; - } - /* Update the frame size, if we can. */ - inst->PacketBuffer_inst.packSizeSamples = - WebRtcNetEQ_UpdatePackSizeSamples(inst, i_bufferpos, - payloadType, inst->PacketBuffer_inst.packSizeSamples); - } - while ((totalTS < wantedNoOfTimeStamps) && (nextSeqNoAvail == 1)); - } - - if ((uw16_instr == BUFSTATS_DO_ACCELERATE) - || (uw16_instr == BUFSTATS_DO_PREEMPTIVE_EXPAND)) - { - /* Check that we have enough data (30ms) to do the Accelearate */ - if ((totalTS + dspInfo.samplesLeft) < WEBRTC_SPL_MUL(3,inst->timestampsPerCall) - && (uw16_instr == BUFSTATS_DO_ACCELERATE)) - { - /* Not enough, do normal operation instead */ - uw16_instr = BUFSTATS_DO_NORMAL; - } - else - { - inst->BufferStat_inst.Automode_inst.sampleMemory - = (int32_t) dspInfo.samplesLeft + totalTS; - inst->BufferStat_inst.Automode_inst.prevTimeScale = 1; - } - } - - /* Close the data with a zero size block */ - *blockPtr = 0; - - /* Write data to DSP */ - switch (uw16_instr) - { - case BUFSTATS_DO_NORMAL: - /* Normal with decoding included */ - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_NORMAL; - break; - case BUFSTATS_DO_ACCELERATE: - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_ACCELERATE; - break; - case BUFSTATS_DO_MERGE: - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_MERGE; - break; - case BUFSTATS_DO_RFC3389CNG_PACKET: - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DO_RFC3389CNG; - break; - case BUFSTATS_DO_ALTERNATIVE_PLC: - inst->pw16_writeAddress[1] = 0; - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DO_ALTERNATIVE_PLC; - break; - case BUFSTATS_DO_ALTERNATIVE_PLC_INC_TS: - inst->pw16_writeAddress[1] = 0; - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DO_ALTERNATIVE_PLC_INC_TS; - break; - case BUFSTATS_DO_AUDIO_REPETITION: - inst->pw16_writeAddress[1] = 0; - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DO_AUDIO_REPETITION; - break; - case BUFSTATS_DO_AUDIO_REPETITION_INC_TS: - inst->pw16_writeAddress[1] = 0; - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_DO_AUDIO_REPETITION_INC_TS; - break; - case BUFSTATS_DO_PREEMPTIVE_EXPAND: - inst->pw16_writeAddress[0] = (inst->pw16_writeAddress[0] & 0x0fff) - | DSP_INSTR_PREEMPTIVE_EXPAND; - break; - default: - return UNKNOWN_BUFSTAT_DECISION; - } - - inst->timeStamp = dspInfo.playedOutTS; - return 0; - -} diff --git a/webrtc/modules/audio_coding/neteq/split_and_insert.c b/webrtc/modules/audio_coding/neteq/split_and_insert.c deleted file mode 100644 index d7f17fdc8..000000000 --- a/webrtc/modules/audio_coding/neteq/split_and_insert.c +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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. - */ - -/* - * Split an RTP payload (if possible and suitable) and insert into packet buffer. - */ - -#include "mcu.h" - -#include - -#include "mcu_dsp_common.h" -#include "neteq_error_codes.h" -#include "signal_processing_library.h" - -int WebRtcNetEQ_SplitAndInsertPayload(RTPPacket_t* packet, - PacketBuf_t* Buffer_inst, - SplitInfo_t* split_inst, - int16_t* flushed, - int av_sync) -{ - - int i_ok; - int len; - int i; - RTPPacket_t temp_packet; - int16_t localFlushed = 0; - const int16_t *pw16_startPayload; - const int is_sync_rtp = av_sync && - WebRtcNetEQ_IsSyncPayload(packet->payload, packet->payloadLen); - *flushed = 0; - - len = packet->payloadLen; - - /* Copy to temp packet that can be modified. */ - - WEBRTC_SPL_MEMCPY_W8(&temp_packet,packet,sizeof(RTPPacket_t)); - - if (split_inst->deltaBytes == NO_SPLIT || - is_sync_rtp) /* Don't split sync RTPs just insert. */ - { - /* Not splittable codec */ - i_ok = WebRtcNetEQ_PacketBufferInsert(Buffer_inst, packet, - &localFlushed, av_sync); - *flushed |= localFlushed; - if (i_ok < 0) - { - return PBUFFER_INSERT_ERROR5; - } - } - else if (split_inst->deltaBytes < -10) - { - /* G711, PCM16B or G722, use "soft splitting" */ - int split_size = packet->payloadLen; - int mult = WEBRTC_SPL_ABS_W32(split_inst->deltaBytes) - 10; - - /* Find "chunk size" >= 20 ms and < 40 ms - * split_inst->deltaTime in this case contains the number of bytes per - * timestamp unit times 2 - */ - while (split_size >= ((80 << split_inst->deltaTime) * mult)) - { - split_size >>= 1; - } - - /* Make the size an even value. */ - if (split_size > 1) - { - split_size >>= 1; - split_size *= 2; - } - - temp_packet.payloadLen = split_size; - pw16_startPayload = temp_packet.payload; - i = 0; - while (len >= (2 * split_size)) - { - /* insert every chunk */ - i_ok = WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, - &localFlushed, av_sync); - *flushed |= localFlushed; - temp_packet.timeStamp += ((2 * split_size) >> split_inst->deltaTime); - i++; - temp_packet.payload = &(pw16_startPayload[(i * split_size) >> 1]); - temp_packet.starts_byte1 = temp_packet.starts_byte1 ^ (split_size & 0x1); - - len -= split_size; - if (i_ok < 0) - { - return PBUFFER_INSERT_ERROR1; - } - } - - /* Insert the rest */ - temp_packet.payloadLen = len; - i_ok = WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, - &localFlushed, av_sync); - *flushed |= localFlushed; - if (i_ok < 0) - { - return PBUFFER_INSERT_ERROR2; - } - } - else - { - /* Frame based codec, use hard splitting. */ - i = 0; - pw16_startPayload = temp_packet.payload; - while (len >= split_inst->deltaBytes) - { - - temp_packet.payloadLen = split_inst->deltaBytes; - i_ok = WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, - &localFlushed, av_sync); - *flushed |= localFlushed; - i++; - temp_packet.payload = &(pw16_startPayload[(i * split_inst->deltaBytes) >> 1]); - temp_packet.timeStamp += split_inst->deltaTime; - temp_packet.starts_byte1 = temp_packet.starts_byte1 ^ (split_inst->deltaBytes - & 0x1); - - if (i_ok < 0) - { - return PBUFFER_INSERT_ERROR3; - } - len -= split_inst->deltaBytes; - - } - if (len > 0) - { - /* Must be a either an error or a SID frame at the end of the packet. */ - temp_packet.payloadLen = len; - i_ok = WebRtcNetEQ_PacketBufferInsert(Buffer_inst, &temp_packet, - &localFlushed, av_sync); - *flushed |= localFlushed; - if (i_ok < 0) - { - return PBUFFER_INSERT_ERROR4; - } - } - } - - return 0; -} - diff --git a/webrtc/modules/audio_coding/neteq4/statistics_calculator.cc b/webrtc/modules/audio_coding/neteq/statistics_calculator.cc similarity index 96% rename from webrtc/modules/audio_coding/neteq4/statistics_calculator.cc rename to webrtc/modules/audio_coding/neteq/statistics_calculator.cc index b6e9222d4..383f70555 100644 --- a/webrtc/modules/audio_coding/neteq4/statistics_calculator.cc +++ b/webrtc/modules/audio_coding/neteq/statistics_calculator.cc @@ -8,13 +8,13 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/statistics_calculator.h" +#include "webrtc/modules/audio_coding/neteq/statistics_calculator.h" #include #include // memset -#include "webrtc/modules/audio_coding/neteq4/decision_logic.h" -#include "webrtc/modules/audio_coding/neteq4/delay_manager.h" +#include "webrtc/modules/audio_coding/neteq/decision_logic.h" +#include "webrtc/modules/audio_coding/neteq/delay_manager.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/statistics_calculator.h b/webrtc/modules/audio_coding/neteq/statistics_calculator.h similarity index 91% rename from webrtc/modules/audio_coding/neteq4/statistics_calculator.h rename to webrtc/modules/audio_coding/neteq/statistics_calculator.h index 25f8a14bb..07ef8536f 100644 --- a/webrtc/modules/audio_coding/neteq4/statistics_calculator.h +++ b/webrtc/modules/audio_coding/neteq/statistics_calculator.h @@ -8,13 +8,13 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_STATISTICS_CALCULATOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_STATISTICS_CALCULATOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_STATISTICS_CALCULATOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_STATISTICS_CALCULATOR_H_ #include -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -106,4 +106,4 @@ class StatisticsCalculator { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_STATISTICS_CALCULATOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_STATISTICS_CALCULATOR_H_ diff --git a/webrtc/modules/audio_coding/neteq4/sync_buffer.cc b/webrtc/modules/audio_coding/neteq/sync_buffer.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/sync_buffer.cc rename to webrtc/modules/audio_coding/neteq/sync_buffer.cc index 75ee6ece0..d1802e174 100644 --- a/webrtc/modules/audio_coding/neteq4/sync_buffer.cc +++ b/webrtc/modules/audio_coding/neteq/sync_buffer.cc @@ -12,7 +12,7 @@ #include // Access to min. -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/sync_buffer.h b/webrtc/modules/audio_coding/neteq/sync_buffer.h similarity index 90% rename from webrtc/modules/audio_coding/neteq4/sync_buffer.h rename to webrtc/modules/audio_coding/neteq/sync_buffer.h index e1e5daf1b..59bd4d87e 100644 --- a/webrtc/modules/audio_coding/neteq4/sync_buffer.h +++ b/webrtc/modules/audio_coding/neteq/sync_buffer.h @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_SYNC_BUFFER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_SYNC_BUFFER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_SYNC_BUFFER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_SYNC_BUFFER_H_ -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -78,7 +78,8 @@ class SyncBuffer : public AudioMultiVector { // created. void Flush(); - const AudioVector& Channel(size_t n) { return *channels_[n]; } + const AudioVector& Channel(size_t n) const { return *channels_[n]; } + AudioVector& Channel(size_t n) { return *channels_[n]; } // Accessors and mutators. size_t next_index() const { return next_index_; } @@ -97,4 +98,4 @@ class SyncBuffer : public AudioMultiVector { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_SYNC_BUFFER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_SYNC_BUFFER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/sync_buffer_unittest.cc b/webrtc/modules/audio_coding/neteq/sync_buffer_unittest.cc similarity index 99% rename from webrtc/modules/audio_coding/neteq4/sync_buffer_unittest.cc rename to webrtc/modules/audio_coding/neteq/sync_buffer_unittest.cc index 1aafa22ab..1a3d0fe78 100644 --- a/webrtc/modules/audio_coding/neteq4/sync_buffer_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/sync_buffer_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/sync_buffer.h" +#include "webrtc/modules/audio_coding/neteq/sync_buffer.h" #include "gtest/gtest.h" diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_CodecClass.cc b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_CodecClass.cc deleted file mode 100644 index 838f3d9ca..000000000 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_CodecClass.cc +++ /dev/null @@ -1,704 +0,0 @@ -/* - * Copyright (c) 2012 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 "NETEQTEST_CodecClass.h" - -#include // exit - -#include "webrtc_neteq_help_macros.h" - -NETEQTEST_Decoder::NETEQTEST_Decoder(enum WebRtcNetEQDecoder type, uint16_t fs, const char * name, uint8_t pt) -: -_decoder(NULL), -_decoderType(type), -_pt(pt), -_fs(fs), -_name(name) -{ -} - -int NETEQTEST_Decoder::loadToNetEQ(NETEQTEST_NetEQClass & neteq, WebRtcNetEQ_CodecDef & codecInst) -{ - SET_CODEC_PAR(codecInst, _decoderType, _pt, _decoder, _fs); - int err = neteq.loadCodec(codecInst); - - if (err) - { - printf("Error loading codec %s into NetEQ database\n", _name.c_str()); - } - - return(err); -} - - -// iSAC -#ifdef CODEC_ISAC -#include "isac.h" - -decoder_iSAC::decoder_iSAC(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderISAC, 16000, "iSAC", pt) -{ - int16_t err = WebRtcIsac_Create((ISACStruct **) &_decoder); - if (err) - { - exit(EXIT_FAILURE); - } - - WebRtcIsac_EncoderInit((ISACStruct *) _decoder, 0); - WebRtcIsac_SetDecSampRate((ISACStruct *) _decoder, 16000); -} - - -decoder_iSAC::~decoder_iSAC() -{ - if (_decoder) - { - WebRtcIsac_Free((ISACStruct *) _decoder); - _decoder = NULL; - } -} - - -int decoder_iSAC::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_ISAC_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); - -} -#endif - -#ifdef CODEC_ISAC_SWB -decoder_iSACSWB::decoder_iSACSWB(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderISACswb, 32000, "iSAC swb", pt) -{ - int16_t err = WebRtcIsac_Create((ISACStruct **) &_decoder); - if (err) - { - exit(EXIT_FAILURE); - } - - WebRtcIsac_EncoderInit((ISACStruct *) _decoder, 0); - WebRtcIsac_SetDecSampRate((ISACStruct *) _decoder, 32000); -} - -decoder_iSACSWB::~decoder_iSACSWB() -{ - if (_decoder) - { - WebRtcIsac_Free((ISACStruct *) _decoder); - _decoder = NULL; - } -} - -int decoder_iSACSWB::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_ISACSWB_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); - -} -#endif - -#ifdef CODEC_ISAC_FB -decoder_iSACFB::decoder_iSACFB(uint8_t pt) - : NETEQTEST_Decoder(kDecoderISACfb, 32000, "iSAC fb", pt) { - int16_t err = WebRtcIsac_Create((ISACStruct **) &_decoder); - if (err) { - exit(EXIT_FAILURE); - } - - WebRtcIsac_EncoderInit((ISACStruct *) _decoder, 0); - WebRtcIsac_SetDecSampRate((ISACStruct *) _decoder, 32000); -} - -decoder_iSACFB::~decoder_iSACFB() { - if (_decoder) { - WebRtcIsac_Free((ISACStruct *) _decoder); - _decoder = NULL; - } -} - -int decoder_iSACFB::loadToNetEQ(NETEQTEST_NetEQClass & neteq){ - WebRtcNetEQ_CodecDef codecInst; - SET_ISACFB_FUNCTIONS(codecInst); - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -// PCM u/A -#ifdef CODEC_G711 -#include "g711_interface.h" - -decoder_PCMU::decoder_PCMU(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderPCMu, 8000, "G.711-u", pt) -{ - // no state to crate or init -} - -int decoder_PCMU::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_PCMU_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); - -} - -decoder_PCMA::decoder_PCMA(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderPCMa, 8000, "G.711-A", pt) -{ - // no state to crate or init -} - -int decoder_PCMA::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_PCMA_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -// Linear PCM16b -#if (defined(CODEC_PCM16B) || defined(CODEC_PCM16B_WB) || \ - defined(CODEC_PCM16B_32KHZ) || defined(CODEC_PCM16B_48KHZ)) -#include "pcm16b.h" -#endif - -#ifdef CODEC_PCM16B -int decoder_PCM16B_NB::loadToNetEQ(NETEQTEST_NetEQClass &neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_PCM16B_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_PCM16B_WB -int decoder_PCM16B_WB::loadToNetEQ(NETEQTEST_NetEQClass &neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_PCM16B_WB_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_PCM16B_32KHZ -int decoder_PCM16B_SWB32::loadToNetEQ(NETEQTEST_NetEQClass &neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_PCM16B_SWB32_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_PCM16B_48KHZ -int decoder_PCM16B_SWB48::loadToNetEQ(NETEQTEST_NetEQClass &neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_PCM16B_SWB48_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_ILBC -#include "ilbc.h" -decoder_ILBC::decoder_ILBC(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderILBC, 8000, "iLBC", pt) -{ - int16_t err = WebRtcIlbcfix_DecoderCreate((iLBC_decinst_t **) &_decoder); - if (err) - { - exit(EXIT_FAILURE); - } -} - -decoder_ILBC::~decoder_ILBC() -{ - WebRtcIlbcfix_DecoderFree((iLBC_decinst_t *) _decoder); -} - -int decoder_ILBC::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_ILBC_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G729 -#include "G729Interface.h" -decoder_G729::decoder_G729(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG729, 8000, "G.729", pt) -{ - int16_t err = WebRtcG729_CreateDec((G729_decinst_t **) &_decoder); - if (err) - { - exit(EXIT_FAILURE); - } -} - -decoder_G729::~decoder_G729() -{ - WebRtcG729_FreeDec((G729_decinst_t *) _decoder); -} - -int decoder_G729::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G729_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G729_1 -#include "G729_1Interface.h" -decoder_G729_1::decoder_G729_1(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG729_1, 16000, "G.729.1", pt) -{ - int16_t err = WebRtcG7291_Create((G729_1_inst_t **) &_decoder); - if (err) - { - exit(EXIT_FAILURE); - } -} - -decoder_G729_1::~decoder_G729_1() -{ - WebRtcG7291_Free((G729_1_inst_t *) _decoder); -} - -int decoder_G729_1::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G729_1_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G722 -#include "g722_interface.h" -decoder_G722::decoder_G722(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG722, 16000, "G.722", pt) -{ - int16_t err = WebRtcG722_CreateDecoder((G722DecInst **) &_decoder); - if (err) - { - exit(EXIT_FAILURE); - } -} - -decoder_G722::~decoder_G722() -{ - WebRtcG722_FreeDecoder((G722DecInst *) _decoder); -} - -int decoder_G722::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G722_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#if (defined(CODEC_G722_1_16) || defined(CODEC_G722_1_24) || \ - defined(CODEC_G722_1_32) || defined(CODEC_G722_1C_24) || \ - defined(CODEC_G722_1C_32) || defined(CODEC_G722_1C_48)) -#include "G722_1Interface.h" -#endif - -#ifdef CODEC_G722_1_16 -decoder_G722_1_16::decoder_G722_1_16(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG722_1_16, 16000, "G.722.1 (16 kbps)", pt) -{ - if (WebRtcG7221_CreateDec16((G722_1_16_decinst_t **) &_decoder)) - { - exit(EXIT_FAILURE); - } -} - -decoder_G722_1_16::~decoder_G722_1_16() -{ - WebRtcG7221_FreeDec16((G722_1_16_decinst_t *) _decoder); -} - -int decoder_G722_1_16::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G722_1_16_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G722_1_24 -decoder_G722_1_24::decoder_G722_1_24(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG722_1_24, 16000, "G.722.1 (24 kbps)", pt) -{ - if (WebRtcG7221_CreateDec24((G722_1_24_decinst_t **) &_decoder)) - { - exit(EXIT_FAILURE); - } -} - -decoder_G722_1_24::~decoder_G722_1_24() -{ - WebRtcG7221_FreeDec24((G722_1_24_decinst_t *) _decoder); -} - -int decoder_G722_1_24::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G722_1_24_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G722_1_32 -decoder_G722_1_32::decoder_G722_1_32(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG722_1_32, 16000, "G.722.1 (32 kbps)", pt) -{ - if (WebRtcG7221_CreateDec32((G722_1_32_decinst_t **) &_decoder)) - { - exit(EXIT_FAILURE); - } -} - -decoder_G722_1_32::~decoder_G722_1_32() -{ - WebRtcG7221_FreeDec32((G722_1_32_decinst_t *) _decoder); -} - -int decoder_G722_1_32::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G722_1_32_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G722_1C_24 -decoder_G722_1C_24::decoder_G722_1C_24(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG722_1C_24, 32000, "G.722.1C (24 kbps)", pt) -{ - if (WebRtcG7221C_CreateDec24((G722_1C_24_decinst_t **) &_decoder)) - exit(EXIT_FAILURE); -} - -decoder_G722_1C_24::~decoder_G722_1C_24() -{ - WebRtcG7221C_FreeDec24((G722_1C_24_decinst_t *) _decoder); -} - -int decoder_G722_1C_24::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G722_1C_24_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G722_1C_32 -decoder_G722_1C_32::decoder_G722_1C_32(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG722_1C_32, 32000, "G.722.1C (32 kbps)", pt) -{ - if (WebRtcG7221C_CreateDec32((G722_1C_32_decinst_t **) &_decoder)) - exit(EXIT_FAILURE); -} - -decoder_G722_1C_32::~decoder_G722_1C_32() -{ - WebRtcG7221C_FreeDec32((G722_1C_32_decinst_t *) _decoder); -} - -int decoder_G722_1C_32::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G722_1C_32_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_G722_1C_48 -decoder_G722_1C_48::decoder_G722_1C_48(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderG722_1C_48, 32000, "G.722.1C (48 kbps)", pt) -{ - if (WebRtcG7221C_CreateDec48((G722_1C_48_decinst_t **) &_decoder)) - exit(EXIT_FAILURE); -} - -decoder_G722_1C_48::~decoder_G722_1C_48() -{ - WebRtcG7221C_FreeDec48((G722_1C_48_decinst_t *) _decoder); -} - -int decoder_G722_1C_48::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_G722_1C_48_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_AMR -#include "AMRInterface.h" -#include "AMRCreation.h" -decoder_AMR::decoder_AMR(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderAMR, 8000, "AMR", pt) -{ - if (WebRtcAmr_CreateDec((AMR_decinst_t **) &_decoder)) - exit(EXIT_FAILURE); - - WebRtcAmr_DecodeBitmode((AMR_decinst_t *) _decoder, AMRBandwidthEfficient); -} - -decoder_AMR::~decoder_AMR() -{ - WebRtcAmr_FreeDec((AMR_decinst_t *) _decoder); -} - -int decoder_AMR::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_AMR_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_AMRWB -#include "AMRWBInterface.h" -#include "AMRWBCreation.h" -decoder_AMRWB::decoder_AMRWB(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderAMRWB, 16000, "AMR wb", pt) -{ - if (WebRtcAmrWb_CreateDec((AMRWB_decinst_t **) &_decoder)) - exit(EXIT_FAILURE); - - WebRtcAmrWb_DecodeBitmode((AMRWB_decinst_t *) _decoder, AMRBandwidthEfficient); -} - -decoder_AMRWB::~decoder_AMRWB() -{ - WebRtcAmrWb_FreeDec((AMRWB_decinst_t *) _decoder); -} - -int decoder_AMRWB::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_AMRWB_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_GSMFR -#include "GSMFRInterface.h" -#include "GSMFRCreation.h" -decoder_GSMFR::decoder_GSMFR(uint8_t pt) -: -NETEQTEST_Decoder(kDecoderGSMFR, 8000, "GSM-FR", pt) -{ - if (WebRtcGSMFR_CreateDec((GSMFR_decinst_t **) &_decoder)) - exit(EXIT_FAILURE); -} - -decoder_GSMFR::~decoder_GSMFR() -{ - WebRtcGSMFR_FreeDec((GSMFR_decinst_t *) _decoder); -} - -int decoder_GSMFR::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_GSMFR_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#if (defined(CODEC_SPEEX_8) || defined (CODEC_SPEEX_16)) -#include "SpeexInterface.h" -decoder_SPEEX::decoder_SPEEX(uint8_t pt, uint16_t fs) -: -NETEQTEST_Decoder(fs == 8000 ? kDecoderSPEEX_8 : kDecoderSPEEX_16, - fs, "SPEEX", pt) -{ - if (fs != 8000 && fs != 16000) - throw std::exception("Wrong sample rate for SPEEX"); - - if (WebRtcSpeex_CreateDec((SPEEX_decinst_t **) &_decoder, fs, 1)) - exit(EXIT_FAILURE); -} - -decoder_SPEEX::~decoder_SPEEX() -{ - WebRtcSpeex_FreeDec((SPEEX_decinst_t *) _decoder); -} - -int decoder_SPEEX::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_SPEEX_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_CELT_32 -#include "celt_interface.h" -decoder_CELT::decoder_CELT(uint8_t pt, uint16_t fs) -: -NETEQTEST_Decoder(kDecoderCELT_32, fs, "CELT", pt) -{ - if (WebRtcCelt_CreateDec((CELT_decinst_t **) &_decoder, 2)) - exit(EXIT_FAILURE); -} - -decoder_CELT::~decoder_CELT() -{ - WebRtcCelt_FreeDec((CELT_decinst_t *) _decoder); -} - -int decoder_CELT::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_CELT_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} - -decoder_CELTslave::decoder_CELTslave(uint8_t pt, uint16_t fs) -: -NETEQTEST_Decoder(kDecoderCELT_32, fs, "CELT", pt) -{ - if (WebRtcCelt_CreateDec((CELT_decinst_t **) &_decoder, 2)) - exit(EXIT_FAILURE); -} - -decoder_CELTslave::~decoder_CELTslave() -{ - WebRtcCelt_FreeDec((CELT_decinst_t *) _decoder); -} - -int decoder_CELTslave::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_CELTSLAVE_FUNCTIONS(codecInst); - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_RED -int decoder_RED::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_RED_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#ifdef CODEC_ATEVENT_DECODE -int decoder_AVT::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_AVT_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif - -#if (defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) -#include "webrtc_cng.h" -decoder_CNG::decoder_CNG(uint8_t pt, uint16_t fs) -: -NETEQTEST_Decoder(kDecoderCNG, fs, "CNG", pt) -{ - if (fs != 8000 && fs != 16000 && fs != 32000 && fs != 48000) - exit(EXIT_FAILURE); - - if (WebRtcCng_CreateDec((CNG_dec_inst **) &_decoder)) - exit(EXIT_FAILURE); -} - -decoder_CNG::~decoder_CNG() -{ - WebRtcCng_FreeDec((CNG_dec_inst *) _decoder); -} - -int decoder_CNG::loadToNetEQ(NETEQTEST_NetEQClass & neteq) -{ - WebRtcNetEQ_CodecDef codecInst; - - SET_CNG_FUNCTIONS(codecInst); - - return(NETEQTEST_Decoder::loadToNetEQ(neteq, codecInst)); -} -#endif diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_CodecClass.h b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_CodecClass.h deleted file mode 100644 index 90f24ee6d..000000000 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_CodecClass.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) 2012 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 NETEQTEST_CODECCLASS_H -#define NETEQTEST_CODECCLASS_H - -#include -#include - -#include "typedefs.h" -#include "webrtc_neteq.h" -#include "NETEQTEST_NetEQClass.h" - -class NETEQTEST_Decoder -{ -public: - NETEQTEST_Decoder(enum WebRtcNetEQDecoder type, uint16_t fs, const char * name, uint8_t pt = 0); - virtual ~NETEQTEST_Decoder() {}; - - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) = 0; - - int getName(char * name, int maxLen) const { strncpy( name, _name.c_str(), maxLen ); return 0;}; - - void setPT(uint8_t pt) { _pt = pt; }; - uint16_t getFs() const { return (_fs); }; - enum WebRtcNetEQDecoder getType() const { return (_decoderType); }; - uint8_t getPT() const { return (_pt); }; - -protected: - int loadToNetEQ(NETEQTEST_NetEQClass & neteq, WebRtcNetEQ_CodecDef & codecInst); - - void * _decoder; - enum WebRtcNetEQDecoder _decoderType; - uint8_t _pt; - uint16_t _fs; - std::string _name; - -private: -}; - - -class decoder_iSAC : public NETEQTEST_Decoder -{ -public: - decoder_iSAC(uint8_t pt = 0); - virtual ~decoder_iSAC(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_iSACSWB : public NETEQTEST_Decoder -{ -public: - decoder_iSACSWB(uint8_t pt = 0); - virtual ~decoder_iSACSWB(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_iSACFB : public NETEQTEST_Decoder { - public: - decoder_iSACFB(uint8_t pt = 0); - virtual ~decoder_iSACFB(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_PCMU : public NETEQTEST_Decoder -{ -public: - decoder_PCMU(uint8_t pt = 0); - virtual ~decoder_PCMU() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_PCMA : public NETEQTEST_Decoder -{ -public: - decoder_PCMA(uint8_t pt = 0); - virtual ~decoder_PCMA() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_PCM16B_NB : public NETEQTEST_Decoder -{ -public: - decoder_PCM16B_NB(uint8_t pt = 0) : NETEQTEST_Decoder(kDecoderPCM16B, 8000, "PCM16 nb", pt) {}; - virtual ~decoder_PCM16B_NB() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; -class decoder_PCM16B_WB : public NETEQTEST_Decoder -{ -public: - decoder_PCM16B_WB(uint8_t pt = 0) : NETEQTEST_Decoder(kDecoderPCM16Bwb, 16000, "PCM16 wb", pt) {}; - virtual ~decoder_PCM16B_WB() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; -class decoder_PCM16B_SWB32 : public NETEQTEST_Decoder -{ -public: - decoder_PCM16B_SWB32(uint8_t pt = 0) : NETEQTEST_Decoder(kDecoderPCM16Bswb32kHz, 32000, "PCM16 swb32", pt) {}; - virtual ~decoder_PCM16B_SWB32() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_PCM16B_SWB48 : public NETEQTEST_Decoder -{ -public: - decoder_PCM16B_SWB48(uint8_t pt = 0) : NETEQTEST_Decoder(kDecoderPCM16Bswb48kHz, 48000, "PCM16 swb48", pt) {}; - virtual ~decoder_PCM16B_SWB48() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_ILBC : public NETEQTEST_Decoder -{ -public: - decoder_ILBC(uint8_t pt = 0); - virtual ~decoder_ILBC(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_G729 : public NETEQTEST_Decoder -{ -public: - decoder_G729(uint8_t pt = 0); - virtual ~decoder_G729(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G729_1 : public NETEQTEST_Decoder -{ -public: - decoder_G729_1(uint8_t pt = 0); - virtual ~decoder_G729_1(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_G722 : public NETEQTEST_Decoder -{ -public: - decoder_G722(uint8_t pt = 0); - virtual ~decoder_G722(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_G722_1_16 : public NETEQTEST_Decoder -{ -public: - decoder_G722_1_16(uint8_t pt = 0); - virtual ~decoder_G722_1_16(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G722_1_24 : public NETEQTEST_Decoder -{ -public: - decoder_G722_1_24(uint8_t pt = 0); - virtual ~decoder_G722_1_24(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G722_1_32 : public NETEQTEST_Decoder -{ -public: - decoder_G722_1_32(uint8_t pt = 0); - virtual ~decoder_G722_1_32(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_G722_1C_24 : public NETEQTEST_Decoder -{ -public: - decoder_G722_1C_24(uint8_t pt = 0); - virtual ~decoder_G722_1C_24(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G722_1C_32 : public NETEQTEST_Decoder -{ -public: - decoder_G722_1C_32(uint8_t pt = 0); - virtual ~decoder_G722_1C_32(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G722_1C_48 : public NETEQTEST_Decoder -{ -public: - decoder_G722_1C_48(uint8_t pt = 0); - virtual ~decoder_G722_1C_48(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_AMR : public NETEQTEST_Decoder -{ -public: - decoder_AMR(uint8_t pt = 0); - virtual ~decoder_AMR(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_AMRWB : public NETEQTEST_Decoder -{ -public: - decoder_AMRWB(uint8_t pt = 0); - virtual ~decoder_AMRWB(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_GSMFR : public NETEQTEST_Decoder -{ -public: - decoder_GSMFR(uint8_t pt = 0); - virtual ~decoder_GSMFR(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G726 : public NETEQTEST_Decoder -{ -public: - //virtual decoder_G726(uint8_t pt = 0) = 0; - decoder_G726(enum WebRtcNetEQDecoder type, const char * name, uint8_t pt = 0); - virtual ~decoder_G726(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) = 0; -}; - -class decoder_G726_16 : public decoder_G726 -{ -public: - decoder_G726_16(uint8_t pt = 0) : decoder_G726(kDecoderG726_16, "G.726 (16 kbps)", pt) {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G726_24 : public decoder_G726 -{ -public: - decoder_G726_24(uint8_t pt = 0) : decoder_G726(kDecoderG726_24, "G.726 (24 kbps)", pt) {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G726_32 : public decoder_G726 -{ -public: - decoder_G726_32(uint8_t pt = 0) : decoder_G726(kDecoderG726_32, "G.726 (32 kbps)", pt) {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_G726_40 : public decoder_G726 -{ -public: - decoder_G726_40(uint8_t pt = 0) : decoder_G726(kDecoderG726_40, "G.726 (40 kbps)", pt) {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_SPEEX : public NETEQTEST_Decoder -{ -public: - decoder_SPEEX(uint8_t pt = 0, uint16_t fs = 8000); - virtual ~decoder_SPEEX(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_CELT : public NETEQTEST_Decoder -{ -public: - decoder_CELT(uint8_t pt = 0, uint16_t fs = 32000); - virtual ~decoder_CELT(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; -class decoder_CELTslave : public NETEQTEST_Decoder -{ -public: - decoder_CELTslave(uint8_t pt = 0, uint16_t fs = 32000); - virtual ~decoder_CELTslave(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_RED : public NETEQTEST_Decoder -{ -public: - decoder_RED(uint8_t pt = 0) : NETEQTEST_Decoder(kDecoderRED, 8000, "RED", pt) {}; - virtual ~decoder_RED() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -class decoder_AVT : public NETEQTEST_Decoder -{ -public: - decoder_AVT(uint8_t pt = 0) : NETEQTEST_Decoder(kDecoderAVT, 8000, "AVT", pt) {}; - virtual ~decoder_AVT() {}; - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - - -class decoder_CNG : public NETEQTEST_Decoder -{ -public: - decoder_CNG(uint8_t pt = 0, uint16_t fs = 8000); - virtual ~decoder_CNG(); - virtual int loadToNetEQ(NETEQTEST_NetEQClass & neteq) OVERRIDE; -}; - -#endif //NETEQTEST_CODECCLASS_H diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.cc b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.cc index f663343ae..e1750912c 100644 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.cc +++ b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.cc @@ -14,8 +14,6 @@ #include #include -#include // max - #ifdef WIN32 #include #else @@ -35,119 +33,109 @@ int NETEQTEST_DummyRTPpacket::readFromFile(FILE *fp) bool readNextPacket = true; while (readNextPacket) { - readNextPacket = false; - if (fread(&length, 2, 1, fp) == 0) - { - reset(); - return -2; - } - length = ntohs(length); - - if (fread(&plen, 2, 1, fp) == 0) - { - reset(); - return -1; - } - packetLen = ntohs(plen); + readNextPacket = false; + if (fread(&length, 2, 1, fp) == 0) + { + reset(); + return -2; + } + length = ntohs(length); - if (fread(&offset, 4, 1, fp) == 0) - { - reset(); - return -1; - } - // Store in local variable until we have passed the reset below. - uint32_t receiveTime = ntohl(offset); - - // Use length here because a plen of 0 specifies rtcp. - length = (uint16_t) (length - _kRDHeaderLen); - - // check buffer size - if (_datagram && _memSize < length + 1) - { - reset(); - } - - if (!_datagram) - { - // Add one extra byte, to be able to fake a dummy payload of one byte. - _datagram = new uint8_t[length + 1]; - _memSize = length + 1; - } - memset(_datagram, 0, length + 1); - - if (length == 0) - { - _datagramLen = 0; - return packetLen; - } - - // Read basic header - if (fread(_datagram, 1, _kBasicHeaderLen, fp) - != (size_t)_kBasicHeaderLen) - { - reset(); - return -1; - } - _receiveTime = receiveTime; - _datagramLen = _kBasicHeaderLen; - int header_length = _kBasicHeaderLen; - - // Parse the basic header - WebRtcNetEQ_RTPInfo tempRTPinfo; - int P, X, CC; - parseBasicHeader(&tempRTPinfo, &P, &X, &CC); - - // Check if we have to extend the header - if (X != 0 || CC != 0) - { - int newLen = _kBasicHeaderLen + CC * 4 + X * 4; - assert(_memSize >= newLen + 1); - - // Read extension from file - size_t readLen = newLen - _kBasicHeaderLen; - if (fread(_datagram + _kBasicHeaderLen, 1, readLen, - fp) != readLen) + if (fread(&plen, 2, 1, fp) == 0) { - reset(); - return -1; + reset(); + return -1; } - _datagramLen = newLen; - header_length = newLen; + packetLen = ntohs(plen); - if (X != 0) + if (fread(&offset, 4, 1, fp) == 0) { - int totHdrLen = calcHeaderLength(X, CC); - assert(_memSize >= totHdrLen); - - // Read extension from file - size_t readLen = totHdrLen - newLen; - if (fread(_datagram + newLen, 1, readLen, fp) - != readLen) - { reset(); return -1; - } - _datagramLen = totHdrLen; - header_length = totHdrLen; } - } - // Make sure that we have at least one byte of dummy payload. - _datagramLen = std::max(static_cast(length), header_length + 1); - assert(_datagramLen <= _memSize); - - if (!_blockList.empty() && _blockList.count(payloadType()) > 0) - { - // discard this payload - readNextPacket = true; - } - - if (_filterSSRC && _selectSSRC != SSRC()) - { - // Discard this payload. - readNextPacket = true; - } + // Store in local variable until we have passed the reset below. + uint32_t receiveTime = ntohl(offset); + + // Use length here because a plen of 0 specifies rtcp. + length = (uint16_t) (length - _kRDHeaderLen); + + // check buffer size + if (_datagram && _memSize < length + 1) + { + reset(); + } + + if (!_datagram) + { + // Add one extra byte, to be able to fake a dummy payload of 1 byte. + _datagram = new uint8_t[length + 1]; + _memSize = length + 1; + } + memset(_datagram, 0, length + 1); + + if (length == 0) + { + _datagramLen = 0; + _rtpParsed = false; + return packetLen; + } + + // Read basic header + if (fread((unsigned short *) _datagram, 1, _kBasicHeaderLen, fp) + != (size_t)_kBasicHeaderLen) + { + reset(); + return -1; + } + _receiveTime = receiveTime; + _datagramLen = _kBasicHeaderLen; + + // Parse the basic header + webrtc::WebRtcRTPHeader tempRTPinfo; + int P, X, CC; + parseBasicHeader(&tempRTPinfo, &P, &X, &CC); + + // Check if we have to extend the header + if (X != 0 || CC != 0) + { + int newLen = _kBasicHeaderLen + CC * 4 + X * 4; + assert(_memSize >= newLen); + + // Read extension from file + size_t readLen = newLen - _kBasicHeaderLen; + if (fread(&_datagram[_kBasicHeaderLen], 1, readLen, fp) != readLen) + { + reset(); + return -1; + } + _datagramLen = newLen; + + if (X != 0) + { + int totHdrLen = calcHeaderLength(X, CC); + assert(_memSize >= totHdrLen); + + // Read extension from file + size_t readLen = totHdrLen - newLen; + if (fread(&_datagram[newLen], 1, readLen, fp) != readLen) + { + reset(); + return -1; + } + _datagramLen = totHdrLen; + } + } + _datagramLen = length; + + if (!_blockList.empty() && _blockList.count(payloadType()) > 0) + { + readNextPacket = true; + } } + _rtpParsed = false; + assert(_memSize > _datagramLen); + _payloadLen = 1; // Set the length to 1 byte. return packetLen; } @@ -208,3 +196,9 @@ int NETEQTEST_DummyRTPpacket::writeToFile(FILE *fp) } +void NETEQTEST_DummyRTPpacket::parseHeader() { + NETEQTEST_RTPpacket::parseHeader(); + // Change _payloadLen to 1 byte. The memory should always be big enough. + assert(_memSize > _datagramLen); + _payloadLen = 1; +} diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.h b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.h index 9dcece780..9f09c9482 100644 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.h +++ b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.h @@ -17,6 +17,7 @@ class NETEQTEST_DummyRTPpacket : public NETEQTEST_RTPpacket { public: virtual int readFromFile(FILE* fp) OVERRIDE; virtual int writeToFile(FILE* fp) OVERRIDE; + virtual void parseHeader() OVERRIDE; }; #endif // NETEQTEST_DUMMYRTPPACKET_H diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.cc b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.cc deleted file mode 100644 index b77c305fa..000000000 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.cc +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (c) 2012 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 "NETEQTEST_NetEQClass.h" - - -NETEQTEST_NetEQClass::NETEQTEST_NetEQClass() - : - _inst(NULL), - _instMem(NULL), - _bufferMem(NULL), - _preparseRTP(false), - _fsmult(1), - _isMaster(true), - _noDecode(false) -{ -#ifdef WINDOWS_TIMING - _totTimeRecIn.QuadPart = 0; - _totTimeRecOut.QuadPart = 0; -#endif -} - -NETEQTEST_NetEQClass::NETEQTEST_NetEQClass(enum WebRtcNetEQDecoder *usedCodec, int noOfCodecs, - uint16_t fs, WebRtcNetEQNetworkType nwType) - : - _inst(NULL), - _instMem(NULL), - _bufferMem(NULL), - _preparseRTP(false), - _fsmult(1), - _isMaster(true), - _noDecode(false) -{ -#ifdef WINDOWS_TIMING - _totTimeRecIn.QuadPart = 0; - _totTimeRecOut.QuadPart = 0; -#endif - - if (assign() == 0) - { - if (init(fs) == 0) - { - assignBuffer(usedCodec, noOfCodecs, nwType); - } - } -} - - -NETEQTEST_NetEQClass::~NETEQTEST_NetEQClass() -{ - if (_instMem) - { - delete [] _instMem; - _instMem = NULL; - } - - if (_bufferMem) - { - delete [] _bufferMem; - _bufferMem = NULL; - } - - _inst = NULL; -} - -int NETEQTEST_NetEQClass::assign() -{ - int memSize; - - WebRtcNetEQ_AssignSize(&memSize); - - if (_instMem) - { - delete [] _instMem; - _instMem = NULL; - } - - _instMem = new int8_t[memSize]; - - int ret = WebRtcNetEQ_Assign(&_inst, _instMem); - - if (ret) - { - printError(); - } - - return (ret); -} - - -int NETEQTEST_NetEQClass::init(uint16_t fs) -{ - int ret; - - if (!_inst) - { - // not assigned - ret = assign(); - - if (ret != 0) - { - printError(); - return (ret); - } - } - - ret = WebRtcNetEQ_Init(_inst, fs); - - if (ret != 0) - { - printError(); - } - - return (ret); - -} - - -int NETEQTEST_NetEQClass::assignBuffer(enum WebRtcNetEQDecoder *usedCodec, int noOfCodecs, WebRtcNetEQNetworkType nwType) -{ - int numPackets, memSize, ret, overhead_bytes; - - if (!_inst) - { - // not assigned - ret = assign(); - - if (ret != 0) - { - printError(); - return (ret); - } - - ret = init(); - - if (ret != 0) - { - printError(); - return (ret); - } - } - - ret = WebRtcNetEQ_GetRecommendedBufferSize(_inst, usedCodec, noOfCodecs, - nwType, &numPackets, &memSize, - &overhead_bytes); - - if (ret != 0) - { - printError(); - return (ret); - } - - if (_bufferMem) - { - delete [] _bufferMem; - _bufferMem = NULL; - } - - _bufferMem = new int8_t[memSize]; - - memset(_bufferMem, -1, memSize); - - ret = WebRtcNetEQ_AssignBuffer(_inst, numPackets, _bufferMem, memSize); - - if (ret != 0) - { - printError(); - } - - return (ret); -} - -int NETEQTEST_NetEQClass::loadCodec(WebRtcNetEQ_CodecDef &codecInst) -{ - int err = WebRtcNetEQ_CodecDbAdd(_inst, &codecInst); - - if (err) - { - printError(); - } - - return (err); -} - -void NETEQTEST_NetEQClass::printError() -{ - if (_inst) - { - int errorCode = WebRtcNetEQ_GetErrorCode(_inst); - - if (errorCode) - { - char errorName[WEBRTC_NETEQ_MAX_ERROR_NAME]; - - WebRtcNetEQ_GetErrorName(errorCode, errorName, WEBRTC_NETEQ_MAX_ERROR_NAME); - - printf("Error %i: %s\n", errorCode, errorName); - } - } -} - -void NETEQTEST_NetEQClass::printError(NETEQTEST_RTPpacket &rtp) -{ - // print regular error info - printError(); - - // print extra info from packet - printf("\tRTP: TS=%u, SN=%u, PT=%u, M=%i, len=%i\n", - rtp.timeStamp(), rtp.sequenceNumber(), rtp.payloadType(), - rtp.markerBit(), rtp.payloadLen()); - -} - -int NETEQTEST_NetEQClass::recIn(NETEQTEST_RTPpacket &rtp) -{ - - int err; -#ifdef WINDOWS_TIMING - LARGE_INTEGER countA, countB; -#endif - - if (_preparseRTP) - { - WebRtcNetEQ_RTPInfo rtpInfo; - // parse RTP header - rtp.parseHeader(rtpInfo); - -#ifdef WINDOWS_TIMING - QueryPerformanceCounter(&countA); // get start count for processor -#endif - - err = WebRtcNetEQ_RecInRTPStruct(_inst, &rtpInfo, rtp.payload(), rtp.payloadLen(), rtp.time() * _fsmult * 8); - -#ifdef WINDOWS_TIMING - QueryPerformanceCounter(&countB); // get stop count for processor - _totTimeRecIn.QuadPart += (countB.QuadPart - countA.QuadPart); -#endif - - } - else - { - -#ifdef WINDOWS_TIMING - QueryPerformanceCounter(&countA); // get start count for processor -#endif - - err = WebRtcNetEQ_RecIn(_inst, (int16_t *) rtp.datagram(), rtp.dataLen(), rtp.time() * _fsmult * 8); - -#ifdef WINDOWS_TIMING - QueryPerformanceCounter(&countB); // get stop count for processor - _totTimeRecIn.QuadPart += (countB.QuadPart - countA.QuadPart); -#endif - - } - - if (err) - { - printError(rtp); - } - - return (err); - -} - - -int16_t NETEQTEST_NetEQClass::recOut(int16_t *outData, void *msInfo, enum WebRtcNetEQOutputType *outputType) -{ - int err; - int16_t outLen = 0; -#ifdef WINDOWS_TIMING - LARGE_INTEGER countA, countB; -#endif - -#ifdef WINDOWS_TIMING - QueryPerformanceCounter(&countA); // get start count for processor -#endif - - if (!msInfo) - { - // no msInfo given, do mono mode - if (_noDecode) - { - err = WebRtcNetEQ_RecOutNoDecode(_inst, outData, &outLen); - } - else - { - err = WebRtcNetEQ_RecOut(_inst, outData, &outLen); - } - } - else - { - // master/slave mode - err = WebRtcNetEQ_RecOutMasterSlave(_inst, outData, &outLen, msInfo, static_cast(_isMaster)); - } - -#ifdef WINDOWS_TIMING - QueryPerformanceCounter(&countB); // get stop count for processor - _totTimeRecOut.QuadPart += (countB.QuadPart - countA.QuadPart); -#endif - - if (err) - { - printError(); - } - else - { - int newfsmult = static_cast(outLen / 80); - - if (newfsmult != _fsmult) - { -#ifdef NETEQTEST_PRINT_WARNINGS - printf("Warning: output sample rate changed\n"); -#endif // NETEQTEST_PRINT_WARNINGS - _fsmult = newfsmult; - } - } - - if (outputType != NULL) - { - err = WebRtcNetEQ_GetSpeechOutputType(_inst, outputType); - - if (err) - { - printError(); - } - } - - return (outLen); -} - - -uint32_t NETEQTEST_NetEQClass::getSpeechTimeStamp() -{ - - uint32_t ts = 0; - int err; - - err = WebRtcNetEQ_GetSpeechTimeStamp(_inst, &ts); - - if (err) - { - printError(); - ts = 0; - } - - return (ts); - -} - -WebRtcNetEQOutputType NETEQTEST_NetEQClass::getOutputType() { - WebRtcNetEQOutputType type; - - int err = WebRtcNetEQ_GetSpeechOutputType(_inst, &type); - if (err) - { - printError(); - type = kOutputNormal; - } - return (type); -} - -//NETEQTEST_NetEQVector::NETEQTEST_NetEQVector(int numChannels) -//: -//channels(numChannels, new NETEQTEST_NetEQClass()) -//{ -// //for (int i = 0; i < numChannels; i++) -// //{ -// // channels.push_back(new NETEQTEST_NetEQClass()); -// //} -//} -// -//NETEQTEST_NetEQVector::NETEQTEST_NetEQVector(int numChannels, enum WebRtcNetEQDecoder *usedCodec, int noOfCodecs, -// uint16_t fs, WebRtcNetEQNetworkType nwType) -// : -//channels(numChannels, new NETEQTEST_NetEQClass(usedCodec, noOfCodecs, fs, nwType)) -//{ -// //for (int i = 0; i < numChannels; i++) -// //{ -// // channels.push_back(new NETEQTEST_NetEQClass(usedCodec, noOfCodecs, fs, nwType)); -// //} -//} -// -//NETEQTEST_NetEQVector::~NETEQTEST_NetEQVector() -//{ -//} - diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.h b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.h deleted file mode 100644 index 8e987b842..000000000 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2012 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 NETEQTEST_NETEQCLASS_H -#define NETEQTEST_NETEQCLASS_H - -#include -#include - -#include "webrtc_neteq.h" -#include "webrtc_neteq_internal.h" - -#include "NETEQTEST_RTPpacket.h" - -#ifdef WIN32 -#define WINDOWS_TIMING // complexity measurement only implemented for windows -//TODO(hlundin):Add complexity testing for Linux. -#include -#endif - -class NETEQTEST_NetEQClass -{ -public: - NETEQTEST_NetEQClass(); - NETEQTEST_NetEQClass(enum WebRtcNetEQDecoder *usedCodec, int noOfCodecs, - uint16_t fs = 8000, WebRtcNetEQNetworkType nwType = kTCPLargeJitter); - ~NETEQTEST_NetEQClass(); - - int assign(); - int init(uint16_t fs = 8000); - int assignBuffer(enum WebRtcNetEQDecoder *usedCodec, int noOfCodecs, WebRtcNetEQNetworkType nwType = kTCPLargeJitter); - int loadCodec(WebRtcNetEQ_CodecDef & codecInst); - int recIn(NETEQTEST_RTPpacket & rtp); - int16_t recOut(int16_t *outData, void *msInfo = NULL, enum WebRtcNetEQOutputType *outputType = NULL); - uint32_t getSpeechTimeStamp(); - WebRtcNetEQOutputType getOutputType(); - - void * instance() { return (_inst); }; - void usePreparseRTP( bool useIt = true ) { _preparseRTP = useIt; }; - bool usingPreparseRTP() { return (_preparseRTP); }; - void setMaster( bool isMaster = true ) { _isMaster = isMaster; }; - void setSlave() { _isMaster = false; }; - void setNoDecode(bool noDecode = true) { _noDecode = noDecode; }; - bool isMaster() { return (_isMaster); }; - bool isSlave() { return (!_isMaster); }; - bool isNoDecode() { return _noDecode; }; - -#ifdef WINDOWS_TIMING - double getRecInTime() { return (static_cast( _totTimeRecIn.QuadPart )); }; - double getRecOutTime() { return (static_cast( _totTimeRecOut.QuadPart )); }; -#else - double getRecInTime() { return (0.0); }; - double getRecOutTime() { return (0.0); }; - -#endif - - void printError(); - void printError(NETEQTEST_RTPpacket & rtp); - -private: - void * _inst; - int8_t * _instMem; - int8_t * _bufferMem; - bool _preparseRTP; - int _fsmult; - bool _isMaster; - bool _noDecode; -#ifdef WINDOWS_TIMING - LARGE_INTEGER _totTimeRecIn; - LARGE_INTEGER _totTimeRecOut; -#endif -}; - -#endif //NETEQTEST_NETEQCLASS_H diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.cc b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.cc index 9eb20a91a..22f18efde 100644 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.cc +++ b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.cc @@ -32,9 +32,7 @@ _datagramLen(-1), _payloadLen(0), _rtpParsed(false), _receiveTime(0), -_lost(false), -_selectSSRC(0), -_filterSSRC(false) +_lost(false) { memset(&_rtpInfo, 0, sizeof(_rtpInfo)); _blockList.clear(); @@ -162,13 +160,9 @@ int NETEQTEST_RTPpacket::readFromFile(FILE *fp) { readNextPacket = true; } - - if (_filterSSRC && _selectSSRC != SSRC()) - { - readNextPacket = true; - } } + _rtpParsed = false; return(packetLen); } @@ -208,6 +202,7 @@ int NETEQTEST_RTPpacket::readFixedFromFile(FILE *fp, size_t length) return readFromFile(fp); } + _rtpParsed = false; return length; } @@ -262,11 +257,6 @@ void NETEQTEST_RTPpacket::blockPT(uint8_t pt) _blockList[pt] = true; } -void NETEQTEST_RTPpacket::selectSSRC(uint32_t ssrc) -{ - _selectSSRC = ssrc; - _filterSSRC = true; -} void NETEQTEST_RTPpacket::parseHeader() { @@ -290,18 +280,20 @@ void NETEQTEST_RTPpacket::parseHeader() } -void NETEQTEST_RTPpacket::parseHeader(WebRtcNetEQ_RTPInfo & rtpInfo) -{ - if (!_rtpParsed) - { - // parse the header - parseHeader(); - } - - memcpy(&rtpInfo, &_rtpInfo, sizeof(WebRtcNetEQ_RTPInfo)); +void NETEQTEST_RTPpacket::parseHeader(webrtc::WebRtcRTPHeader* rtp_header) { + if (!_rtpParsed) { + parseHeader(); + } + if (rtp_header) { + rtp_header->header.markerBit = _rtpInfo.header.markerBit; + rtp_header->header.payloadType = _rtpInfo.header.payloadType; + rtp_header->header.sequenceNumber = _rtpInfo.header.sequenceNumber; + rtp_header->header.timestamp = _rtpInfo.header.timestamp; + rtp_header->header.ssrc = _rtpInfo.header.ssrc; + } } -WebRtcNetEQ_RTPInfo const * NETEQTEST_RTPpacket::RTPinfo() const +const webrtc::WebRtcRTPHeader* NETEQTEST_RTPpacket::RTPinfo() const { if (_rtpParsed) { @@ -360,7 +352,7 @@ bool NETEQTEST_RTPpacket::isLost() const uint8_t NETEQTEST_RTPpacket::payloadType() const { - WebRtcNetEQ_RTPInfo tempRTPinfo; + webrtc::WebRtcRTPHeader tempRTPinfo; if(_datagram && _datagramLen >= _kBasicHeaderLen) { @@ -371,12 +363,12 @@ uint8_t NETEQTEST_RTPpacket::payloadType() const return 0; } - return tempRTPinfo.payloadType; + return tempRTPinfo.header.payloadType; } uint16_t NETEQTEST_RTPpacket::sequenceNumber() const { - WebRtcNetEQ_RTPInfo tempRTPinfo; + webrtc::WebRtcRTPHeader tempRTPinfo; if(_datagram && _datagramLen >= _kBasicHeaderLen) { @@ -387,12 +379,12 @@ uint16_t NETEQTEST_RTPpacket::sequenceNumber() const return 0; } - return tempRTPinfo.sequenceNumber; + return tempRTPinfo.header.sequenceNumber; } uint32_t NETEQTEST_RTPpacket::timeStamp() const { - WebRtcNetEQ_RTPInfo tempRTPinfo; + webrtc::WebRtcRTPHeader tempRTPinfo; if(_datagram && _datagramLen >= _kBasicHeaderLen) { @@ -403,12 +395,12 @@ uint32_t NETEQTEST_RTPpacket::timeStamp() const return 0; } - return tempRTPinfo.timeStamp; + return tempRTPinfo.header.timestamp; } uint32_t NETEQTEST_RTPpacket::SSRC() const { - WebRtcNetEQ_RTPInfo tempRTPinfo; + webrtc::WebRtcRTPHeader tempRTPinfo; if(_datagram && _datagramLen >= _kBasicHeaderLen) { @@ -419,12 +411,12 @@ uint32_t NETEQTEST_RTPpacket::SSRC() const return 0; } - return tempRTPinfo.SSRC; + return tempRTPinfo.header.ssrc; } uint8_t NETEQTEST_RTPpacket::markerBit() const { - WebRtcNetEQ_RTPInfo tempRTPinfo; + webrtc::WebRtcRTPHeader tempRTPinfo; if(_datagram && _datagramLen >= _kBasicHeaderLen) { @@ -435,7 +427,7 @@ uint8_t NETEQTEST_RTPpacket::markerBit() const return 0; } - return tempRTPinfo.markerBit; + return tempRTPinfo.header.markerBit; } @@ -450,7 +442,7 @@ int NETEQTEST_RTPpacket::setPayloadType(uint8_t pt) if (!_rtpParsed) { - _rtpInfo.payloadType = pt; + _rtpInfo.header.payloadType = pt; } _datagram[1]=(unsigned char)(pt & 0xFF); @@ -469,7 +461,7 @@ int NETEQTEST_RTPpacket::setSequenceNumber(uint16_t sn) if (!_rtpParsed) { - _rtpInfo.sequenceNumber = sn; + _rtpInfo.header.sequenceNumber = sn; } _datagram[2]=(unsigned char)((sn>>8)&0xFF); @@ -489,7 +481,7 @@ int NETEQTEST_RTPpacket::setTimeStamp(uint32_t ts) if (!_rtpParsed) { - _rtpInfo.timeStamp = ts; + _rtpInfo.header.timestamp = ts; } _datagram[4]=(unsigned char)((ts>>24)&0xFF); @@ -511,7 +503,7 @@ int NETEQTEST_RTPpacket::setSSRC(uint32_t ssrc) if (!_rtpParsed) { - _rtpInfo.SSRC = ssrc; + _rtpInfo.header.ssrc = ssrc; } _datagram[8]=(unsigned char)((ssrc>>24)&0xFF); @@ -533,7 +525,7 @@ int NETEQTEST_RTPpacket::setMarkerBit(uint8_t mb) if (_rtpParsed) { - _rtpInfo.markerBit = mb; + _rtpInfo.header.markerBit = mb; } if (mb) @@ -549,7 +541,7 @@ int NETEQTEST_RTPpacket::setMarkerBit(uint8_t mb) } -int NETEQTEST_RTPpacket::setRTPheader(const WebRtcNetEQ_RTPInfo *RTPinfo) +int NETEQTEST_RTPpacket::setRTPheader(const webrtc::WebRtcRTPHeader* RTPinfo) { if (_datagramLen < 12) { @@ -558,11 +550,11 @@ int NETEQTEST_RTPpacket::setRTPheader(const WebRtcNetEQ_RTPInfo *RTPinfo) } makeRTPheader(_datagram, - RTPinfo->payloadType, - RTPinfo->sequenceNumber, - RTPinfo->timeStamp, - RTPinfo->SSRC, - RTPinfo->markerBit); + RTPinfo->header.payloadType, + RTPinfo->header.sequenceNumber, + RTPinfo->header.timestamp, + RTPinfo->header.ssrc, + RTPinfo->header.markerBit); return 0; } @@ -660,7 +652,7 @@ void NETEQTEST_RTPpacket::makeRTPheader(unsigned char* rtp_data, uint8_t payload } uint16_t - NETEQTEST_RTPpacket::parseRTPheader(WebRtcNetEQ_RTPInfo *RTPinfo, + NETEQTEST_RTPpacket::parseRTPheader(webrtc::WebRtcRTPHeader* RTPinfo, uint8_t **payloadPtr) const { int16_t *rtp_data = (int16_t *) _datagram; @@ -682,7 +674,7 @@ uint16_t } -void NETEQTEST_RTPpacket::parseBasicHeader(WebRtcNetEQ_RTPInfo *RTPinfo, +void NETEQTEST_RTPpacket::parseBasicHeader(webrtc::WebRtcRTPHeader* RTPinfo, int *i_P, int *i_X, int *i_CC) const { int16_t *rtp_data = (int16_t *) _datagram; @@ -696,19 +688,20 @@ void NETEQTEST_RTPpacket::parseBasicHeader(WebRtcNetEQ_RTPInfo *RTPinfo, *i_X=(((uint16_t)(rtp_data[0] & 0x10))>>4); /* Extract the X bit */ *i_CC=(uint16_t)(rtp_data[0] & 0xF); /* Get the CC number */ /* Get the marker bit */ - RTPinfo->markerBit = (uint8_t) ((rtp_data[0] >> 15) & 0x01); + RTPinfo->header.markerBit = (uint8_t) ((rtp_data[0] >> 15) & 0x01); /* Get the coder type */ - RTPinfo->payloadType = (uint8_t) ((rtp_data[0] >> 8) & 0x7F); + RTPinfo->header.payloadType = (uint8_t) ((rtp_data[0] >> 8) & 0x7F); /* Get the packet number */ - RTPinfo->sequenceNumber = ((( ((uint16_t)rtp_data[1]) >> 8) & 0xFF) | + RTPinfo->header.sequenceNumber = + ((( ((uint16_t)rtp_data[1]) >> 8) & 0xFF) | ( ((uint16_t)(rtp_data[1] & 0xFF)) << 8)); /* Get timestamp */ - RTPinfo->timeStamp = ((((uint16_t)rtp_data[2]) & 0xFF) << 24) | + RTPinfo->header.timestamp = ((((uint16_t)rtp_data[2]) & 0xFF) << 24) | ((((uint16_t)rtp_data[2]) & 0xFF00) << 8) | ((((uint16_t)rtp_data[3]) >> 8) & 0xFF) | ((((uint16_t)rtp_data[3]) & 0xFF) << 8); /* Get the SSRC */ - RTPinfo->SSRC=((((uint16_t)rtp_data[4]) & 0xFF) << 24) | + RTPinfo->header.ssrc = ((((uint16_t)rtp_data[4]) & 0xFF) << 24) | ((((uint16_t)rtp_data[4]) & 0xFF00) << 8) | ((((uint16_t)rtp_data[5]) >> 8) & 0xFF) | ((((uint16_t)rtp_data[5]) & 0xFF) << 8); @@ -817,7 +810,7 @@ void NETEQTEST_RTPpacket::splitStereoDouble(NETEQTEST_RTPpacket* slaveRtp) // Get the RTP header for the RED payload indicated by argument index. // The first RED payload is index = 0. -int NETEQTEST_RTPpacket::extractRED(int index, WebRtcNetEQ_RTPInfo& red) +int NETEQTEST_RTPpacket::extractRED(int index, webrtc::WebRtcRTPHeader& red) { // // 0 1 2 3 @@ -844,12 +837,12 @@ int NETEQTEST_RTPpacket::extractRED(int index, WebRtcNetEQ_RTPInfo& red) if (num_encodings == index) { // Header found. - red.payloadType = ptr[0] & 0x7F; + red.header.payloadType = ptr[0] & 0x7F; uint32_t offset = (ptr[1] << 6) + ((ptr[2] & 0xFC) >> 2); - red.sequenceNumber = sequenceNumber(); - red.timeStamp = timeStamp() - offset; - red.markerBit = markerBit(); - red.SSRC = SSRC(); + red.header.sequenceNumber = sequenceNumber(); + red.header.timestamp = timeStamp() - offset; + red.header.markerBit = markerBit(); + red.header.ssrc = SSRC(); return len; } ++num_encodings; @@ -859,11 +852,11 @@ int NETEQTEST_RTPpacket::extractRED(int index, WebRtcNetEQ_RTPInfo& red) if ((ptr < payloadEndPtr) && (num_encodings == index)) { // Last header. - red.payloadType = ptr[0] & 0x7F; - red.sequenceNumber = sequenceNumber(); - red.timeStamp = timeStamp(); - red.markerBit = markerBit(); - red.SSRC = SSRC(); + red.header.payloadType = ptr[0] & 0x7F; + red.header.sequenceNumber = sequenceNumber(); + red.header.timestamp = timeStamp(); + red.header.markerBit = markerBit(); + red.header.ssrc = SSRC(); ++ptr; return payloadLen() - (ptr - payload()) - total_len; } diff --git a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h index fda7b95cd..8a31274ab 100644 --- a/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h +++ b/webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h @@ -13,8 +13,8 @@ #include #include -#include "typedefs.h" -#include "webrtc_neteq_internal.h" +#include "webrtc/typedefs.h" +#include "webrtc/modules/interface/module_common_types.h" enum stereoModes { stereoModeMono, @@ -36,11 +36,10 @@ class NETEQTEST_RTPpacket int readFixedFromFile(FILE *fp, size_t len); virtual int writeToFile(FILE *fp); void blockPT(uint8_t pt); - void selectSSRC(uint32_t ssrc); //int16_t payloadType(); - void parseHeader(); - void parseHeader(WebRtcNetEQ_RTPInfo & rtpInfo); - WebRtcNetEQ_RTPInfo const * RTPinfo() const; + virtual void parseHeader(); + void parseHeader(webrtc::WebRtcRTPHeader* rtp_header); + const webrtc::WebRtcRTPHeader* RTPinfo() const; uint8_t * datagram() const; uint8_t * payload() const; int16_t payloadLen(); @@ -62,11 +61,11 @@ class NETEQTEST_RTPpacket int setMarkerBit(uint8_t mb); void setTime(uint32_t receiveTime) { _receiveTime = receiveTime; }; - int setRTPheader(const WebRtcNetEQ_RTPInfo *RTPinfo); + int setRTPheader(const webrtc::WebRtcRTPHeader* RTPinfo); int splitStereo(NETEQTEST_RTPpacket* slaveRtp, enum stereoModes mode); - int extractRED(int index, WebRtcNetEQ_RTPInfo& red); + int extractRED(int index, webrtc::WebRtcRTPHeader& red); void scramblePayload(void); @@ -75,19 +74,17 @@ class NETEQTEST_RTPpacket int _memSize; int16_t _datagramLen; int16_t _payloadLen; - WebRtcNetEQ_RTPInfo _rtpInfo; + webrtc::WebRtcRTPHeader _rtpInfo; bool _rtpParsed; uint32_t _receiveTime; bool _lost; std::map _blockList; - uint32_t _selectSSRC; - bool _filterSSRC; protected: static const int _kRDHeaderLen; static const int _kBasicHeaderLen; - void parseBasicHeader(WebRtcNetEQ_RTPInfo *RTPinfo, int *i_P, int *i_X, + void parseBasicHeader(webrtc::WebRtcRTPHeader* RTPinfo, int *i_P, int *i_X, int *i_CC) const; int calcHeaderLength(int i_X, int i_CC) const; @@ -95,7 +92,7 @@ class NETEQTEST_RTPpacket void makeRTPheader(unsigned char* rtp_data, uint8_t payloadType, uint16_t seqNo, uint32_t timestamp, uint32_t ssrc, uint8_t markerBit) const; - uint16_t parseRTPheader(WebRtcNetEQ_RTPInfo *RTPinfo, + uint16_t parseRTPheader(webrtc::WebRtcRTPHeader* RTPinfo, uint8_t **payloadPtr = NULL) const; uint16_t parseRTPheader(uint8_t **payloadPtr = NULL) { return parseRTPheader(&_rtpInfo, payloadPtr);}; diff --git a/webrtc/modules/audio_coding/neteq/test/NetEqRTPplay.cc b/webrtc/modules/audio_coding/neteq/test/NetEqRTPplay.cc deleted file mode 100644 index bbfcdf9aa..000000000 --- a/webrtc/modules/audio_coding/neteq/test/NetEqRTPplay.cc +++ /dev/null @@ -1,1811 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -//TODO(hlundin): Reformat file to meet style guide. - -#include -#include - -/* header includes */ -#include "typedefs.h" -#include "webrtc_neteq.h" -#include "webrtc_neteq_internal.h" -#include "webrtc_neteq_help_macros.h" -#include "neteq_error_codes.h" // for the API test - -#include "NETEQTEST_RTPpacket.h" -#include "NETEQTEST_DummyRTPpacket.h" -#include "NETEQTEST_NetEQClass.h" -#include "NETEQTEST_CodecClass.h" - -#ifdef WEBRTC_ANDROID -#include // isalpha -#endif -#include -#include -#include -#include -#include - -#ifdef WIN32 -#include -#include -#endif - -#ifdef WEBRTC_LINUX -#include -#include -#include -#endif - -//#include "vld.h" - -//#define NETEQ_DELAY_LOGGING -//#define DUMMY_SLAVE_CHANNEL - -#ifdef NETEQ_DELAY_LOGGING -#include "delay_logging.h" -#define DUMMY_SLAVE_CHANNEL // do not use a slave channel, only generate zeros instead -#endif - - -/************************/ -/* Define payload types */ -/************************/ - -// Payload types are defined in the textfile ptypes.txt, and can be changed after compilation. - - - -/*********************/ -/* Misc. definitions */ -/*********************/ - -#define TIME_STEP 1 -#define FIRSTLINELEN 40 -#define MAX_NETEQ_BUFFERSIZE 170000 //100000 -#define CHECK_ZERO(a) {int errCode = a; char tempErrName[WEBRTC_NETEQ_MAX_ERROR_NAME]; if((errCode)!=0){errCode = WebRtcNetEQ_GetErrorCode(inst); WebRtcNetEQ_GetErrorName(errCode, tempErrName, WEBRTC_NETEQ_MAX_ERROR_NAME); printf("\n %s \n line: %d \n error at %s\n Error Code = %d\n",__FILE__,__LINE__,#a, errCode); exit(0);}} -#define CHECK_NOT_NULL(a) if((a)==NULL){printf("\n %s \n line: %d \nerror at %s\n",__FILE__,__LINE__,#a );return(-1);} -//#define PLAY_CLEAN // ignore arrival times and let the packets arrive according to RTP timestamps -#define HDR_SIZE 8 // rtpplay packet header size in bytes -//#define JUNK_DATA // scramble the payloads to test error resilience -//#define ZERO_TS_START - -#ifdef JUNK_DATA - #define SEED_FILE "randseed.txt" -#endif - -#ifdef WIN32 -#define MY_MAX_DRIVE _MAX_DRIVE -#define MY_MAX_PATH _MAX_PATH -#define MY_MAX_FNAME _MAX_FNAME -#define MY_MAX_EXT _MAX_EXT - -#elif defined(WEBRTC_LINUX) -#include -#define MY_MAX_PATH PATH_MAX - -#elif defined(WEBRTC_MAC) -#include -#define MY_MAX_PATH PATH_MAX -#endif // WEBRTC_MAC - -/************/ -/* Typedefs */ -/************/ - -typedef struct { - enum WebRtcNetEQDecoder codec; - enum stereoModes stereo; - NETEQTEST_Decoder * decoder[2]; - int fs; -} decoderStruct; - - -/*************************/ -/* Function declarations */ -/*************************/ - -void stereoInterleave(int16_t *data, int16_t totalLen); -int getNextRecoutTime(FILE *fp, uint32_t *nextTime); -void getNextExtraDelay(FILE *fp, uint32_t *t, int *d); -bool splitStereo(NETEQTEST_RTPpacket* rtp, NETEQTEST_RTPpacket* rtpSlave, - const int16_t *stereoPtype, const enum stereoModes *stereoMode, int noOfStereoCodecs, - const int16_t *cngPtype, int noOfCngCodecs, - bool *isStereo); -void parsePtypeFile(FILE *ptypeFile, std::map* decoders); -int populateUsedCodec(std::map* decoders, enum WebRtcNetEQDecoder *usedCodec); -void createAndInsertDecoders (NETEQTEST_NetEQClass *neteq, std::map* decoders, int channelNumber); -void free_coders(std::map & decoders); -int doAPItest(); -bool changeStereoMode(NETEQTEST_RTPpacket & rtp, std::map & decoders, enum stereoModes *stereoMode); - - - -/********************/ -/* Global variables */ -/********************/ - -int16_t NetEqPacketBuffer[MAX_NETEQ_BUFFERSIZE>>1]; -int16_t NetEqPacketBufferSlave[MAX_NETEQ_BUFFERSIZE>>1]; - -#ifdef NETEQ_DELAY_LOGGING -extern "C" { - FILE *delay_fid2; /* file pointer */ - uint32_t tot_received_packets=0; -} -#endif - -#ifdef DEF_BUILD_DATE -extern char BUILD_DATE; -#endif - -uint32_t writtenSamples = 0; -uint32_t simClock=0; - -int main(int argc, char* argv[]) -{ - std::vector NetEQvector; - - enum WebRtcNetEQDecoder usedCodec[kDecoderReservedEnd-1]; - int noOfCodecs; - int ok; - int16_t out_data[640*2]; - int16_t outLen, writeLen; - int fs = 8000; - WebRtcNetEQ_RTCPStat RTCPstat; -#ifdef WIN32 - char outdrive[MY_MAX_DRIVE]; - char outpath[MY_MAX_PATH]; - char outfile[MY_MAX_FNAME]; - char outext[MY_MAX_EXT]; -#endif - char outfilename[MY_MAX_PATH]; -#ifdef NETEQ_DELAY_LOGGING - float clock_float; - int temp_var; -#endif -#ifdef JUNK_DATA - FILE *seedfile; -#endif - FILE *recoutTimes = NULL; - FILE *extraDelays = NULL; - WebRtcNetEQPlayoutMode streamingMode = kPlayoutOn; - bool preParseRTP = false; - bool rtpOnly = false; - int packetLen = 0; - int packetCount = 0; - std::map decoders; - bool dummyRtp = false; - bool noDecode = false; - bool filterSSRC = false; - uint32_t ssrc; - -#ifdef DEF_BUILD_DATE - printf("Build time: %s\n", __BUILD_DATE); -#endif - - /* check number of parameters */ - if ((argc < 3) -#ifdef WIN32 // implicit output file name possible for windows - && (argc < 2) -#endif - ) { - /* print help text and exit */ - printf("Test program for NetEQ.\n"); - printf("The program reads an RTP stream from file and inserts it into NetEQ.\n"); - printf("The format of the RTP stream file should be the same as for rtpplay,\n"); - printf("and can be obtained e.g., from Ethereal by using\n"); - printf("Statistics -> RTP -> Show All Streams -> [select a stream] -> Save As\n\n"); - printf("Usage:\n\n"); -#ifdef WIN32 - printf("%s RTPfile [outfile] [-options]\n", argv[0]); -#else - printf("%s RTPfile outfile [-options]\n", argv[0]); -#endif - printf("where:\n"); - - printf("RTPfile : RTP stream input file\n\n"); - - printf("outfile : PCM speech output file\n"); - printf(" Output file name is derived from RTP file name if omitted\n\n"); - - printf("-options are optional switches:\n"); - printf("\t-recout datfile : supply recout times\n"); - printf("\t-extradelay datfile : supply extra delay settings and timing\n"); - printf("\t-streaming : engage streaming mode\n"); - printf("\t-fax : engage fax mode\n"); - printf("\t-preparsertp : use RecIn with pre-parsed RTP\n"); - printf("\t-rtponly packLenBytes : input file consists of constant size RTP packets without RTPplay headers\n"); - printf("\t-dummyrtp : input file contains only RTP headers\n"); - printf("\t-nodecode : no decoding will be done\n"); - printf("\t-ssrc 0xNNNNNNNN : discard all other SSRCs\n"); - //printf("\t-switchms : switch from mono to stereo (copy channel) after 10 seconds\n"); - //printf("\t-duplicate : use two instances with identical input (2-channel mono)\n"); - - return(0); - } - - if (strcmp(argv[1], "-apitest")==0) { - // do API test and then return - ok=doAPItest(); - - if (ok==0) - printf("API test successful!\n"); - else - printf("API test failed!\n"); - - return(ok); - } - - FILE* in_file=fopen(argv[1],"rb"); - CHECK_NOT_NULL(in_file); - printf("Input file: %s\n",argv[1]); - - int argIx = 2; // index of next argument from command line - - if ( argc >= 3 && argv[2][0] != '-' ) { // output name given on command line - strcpy(outfilename, argv[2]); - argIx++; - } else { // derive output name from input name -#ifdef WIN32 - _splitpath(argv[1],outdrive,outpath,outfile,outext); - _makepath(outfilename,outdrive,outpath,outfile,"pcm"); -#else - fprintf(stderr,"Output file name must be specified.\n"); - return(-1); -#endif - } - FILE* out_file=fopen(outfilename,"wb"); - if (out_file==NULL) { - fprintf(stderr,"Could not open file %s for writing\n", outfilename); - return(-1); - } - printf("Output file: %s\n",outfilename); - - // Parse for more arguments, all beginning with '-' - while( argIx < argc ) { - if (argv[argIx][0] != '-') { - fprintf(stderr,"Unknown input argument %s\n", argv[argIx]); - return(-1); - } - - if( strcmp(argv[argIx], "-recout") == 0 ) { - argIx++; - recoutTimes = fopen(argv[argIx], "rb"); - CHECK_NOT_NULL(recoutTimes); - argIx++; - } - else if( strcmp(argv[argIx], "-extradelay") == 0 ) { - argIx++; - extraDelays = fopen(argv[argIx], "rb"); - CHECK_NOT_NULL(extraDelays); - argIx++; - } - else if( strcmp(argv[argIx], "-streaming") == 0 ) { - argIx++; - streamingMode = kPlayoutStreaming; - } - else if( strcmp(argv[argIx], "-fax") == 0 ) { - argIx++; - streamingMode = kPlayoutFax; - } - else if( strcmp(argv[argIx], "-preparsertp") == 0 ) { - argIx++; - preParseRTP = true; - } - else if( strcmp(argv[argIx], "-rtponly") == 0 ) { - argIx++; - rtpOnly = true; - packetLen = atoi(argv[argIx]); - argIx++; - if (packetLen <= 0) - { - printf("Wrong packet size used with argument -rtponly.\n"); - exit(1); - } - } - else if (strcmp(argv[argIx], "-dummyrtp") == 0 - || strcmp(argv[argIx], "-dummy") == 0) - { - argIx++; - dummyRtp = true; - noDecode = true; // force noDecode since there are no payloads - } - else if (strcmp(argv[argIx], "-nodecode") == 0) - { - argIx++; - noDecode = true; - } - else if (strcmp(argv[argIx], "-ssrc") == 0) - { - argIx++; - filterSSRC = true; - if (sscanf(argv[argIx], "%X", &ssrc) != 1) - { - printf("Could not read SSRC argument.\n"); - exit(1); - } - argIx++; - } - //else if( strcmp(argv[argIx], "-switchms") == 0 ) { - // argIx++; - // switchMS = true; - //} - //else if( strcmp(argv[argIx], "-duplicate") == 0 ) { - // argIx++; - // duplicatePayload = true; - //} - else { - fprintf(stderr,"Unknown input argument %s\n", argv[argIx]); - return(-1); - } - } - - - -#ifdef NETEQ_DELAY_LOGGING - char delayfile[MY_MAX_PATH]; -#ifdef WIN32 - _splitpath(outfilename,outdrive,outpath,outfile,outext); - _makepath(delayfile,outdrive,outpath,outfile,"d"); -#else - sprintf(delayfile, "%s.d", outfilename); -#endif - delay_fid2 = fopen(delayfile,"wb"); - fprintf(delay_fid2, "#!NetEQ_Delay_Logging%s\n", NETEQ_DELAY_LOGGING_VERSION_STRING); -#endif - - char ptypesfile[MY_MAX_PATH]; -#ifdef WIN32 - _splitpath(argv[0],outdrive,outpath,outfile,outext); - _makepath(ptypesfile,outdrive,outpath,"ptypes","txt"); -#elif defined(WEBRTC_ANDROID) - strcpy(ptypesfile, "/sdcard/ptypes.txt"); -#else - // TODO(hlundin): Include path to ptypes, as for WIN32 above. - strcpy(ptypesfile, "ptypes.txt"); -#endif - FILE *ptypeFile = fopen(ptypesfile,"rt"); - if (!ptypeFile) { - // Check if we can find the file at the usual place in the trunk. - if (strstr(argv[0], "out/Debug/")) { - int path_len = strstr(argv[0], "out/Debug/") - argv[0]; - strncpy(ptypesfile, argv[0], path_len); - ptypesfile[path_len] = '\0'; - strcat(ptypesfile, - "webrtc/modules/audio_coding/neteq/test/ptypes.txt"); - ptypeFile = fopen(ptypesfile,"rt"); - } - } - CHECK_NOT_NULL(ptypeFile); - printf("Ptypes file: %s\n\n", ptypesfile); - - parsePtypeFile(ptypeFile, &decoders); - fclose(ptypeFile); - - noOfCodecs = populateUsedCodec(&decoders, usedCodec); - - - /* read RTP file header */ - if (!rtpOnly) - { - if (NETEQTEST_RTPpacket::skipFileHeader(in_file) != 0) - { - fprintf(stderr, "Wrong format in RTP file.\n"); - return -1; - } - } - - /* check payload type for first speech packet */ - long tempFilePos = ftell(in_file); - enum stereoModes stereoMode = stereoModeMono; - - NETEQTEST_RTPpacket *rtp; - NETEQTEST_RTPpacket *slaveRtp; - if (!dummyRtp) - { - rtp = new NETEQTEST_RTPpacket(); - slaveRtp = new NETEQTEST_RTPpacket(); - } - else - { - rtp = new NETEQTEST_DummyRTPpacket(); - slaveRtp = new NETEQTEST_DummyRTPpacket(); - } - - /* Uncomment and edit the line(s) below to block some payload types. */ - //rtp->blockPT(72); - //rtp->blockPT(23); - - /* Select a specific SSRC. */ - if (filterSSRC) { - rtp->selectSSRC(ssrc); - } - - if (!rtpOnly) - { - while (rtp->readFromFile(in_file) >= 0) - { - if (decoders.count(rtp->payloadType()) > 0 - && decoders[rtp->payloadType()].codec != kDecoderRED - && decoders[rtp->payloadType()].codec != kDecoderAVT - && decoders[rtp->payloadType()].codec != kDecoderCNG ) - { - stereoMode = decoders[rtp->payloadType()].stereo; - fs = decoders[rtp->payloadType()].fs; - break; - } - } - } - else - { - while (rtp->readFixedFromFile(in_file, packetLen) >= 0) - { - if (decoders.count(rtp->payloadType()) > 0 - && decoders[rtp->payloadType()].codec != kDecoderRED - && decoders[rtp->payloadType()].codec != kDecoderAVT - && decoders[rtp->payloadType()].codec != kDecoderCNG ) - { - stereoMode = decoders[rtp->payloadType()].stereo; - fs = decoders[rtp->payloadType()].fs; - break; - } - } - } - - fseek(in_file, tempFilePos, SEEK_SET /* from beginning */); - - /* read first packet */ - if (!rtpOnly) - { - rtp->readFromFile(in_file); - } - else - { - rtp->readFixedFromFile(in_file, packetLen); - rtp->setTime((1000 * rtp->timeStamp()) / fs); - } - if (!rtp) - { - printf("\nWarning: RTP file is empty\n\n"); - } - - - /* Initialize NetEQ instances */ - int numInst = 1; - if (stereoMode > stereoModeMono) - { - numInst = 2; - } - - for (int i = 0; i < numInst; i++) - { - // create memory, allocate, initialize, and allocate packet buffer memory - NetEQvector.push_back (new NETEQTEST_NetEQClass(usedCodec, noOfCodecs, static_cast(fs), kTCPLargeJitter)); - - createAndInsertDecoders (NetEQvector[i], &decoders, i /* channel */); - - WebRtcNetEQ_SetAVTPlayout(NetEQvector[i]->instance(),1); // enable DTMF playout - - WebRtcNetEQ_SetPlayoutMode(NetEQvector[i]->instance(), streamingMode); - - NetEQvector[i]->usePreparseRTP(preParseRTP); - - NetEQvector[i]->setNoDecode(noDecode); - - if (numInst > 1) - { - // we are using master/slave mode - if (i == 0) - { - // first instance is master - NetEQvector[i]->setMaster(); - } - else - { - // all other are slaves - NetEQvector[i]->setSlave(); - } - } - } - - -#ifdef ZERO_TS_START - uint32_t firstTS = rtp->timeStamp(); - rtp->setTimeStamp(0); -#else - uint32_t firstTS = 0; -#endif - - // check stereo mode - if (stereoMode > stereoModeMono) - { - if(rtp->splitStereo(slaveRtp, stereoMode)) - { - printf("Error in splitStereo\n"); - } - } - -#ifdef PLAY_CLEAN - uint32_t prevTS = rtp->timeStamp(); - uint32_t currTS, prev_time; -#endif - -#ifdef JUNK_DATA - unsigned int random_seed = (unsigned int) /*1196764538; */time(NULL); - srand(random_seed); - - if ( (seedfile = fopen(SEED_FILE, "a+t") ) == NULL ) { - fprintf(stderr, "Error: Could not open file %s\n", SEED_FILE); - } - else { - fprintf(seedfile, "%u\n", random_seed); - fclose(seedfile); - } -#endif - - uint32_t nextRecoutTime; - int lastRecout = getNextRecoutTime(recoutTimes, &nextRecoutTime); // does nothing if recoutTimes == NULL - - if (recoutTimes) - simClock = (rtp->time() < nextRecoutTime ? rtp->time(): nextRecoutTime); - else - simClock = rtp->time(); // start immediately with first packet - - uint32_t start_clock = simClock; - - uint32_t nextExtraDelayTime; - int extraDelay = -1; - getNextExtraDelay(extraDelays, &nextExtraDelayTime, &extraDelay); - - void *msInfo; - msInfo = malloc(WebRtcNetEQ_GetMasterSlaveInfoSize()); - if(msInfo == NULL) - return(-1); - - while(rtp->dataLen() >= 0 || (recoutTimes && !lastRecout)) { -// printf("simClock = %Lu\n", simClock); - -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_CLOCK; - clock_float = (float) simClock; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - if (fwrite(&clock_float, sizeof(float), 1, delay_fid2) != 1) { - return -1; - } -#endif - /* time to set extra delay */ - if (extraDelay > -1 && simClock >= nextExtraDelayTime) { - // set extra delay for all instances - for (int i = 0; i < numInst; i++) - { - WebRtcNetEQ_SetExtraDelay(NetEQvector[i]->instance(), extraDelay); - } - getNextExtraDelay(extraDelays, &nextExtraDelayTime, &extraDelay); - } - - /* check if time to receive */ - while (simClock >= rtp->time() && rtp->dataLen() >= 0) - { - if (rtp->dataLen() > 0) - { - - // insert main packet - NetEQvector[0]->recIn(*rtp); - - if (stereoMode > stereoModeMono - && slaveRtp->dataLen() > 0) - { - // insert slave packet - NetEQvector[1]->recIn(*slaveRtp); - } - - } - - /* get next packet */ -#ifdef PLAY_CLEAN - prev_time = rtp->time(); -#endif - if (!rtpOnly) - { - rtp->readFromFile(in_file); - } - else - { - rtp->readFixedFromFile(in_file, packetLen); - rtp->setTime((1000 * rtp->timeStamp()) / fs); - } - - if (rtp->dataLen() >= 0) - { - rtp->setTimeStamp(rtp->timeStamp() - firstTS); - } - - packetCount++; - - if (changeStereoMode(*rtp, decoders, &stereoMode)) - { - printf("Warning: stereo mode changed\n"); - } - - if (stereoMode > stereoModeMono) - { - if(rtp->splitStereo(slaveRtp, stereoMode)) - { - printf("Error in splitStereo\n"); - } - } - -#ifdef PLAY_CLEAN - currTS = rtp->timeStamp(); - rtp->setTime(prev_time + (currTS-prevTS)/(fs/1000)); - prevTS = currTS; -#endif - } - - /* check if time to RecOut */ - if ( (!recoutTimes && (simClock%10)==0) // recout times not given from file - || ( recoutTimes && (simClock >= nextRecoutTime) ) ) // recout times given from file - { - if (stereoMode > stereoModeMono) - { - // stereo - int16_t tempLen; - tempLen = NetEQvector[0]->recOut( out_data, msInfo ); // master - outLen = NetEQvector[1]->recOut( &out_data[tempLen], msInfo ); // slave - - assert(tempLen == outLen); - - writeLen = outLen * 2; - stereoInterleave(out_data, writeLen); - } - else - { - // mono - outLen = NetEQvector[0]->recOut( out_data ); - writeLen = outLen; - } - - // write to file - if (fwrite(out_data, writeLen, 2, out_file) != 2) { - return -1; - } - writtenSamples += writeLen; - - - lastRecout = getNextRecoutTime(recoutTimes, &nextRecoutTime); // does nothing if recoutTimes == NULL - - /* ask for statistics */ - WebRtcNetEQ_NetworkStatistics inCallStats; - WebRtcNetEQ_GetNetworkStatistics(NetEQvector[0]->instance(), &inCallStats); - - } - - /* increase time */ - simClock+=TIME_STEP; - } - - fclose(in_file); - fclose(out_file); - -#ifdef NETEQ_DELAY_LOGGING - temp_var = NETEQ_DELAY_LOGGING_SIGNAL_EOF; - if (fwrite(&temp_var, sizeof(int), 1, delay_fid2) != 1) { - return -1; - } - if (fwrite(&tot_received_packets, sizeof(uint32_t), - 1, delay_fid2) != 1) { - return -1; - } - fprintf(delay_fid2,"End of file\n"); - fclose(delay_fid2); -#endif - - WebRtcNetEQ_GetRTCPStats(NetEQvector[0]->instance(), &RTCPstat); - printf("RTCP statistics:\n"); - printf(" cum_lost : %d\n", (int) RTCPstat.cum_lost); - printf(" ext_max : %d\n", (int) RTCPstat.ext_max); - printf(" fraction_lost : %d (%f%%)\n", RTCPstat.fraction_lost, (float)(100.0*RTCPstat.fraction_lost/256.0)); - printf(" jitter : %d\n", (int) RTCPstat.jitter); - - printf("\n Call duration ms : %u\n", simClock-start_clock); - - printf("\nComplexity estimates (including sub-components):\n"); - printf(" RecIn complexity : %.2f MCPS\n", NetEQvector[0]->getRecInTime() / ((float) 1000*(simClock-start_clock))); - printf(" RecOut complexity : %.2f MCPS\n", NetEQvector[0]->getRecOutTime() / ((float) 1000*(simClock-start_clock))); - - free_coders(decoders); - //free_coders(0 /* first channel */); - // if (stereoMode > stereoModeMono) { - // free_coders(1 /* second channel */); - // } - free(msInfo); - - for (std::vector::iterator it = NetEQvector.begin(); - it < NetEQvector.end(); delete *it++) { - } - - printf("\nSimulation done!\n"); - -#ifdef JUNK_DATA - if ( (seedfile = fopen(SEED_FILE, "a+t") ) == NULL ) { - fprintf(stderr, "Error: Could not open file %s\n", SEED_FILE); - } - else { - fprintf(seedfile, "ok\n\n"); - fclose(seedfile); - } -#endif - - - // Log complexity to file -/* FILE *statfile; - statfile = fopen("complexity.txt","at"); - fprintf(statfile,"%.4f, %.4f\n", (float) totTime_RecIn.QuadPart / ((float) 1000*(simClock-start_clock)), (float) totTime_RecOut.QuadPart / ((float) 1000*(simClock-start_clock))); - fclose(statfile);*/ - - return(0); - -} - - - - - -/****************/ -/* Subfunctions */ -/****************/ - -bool splitStereo(NETEQTEST_RTPpacket* rtp, NETEQTEST_RTPpacket* rtpSlave, - const int16_t *stereoPtype, const enum stereoModes *stereoMode, int noOfStereoCodecs, - const int16_t *cngPtype, int noOfCngCodecs, - bool *isStereo) -{ - - // init - //bool isStereo = false; - enum stereoModes tempStereoMode = stereoModeMono; - bool isCng = false; - - // check payload length - if (rtp->dataLen() <= 0) { - //*isStereo = false; // don't change - return(*isStereo); - } - - // check payload type - int16_t ptype = rtp->payloadType(); - - // is this a cng payload? - for (int k = 0; k < noOfCngCodecs; k++) { - if (ptype == cngPtype[k]) { - // do not change stereo state - isCng = true; - tempStereoMode = stereoModeFrame; - } - } - - if (!isCng) - { - *isStereo = false; - - // is this payload type a stereo codec? which type? - for (int k = 0; k < noOfStereoCodecs; k++) { - if (ptype == stereoPtype[k]) { - tempStereoMode = stereoMode[k]; - *isStereo = true; - break; // exit for loop - } - } - } - - if (*isStereo) - { - // split the payload if stereo - - if(rtp->splitStereo(rtpSlave, tempStereoMode)) - { - printf("Error in splitStereo\n"); - } - - } - - return(*isStereo); - -} - -void stereoInterleave(int16_t *data, int16_t totalLen) -{ - int k; - - for(k = totalLen/2; k < totalLen; k++) { - int16_t temp = data[k]; - memmove(&data[2*k - totalLen + 2], &data[2*k - totalLen + 1], (totalLen - k -1) * sizeof(int16_t)); - data[2*k - totalLen + 1] = temp; - } -} - - -int getNextRecoutTime(FILE *fp, uint32_t *nextTime) { - - float tempTime; - - if (!fp) { - return -1; - } - - if (fread(&tempTime, sizeof(float), 1, fp) != 0) { - // not end of file - *nextTime = (uint32_t) tempTime; - return 0; - } - - *nextTime = 0; - fclose(fp); - - return 1; -} - -void getNextExtraDelay(FILE *fp, uint32_t *t, int *d) { - - float temp[2]; - - if(!fp) { - *d = -1; - return; - } - - if (fread(&temp, sizeof(float), 2, fp) != 0) { - // not end of file - *t = (uint32_t) temp[0]; - *d = (int) temp[1]; - return; - } - - *d = -1; - fclose(fp); - - return; -} - - -void parsePtypeFile(FILE *ptypeFile, std::map* decoders) -{ - int n, pt; - char codec[100]; - decoderStruct tempDecoder; - - // read first line - n = fscanf(ptypeFile, "%s %i\n", codec, &pt); - - while (n==2) - { - memset(&tempDecoder, 0, sizeof(decoderStruct)); - tempDecoder.stereo = stereoModeMono; - - if( pt >= 0 // < 0 disables this codec - && isalpha(codec[0]) ) // and is a letter - { - - /* check for stereo */ - int L = strlen(codec); - bool isStereo = false; - - if (codec[L-1] == '*') { - // stereo codec - isStereo = true; - - // remove '*' - codec[L-1] = '\0'; - } - -#ifdef CODEC_G711 - if(strcmp(codec, "pcmu") == 0) { - tempDecoder.codec = kDecoderPCMu; - tempDecoder.fs = 8000; - } - else if(strcmp(codec, "pcma") == 0) { - tempDecoder.codec = kDecoderPCMa; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_IPCMU - else if(strcmp(codec, "eg711u") == 0) { - tempDecoder.codec = kDecoderEG711u; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_IPCMA - else if(strcmp(codec, "eg711a") == 0) { - tempDecoder.codec = kDecoderEG711a; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_ILBC - else if(strcmp(codec, "ilbc") == 0) { - tempDecoder.codec = kDecoderILBC; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_ISAC - else if(strcmp(codec, "isac") == 0) { - tempDecoder.codec = kDecoderISAC; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_ISACLC - else if(strcmp(codec, "isaclc") == 0) { - tempDecoder.codec = NETEQ_CODEC_ISACLC; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_ISAC_SWB - else if(strcmp(codec, "isacswb") == 0) { - tempDecoder.codec = kDecoderISACswb; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_ISAC_FB - else if(strcmp(codec, "isacfb") == 0) { - tempDecoder.codec = kDecoderISACfb; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_IPCMWB - else if(strcmp(codec, "ipcmwb") == 0) { - tempDecoder.codec = kDecoderIPCMwb; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_G722 - else if(strcmp(codec, "g722") == 0) { - tempDecoder.codec = kDecoderG722; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_G722_1_16 - else if(strcmp(codec, "g722_1_16") == 0) { - tempDecoder.codec = kDecoderG722_1_16; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_G722_1_24 - else if(strcmp(codec, "g722_1_24") == 0) { - tempDecoder.codec = kDecoderG722_1_24; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_G722_1_32 - else if(strcmp(codec, "g722_1_32") == 0) { - tempDecoder.codec = kDecoderG722_1_32; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_G722_1C_24 - else if(strcmp(codec, "g722_1c_24") == 0) { - tempDecoder.codec = kDecoderG722_1C_24; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_G722_1C_32 - else if(strcmp(codec, "g722_1c_32") == 0) { - tempDecoder.codec = kDecoderG722_1C_32; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_G722_1C_48 - else if(strcmp(codec, "g722_1c_48") == 0) { - tempDecoder.codec = kDecoderG722_1C_48; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_G723 - else if(strcmp(codec, "g723") == 0) { - tempDecoder.codec = NETEQ_CODEC_G723; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_G726 - else if(strcmp(codec, "g726_16") == 0) { - tempDecoder.codec = kDecoderG726_16; - tempDecoder.fs = 8000; - } - else if(strcmp(codec, "g726_24") == 0) { - tempDecoder.codec = kDecoderG726_24; - tempDecoder.fs = 8000; - } - else if(strcmp(codec, "g726_32") == 0) { - tempDecoder.codec = kDecoderG726_32; - tempDecoder.fs = 8000; - } - else if(strcmp(codec, "g726_40") == 0) { - tempDecoder.codec = kDecoderG726_40; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_G729 - else if(strcmp(codec, "g729") == 0) { - tempDecoder.codec = kDecoderG729; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_G729D - else if(strcmp(codec, "g729d") == 0) { - tempDecoder.codec = NETEQ_CODEC_G729D; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_G729_1 - else if(strcmp(codec, "g729_1") == 0) { - tempDecoder.codec = kDecoderG729_1; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_GSMFR - else if(strcmp(codec, "gsmfr") == 0) { - tempDecoder.codec = kDecoderGSMFR; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_GSMEFR - else if(strcmp(codec, "gsmefr") == 0) { - tempDecoder.codec = NETEQ_CODEC_GSMEFR; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_AMR - else if(strcmp(codec, "amr") == 0) { - tempDecoder.codec = kDecoderAMR; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_AMRWB - else if(strcmp(codec, "amrwb") == 0) { - tempDecoder.codec = kDecoderAMRWB; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_DVI4 - else if(strcmp(codec, "dvi4") == 0) { - tempDecoder.codec = NETEQ_CODEC_DVI4; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_SPEEX_8 - else if(strcmp(codec, "speex8") == 0) { - tempDecoder.codec = kDecoderSPEEX_8; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_SPEEX_16 - else if(strcmp(codec, "speex16") == 0) { - tempDecoder.codec = kDecoderSPEEX_16; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_CELT_32 - else if(strcmp(codec, "celt32") == 0) { - tempDecoder.codec = kDecoderCELT_32; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_SILK_NB - else if(strcmp(codec, "silk8") == 0) { - tempDecoder.codec = NETEQ_CODEC_SILK_8; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_SILK_WB - else if(strcmp(codec, "silk12") == 0) { - tempDecoder.codec = NETEQ_CODEC_SILK_12; - tempDecoder.fs = 16000; - } - else if(strcmp(codec, "silk16") == 0) { - tempDecoder.codec = NETEQ_CODEC_SILK_16; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_SILK_SWB - else if(strcmp(codec, "silk24") == 0) { - tempDecoder.codec = NETEQ_CODEC_SILK_24; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_MELPE - else if(strcmp(codec, "melpe") == 0) { - tempDecoder.codec = NETEQ_CODEC_MELPE; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_PCM16B - else if(strcmp(codec, "pcm16b") == 0) { - tempDecoder.codec = kDecoderPCM16B; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_PCM16B_WB - else if(strcmp(codec, "pcm16b_wb") == 0) { - tempDecoder.codec = kDecoderPCM16Bwb; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_PCM16B_32KHZ - else if(strcmp(codec, "pcm16b_swb32khz") == 0) { - tempDecoder.codec = kDecoderPCM16Bswb32kHz; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_PCM16B_48KHZ - else if(strcmp(codec, "pcm16b_swb48khz") == 0) { - tempDecoder.codec = kDecoderPCM16Bswb48kHz; - tempDecoder.fs = 48000; - } -#endif -#ifdef CODEC_CNGCODEC8 - else if(strcmp(codec, "cn") == 0) { - tempDecoder.codec = kDecoderCNG; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_CNGCODEC16 - else if(strcmp(codec, "cn_wb") == 0) { - tempDecoder.codec = kDecoderCNG; - tempDecoder.fs = 16000; - } -#endif -#ifdef CODEC_CNGCODEC32 - else if(strcmp(codec, "cn_swb32") == 0) { - tempDecoder.codec = kDecoderCNG; - tempDecoder.fs = 32000; - } -#endif -#ifdef CODEC_CNGCODEC48 - else if(strcmp(codec, "cn_swb48") == 0) { - tempDecoder.codec = kDecoderCNG; - tempDecoder.fs = 48000; - } -#endif -#ifdef CODEC_ATEVENT_DECODE - else if(strcmp(codec, "avt") == 0) { - tempDecoder.codec = kDecoderAVT; - tempDecoder.fs = 8000; - } -#endif -#ifdef CODEC_RED - else if(strcmp(codec, "red") == 0) { - tempDecoder.codec = kDecoderRED; - tempDecoder.fs = 8000; - } -#endif - else if(isalpha(codec[0])) { - printf("Unsupported codec %s\n", codec); - // read next line and continue while loop - n = fscanf(ptypeFile, "%s %i\n", codec, &pt); - continue; - } - else { - // name is not recognized, and does not start with a letter - // hence, it is commented out - // read next line and continue while loop - n = fscanf(ptypeFile, "%s %i\n", codec, &pt); - continue; - } - - // handle stereo - if (tempDecoder.codec == kDecoderCNG) - { - // always set stereo mode for CNG, even if it is not marked at stereo - tempDecoder.stereo = stereoModeFrame; - } - else if(isStereo) - { - switch(tempDecoder.codec) { - // sample based codecs - case kDecoderPCMu: - case kDecoderPCMa: - case kDecoderG722: - { - // 1 octet per sample - tempDecoder.stereo = stereoModeSample1; - break; - } - case kDecoderPCM16B: - case kDecoderPCM16Bwb: - case kDecoderPCM16Bswb32kHz: - case kDecoderPCM16Bswb48kHz: - { - // 2 octets per sample - tempDecoder.stereo = stereoModeSample2; - break; - } - - case kDecoderCELT_32: - { - tempDecoder.stereo = stereoModeDuplicate; - break; - } - // fixed-rate frame codecs -// case kDecoderG729: -// case NETEQ_CODEC_G729D: -// case NETEQ_CODEC_G729E: -// case kDecoderG722_1_16: -// case kDecoderG722_1_24: -// case kDecoderG722_1_32: -// case kDecoderG722_1C_24: -// case kDecoderG722_1C_32: -// case kDecoderG722_1C_48: -// case NETEQ_CODEC_MELPE: -// { -// tempDecoder.stereo = stereoModeFrame; -// break; -// } - default: - { - printf("Cannot use codec %s as stereo codec\n", codec); - exit(0); - } - } - } - - if (pt > 127) - { - printf("Payload type must be less than 128\n"); - exit(0); - } - - // insert into codecs map - (*decoders)[static_cast(pt)] = tempDecoder; - - } - - n = fscanf(ptypeFile, "%s %i\n", codec, &pt); - } // end while - -} - - -bool changeStereoMode(NETEQTEST_RTPpacket & rtp, std::map & decoders, enum stereoModes *stereoMode) -{ - if (decoders.count(rtp.payloadType()) > 0 - && decoders[rtp.payloadType()].codec != kDecoderRED - && decoders[rtp.payloadType()].codec != kDecoderAVT - && decoders[rtp.payloadType()].codec != kDecoderCNG ) - { - if (decoders[rtp.payloadType()].stereo != *stereoMode) - { - *stereoMode = decoders[rtp.payloadType()].stereo; - return true; // stereo mode did change - } - } - - return false; // stereo mode did not change -} - - -int populateUsedCodec(std::map* decoders, enum WebRtcNetEQDecoder *usedCodec) -{ - int numCodecs = 0; - - std::map::iterator it; - - it = decoders->begin(); - - for (int i = 0; i < static_cast(decoders->size()); i++, it++) - { - usedCodec[numCodecs] = (*it).second.codec; - numCodecs++; - } - - return numCodecs; -} - - -void createAndInsertDecoders (NETEQTEST_NetEQClass *neteq, std::map* decoders, int channelNumber) -{ - std::map::iterator it; - - for (it = decoders->begin(); it != decoders->end(); it++) - { - if (channelNumber == 0 || - ((*it).second.stereo > stereoModeMono )) - { - // create decoder instance - uint8_t pt = static_cast( (*it).first ); - NETEQTEST_Decoder **dec = &((*it).second.decoder[channelNumber]); - enum WebRtcNetEQDecoder type = (*it).second.codec; - - switch (type) - { -#ifdef CODEC_G711 - case kDecoderPCMu: - *dec = new decoder_PCMU( pt ); - break; - case kDecoderPCMa: - *dec = new decoder_PCMA( pt ); - break; -#endif -#ifdef CODEC_IPCMU - case kDecoderEG711u: - *dec = new decoder_IPCMU( pt ); - break; -#endif -#ifdef CODEC_IPCMA - case kDecoderEG711a: - *dec = new decoder_IPCMA( pt ); - break; -#endif -#ifdef CODEC_IPCMWB - case kDecoderIPCMwb: - *dec = new decoder_IPCMWB( pt ); - break; -#endif -#ifdef CODEC_ILBC - case kDecoderILBC: - *dec = new decoder_ILBC( pt ); - break; -#endif -#ifdef CODEC_ISAC - case kDecoderISAC: - *dec = new decoder_iSAC( pt ); - break; -#endif -#ifdef CODEC_ISAC_SWB - case kDecoderISACswb: - *dec = new decoder_iSACSWB( pt ); - break; -#endif -#ifdef CODEC_ISAC_FB - case kDecoderISACfb: - *dec = new decoder_iSACFB(pt); - break; -#endif -#ifdef CODEC_G729 - case kDecoderG729: - *dec = new decoder_G729( pt ); - break; - case NETEQ_CODEC_G729D: - printf("Error: G729D not supported\n"); - break; -#endif -#ifdef CODEC_G729E - case NETEQ_CODEC_G729E: - *dec = new decoder_G729E( pt ); - break; -#endif -#ifdef CODEC_G729_1 - case kDecoderG729_1: - *dec = new decoder_G729_1( pt ); - break; -#endif -#ifdef CODEC_G723 - case NETEQ_CODEC_G723: - *dec = new decoder_G723( pt ); - break; -#endif -#ifdef CODEC_PCM16B - case kDecoderPCM16B: - *dec = new decoder_PCM16B_NB( pt ); - break; -#endif -#ifdef CODEC_PCM16B_WB - case kDecoderPCM16Bwb: - *dec = new decoder_PCM16B_WB( pt ); - break; -#endif -#ifdef CODEC_PCM16B_32KHZ - case kDecoderPCM16Bswb32kHz: - *dec = new decoder_PCM16B_SWB32( pt ); - break; -#endif -#ifdef CODEC_PCM16B_48KHZ - case kDecoderPCM16Bswb48kHz: - *dec = new decoder_PCM16B_SWB48( pt ); - break; -#endif -#ifdef CODEC_DVI4 - case NETEQ_CODEC_DVI4: - *dec = new decoder_DVI4( pt ); - break; -#endif -#ifdef CODEC_G722 - case kDecoderG722: - *dec = new decoder_G722( pt ); - break; -#endif -#ifdef CODEC_G722_1_16 - case kDecoderG722_1_16: - *dec = new decoder_G722_1_16( pt ); - break; -#endif -#ifdef CODEC_G722_1_24 - case kDecoderG722_1_24: - *dec = new decoder_G722_1_24( pt ); - break; -#endif -#ifdef CODEC_G722_1_32 - case kDecoderG722_1_32: - *dec = new decoder_G722_1_32( pt ); - break; -#endif -#ifdef CODEC_G722_1C_24 - case kDecoderG722_1C_24: - *dec = new decoder_G722_1C_24( pt ); - break; -#endif -#ifdef CODEC_G722_1C_32 - case kDecoderG722_1C_32: - *dec = new decoder_G722_1C_32( pt ); - break; -#endif -#ifdef CODEC_G722_1C_48 - case kDecoderG722_1C_48: - *dec = new decoder_G722_1C_48( pt ); - break; -#endif -#ifdef CODEC_AMR - case kDecoderAMR: - *dec = new decoder_AMR( pt ); - break; -#endif -#ifdef CODEC_AMRWB - case kDecoderAMRWB: - *dec = new decoder_AMRWB( pt ); - break; -#endif -#ifdef CODEC_GSMFR - case kDecoderGSMFR: - *dec = new decoder_GSMFR( pt ); - break; -#endif -#ifdef CODEC_GSMEFR - case NETEQ_CODEC_GSMEFR: - *dec = new decoder_GSMEFR( pt ); - break; -#endif -#ifdef CODEC_G726 - case kDecoderG726_16: - *dec = new decoder_G726_16( pt ); - break; - case kDecoderG726_24: - *dec = new decoder_G726_24( pt ); - break; - case kDecoderG726_32: - *dec = new decoder_G726_32( pt ); - break; - case kDecoderG726_40: - *dec = new decoder_G726_40( pt ); - break; -#endif -#ifdef CODEC_MELPE - case NETEQ_CODEC_MELPE: -#if (_MSC_VER >= 1400) && !defined(_WIN64) // only for Visual 2005 or later, and not for x64 - *dec = new decoder_MELPE( pt ); -#endif - break; -#endif -#ifdef CODEC_SPEEX_8 - case kDecoderSPEEX_8: - *dec = new decoder_SPEEX( pt, 8000 ); - break; -#endif -#ifdef CODEC_SPEEX_16 - case kDecoderSPEEX_16: - *dec = new decoder_SPEEX( pt, 16000 ); - break; -#endif -#ifdef CODEC_CELT_32 - case kDecoderCELT_32: - if (channelNumber == 0) - *dec = new decoder_CELT( pt, 32000 ); - else - *dec = new decoder_CELTslave( pt, 32000 ); - break; -#endif -#ifdef CODEC_RED - case kDecoderRED: - *dec = new decoder_RED( pt ); - break; -#endif -#ifdef CODEC_ATEVENT_DECODE - case kDecoderAVT: - *dec = new decoder_AVT( pt ); - break; -#endif -#if (defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) - case kDecoderCNG: - *dec = new decoder_CNG( pt, static_cast((*it).second.fs) ); - break; -#endif -#ifdef CODEC_ISACLC - case NETEQ_CODEC_ISACLC: - *dec = new decoder_iSACLC( pt ); - break; -#endif -#ifdef CODEC_SILK_NB - case NETEQ_CODEC_SILK_8: -#if (_MSC_VER >= 1400) && !defined(_WIN64) // only for Visual 2005 or later, and not for x64 - *dec = new decoder_SILK8( pt ); -#endif - break; -#endif -#ifdef CODEC_SILK_WB - case NETEQ_CODEC_SILK_12: -#if (_MSC_VER >= 1400) && !defined(_WIN64) // only for Visual 2005 or later, and not for x64 - *dec = new decoder_SILK12( pt ); -#endif - break; -#endif -#ifdef CODEC_SILK_WB - case NETEQ_CODEC_SILK_16: -#if (_MSC_VER >= 1400) && !defined(_WIN64) // only for Visual 2005 or later, and not for x64 - *dec = new decoder_SILK16( pt ); -#endif - break; -#endif -#ifdef CODEC_SILK_SWB - case NETEQ_CODEC_SILK_24: -#if (_MSC_VER >= 1400) && !defined(_WIN64) // only for Visual 2005 or later, and not for x64 - *dec = new decoder_SILK24( pt ); -#endif - break; -#endif - - default: - printf("Unknown codec type encountered in createAndInsertDecoders\n"); - exit(0); - } - - // insert into codec DB - if (*dec) - { - (*dec)->loadToNetEQ(*neteq); - } - } - } - -} - - -void free_coders(std::map & decoders) -{ - std::map::iterator it; - - for (it = decoders.begin(); it != decoders.end(); it++) - { - if ((*it).second.decoder[0]) - { - delete (*it).second.decoder[0]; - } - - if ((*it).second.decoder[1]) - { - delete (*it).second.decoder[1]; - } - } -} - - - -#include "pcm16b.h" -#include "g711_interface.h" -#include "isac.h" - -int doAPItest() { - - void *inst; - enum WebRtcNetEQDecoder usedCodec; - int NetEqBufferMaxPackets, BufferSizeInBytes; - WebRtcNetEQ_CodecDef codecInst; - WebRtcNetEQ_RTCPStat RTCPstat; - uint32_t timestamp; - int memorySize; - int ok; - int overhead_bytes; - - printf("API-test:\n\n"); - - /* test that API functions return -1 if instance is NULL */ -#define CHECK_MINUS_ONE(x) {int errCode = x; if((errCode)!=-1){printf("\n API test failed at line %d: %s. Function did not return -1 as expected\n",__LINE__,#x); return(-1);}} -//#define RESET_ERROR(x) ((MainInst_t*) x)->ErrorCode = 0; - inst = NULL; - - CHECK_MINUS_ONE(WebRtcNetEQ_GetErrorCode(inst)) - CHECK_MINUS_ONE(WebRtcNetEQ_Assign(&inst, NULL)) -// printf("WARNING: Test of WebRtcNetEQ_Assign() is disabled due to a bug.\n"); - usedCodec=kDecoderPCMu; - CHECK_MINUS_ONE(WebRtcNetEQ_GetRecommendedBufferSize(inst, &usedCodec, 1, kTCPLargeJitter, &NetEqBufferMaxPackets, &BufferSizeInBytes, &overhead_bytes)) - CHECK_MINUS_ONE(WebRtcNetEQ_AssignBuffer(inst, NetEqBufferMaxPackets, NetEqPacketBuffer, BufferSizeInBytes)) - - CHECK_MINUS_ONE(WebRtcNetEQ_Init(inst, 8000)) - CHECK_MINUS_ONE(WebRtcNetEQ_SetAVTPlayout(inst, 0)) - CHECK_MINUS_ONE(WebRtcNetEQ_SetExtraDelay(inst, 17)) - CHECK_MINUS_ONE(WebRtcNetEQ_SetPlayoutMode(inst, kPlayoutOn)) - - CHECK_MINUS_ONE(WebRtcNetEQ_CodecDbReset(inst)) - CHECK_MINUS_ONE(WebRtcNetEQ_CodecDbAdd(inst, &codecInst)) - CHECK_MINUS_ONE(WebRtcNetEQ_CodecDbRemove(inst, usedCodec)) - int16_t temp1, temp2; - CHECK_MINUS_ONE(WebRtcNetEQ_CodecDbGetSizeInfo(inst, &temp1, &temp2)) - CHECK_MINUS_ONE(WebRtcNetEQ_CodecDbGetCodecInfo(inst, 0, &usedCodec)) - - CHECK_MINUS_ONE(WebRtcNetEQ_RecIn(inst, &temp1, 17, 4711)) - CHECK_MINUS_ONE(WebRtcNetEQ_RecOut(inst, &temp1, &temp2)) - CHECK_MINUS_ONE(WebRtcNetEQ_GetRTCPStats(inst, &RTCPstat)); // error here!!! - CHECK_MINUS_ONE(WebRtcNetEQ_GetSpeechTimeStamp(inst, ×tamp)) - WebRtcNetEQOutputType temptype; - CHECK_MINUS_ONE(WebRtcNetEQ_GetSpeechOutputType(inst, &temptype)) - - uint8_t tempFlags; - uint16_t utemp1, utemp2; - CHECK_MINUS_ONE(WebRtcNetEQ_VQmonRecOutStatistics(inst, &utemp1, &utemp2, &tempFlags)) - CHECK_MINUS_ONE(WebRtcNetEQ_VQmonGetRxStatistics(inst, &utemp1, &utemp2)) - - WebRtcNetEQ_AssignSize(&memorySize); - CHECK_ZERO(WebRtcNetEQ_Assign(&inst, malloc(memorySize))) - - /* init with wrong sample frequency */ - CHECK_MINUS_ONE(WebRtcNetEQ_Init(inst, 17)) - - /* init with correct fs */ - CHECK_ZERO(WebRtcNetEQ_Init(inst, 8000)) - - /* GetRecommendedBufferSize with wrong codec */ - usedCodec=kDecoderReservedStart; - ok = WebRtcNetEQ_GetRecommendedBufferSize(inst, &usedCodec, 1, kTCPLargeJitter , &NetEqBufferMaxPackets, &BufferSizeInBytes, &overhead_bytes); - if((ok!=-1) || ((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_UNKNOWN_CODEC))){ - printf("WebRtcNetEQ_GetRecommendedBufferSize() did not return proper error code for wrong codec.\n"); - printf("return value = %d; error code = %d\n", ok, WebRtcNetEQ_GetErrorCode(inst)); - } - //RESET_ERROR(inst) - - /* GetRecommendedBufferSize with wrong network type */ - usedCodec = kDecoderPCMu; - ok=WebRtcNetEQ_GetRecommendedBufferSize(inst, &usedCodec, 1, (enum WebRtcNetEQNetworkType) 4711 , &NetEqBufferMaxPackets, &BufferSizeInBytes, &overhead_bytes); - if ((ok!=-1) || ((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-FAULTY_NETWORK_TYPE))) { - printf("WebRtcNetEQ_GetRecommendedBufferSize() did not return proper error code for wrong network type.\n"); - printf("return value = %d; error code = %d\n", ok, WebRtcNetEQ_GetErrorCode(inst)); - //RESET_ERROR(inst) - } - CHECK_ZERO(WebRtcNetEQ_GetRecommendedBufferSize(inst, &usedCodec, 1, kTCPLargeJitter , &NetEqBufferMaxPackets, &BufferSizeInBytes, &overhead_bytes)) - - /* try to do RecIn before assigning the packet buffer */ -/* makeRTPheader(rtp_data, NETEQ_CODEC_AVT_PT, 17,4711, 1235412312); - makeDTMFpayload(&rtp_data[12], 1, 1, 10, 100); - ok = WebRtcNetEQ_RecIn(inst, (short *) rtp_data, 12+4, 4711); - printf("return value = %d; error code = %d\n", ok, WebRtcNetEQ_GetErrorCode(inst));*/ - - /* check all limits of WebRtcNetEQ_AssignBuffer */ - ok=WebRtcNetEQ_AssignBuffer(inst, NetEqBufferMaxPackets, NetEqPacketBuffer, 149<<1); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-PBUFFER_INIT_ERROR))) { - printf("WebRtcNetEQ_AssignBuffer() did not return proper error code for wrong sizeinbytes\n"); - } - ok=WebRtcNetEQ_AssignBuffer(inst, NetEqBufferMaxPackets, NULL, BufferSizeInBytes); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-PBUFFER_INIT_ERROR))) { - printf("WebRtcNetEQ_AssignBuffer() did not return proper error code for NULL memory pointer\n"); - } - ok=WebRtcNetEQ_AssignBuffer(inst, 1, NetEqPacketBuffer, BufferSizeInBytes); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-PBUFFER_INIT_ERROR))) { - printf("WebRtcNetEQ_AssignBuffer() did not return proper error code for wrong MaxNoOfPackets\n"); - } - ok=WebRtcNetEQ_AssignBuffer(inst, 601, NetEqPacketBuffer, BufferSizeInBytes); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-PBUFFER_INIT_ERROR))) { - printf("WebRtcNetEQ_AssignBuffer() did not return proper error code for wrong MaxNoOfPackets\n"); - } - - /* do correct assignbuffer */ - CHECK_ZERO(WebRtcNetEQ_AssignBuffer(inst, NetEqBufferMaxPackets, NetEqPacketBuffer, BufferSizeInBytes)) - - ok=WebRtcNetEQ_SetExtraDelay(inst, -1); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-FAULTY_DELAYVALUE))) { - printf("WebRtcNetEQ_SetExtraDelay() did not return proper error code for too small delay\n"); - } - ok=WebRtcNetEQ_SetExtraDelay(inst, 1001); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-FAULTY_DELAYVALUE))) { - printf("WebRtcNetEQ_SetExtraDelay() did not return proper error code for too large delay\n"); - } - - ok=WebRtcNetEQ_SetPlayoutMode(inst,(enum WebRtcNetEQPlayoutMode) 4711); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-FAULTY_PLAYOUTMODE))) { - printf("WebRtcNetEQ_SetPlayoutMode() did not return proper error code for wrong mode\n"); - } - - /* number of codecs should return zero before adding any codecs */ - WebRtcNetEQ_CodecDbGetSizeInfo(inst, &temp1, &temp2); - if(temp1!=0) - printf("WebRtcNetEQ_CodecDbGetSizeInfo() return non-zero number of codecs in DB before adding any codecs\n"); - - /* get info from empty database */ - ok=WebRtcNetEQ_CodecDbGetCodecInfo(inst, 17, &usedCodec); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_NOT_EXIST1))) { - printf("WebRtcNetEQ_CodecDbGetCodecInfo() did not return proper error code for out-of-range entry number\n"); - } - - /* remove codec from empty database */ - ok=WebRtcNetEQ_CodecDbRemove(inst,kDecoderPCMa); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_NOT_EXIST4))) { - printf("WebRtcNetEQ_CodecDbRemove() did not return proper error code when removing codec that has not been added\n"); - } - - /* add codec with unsupported fs */ -#ifdef CODEC_PCM16B -#ifndef NETEQ_48KHZ_WIDEBAND - SET_CODEC_PAR(codecInst,kDecoderPCM16Bswb48kHz,77,NULL,48000); - SET_PCM16B_SWB48_FUNCTIONS(codecInst); - ok=WebRtcNetEQ_CodecDbAdd(inst, &codecInst); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_UNSUPPORTED_FS))) { - printf("WebRtcNetEQ_CodecDbAdd() did not return proper error code when adding codec with unsupported sample freq\n"); - } -#else - printf("Could not test adding codec with unsupported sample frequency since NetEQ is compiled with 48kHz support.\n"); -#endif -#else - printf("Could not test adding codec with unsupported sample frequency since NetEQ is compiled without PCM16B support.\n"); -#endif - - /* add two codecs with identical payload types */ - SET_CODEC_PAR(codecInst,kDecoderPCMa,17,NULL,8000); - SET_PCMA_FUNCTIONS(codecInst); - CHECK_ZERO(WebRtcNetEQ_CodecDbAdd(inst, &codecInst)) - - SET_CODEC_PAR(codecInst,kDecoderPCMu,17,NULL,8000); - SET_PCMU_FUNCTIONS(codecInst); - ok=WebRtcNetEQ_CodecDbAdd(inst, &codecInst); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_PAYLOAD_TAKEN))) { - printf("WebRtcNetEQ_CodecDbAdd() did not return proper error code when adding two codecs with identical payload types\n"); - } - - /* try adding several payload types for CNG codecs */ - SET_CODEC_PAR(codecInst,kDecoderCNG,105,NULL,16000); - SET_CNG_FUNCTIONS(codecInst); - CHECK_ZERO(WebRtcNetEQ_CodecDbAdd(inst, &codecInst)) - SET_CODEC_PAR(codecInst,kDecoderCNG,13,NULL,8000); - SET_CNG_FUNCTIONS(codecInst); - CHECK_ZERO(WebRtcNetEQ_CodecDbAdd(inst, &codecInst)) - - /* try adding a speech codec over a CNG codec */ - SET_CODEC_PAR(codecInst,kDecoderISAC,105,NULL,16000); /* same as WB CNG above */ - SET_ISAC_FUNCTIONS(codecInst); - ok=WebRtcNetEQ_CodecDbAdd(inst, &codecInst); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_PAYLOAD_TAKEN))) { - printf("WebRtcNetEQ_CodecDbAdd() did not return proper error code when adding a speech codec over a CNG codec\n"); - } - - /* try adding a CNG codec over a speech codec */ - SET_CODEC_PAR(codecInst,kDecoderCNG,17,NULL,32000); /* same as PCMU above */ - SET_CNG_FUNCTIONS(codecInst); - ok=WebRtcNetEQ_CodecDbAdd(inst, &codecInst); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_PAYLOAD_TAKEN))) { - printf("WebRtcNetEQ_CodecDbAdd() did not return proper error code when adding a speech codec over a CNG codec\n"); - } - - - /* remove codec out of range */ - ok=WebRtcNetEQ_CodecDbRemove(inst,kDecoderReservedStart); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_UNSUPPORTED_CODEC))) { - printf("WebRtcNetEQ_CodecDbRemove() did not return proper error code when removing codec that is out of range\n"); - } - ok=WebRtcNetEQ_CodecDbRemove(inst,kDecoderReservedEnd); - if((ok!=-1)||((ok==-1)&&(WebRtcNetEQ_GetErrorCode(inst)!=-CODEC_DB_UNSUPPORTED_CODEC))) { - printf("WebRtcNetEQ_CodecDbRemove() did not return proper error code when removing codec that is out of range\n"); - } - - /*SET_CODEC_PAR(codecInst,kDecoderEG711a,NETEQ_CODEC_EG711A_PT,NetEqiPCMAState,8000); - SET_IPCMA_FUNCTIONS(codecInst); - CHECK_ZERO(WebRtcNetEQ_CodecDbAdd(inst, &codecInst)) -*/ - free(inst); - - return(0); - -} diff --git a/webrtc/modules/audio_coding/neteq/test/PayloadTypes.h b/webrtc/modules/audio_coding/neteq/test/PayloadTypes.h index f74e24581..f6cc3da80 100644 --- a/webrtc/modules/audio_coding/neteq/test/PayloadTypes.h +++ b/webrtc/modules/audio_coding/neteq/test/PayloadTypes.h @@ -35,7 +35,6 @@ #define NETEQ_CODEC_ISAC_PT 103 #define NETEQ_CODEC_ISACLC_PT 119 #define NETEQ_CODEC_ISACSWB_PT 104 -#define NETEQ_CODEC_ISACFB_PT 124 #define NETEQ_CODEC_AVT_PT 106 #define NETEQ_CODEC_G722_1_16_PT 108 #define NETEQ_CODEC_G722_1_24_PT 109 @@ -54,7 +53,7 @@ #define NETEQ_CODEC_CN_SWB_PT 126 #define NETEQ_CODEC_G729_1_PT 107 #define NETEQ_CODEC_G729D_PT 123 -//#define NETEQ_CODEC_MELPE_PT 124 +#define NETEQ_CODEC_MELPE_PT 124 #define NETEQ_CODEC_CELT32_PT 114 /* Extra dynamic codepoints */ diff --git a/webrtc/modules/audio_coding/neteq/test/RTPcat.cc b/webrtc/modules/audio_coding/neteq/test/RTPcat.cc index 001b00b54..f06b574f0 100644 --- a/webrtc/modules/audio_coding/neteq/test/RTPcat.cc +++ b/webrtc/modules/audio_coding/neteq/test/RTPcat.cc @@ -14,7 +14,7 @@ #include #include "gtest/gtest.h" -#include "modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" +#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" #define FIRSTLINELEN 40 diff --git a/webrtc/modules/audio_coding/neteq/test/RTPchange.cc b/webrtc/modules/audio_coding/neteq/test/RTPchange.cc index bdd5adef6..54395c026 100644 --- a/webrtc/modules/audio_coding/neteq/test/RTPchange.cc +++ b/webrtc/modules/audio_coding/neteq/test/RTPchange.cc @@ -13,8 +13,8 @@ #include #include -#include "modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" -#include "modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.h" +#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.h" +#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" #define FIRSTLINELEN 40 //#define WEBRTC_DUMMY_RTP @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) { uint32_t send_time; while (fscanf(stat_file, - "%hu %u %u %*i %*i %*i %*x\n", &seq_no, &ts, &send_time) == 3) { + "%hu %u %u %*i %*i\n", &seq_no, &ts, &send_time) == 3) { std::pair temp_pair = std::pair(seq_no, ts); diff --git a/webrtc/modules/audio_coding/neteq/test/RTPencode.cc b/webrtc/modules/audio_coding/neteq/test/RTPencode.cc index 46b9cd173..93b366b9a 100644 --- a/webrtc/modules/audio_coding/neteq/test/RTPencode.cc +++ b/webrtc/modules/audio_coding/neteq/test/RTPencode.cc @@ -11,13 +11,9 @@ //TODO(hlundin): Reformat file to meet style guide. /* header includes */ -#include "stdio.h" -#include "typedefs.h" -#include "webrtc_neteq.h" // needed for enum WebRtcNetEQDecoder -#include +#include #include #include - #ifdef WIN32 #include #endif @@ -25,6 +21,12 @@ #include #endif +#include + +#include "webrtc/typedefs.h" +// needed for NetEqDecoder +#include "webrtc/modules/audio_coding/neteq/interface/audio_decoder.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" /************************/ /* Define payload types */ @@ -69,10 +71,10 @@ /* Function declarations */ /*************************/ -void NetEQTest_GetCodec_and_PT(char * name, enum WebRtcNetEQDecoder *codec, int *PT, int frameLen, int *fs, int *bitrate, int *useRed); -int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int bitrate, int sampfreq , int vad, int numChannels); -void defineCodecs(enum WebRtcNetEQDecoder *usedCodec, int *noOfCodecs ); -int NetEQTest_free_coders(enum WebRtcNetEQDecoder coder, int numChannels); +void NetEQTest_GetCodec_and_PT(char * name, webrtc::NetEqDecoder *codec, int *PT, int frameLen, int *fs, int *bitrate, int *useRed); +int NetEQTest_init_coders(webrtc::NetEqDecoder coder, int enc_frameSize, int bitrate, int sampfreq , int vad, int numChannels); +void defineCodecs(webrtc::NetEqDecoder *usedCodec, int *noOfCodecs ); +int NetEQTest_free_coders(webrtc::NetEqDecoder coder, int numChannels); int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * encoded,int sampleRate , int * vad, int useVAD, int bitrate, int numChannels); void makeRTPheader(unsigned char* rtp_data, int payloadType, int seqNo, uint32_t timestamp, uint32_t ssrc); int makeRedundantHeader(unsigned char* rtp_data, int *payloadType, int numPayloads, uint32_t *timestamp, uint16_t *blockLen, @@ -110,7 +112,7 @@ void stereoInterleave(unsigned char* data, int dataLen, int stride); #ifdef CODEC_ILBC #include "ilbc.h" #endif -#if (defined CODEC_ISAC || defined CODEC_ISAC_SWB || defined CODEC_ISAC_FB) +#if (defined CODEC_ISAC || defined CODEC_ISAC_SWB) #include "isac.h" #endif #ifdef NETEQ_ISACFIX_CODEC @@ -217,9 +219,6 @@ WebRtcVadInst *VAD_inst[2]; #ifdef CODEC_ISAC_SWB ISACStruct *ISACSWB_inst[2]; #endif -#ifdef CODEC_ISAC_FB - ISACStruct *ISACFB_inst[2]; -#endif #ifdef CODEC_GSMFR GSMFR_encinst_t *GSMFRenc_inst[2]; #endif @@ -244,7 +243,7 @@ WebRtcVadInst *VAD_inst[2]; int main(int argc, char* argv[]) { int packet_size, fs; - enum WebRtcNetEQDecoder usedCodec; + webrtc::NetEqDecoder usedCodec; int payloadType; int bitrate = 0; int useVAD, vad; @@ -365,9 +364,6 @@ int main(int argc, char* argv[]) #ifdef CODEC_ISAC_SWB printf(" : isacswb iSAC SWB (32kHz and 32.0-52.0 kbps). To set rate specify a rate parameter as last parameter\n"); #endif -#ifdef CODEC_ISAC_FB - printf(" : isacfb iSAC FB (48kHz encoder 32kHz decoder and 32.0-52.0 kbps). To set rate specify a rate parameter as last parameter\n"); -#endif #ifdef CODEC_GSMFR printf(" : gsmfr GSM FR codec (8kHz and 13kbps)\n"); #endif @@ -443,18 +439,18 @@ int main(int argc, char* argv[]) switch(usedCodec) { // sample based codecs - case kDecoderPCMu: - case kDecoderPCMa: - case kDecoderG722: + case webrtc::kDecoderPCMu: + case webrtc::kDecoderPCMa: + case webrtc::kDecoderG722: { // 1 octet per sample stereoMode = STEREO_MODE_SAMPLE_1; break; } - case kDecoderPCM16B: - case kDecoderPCM16Bwb: - case kDecoderPCM16Bswb32kHz: - case kDecoderPCM16Bswb48kHz: + case webrtc::kDecoderPCM16B: + case webrtc::kDecoderPCM16Bwb: + case webrtc::kDecoderPCM16Bswb32kHz: + case webrtc::kDecoderPCM16Bswb48kHz: { // 2 octets per sample stereoMode = STEREO_MODE_SAMPLE_2; @@ -462,24 +458,6 @@ int main(int argc, char* argv[]) } // fixed-rate frame codecs (with internal VAD) - case kDecoderG729: - { - if(useVAD) { - printf("Cannot use codec-internal VAD and stereo\n"); - exit(0); - } - // break intentionally omitted - } - case kDecoderG722_1_16: - case kDecoderG722_1_24: - case kDecoderG722_1_32: - case kDecoderG722_1C_24: - case kDecoderG722_1C_32: - case kDecoderG722_1C_48: - { - stereoMode = STEREO_MODE_FRAME; - break; - } default: { printf("Cannot use codec %s as stereo codec\n", argv[4]); @@ -488,18 +466,17 @@ int main(int argc, char* argv[]) } } - if ((usedCodec == kDecoderISAC) || (usedCodec == kDecoderISACswb) || - (usedCodec == kDecoderISACfb)) + if ((usedCodec == webrtc::kDecoderISAC) || (usedCodec == webrtc::kDecoderISACswb)) { if (argc != 7) { - if (usedCodec == kDecoderISAC) + if (usedCodec == webrtc::kDecoderISAC) { bitrate = 32000; printf( "Running iSAC at default bitrate of 32000 bps (to specify explicitly add the bps as last parameter)\n"); } - else // usedCodec == kDecoderISACswb || usedCodec == kDecoderISACfb + else // (usedCodec==webrtc::kDecoderISACswb) { bitrate = 56000; printf( @@ -509,7 +486,7 @@ int main(int argc, char* argv[]) else { bitrate = atoi(argv[6]); - if (usedCodec == kDecoderISAC) + if (usedCodec == webrtc::kDecoderISAC) { if ((bitrate < 10000) || (bitrate > 32000)) { @@ -520,12 +497,12 @@ int main(int argc, char* argv[]) } printf("Running iSAC at bitrate of %i bps\n", bitrate); } - else // usedCodec == kDecoderISACswb || usedCodec == kDecoderISACfb + else // (usedCodec==webrtc::kDecoderISACswb) { if ((bitrate < 32000) || (bitrate > 56000)) { printf( - "Error: iSAC SWB/FB bitrate must be between 32000 and 56000 bps (%i is invalid)\n", + "Error: iSAC SWB bitrate must be between 32000 and 56000 bps (%i is invalid)\n", bitrate); exit(0); } @@ -775,7 +752,7 @@ int main(int argc, char* argv[]) if(useRed) { /* move data to redundancy store */ #ifdef CODEC_ISAC - if(usedCodec==kDecoderISAC) + if(usedCodec==webrtc::kDecoderISAC) { assert(!usingStereo); // Cannot handle stereo yet red_len[0] = WebRtcIsac_GetRedPayload(ISAC_inst[0], (int16_t*)red_data); @@ -825,205 +802,73 @@ int main(int argc, char* argv[]) /* Subfunctions */ /****************/ -void NetEQTest_GetCodec_and_PT(char * name, enum WebRtcNetEQDecoder *codec, int *PT, int frameLen, int *fs, int *bitrate, int *useRed) { +void NetEQTest_GetCodec_and_PT(char * name, webrtc::NetEqDecoder *codec, int *PT, int frameLen, int *fs, int *bitrate, int *useRed) { *bitrate = 0; /* Default bitrate setting */ *useRed = 0; /* Default no redundancy */ if(!strcmp(name,"pcmu")){ - *codec=kDecoderPCMu; + *codec=webrtc::kDecoderPCMu; *PT=NETEQ_CODEC_PCMU_PT; *fs=8000; } else if(!strcmp(name,"pcma")){ - *codec=kDecoderPCMa; + *codec=webrtc::kDecoderPCMa; *PT=NETEQ_CODEC_PCMA_PT; *fs=8000; } else if(!strcmp(name,"pcm16b")){ - *codec=kDecoderPCM16B; + *codec=webrtc::kDecoderPCM16B; *PT=NETEQ_CODEC_PCM16B_PT; *fs=8000; } else if(!strcmp(name,"pcm16b_wb")){ - *codec=kDecoderPCM16Bwb; + *codec=webrtc::kDecoderPCM16Bwb; *PT=NETEQ_CODEC_PCM16B_WB_PT; *fs=16000; } else if(!strcmp(name,"pcm16b_swb32")){ - *codec=kDecoderPCM16Bswb32kHz; + *codec=webrtc::kDecoderPCM16Bswb32kHz; *PT=NETEQ_CODEC_PCM16B_SWB32KHZ_PT; *fs=32000; } else if(!strcmp(name,"pcm16b_swb48")){ - *codec=kDecoderPCM16Bswb48kHz; + *codec=webrtc::kDecoderPCM16Bswb48kHz; *PT=NETEQ_CODEC_PCM16B_SWB48KHZ_PT; *fs=48000; } else if(!strcmp(name,"g722")){ - *codec=kDecoderG722; + *codec=webrtc::kDecoderG722; *PT=NETEQ_CODEC_G722_PT; *fs=16000; } - else if(!strcmp(name,"g722.1_16")){ - *codec=kDecoderG722_1_16; - *PT=NETEQ_CODEC_G722_1_16_PT; - *fs=16000; - } - else if(!strcmp(name,"g722.1_24")){ - *codec=kDecoderG722_1_24; - *PT=NETEQ_CODEC_G722_1_24_PT; - *fs=16000; - } - else if(!strcmp(name,"g722.1_32")){ - *codec=kDecoderG722_1_32; - *PT=NETEQ_CODEC_G722_1_32_PT; - *fs=16000; - } - else if(!strcmp(name,"g722.1C_24")){ - *codec=kDecoderG722_1C_24; - *PT=NETEQ_CODEC_G722_1C_24_PT; - *fs=32000; - } - else if(!strcmp(name,"g722.1C_32")){ - *codec=kDecoderG722_1C_32; - *PT=NETEQ_CODEC_G722_1C_32_PT; - *fs=32000; - } - else if(!strcmp(name,"g722.1C_48")){ - *codec=kDecoderG722_1C_48; - *PT=NETEQ_CODEC_G722_1C_48_PT; - *fs=32000; - } - else if(!strcmp(name,"g726_16")){ - *fs=8000; - *codec=kDecoderG726_16; - *PT=NETEQ_CODEC_G726_16_PT; - *bitrate=16; - } - else if(!strcmp(name,"g726_24")){ - *fs=8000; - *codec=kDecoderG726_24; - *PT=NETEQ_CODEC_G726_24_PT; - *bitrate=24; - } - else if(!strcmp(name,"g726_32")){ - *fs=8000; - *codec=kDecoderG726_32; - *PT=NETEQ_CODEC_G726_32_PT; - *bitrate=32; - } - else if(!strcmp(name,"g726_40")){ - *fs=8000; - *codec=kDecoderG726_40; - *PT=NETEQ_CODEC_G726_40_PT; - *bitrate=40; - } - else if((!strcmp(name,"amr4.75k"))||(!strcmp(name,"amr5.15k"))||(!strcmp(name,"amr5.9k"))|| - (!strcmp(name,"amr6.7k"))||(!strcmp(name,"amr7.4k"))||(!strcmp(name,"amr7.95k"))|| - (!strcmp(name,"amr10.2k"))||(!strcmp(name,"amr12.2k"))) { - *fs=8000; - if (!strcmp(name,"amr4.75k")) - *bitrate = 0; - if (!strcmp(name,"amr5.15k")) - *bitrate = 1; - if (!strcmp(name,"amr5.9k")) - *bitrate = 2; - if (!strcmp(name,"amr6.7k")) - *bitrate = 3; - if (!strcmp(name,"amr7.4k")) - *bitrate = 4; - if (!strcmp(name,"amr7.95k")) - *bitrate = 5; - if (!strcmp(name,"amr10.2k")) - *bitrate = 6; - if (!strcmp(name,"amr12.2k")) - *bitrate = 7; - *codec=kDecoderAMR; - *PT=NETEQ_CODEC_AMR_PT; - } - else if((!strcmp(name,"amrwb7k"))||(!strcmp(name,"amrwb9k"))||(!strcmp(name,"amrwb12k"))|| - (!strcmp(name,"amrwb14k"))||(!strcmp(name,"amrwb16k"))||(!strcmp(name,"amrwb18k"))|| - (!strcmp(name,"amrwb20k"))||(!strcmp(name,"amrwb23k"))||(!strcmp(name,"amrwb24k"))) { - *fs=16000; - if (!strcmp(name,"amrwb7k")) - *bitrate = 7000; - if (!strcmp(name,"amrwb9k")) - *bitrate = 9000; - if (!strcmp(name,"amrwb12k")) - *bitrate = 12000; - if (!strcmp(name,"amrwb14k")) - *bitrate = 14000; - if (!strcmp(name,"amrwb16k")) - *bitrate = 16000; - if (!strcmp(name,"amrwb18k")) - *bitrate = 18000; - if (!strcmp(name,"amrwb20k")) - *bitrate = 20000; - if (!strcmp(name,"amrwb23k")) - *bitrate = 23000; - if (!strcmp(name,"amrwb24k")) - *bitrate = 24000; - *codec=kDecoderAMRWB; - *PT=NETEQ_CODEC_AMRWB_PT; - } else if((!strcmp(name,"ilbc"))&&((frameLen%240==0)||(frameLen%160==0))){ *fs=8000; - *codec=kDecoderILBC; + *codec=webrtc::kDecoderILBC; *PT=NETEQ_CODEC_ILBC_PT; } else if(!strcmp(name,"isac")){ *fs=16000; - *codec=kDecoderISAC; + *codec=webrtc::kDecoderISAC; *PT=NETEQ_CODEC_ISAC_PT; } - else if(!strcmp(name,"isacswb")){ - *fs=32000; - *codec=kDecoderISACswb; - *PT=NETEQ_CODEC_ISACSWB_PT; - } - else if(!strcmp(name,"isacfb")){ - *fs=48000; - *codec=kDecoderISACfb; - *PT=NETEQ_CODEC_ISACFB_PT; - } - else if(!strcmp(name,"g729")){ - *fs=8000; - *codec=kDecoderG729; - *PT=NETEQ_CODEC_G729_PT; - } - else if(!strcmp(name,"g729.1")){ - *fs=16000; - *codec=kDecoderG729_1; - *PT=NETEQ_CODEC_G729_1_PT; - } - else if(!strcmp(name,"gsmfr")){ - *fs=8000; - *codec=kDecoderGSMFR; - *PT=NETEQ_CODEC_GSMFR_PT; - } - else if(!strcmp(name,"speex8")){ - *fs=8000; - *codec=kDecoderSPEEX_8; - *PT=NETEQ_CODEC_SPEEX8_PT; - } - else if(!strcmp(name,"speex16")){ - *fs=16000; - *codec=kDecoderSPEEX_16; - *PT=NETEQ_CODEC_SPEEX16_PT; + else if(!strcmp(name,"isacswb")){ + *fs=32000; + *codec=webrtc::kDecoderISACswb; + *PT=NETEQ_CODEC_ISACSWB_PT; } else if(!strcmp(name,"celt32")){ *fs=32000; - *codec=kDecoderCELT_32; + *codec=webrtc::kDecoderCELT_32; *PT=NETEQ_CODEC_CELT32_PT; } else if(!strcmp(name,"red_pcm")){ - *codec=kDecoderPCMa; + *codec=webrtc::kDecoderPCMa; *PT=NETEQ_CODEC_PCMA_PT; /* this will be the PT for the sub-headers */ *fs=8000; *useRed = 1; } else if(!strcmp(name,"red_isac")){ - *codec=kDecoderISAC; + *codec=webrtc::kDecoderISAC; *PT=NETEQ_CODEC_ISAC_PT; /* this will be the PT for the sub-headers */ *fs=16000; *useRed = 1; @@ -1037,7 +882,7 @@ void NetEQTest_GetCodec_and_PT(char * name, enum WebRtcNetEQDecoder *codec, int -int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int bitrate, int sampfreq , int vad, int numChannels){ +int NetEQTest_init_coders(webrtc::NetEqDecoder coder, int enc_frameSize, int bitrate, int sampfreq , int vad, int numChannels){ int ok=0; @@ -1073,27 +918,26 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int #endif switch (coder) { - case kDecoderReservedStart : // dummy codec #ifdef CODEC_PCM16B - case kDecoderPCM16B : + case webrtc::kDecoderPCM16B : #endif #ifdef CODEC_PCM16B_WB - case kDecoderPCM16Bwb : + case webrtc::kDecoderPCM16Bwb : #endif #ifdef CODEC_PCM16B_32KHZ - case kDecoderPCM16Bswb32kHz : + case webrtc::kDecoderPCM16Bswb32kHz : #endif #ifdef CODEC_PCM16B_48KHZ - case kDecoderPCM16Bswb48kHz : + case webrtc::kDecoderPCM16Bswb48kHz : #endif #ifdef CODEC_G711 - case kDecoderPCMu : - case kDecoderPCMa : + case webrtc::kDecoderPCMu : + case webrtc::kDecoderPCMa : #endif // do nothing break; #ifdef CODEC_G729 - case kDecoderG729: + case webrtc::kDecoderG729: if (sampfreq==8000) { if ((enc_frameSize==80)||(enc_frameSize==160)||(enc_frameSize==240)||(enc_frameSize==320)||(enc_frameSize==400)||(enc_frameSize==480)) { ok=WebRtcG729_CreateEnc(&G729enc_inst[k]); @@ -1116,7 +960,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_G729_1 - case kDecoderG729_1: + case webrtc::kDecoderG729_1: if (sampfreq==16000) { if ((enc_frameSize==320)||(enc_frameSize==640)||(enc_frameSize==960) ) { @@ -1142,7 +986,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_SPEEX_8 - case kDecoderSPEEX_8 : + case webrtc::kDecoderSPEEX_8 : if (sampfreq==8000) { if ((enc_frameSize==160)||(enc_frameSize==320)||(enc_frameSize==480)) { ok=WebRtcSpeex_CreateEnc(&SPEEX8enc_inst[k], sampfreq); @@ -1166,7 +1010,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_SPEEX_16 - case kDecoderSPEEX_16 : + case webrtc::kDecoderSPEEX_16 : if (sampfreq==16000) { if ((enc_frameSize==320)||(enc_frameSize==640)||(enc_frameSize==960)) { ok=WebRtcSpeex_CreateEnc(&SPEEX16enc_inst[k], sampfreq); @@ -1190,7 +1034,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_CELT_32 - case kDecoderCELT_32 : + case webrtc::kDecoderCELT_32 : if (sampfreq==32000) { if (enc_frameSize==320) { ok=WebRtcCelt_CreateEnc(&CELT32enc_inst[k], 1 /*mono*/); @@ -1211,7 +1055,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int #endif #ifdef CODEC_G722_1_16 - case kDecoderG722_1_16 : + case webrtc::kDecoderG722_1_16 : if (sampfreq==16000) { ok=WebRtcG7221_CreateEnc16(&G722_1_16enc_inst[k]); if (ok!=0) { @@ -1231,7 +1075,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_G722_1_24 - case kDecoderG722_1_24 : + case webrtc::kDecoderG722_1_24 : if (sampfreq==16000) { ok=WebRtcG7221_CreateEnc24(&G722_1_24enc_inst[k]); if (ok!=0) { @@ -1251,7 +1095,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_G722_1_32 - case kDecoderG722_1_32 : + case webrtc::kDecoderG722_1_32 : if (sampfreq==16000) { ok=WebRtcG7221_CreateEnc32(&G722_1_32enc_inst[k]); if (ok!=0) { @@ -1271,7 +1115,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_G722_1C_24 - case kDecoderG722_1C_24 : + case webrtc::kDecoderG722_1C_24 : if (sampfreq==32000) { ok=WebRtcG7221C_CreateEnc24(&G722_1C_24enc_inst[k]); if (ok!=0) { @@ -1291,7 +1135,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_G722_1C_32 - case kDecoderG722_1C_32 : + case webrtc::kDecoderG722_1C_32 : if (sampfreq==32000) { ok=WebRtcG7221C_CreateEnc32(&G722_1C_32enc_inst[k]); if (ok!=0) { @@ -1311,7 +1155,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_G722_1C_48 - case kDecoderG722_1C_48 : + case webrtc::kDecoderG722_1C_48 : if (sampfreq==32000) { ok=WebRtcG7221C_CreateEnc48(&G722_1C_48enc_inst[k]); if (ok!=0) { @@ -1331,7 +1175,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_G722 - case kDecoderG722 : + case webrtc::kDecoderG722 : if (sampfreq==16000) { if (enc_frameSize%2==0) { } else { @@ -1347,7 +1191,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_AMR - case kDecoderAMR : + case webrtc::kDecoderAMR : if (sampfreq==8000) { ok=WebRtcAmr_CreateEnc(&AMRenc_inst[k]); if (ok!=0) { @@ -1368,7 +1212,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_AMRWB - case kDecoderAMRWB : + case webrtc::kDecoderAMRWB : if (sampfreq==16000) { ok=WebRtcAmrWb_CreateEnc(&AMRWBenc_inst[k]); if (ok!=0) { @@ -1408,7 +1252,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_ILBC - case kDecoderILBC : + case webrtc::kDecoderILBC : if (sampfreq==8000) { ok=WebRtcIlbcfix_EncoderCreate(&iLBCenc_inst[k]); if (ok!=0) { @@ -1434,7 +1278,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_ISAC - case kDecoderISAC: + case webrtc::kDecoderISAC: if (sampfreq==16000) { ok=WebRtcIsac_Create(&ISAC_inst[k]); if (ok!=0) { @@ -1458,7 +1302,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef NETEQ_ISACFIX_CODEC - case kDecoderISAC: + case webrtc::kDecoderISAC: if (sampfreq==16000) { ok=WebRtcIsacfix_Create(&ISAC_inst[k]); if (ok!=0) { @@ -1482,7 +1326,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int break; #endif #ifdef CODEC_ISAC_SWB - case kDecoderISACswb: + case webrtc::kDecoderISACswb: if (sampfreq==32000) { ok=WebRtcIsac_Create(&ISACSWB_inst[k]); if (ok!=0) { @@ -1510,40 +1354,8 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int } break; #endif -#ifdef CODEC_ISAC_FB - case kDecoderISACfb: - if (sampfreq == 48000) { - ok = WebRtcIsac_Create(&ISACFB_inst[k]); - if (ok != 0) { - printf("Error: Couldn't allocate memory for iSAC FB " - "instance\n"); - exit(0); - } - if (enc_frameSize != 1440) { - printf("\nError - iSAC FB only supports frameSize 30 ms\n"); - exit(0); - } - ok = WebRtcIsac_SetEncSampRate(ISACFB_inst[k], 48000); - if (ok != 0) { - printf("Error: Couldn't set sample rate for iSAC FB " - "instance\n"); - exit(0); - } - WebRtcIsac_EncoderInit(ISACFB_inst[k], 1); - if ((bitrate < 32000) || (bitrate > 56000)) { - printf("\nError - iSAC FB bitrate has to be between 32000 and" - "56000 bps (not %i)\n", bitrate); - exit(0); - } - WebRtcIsac_Control(ISACFB_inst[k], bitrate, 30); - } else { - printf("\nError - iSAC FB only support 48 kHz sampling rate.\n"); - exit(0); - } - break; -#endif #ifdef CODEC_GSMFR - case kDecoderGSMFR: + case webrtc::kDecoderGSMFR: if (sampfreq==8000) { ok=WebRtcGSMFR_CreateEnc(&GSMFRenc_inst[k]); if (ok!=0) { @@ -1579,7 +1391,7 @@ int NetEQTest_init_coders(enum WebRtcNetEQDecoder coder, int enc_frameSize, int -int NetEQTest_free_coders(enum WebRtcNetEQDecoder coder, int numChannels) { +int NetEQTest_free_coders(webrtc::NetEqDecoder coder, int numChannels) { for (int k = 0; k < numChannels; k++) { @@ -1591,123 +1403,117 @@ int NetEQTest_free_coders(enum WebRtcNetEQDecoder coder, int numChannels) { switch (coder) { - case kDecoderReservedStart : // dummy codec #ifdef CODEC_PCM16B - case kDecoderPCM16B : + case webrtc::kDecoderPCM16B : #endif #ifdef CODEC_PCM16B_WB - case kDecoderPCM16Bwb : + case webrtc::kDecoderPCM16Bwb : #endif #ifdef CODEC_PCM16B_32KHZ - case kDecoderPCM16Bswb32kHz : + case webrtc::kDecoderPCM16Bswb32kHz : #endif #ifdef CODEC_PCM16B_48KHZ - case kDecoderPCM16Bswb48kHz : + case webrtc::kDecoderPCM16Bswb48kHz : #endif #ifdef CODEC_G711 - case kDecoderPCMu : - case kDecoderPCMa : + case webrtc::kDecoderPCMu : + case webrtc::kDecoderPCMa : #endif // do nothing break; #ifdef CODEC_G729 - case kDecoderG729: + case webrtc::kDecoderG729: WebRtcG729_FreeEnc(G729enc_inst[k]); break; #endif #ifdef CODEC_G729_1 - case kDecoderG729_1: + case webrtc::kDecoderG729_1: WebRtcG7291_Free(G729_1_inst[k]); break; #endif #ifdef CODEC_SPEEX_8 - case kDecoderSPEEX_8 : + case webrtc::kDecoderSPEEX_8 : WebRtcSpeex_FreeEnc(SPEEX8enc_inst[k]); break; #endif #ifdef CODEC_SPEEX_16 - case kDecoderSPEEX_16 : + case webrtc::kDecoderSPEEX_16 : WebRtcSpeex_FreeEnc(SPEEX16enc_inst[k]); break; #endif #ifdef CODEC_CELT_32 - case kDecoderCELT_32 : + case webrtc::kDecoderCELT_32 : WebRtcCelt_FreeEnc(CELT32enc_inst[k]); break; #endif #ifdef CODEC_G722_1_16 - case kDecoderG722_1_16 : + case webrtc::kDecoderG722_1_16 : WebRtcG7221_FreeEnc16(G722_1_16enc_inst[k]); break; #endif #ifdef CODEC_G722_1_24 - case kDecoderG722_1_24 : + case webrtc::kDecoderG722_1_24 : WebRtcG7221_FreeEnc24(G722_1_24enc_inst[k]); break; #endif #ifdef CODEC_G722_1_32 - case kDecoderG722_1_32 : + case webrtc::kDecoderG722_1_32 : WebRtcG7221_FreeEnc32(G722_1_32enc_inst[k]); break; #endif #ifdef CODEC_G722_1C_24 - case kDecoderG722_1C_24 : + case webrtc::kDecoderG722_1C_24 : WebRtcG7221C_FreeEnc24(G722_1C_24enc_inst[k]); break; #endif #ifdef CODEC_G722_1C_32 - case kDecoderG722_1C_32 : + case webrtc::kDecoderG722_1C_32 : WebRtcG7221C_FreeEnc32(G722_1C_32enc_inst[k]); break; #endif #ifdef CODEC_G722_1C_48 - case kDecoderG722_1C_48 : + case webrtc::kDecoderG722_1C_48 : WebRtcG7221C_FreeEnc48(G722_1C_48enc_inst[k]); break; #endif #ifdef CODEC_G722 - case kDecoderG722 : + case webrtc::kDecoderG722 : WebRtcG722_FreeEncoder(g722EncState[k]); break; #endif #ifdef CODEC_AMR - case kDecoderAMR : + case webrtc::kDecoderAMR : WebRtcAmr_FreeEnc(AMRenc_inst[k]); break; #endif #ifdef CODEC_AMRWB - case kDecoderAMRWB : + case webrtc::kDecoderAMRWB : WebRtcAmrWb_FreeEnc(AMRWBenc_inst[k]); break; #endif #ifdef CODEC_ILBC - case kDecoderILBC : + case webrtc::kDecoderILBC : WebRtcIlbcfix_EncoderFree(iLBCenc_inst[k]); break; #endif #ifdef CODEC_ISAC - case kDecoderISAC: + case webrtc::kDecoderISAC: WebRtcIsac_Free(ISAC_inst[k]); break; #endif #ifdef NETEQ_ISACFIX_CODEC - case kDecoderISAC: + case webrtc::kDecoderISAC: WebRtcIsacfix_Free(ISAC_inst[k]); break; #endif #ifdef CODEC_ISAC_SWB - case kDecoderISACswb: + case webrtc::kDecoderISACswb: WebRtcIsac_Free(ISACSWB_inst[k]); break; #endif -#ifdef CODEC_ISAC_FB - case kDecoderISACfb: - WebRtcIsac_Free(ISACFB_inst[k]); - break; -#endif #ifdef CODEC_GSMFR - case kDecoderGSMFR: + case webrtc::kDecoderGSMFR: WebRtcGSMFR_FreeEnc(GSMFRenc_inst[k]); break; #endif @@ -1737,9 +1543,7 @@ int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * e *vad =1; // check VAD first - if(useVAD&& - (coder!=kDecoderG729)&&(coder!=kDecoderAMR)&& - (coder!=kDecoderSPEEX_8)&&(coder!=kDecoderSPEEX_16)) + if(useVAD) { *vad = 0; @@ -1795,97 +1599,35 @@ int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * e for (int k = 0; k < numChannels; k++) { /* Encode with the selected coder type */ - if (coder==kDecoderPCMu) { /*g711 u-law */ + if (coder==webrtc::kDecoderPCMu) { /*g711 u-law */ #ifdef CODEC_G711 cdlen = WebRtcG711_EncodeU(G711state[k], indata, frameLen, (int16_t*) encoded); #endif } - else if (coder==kDecoderPCMa) { /*g711 A-law */ + else if (coder==webrtc::kDecoderPCMa) { /*g711 A-law */ #ifdef CODEC_G711 cdlen = WebRtcG711_EncodeA(G711state[k], indata, frameLen, (int16_t*) encoded); } #endif #ifdef CODEC_PCM16B - else if ((coder==kDecoderPCM16B)||(coder==kDecoderPCM16Bwb)|| - (coder==kDecoderPCM16Bswb32kHz)||(coder==kDecoderPCM16Bswb48kHz)) { /*pcm16b (8kHz, 16kHz, 32kHz or 48kHz) */ + else if ((coder==webrtc::kDecoderPCM16B)||(coder==webrtc::kDecoderPCM16Bwb)|| + (coder==webrtc::kDecoderPCM16Bswb32kHz)||(coder==webrtc::kDecoderPCM16Bswb48kHz)) { /*pcm16b (8kHz, 16kHz, 32kHz or 48kHz) */ cdlen = WebRtcPcm16b_EncodeW16(indata, frameLen, (int16_t*) encoded); } #endif #ifdef CODEC_G722 - else if (coder==kDecoderG722) { /*g722 */ + else if (coder==webrtc::kDecoderG722) { /*g722 */ cdlen=WebRtcG722_Encode(g722EncState[k], indata, frameLen, (int16_t*)encoded); - cdlen=frameLen>>1; - } -#endif -#ifdef CODEC_G722_1_16 - else if (coder==kDecoderG722_1_16) { /* g722.1 16kbit/s mode */ - cdlen=WebRtcG7221_Encode16((G722_1_16_encinst_t*)G722_1_16enc_inst[k], indata, frameLen, (int16_t*)encoded); - } -#endif -#ifdef CODEC_G722_1_24 - else if (coder==kDecoderG722_1_24) { /* g722.1 24kbit/s mode*/ - cdlen=WebRtcG7221_Encode24((G722_1_24_encinst_t*)G722_1_24enc_inst[k], indata, frameLen, (int16_t*)encoded); - } -#endif -#ifdef CODEC_G722_1_32 - else if (coder==kDecoderG722_1_32) { /* g722.1 32kbit/s mode */ - cdlen=WebRtcG7221_Encode32((G722_1_32_encinst_t*)G722_1_32enc_inst[k], indata, frameLen, (int16_t*)encoded); - } -#endif -#ifdef CODEC_G722_1C_24 - else if (coder==kDecoderG722_1C_24) { /* g722.1 32 kHz 24kbit/s mode*/ - cdlen=WebRtcG7221C_Encode24((G722_1C_24_encinst_t*)G722_1C_24enc_inst[k], indata, frameLen, (int16_t*)encoded); - } -#endif -#ifdef CODEC_G722_1C_32 - else if (coder==kDecoderG722_1C_32) { /* g722.1 32 kHz 32kbit/s mode */ - cdlen=WebRtcG7221C_Encode32((G722_1C_32_encinst_t*)G722_1C_32enc_inst[k], indata, frameLen, (int16_t*)encoded); - } -#endif -#ifdef CODEC_G722_1C_48 - else if (coder==kDecoderG722_1C_48) { /* g722.1 32 kHz 48kbit/s mode */ - cdlen=WebRtcG7221C_Encode48((G722_1C_48_encinst_t*)G722_1C_48enc_inst[k], indata, frameLen, (int16_t*)encoded); - } -#endif -#ifdef CODEC_G729 - else if (coder==kDecoderG729) { /*g729 */ - int16_t dataPos=0; - int16_t len=0; - cdlen = 0; - for (dataPos=0;dataPos>1); } #endif #ifdef CODEC_ILBC - else if (coder==kDecoderILBC) { /*iLBC */ + else if (coder==webrtc::kDecoderILBC) { /*iLBC */ cdlen=WebRtcIlbcfix_Encode(iLBCenc_inst[k], indata,frameLen,(int16_t*)encoded); } #endif #if (defined(CODEC_ISAC) || defined(NETEQ_ISACFIX_CODEC)) // TODO(hlundin): remove all NETEQ_ISACFIX_CODEC - else if (coder==kDecoderISAC) { /*iSAC */ + else if (coder==webrtc::kDecoderISAC) { /*iSAC */ int noOfCalls=0; cdlen=0; while (cdlen<=0) { @@ -1899,7 +1641,7 @@ int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * e } #endif #ifdef CODEC_ISAC_SWB - else if (coder==kDecoderISACswb) { /* iSAC SWB */ + else if (coder==webrtc::kDecoderISACswb) { /* iSAC SWB */ int noOfCalls=0; cdlen=0; while (cdlen<=0) { @@ -1908,55 +1650,8 @@ int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * e } } #endif -#ifdef CODEC_ISAC_FB - else if (coder == kDecoderISACfb) { /* iSAC FB */ - int noOfCalls = 0; - cdlen = 0; - while (cdlen <= 0) { - cdlen = WebRtcIsac_Encode(ISACFB_inst[k], - &indata[noOfCalls * 480], - (int16_t*)encoded); - noOfCalls++; - } - } -#endif -#ifdef CODEC_GSMFR - else if (coder==kDecoderGSMFR) { /* GSM FR */ - cdlen=WebRtcGSMFR_Encode(GSMFRenc_inst[k], indata, frameLen, (int16_t*)encoded); - } -#endif -#ifdef CODEC_SPEEX_8 - else if (coder==kDecoderSPEEX_8) { /* Speex */ - int encodedLen = 0; - int retVal = 1; - while (retVal == 1 && encodedLen < frameLen) { - retVal = WebRtcSpeex_Encode(SPEEX8enc_inst[k], &indata[encodedLen], 15000); - encodedLen += 20*8; /* 20 ms */ - } - if( (retVal == 0 && encodedLen != frameLen) || retVal < 0) { - printf("Error encoding speex frame!\n"); - exit(0); - } - cdlen=WebRtcSpeex_GetBitstream(SPEEX8enc_inst[k], (int16_t*)encoded); - } -#endif -#ifdef CODEC_SPEEX_16 - else if (coder==kDecoderSPEEX_16) { /* Speex */ - int encodedLen = 0; - int retVal = 1; - while (retVal == 1 && encodedLen < frameLen) { - retVal = WebRtcSpeex_Encode(SPEEX16enc_inst[k], &indata[encodedLen], 15000); - encodedLen += 20*16; /* 20 ms */ - } - if( (retVal == 0 && encodedLen != frameLen) || retVal < 0) { - printf("Error encoding speex frame!\n"); - exit(0); - } - cdlen=WebRtcSpeex_GetBitstream(SPEEX16enc_inst[k], (int16_t*)encoded); - } -#endif #ifdef CODEC_CELT_32 - else if (coder==kDecoderCELT_32) { /* Celt */ + else if (coder==webrtc::kDecoderCELT_32) { /* Celt */ int encodedLen = 0; cdlen = 0; while (cdlen <= 0) { diff --git a/webrtc/modules/audio_coding/neteq/test/RTPjitter.cc b/webrtc/modules/audio_coding/neteq/test/RTPjitter.cc index 3f9435096..eeb4c9014 100644 --- a/webrtc/modules/audio_coding/neteq/test/RTPjitter.cc +++ b/webrtc/modules/audio_coding/neteq/test/RTPjitter.cc @@ -11,9 +11,10 @@ //TODO(hlundin): Reformat file to meet style guide. /* header includes */ -#include "typedefs.h" +#include #include #include +#include #ifdef WIN32 #include #include @@ -21,16 +22,17 @@ #ifdef WEBRTC_LINUX #include #endif -#include + +#include #include "gtest/gtest.h" +#include "webrtc/typedefs.h" /*********************/ /* Misc. definitions */ /*********************/ #define FIRSTLINELEN 40 -#define CHECK_ZERO(a) {int errCode = a; if((errCode)!=0){fprintf(stderr,"\n %s \n line: %d \n error at %s\n Error Code = %d\n",__FILE__,__LINE__,#a, WebRtcNetEQ_GetErrorCode(inst)); exit(0);}} #define CHECK_NOT_NULL(a) if((a)==NULL){fprintf(stderr,"\n %s \n line: %d \nerror at %s\n",__FILE__,__LINE__,#a );return(-1);} struct arr_time { @@ -62,7 +64,9 @@ int main(int argc, char* argv[]) unsigned int dat_len, rtp_len, Npack, k; arr_time *time_vec; char firstline[FIRSTLINELEN]; - unsigned char *rtp_vec = NULL, **packet_ptr, *temp_packet; + unsigned char* rtp_vec = NULL; + unsigned char** packet_ptr = NULL; + unsigned char* temp_packet = NULL; const unsigned int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; uint16_t len; uint32_t *offset; @@ -111,6 +115,11 @@ int main(int argc, char* argv[]) dat_len++; } + if (dat_len == 0) { + fprintf(stderr, "Error: dat_file is empty, no arrival time is given.\n"); + goto closing; + } + qsort(time_vec,dat_len,sizeof(arr_time),compare_arr_time); @@ -144,6 +153,11 @@ int main(int argc, char* argv[]) len=(uint16_t) fread(&rtp_vec[rtp_len], sizeof(unsigned char), 2, in_file); // read length of next packet } + if (Npack == 0) { + fprintf(stderr, "Error: No RTP packet found.\n"); + goto closing; + } + packet_ptr = (unsigned char **) malloc(Npack*sizeof(unsigned char*)); packet_ptr[0]=rtp_vec; @@ -180,7 +194,10 @@ int main(int argc, char* argv[]) closing: free(time_vec); free(rtp_vec); - fclose(in_file); + if (packet_ptr != NULL) { + free(packet_ptr); + } + fclose(in_file); fclose(dat_file); fclose(out_file); diff --git a/webrtc/modules/audio_coding/neteq/test/RTPtimeshift.cc b/webrtc/modules/audio_coding/neteq/test/RTPtimeshift.cc index 1a0fc672f..15ffdf6a5 100644 --- a/webrtc/modules/audio_coding/neteq/test/RTPtimeshift.cc +++ b/webrtc/modules/audio_coding/neteq/test/RTPtimeshift.cc @@ -22,68 +22,78 @@ #define FIRSTLINELEN 40 -int main(int argc, char* argv[]) { - if (argc < 4 || argc > 6) { - printf( - "Usage: RTPtimeshift in.rtp out.rtp newStartTS " - "[newStartSN [newStartArrTime]]\n"); - exit(1); - } - - FILE *inFile = fopen(argv[1], "rb"); - if (!inFile) { - printf("Cannot open input file %s\n", argv[1]); - return (-1); - } - printf("Input RTP file: %s\n", argv[1]); - - FILE *outFile = fopen(argv[2], "wb"); - if (!outFile) { - printf("Cannot open output file %s\n", argv[2]); - return (-1); - } - printf("Output RTP file: %s\n\n", argv[2]); - - // read file header and write directly to output file - const unsigned int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; - char firstline[FIRSTLINELEN]; - EXPECT_TRUE(fgets(firstline, FIRSTLINELEN, inFile) != NULL); - EXPECT_GT(fputs(firstline, outFile), 0); - EXPECT_EQ(kRtpDumpHeaderSize, - fread(firstline, 1, kRtpDumpHeaderSize, inFile)); - EXPECT_EQ(kRtpDumpHeaderSize, - fwrite(firstline, 1, kRtpDumpHeaderSize, outFile)); - NETEQTEST_RTPpacket packet; - int packLen = packet.readFromFile(inFile); - if (packLen < 0) { - exit(1); - } - - // get new start TS and start SeqNo from arguments - uint32_t TSdiff = atoi(argv[3]) - packet.timeStamp(); - uint16_t SNdiff = 0; - uint32_t ATdiff = 0; - if (argc > 4) { - SNdiff = atoi(argv[4]) - packet.sequenceNumber(); - if (argc > 5) { - ATdiff = atoi(argv[5]) - packet.time(); +int main(int argc, char* argv[]) +{ + if(argc < 4 || argc > 6) + { + printf("Usage: RTPtimeshift in.rtp out.rtp newStartTS [newStartSN [newStartArrTime]]\n"); + exit(1); } - } - while (packLen >= 0) { + FILE *inFile=fopen(argv[1],"rb"); + if (!inFile) + { + printf("Cannot open input file %s\n", argv[1]); + return(-1); + } + printf("Input RTP file: %s\n",argv[1]); + + FILE *outFile=fopen(argv[2],"wb"); + if (!outFile) + { + printf("Cannot open output file %s\n", argv[2]); + return(-1); + } + printf("Output RTP file: %s\n\n",argv[2]); + + // read file header and write directly to output file + const unsigned int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; + char firstline[FIRSTLINELEN]; + EXPECT_TRUE(fgets(firstline, FIRSTLINELEN, inFile) != NULL); + EXPECT_GT(fputs(firstline, outFile), 0); + EXPECT_EQ(kRtpDumpHeaderSize, + fread(firstline, 1, kRtpDumpHeaderSize, inFile)); + EXPECT_EQ(kRtpDumpHeaderSize, + fwrite(firstline, 1, kRtpDumpHeaderSize, outFile)); + NETEQTEST_RTPpacket packet; + int packLen = packet.readFromFile(inFile); + if (packLen < 0) + { + exit(1); + } + + // get new start TS and start SeqNo from arguments + uint32_t TSdiff = atoi(argv[3]) - packet.timeStamp(); + uint16_t SNdiff = 0; + uint32_t ATdiff = 0; + if (argc > 4) + { + int startSN = atoi(argv[4]); + if (startSN >= 0) + SNdiff = startSN - packet.sequenceNumber(); + if (argc > 5) + { + int startTS = atoi(argv[5]); + if (startTS >= 0) + ATdiff = startTS - packet.time(); + } + } + + while (packLen >= 0) + { - packet.setTimeStamp(packet.timeStamp() + TSdiff); - packet.setSequenceNumber(packet.sequenceNumber() + SNdiff); - packet.setTime(packet.time() + ATdiff); + packet.setTimeStamp(packet.timeStamp() + TSdiff); + packet.setSequenceNumber(packet.sequenceNumber() + SNdiff); + packet.setTime(packet.time() + ATdiff); - packet.writeToFile(outFile); + packet.writeToFile(outFile); - packLen = packet.readFromFile(inFile); + packLen = packet.readFromFile(inFile); - } + } - fclose(inFile); - fclose(outFile); + fclose(inFile); + fclose(outFile); - return 0; + return 0; } diff --git a/webrtc/modules/audio_coding/neteq4/test/audio_classifier_test.cc b/webrtc/modules/audio_coding/neteq/test/audio_classifier_test.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/test/audio_classifier_test.cc rename to webrtc/modules/audio_coding/neteq/test/audio_classifier_test.cc index 730406bb9..aa2b61d06 100644 --- a/webrtc/modules/audio_coding/neteq4/test/audio_classifier_test.cc +++ b/webrtc/modules/audio_coding/neteq/test/audio_classifier_test.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/audio_classifier.h" +#include "webrtc/modules/audio_coding/neteq/audio_classifier.h" #include #include diff --git a/webrtc/modules/audio_coding/neteq/test/delay_tool/plot_neteq_delay.m b/webrtc/modules/audio_coding/neteq/test/delay_tool/plot_neteq_delay.m index d1d0cb8ed..bc1c85a20 100644 --- a/webrtc/modules/audio_coding/neteq/test/delay_tool/plot_neteq_delay.m +++ b/webrtc/modules/audio_coding/neteq/test/delay_tool/plot_neteq_delay.m @@ -57,6 +57,7 @@ s.playout_delay=s.playout_delay(ix); s.pt=s.pt(ix); s.optbuf=s.optbuf(ix); + plen=plen(ix); s.decode=s.decode(ix); end @@ -75,30 +76,17 @@ s.playout_delay=s.playout_delay(sort_ix); s.pt=s.pt(sort_ix); -ts_unw = unwrap_ts(s.ts); -unwrapped = any(ts_unw ~= s.ts); -send_t = ts_unw - ts_unw(1); - +send_t=s.ts-s.ts(1); if length(s.fs)<1 warning('No info about sample rate found in file. Using default 8000.'); s.fs(1)=8000; s.fschange_ts(1)=min(s.ts); -elseif s.fschange_ts(1) ~= s.ts(1) - if ~unwrapped - s.fschange_ts(1) = s.ts(1); - else - error('TS wrapped, and sample rate change info is not found at the start of file => problem...') - end +elseif s.fschange_ts(1)>min(s.ts) + s.fschange_ts(1)=min(s.ts); end end_ix=length(send_t); for k=length(s.fs):-1:1 - if (k < length(s.fs) && s.fschange_ts(k) > s.fschange_ts(k+1)) - % The sample rate changes are out of order, probably due to - % packet re-ordering. - warning('fschange_ts is out of order') - continue % Skip to the next one. - end start_ix=find(s.ts==s.fschange_ts(k)); send_t(start_ix:end_ix)=send_t(start_ix:end_ix)/s.fs(k)*1000; s.playout_delay(start_ix:end_ix)=s.playout_delay(start_ix:end_ix)/s.fs(k)*1000; @@ -154,14 +142,12 @@ mean_delay = mean(s.decode(use_ix)+s.playout_delay(use_ix)-send_t(use_ix)); neteq_delay = mean(s.decode(use_ix)+s.playout_delay(use_ix)-s.arrival(use_ix)); -max_neteq_delay = max(s.decode(use_ix)+s.playout_delay(use_ix)-s.arrival(use_ix)); Npack=max(s.sn(delayskip_ix:end))-min(s.sn(delayskip_ix:end))+1; nw_lossrate=(Npack-length(s.sn(delayskip_ix:end)))/Npack; neteq_lossrate=(length(s.sn(delayskip_ix:end))-length(use_ix))/Npack; delay_struct=struct('mean_delay',mean_delay,'neteq_delay',neteq_delay,... - 'max_neteq_delay', max_neteq_delay,... 'nw_lossrate',nw_lossrate,'neteq_lossrate',neteq_lossrate,... 'tot_expand',round(s.tot_expand),'tot_accelerate',round(s.tot_accelerate),... 'tot_preemptive',round(s.tot_preemptive),'tot_time',tot_time,... @@ -174,7 +160,7 @@ else delayvalues=[]; end -end + % SUBFUNCTIONS % @@ -193,15 +179,9 @@ x(n+1:end)=x(n+1:end)-65536; end - jumps=find(abs((diff(x)-1))>65000); + jumps=find(abs((diff(x(n+1:end))-1))>65000); end y=x; -end - -function y = unwrap_ts(x) - max_u32 = 4294967295; % 0xFFFFFFFF - % Use the unwrap function made for unrwapping phase angle in radians. - y = round(max_u32 / (2*pi) * unwrap(x * 2*pi / max_u32)); -end +return; diff --git a/webrtc/modules/audio_coding/neteq/test/neteq_isac_quality_test.cc b/webrtc/modules/audio_coding/neteq/test/neteq_isac_quality_test.cc new file mode 100644 index 000000000..6b0f48286 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/test/neteq_isac_quality_test.cc @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/codecs/isac/fix/interface/isacfix.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h" +#include "webrtc/test/testsupport/fileutils.h" + +using google::RegisterFlagValidator; +using google::ParseCommandLineFlags; +using std::string; +using testing::InitGoogleTest; + +namespace webrtc { +namespace test { + +static const int kIsacBlockDurationMs = 30; +static const int kIsacInputSamplingKhz = 16; +static const int kIsacOutputSamplingKhz = 16; + +// Define switch for input file name. +static bool ValidateInFilename(const char* flagname, const string& value) { + FILE* fid = fopen(value.c_str(), "rb"); + if (fid != NULL) { + fclose(fid); + return true; + } + printf("Invalid input filename."); + return false; +} + +DEFINE_string(in_filename, + ResourcePath("audio_coding/speech_mono_16kHz", "pcm"), + "Filename for input audio (should be 16 kHz sampled mono)."); + +static const bool in_filename_dummy = + RegisterFlagValidator(&FLAGS_in_filename, &ValidateInFilename); + +// Define switch for output file name. +static bool ValidateOutFilename(const char* flagname, const string& value) { + FILE* fid = fopen(value.c_str(), "wb"); + if (fid != NULL) { + fclose(fid); + return true; + } + printf("Invalid output filename."); + return false; +} + +DEFINE_string(out_filename, OutputPath() + "neteq4_isac_quality_test.pcm", + "Name of output audio file."); + +static const bool out_filename_dummy = + RegisterFlagValidator(&FLAGS_out_filename, &ValidateOutFilename); + +// Define switch for bir rate. +static bool ValidateBitRate(const char* flagname, int32_t value) { + if (value >= 10 && value <= 32) + return true; + printf("Invalid bit rate, should be between 10 and 32 kbps."); + return false; +} + +DEFINE_int32(bit_rate_kbps, 32, "Target bit rate (kbps)."); + +static const bool bit_rate_dummy = + RegisterFlagValidator(&FLAGS_bit_rate_kbps, &ValidateBitRate); + +// Define switch for runtime. +static bool ValidateRuntime(const char* flagname, int32_t value) { + if (value > 0) + return true; + printf("Invalid runtime, should be greater than 0."); + return false; +} + +DEFINE_int32(runtime_ms, 10000, "Simulated runtime (milliseconds)."); + +static const bool runtime_dummy = + RegisterFlagValidator(&FLAGS_runtime_ms, &ValidateRuntime); + +class NetEqIsacQualityTest : public NetEqQualityTest { + protected: + NetEqIsacQualityTest(); + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + virtual int EncodeBlock(int16_t* in_data, int block_size_samples, + uint8_t* payload, int max_bytes); + private: + ISACFIX_MainStruct* isac_encoder_; + int bit_rate_kbps_; +}; + +NetEqIsacQualityTest::NetEqIsacQualityTest() + : NetEqQualityTest(kIsacBlockDurationMs, kIsacInputSamplingKhz, + kIsacOutputSamplingKhz, + kDecoderISAC, + 1, + FLAGS_in_filename, + FLAGS_out_filename), + isac_encoder_(NULL), + bit_rate_kbps_(FLAGS_bit_rate_kbps) { +} + +void NetEqIsacQualityTest::SetUp() { + // Create encoder memory. + WebRtcIsacfix_Create(&isac_encoder_); + ASSERT_TRUE(isac_encoder_ != NULL); + EXPECT_EQ(0, WebRtcIsacfix_EncoderInit(isac_encoder_, 1)); + // Set bitrate and block length. + EXPECT_EQ(0, WebRtcIsacfix_Control(isac_encoder_, bit_rate_kbps_ * 1000, + kIsacBlockDurationMs)); + NetEqQualityTest::SetUp(); +} + +void NetEqIsacQualityTest::TearDown() { + // Free memory. + EXPECT_EQ(0, WebRtcIsacfix_Free(isac_encoder_)); + NetEqQualityTest::TearDown(); +} + +int NetEqIsacQualityTest::EncodeBlock(int16_t* in_data, + int block_size_samples, + uint8_t* payload, int max_bytes) { + // ISAC takes 10 ms for every call. + const int subblocks = kIsacBlockDurationMs / 10; + const int subblock_length = 10 * kIsacInputSamplingKhz; + int value = 0; + + int pointer = 0; + for (int idx = 0; idx < subblocks; idx++, pointer += subblock_length) { + // The Isac encoder does not perform encoding (and returns 0) until it + // receives a sequence of sub-blocks that amount to the frame duration. + EXPECT_EQ(0, value); + value = WebRtcIsacfix_Encode(isac_encoder_, &in_data[pointer], + reinterpret_cast(payload)); + } + EXPECT_GT(value, 0); + return value; +} + +TEST_F(NetEqIsacQualityTest, Test) { + Simulate(FLAGS_runtime_ms); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc b/webrtc/modules/audio_coding/neteq/test/neteq_opus_fec_quality_test.cc similarity index 85% rename from webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc rename to webrtc/modules/audio_coding/neteq/test/neteq_opus_fec_quality_test.cc index aa4522b1f..e8fd06a45 100644 --- a/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc +++ b/webrtc/modules/audio_coding/neteq/test/neteq_opus_fec_quality_test.cc @@ -8,9 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include #include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" -#include "webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h" #include "webrtc/test/testsupport/fileutils.h" using google::RegisterFlagValidator; @@ -25,6 +24,7 @@ static const int kOpusBlockDurationMs = 20; static const int kOpusInputSamplingKhz = 48; static const int kOpusOutputSamplingKhz = 32; +// Define switch for input file name. static bool ValidateInFilename(const char* flagname, const string& value) { FILE* fid = fopen(value.c_str(), "rb"); if (fid != NULL) { @@ -34,12 +34,15 @@ static bool ValidateInFilename(const char* flagname, const string& value) { printf("Invalid input filename."); return false; } + DEFINE_string(in_filename, ResourcePath("audio_coding/speech_mono_32_48kHz", "pcm"), "Filename for input audio (should be 48 kHz sampled raw data)."); + static const bool in_filename_dummy = RegisterFlagValidator(&FLAGS_in_filename, &ValidateInFilename); +// Define switch for output file name. static bool ValidateOutFilename(const char* flagname, const string& value) { FILE* fid = fopen(value.c_str(), "wb"); if (fid != NULL) { @@ -49,50 +52,60 @@ static bool ValidateOutFilename(const char* flagname, const string& value) { printf("Invalid output filename."); return false; } + DEFINE_string(out_filename, OutputPath() + "neteq4_opus_fec_quality_test.pcm", "Name of output audio file."); + static const bool out_filename_dummy = RegisterFlagValidator(&FLAGS_out_filename, &ValidateOutFilename); +// Define switch for channels. static bool ValidateChannels(const char* flagname, int32_t value) { if (value == 1 || value == 2) return true; printf("Invalid number of channels, should be either 1 or 2."); return false; } + DEFINE_int32(channels, 1, "Number of channels in input audio."); + static const bool channels_dummy = RegisterFlagValidator(&FLAGS_channels, &ValidateChannels); +// Define switch for bit rate. static bool ValidateBitRate(const char* flagname, int32_t value) { if (value >= 6 && value <= 510) return true; printf("Invalid bit rate, should be between 6 and 510 kbps."); return false; } + DEFINE_int32(bit_rate_kbps, 32, "Target bit rate (kbps)."); + static const bool bit_rate_dummy = RegisterFlagValidator(&FLAGS_bit_rate_kbps, &ValidateBitRate); +// Define switch for reported packet loss rate. static bool ValidatePacketLossRate(const char* flagname, int32_t value) { if (value >= 0 && value <= 100) return true; printf("Invalid packet loss percentile, should be between 0 and 100."); return false; } + DEFINE_int32(reported_loss_rate, 10, "Reported percentile of packet loss."); + static const bool reported_loss_rate_dummy = RegisterFlagValidator(&FLAGS_reported_loss_rate, &ValidatePacketLossRate); -DEFINE_int32(actual_loss_rate, 0, "Actual percentile of packet loss."); -static const bool actual_loss_rate_dummy = - RegisterFlagValidator(&FLAGS_actual_loss_rate, &ValidatePacketLossRate); +// Define switch for runtime. static bool ValidateRuntime(const char* flagname, int32_t value) { if (value > 0) return true; printf("Invalid runtime, should be greater than 0."); return false; } + DEFINE_int32(runtime_ms, 10000, "Simulated runtime (milliseconds)."); static const bool runtime_dummy = RegisterFlagValidator(&FLAGS_runtime_ms, &ValidateRuntime); @@ -106,28 +119,26 @@ class NetEqOpusFecQualityTest : public NetEqQualityTest { virtual void TearDown() OVERRIDE; virtual int EncodeBlock(int16_t* in_data, int block_size_samples, uint8_t* payload, int max_bytes); - virtual bool PacketLost(int packet_input_time_ms); private: WebRtcOpusEncInst* opus_encoder_; int channels_; int bit_rate_kbps_; bool fec_; int target_loss_rate_; - int actual_loss_rate_; }; NetEqOpusFecQualityTest::NetEqOpusFecQualityTest() : NetEqQualityTest(kOpusBlockDurationMs, kOpusInputSamplingKhz, kOpusOutputSamplingKhz, (FLAGS_channels == 1) ? kDecoderOpus : kDecoderOpus_2ch, - FLAGS_channels, 0.0f, FLAGS_in_filename, + FLAGS_channels, + FLAGS_in_filename, FLAGS_out_filename), opus_encoder_(NULL), channels_(FLAGS_channels), bit_rate_kbps_(FLAGS_bit_rate_kbps), fec_(FLAGS_fec), - target_loss_rate_(FLAGS_reported_loss_rate), - actual_loss_rate_(FLAGS_actual_loss_rate) { + target_loss_rate_(FLAGS_reported_loss_rate) { } void NetEqOpusFecQualityTest::SetUp() { @@ -138,9 +149,9 @@ void NetEqOpusFecQualityTest::SetUp() { EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, bit_rate_kbps_ * 1000)); if (fec_) { EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_encoder_)); - EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_, - target_loss_rate_)); } + EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_, + target_loss_rate_)); NetEqQualityTest::SetUp(); } @@ -160,16 +171,6 @@ int NetEqOpusFecQualityTest::EncodeBlock(int16_t* in_data, return value; } -bool NetEqOpusFecQualityTest::PacketLost(int packet_input_time_ms) { - static int packets = 0, lost_packets = 0; - packets++; - if (lost_packets * 100 < actual_loss_rate_ * packets) { - lost_packets++; - return true; - } - return false; -} - TEST_F(NetEqOpusFecQualityTest, Test) { Simulate(FLAGS_runtime_ms); } diff --git a/webrtc/modules/audio_coding/neteq4/test/neteq_performance_unittest.cc b/webrtc/modules/audio_coding/neteq/test/neteq_performance_unittest.cc similarity index 95% rename from webrtc/modules/audio_coding/neteq4/test/neteq_performance_unittest.cc rename to webrtc/modules/audio_coding/neteq/test/neteq_performance_unittest.cc index f669742ce..14857c772 100644 --- a/webrtc/modules/audio_coding/neteq4/test/neteq_performance_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/test/neteq_performance_unittest.cc @@ -9,7 +9,7 @@ */ #include "testing/gtest/include/gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.h" #include "webrtc/test/testsupport/perf_test.h" #include "webrtc/typedefs.h" diff --git a/webrtc/modules/audio_coding/neteq/test/neteq_speed_test.cc b/webrtc/modules/audio_coding/neteq/test/neteq_speed_test.cc index d1eaa0a18..05e75f34e 100644 --- a/webrtc/modules/audio_coding/neteq/test/neteq_speed_test.cc +++ b/webrtc/modules/audio_coding/neteq/test/neteq_speed_test.cc @@ -13,19 +13,9 @@ #include #include "gflags/gflags.h" -#include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h" -#include "webrtc/modules/audio_coding/neteq4/tools/audio_loop.h" -#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" -#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.h" #include "webrtc/typedefs.h" -using webrtc::test::AudioLoop; -using webrtc::test::RtpGenerator; -using webrtc::WebRtcRTPHeader; - // Flag validators. static bool ValidateRuntime(const char* flagname, int value) { if (value > 0) // Value is ok. @@ -60,15 +50,6 @@ static const bool drift_dummy = google::RegisterFlagValidator(&FLAGS_drift, &ValidateDriftfactor); int main(int argc, char* argv[]) { - static const int kMaxChannels = 1; - static const int kMaxSamplesPerMs = 48000 / 1000; - static const int kOutputBlockSizeMs = 10; - const std::string kInputFileName = - webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); - const int kSampRateHz = 32000; - const WebRtcNetEQDecoder kDecoderType = kDecoderPCM16Bswb32kHz; - const int kPayloadType = 95; - std::string program_name = argv[0]; std::string usage = "Tool for measuring the speed of NetEq.\n" "Usage: " + program_name + " [options]\n\n" @@ -85,149 +66,15 @@ int main(int argc, char* argv[]) { return 0; } - // Initialize NetEq instance. - int error; - int inst_size_bytes; - error = WebRtcNetEQ_AssignSize(&inst_size_bytes); - if (error) { - std::cerr << "Error returned from WebRtcNetEQ_AssignSize." << std::endl; - exit(1); - } - char* inst_mem = new char[inst_size_bytes]; - void* neteq_inst; - error = WebRtcNetEQ_Assign(&neteq_inst, inst_mem); - if (error) { - std::cerr << "Error returned from WebRtcNetEQ_Assign." << std::endl; - exit(1); - } - // Select decoders. - WebRtcNetEQDecoder decoder_list[] = {kDecoderType}; - int max_number_of_packets; - int buffer_size_bytes; - int overhead_bytes_dummy; - error = WebRtcNetEQ_GetRecommendedBufferSize( - neteq_inst, decoder_list, sizeof(decoder_list) / sizeof(decoder_list[1]), - kTCPLargeJitter, &max_number_of_packets, &buffer_size_bytes, - &overhead_bytes_dummy); - if (error) { - std::cerr << "Error returned from WebRtcNetEQ_GetRecommendedBufferSize." - << std::endl; - exit(1); - } - char* buffer_mem = new char[buffer_size_bytes]; - error = WebRtcNetEQ_AssignBuffer(neteq_inst, max_number_of_packets, - buffer_mem, buffer_size_bytes); - if (error) { - std::cerr << "Error returned from WebRtcNetEQ_AssignBuffer." << std::endl; - exit(1); - } - error = WebRtcNetEQ_Init(neteq_inst, kSampRateHz); - if (error) { - std::cerr << "Error returned from WebRtcNetEQ_Init." << std::endl; - exit(1); - } - - // Register decoder. - WebRtcNetEQ_CodecDef codec_definition; - SET_CODEC_PAR(codec_definition, kDecoderType, kPayloadType, NULL, - kSampRateHz); - SET_PCM16B_SWB32_FUNCTIONS(codec_definition); - error = WebRtcNetEQ_CodecDbAdd(neteq_inst, &codec_definition); - if (error) { - std::cerr << "Cannot register decoder." << std::endl; - exit(1); - } - - // Set up AudioLoop object. - AudioLoop audio_loop; - const size_t kMaxLoopLengthSamples = kSampRateHz * 10; // 10 second loop. - const size_t kInputBlockSizeSamples = 60 * kSampRateHz / 1000; // 60 ms. - if (!audio_loop.Init(kInputFileName, kMaxLoopLengthSamples, - kInputBlockSizeSamples)) { - std::cerr << "Cannot initialize AudioLoop object." << std::endl; - exit(1); - } - - int32_t time_now_ms = 0; - - // Get first input packet. - WebRtcRTPHeader rtp_header; - RtpGenerator rtp_gen(kSampRateHz / 1000); - // Start with positive drift first half of simulation. - double drift_factor = 0.1; - rtp_gen.set_drift_factor(drift_factor); - bool drift_flipped = false; - int32_t packet_input_time_ms = - rtp_gen.GetRtpHeader(kPayloadType, kInputBlockSizeSamples, &rtp_header); - const int16_t* input_samples = audio_loop.GetNextBlock(); - if (!input_samples) exit(1); - uint8_t input_payload[kInputBlockSizeSamples * sizeof(int16_t)]; - int payload_len = WebRtcPcm16b_Encode(const_cast(input_samples), - kInputBlockSizeSamples, - input_payload); - assert(payload_len == kInputBlockSizeSamples * sizeof(int16_t)); - - // Main loop. - while (time_now_ms < FLAGS_runtime_ms) { - while (packet_input_time_ms <= time_now_ms) { - // Drop every N packets, where N = FLAGS_lossrate. - bool lost = false; - if (FLAGS_lossrate > 0) { - lost = ((rtp_header.header.sequenceNumber - 1) % FLAGS_lossrate) == 0; - } - if (!lost) { - WebRtcNetEQ_RTPInfo rtp_info; - rtp_info.payloadType = rtp_header.header.payloadType; - rtp_info.sequenceNumber = rtp_header.header.sequenceNumber; - rtp_info.timeStamp = rtp_header.header.timestamp; - rtp_info.SSRC = rtp_header.header.ssrc; - rtp_info.markerBit = rtp_header.header.markerBit; - // Insert packet. - error = WebRtcNetEQ_RecInRTPStruct( - neteq_inst, &rtp_info, input_payload, payload_len, - packet_input_time_ms * kSampRateHz / 1000); - if (error != 0) { - std::cerr << "WebRtcNetEQ_RecInRTPStruct returned error code " << - WebRtcNetEQ_GetErrorCode(neteq_inst) << std::endl; - exit(1); - } - } - - // Get next packet. - packet_input_time_ms = rtp_gen.GetRtpHeader(kPayloadType, - kInputBlockSizeSamples, - &rtp_header); - input_samples = audio_loop.GetNextBlock(); - if (!input_samples) exit(1); - payload_len = WebRtcPcm16b_Encode(const_cast(input_samples), - kInputBlockSizeSamples, - input_payload); - assert(payload_len == kInputBlockSizeSamples * sizeof(int16_t)); - } - - // Get output audio, but don't do anything with it. - static const int kOutDataLen = kOutputBlockSizeMs * kMaxSamplesPerMs * - kMaxChannels; - int16_t out_data[kOutDataLen]; - int16_t samples_per_channel; - error = WebRtcNetEQ_RecOut(neteq_inst, out_data, &samples_per_channel); - if (error != 0) { - std::cerr << "WebRtcNetEQ_RecOut returned error code " << - WebRtcNetEQ_GetErrorCode(neteq_inst) << std::endl; - exit(1); - } - assert(samples_per_channel == kSampRateHz * 10 / 1000); - - time_now_ms += kOutputBlockSizeMs; - if (time_now_ms >= FLAGS_runtime_ms / 2 && !drift_flipped) { - // Apply negative drift second half of simulation. - rtp_gen.set_drift_factor(-drift_factor); - drift_flipped = true; - } + int64_t result = + webrtc::test::NetEqPerformanceTest::Run(FLAGS_runtime_ms, FLAGS_lossrate, + FLAGS_drift); + if (result <= 0) { + std::cout << "There was an error" << std::endl; + return -1; } std::cout << "Simulation done" << std::endl; - delete [] buffer_mem; - delete [] inst_mem; + std::cout << "Runtime = " << result << " ms" << std::endl; return 0; } diff --git a/webrtc/modules/audio_coding/neteq/test/ptypes.txt b/webrtc/modules/audio_coding/neteq/test/ptypes.txt deleted file mode 100644 index 18e033bd2..000000000 --- a/webrtc/modules/audio_coding/neteq/test/ptypes.txt +++ /dev/null @@ -1,15 +0,0 @@ -pcmu 0 -pcma 8 -cn 13 -ilbc 102 -isac 103 -isacswb 104 -isacfb 124 -avt 106 -red 117 -cn_wb 98 -cn_swb32 99 -pcm16b 93 -pcm16b_wb 94 -pcm16b_swb32khz 95 -g722 9 diff --git a/webrtc/modules/audio_coding/neteq4/time_stretch.cc b/webrtc/modules/audio_coding/neteq/time_stretch.cc similarity index 97% rename from webrtc/modules/audio_coding/neteq4/time_stretch.cc rename to webrtc/modules/audio_coding/neteq/time_stretch.cc index 5b6b3ba96..a9228d498 100644 --- a/webrtc/modules/audio_coding/neteq4/time_stretch.cc +++ b/webrtc/modules/audio_coding/neteq/time_stretch.cc @@ -8,13 +8,13 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/time_stretch.h" +#include "webrtc/modules/audio_coding/neteq/time_stretch.h" #include // min, max #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" -#include "webrtc/modules/audio_coding/neteq4/dsp_helper.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/dsp_helper.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { @@ -29,7 +29,7 @@ TimeStretch::ReturnCodes TimeStretch::Process( int fs_mult_120 = fs_mult_ * 120; // Corresponds to 15 ms. const int16_t* signal; - scoped_array signal_array; + scoped_ptr signal_array; size_t signal_len; if (num_channels_ == 1) { signal = input; diff --git a/webrtc/modules/audio_coding/neteq4/time_stretch.h b/webrtc/modules/audio_coding/neteq/time_stretch.h similarity index 92% rename from webrtc/modules/audio_coding/neteq4/time_stretch.h rename to webrtc/modules/audio_coding/neteq/time_stretch.h index f0f58b83a..9396d8ff5 100644 --- a/webrtc/modules/audio_coding/neteq4/time_stretch.h +++ b/webrtc/modules/audio_coding/neteq/time_stretch.h @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TIME_STRETCH_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TIME_STRETCH_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TIME_STRETCH_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TIME_STRETCH_H_ #include #include // memset, size_t -#include "webrtc/modules/audio_coding/neteq4/audio_multi_vector.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/audio_multi_vector.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -108,4 +108,4 @@ class TimeStretch { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TIME_STRETCH_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TIME_STRETCH_H_ diff --git a/webrtc/modules/audio_coding/neteq4/time_stretch_unittest.cc b/webrtc/modules/audio_coding/neteq/time_stretch_unittest.cc similarity index 70% rename from webrtc/modules/audio_coding/neteq4/time_stretch_unittest.cc rename to webrtc/modules/audio_coding/neteq/time_stretch_unittest.cc index 188c18b71..64789b4d4 100644 --- a/webrtc/modules/audio_coding/neteq4/time_stretch_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/time_stretch_unittest.cc @@ -10,25 +10,28 @@ // Unit tests for Accelerate and PreemptiveExpand classes. -#include "webrtc/modules/audio_coding/neteq4/accelerate.h" -#include "webrtc/modules/audio_coding/neteq4/preemptive_expand.h" +#include "webrtc/modules/audio_coding/neteq/accelerate.h" +#include "webrtc/modules/audio_coding/neteq/preemptive_expand.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/background_noise.h" +#include "webrtc/modules/audio_coding/neteq/background_noise.h" namespace webrtc { TEST(TimeStretch, CreateAndDestroy) { const int kSampleRate = 8000; const size_t kNumChannels = 1; + const int kOverlapSamples = 5 * kSampleRate / 8000; BackgroundNoise bgn(kNumChannels); Accelerate accelerate(kSampleRate, kNumChannels, bgn); - PreemptiveExpand preemptive_expand(kSampleRate, kNumChannels, bgn); + PreemptiveExpand preemptive_expand( + kSampleRate, kNumChannels, bgn, kOverlapSamples); } TEST(TimeStretch, CreateUsingFactory) { const int kSampleRate = 8000; const size_t kNumChannels = 1; + const int kOverlapSamples = 5 * kSampleRate / 8000; BackgroundNoise bgn(kNumChannels); AccelerateFactory accelerate_factory; @@ -38,8 +41,8 @@ TEST(TimeStretch, CreateUsingFactory) { delete accelerate; PreemptiveExpandFactory preemptive_expand_factory; - PreemptiveExpand* preemptive_expand = - preemptive_expand_factory.Create(kSampleRate, kNumChannels, bgn); + PreemptiveExpand* preemptive_expand = preemptive_expand_factory.Create( + kSampleRate, kNumChannels, bgn, kOverlapSamples); EXPECT_TRUE(preemptive_expand != NULL); delete preemptive_expand; } diff --git a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc b/webrtc/modules/audio_coding/neteq/timestamp_scaler.cc similarity index 94% rename from webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc rename to webrtc/modules/audio_coding/neteq/timestamp_scaler.cc index b2b5b40a3..01890136a 100644 --- a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc +++ b/webrtc/modules/audio_coding/neteq/timestamp_scaler.cc @@ -8,10 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/timestamp_scaler.h" +#include "webrtc/modules/audio_coding/neteq/timestamp_scaler.h" -#include "webrtc/modules/audio_coding/neteq4/decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/defines.h" +#include "webrtc/modules/audio_coding/neteq/decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/defines.h" #include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { diff --git a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.h b/webrtc/modules/audio_coding/neteq/timestamp_scaler.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/timestamp_scaler.h rename to webrtc/modules/audio_coding/neteq/timestamp_scaler.h index e165076a5..59b8cc7d1 100644 --- a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.h +++ b/webrtc/modules/audio_coding/neteq/timestamp_scaler.h @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TIMESTAMP_SCALER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TIMESTAMP_SCALER_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TIMESTAMP_SCALER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TIMESTAMP_SCALER_H_ -#include "webrtc/modules/audio_coding/neteq4/packet.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -65,4 +65,4 @@ class TimestampScaler { }; } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TIMESTAMP_SCALER_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TIMESTAMP_SCALER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/timestamp_scaler_unittest.cc b/webrtc/modules/audio_coding/neteq/timestamp_scaler_unittest.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/timestamp_scaler_unittest.cc rename to webrtc/modules/audio_coding/neteq/timestamp_scaler_unittest.cc index c67609467..8cbbfa393 100644 --- a/webrtc/modules/audio_coding/neteq4/timestamp_scaler_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/timestamp_scaler_unittest.cc @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/timestamp_scaler.h" +#include "webrtc/modules/audio_coding/neteq/timestamp_scaler.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/mock/mock_decoder_database.h" -#include "webrtc/modules/audio_coding/neteq4/packet.h" +#include "webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h" +#include "webrtc/modules/audio_coding/neteq/packet.h" using ::testing::Return; using ::testing::ReturnNull; diff --git a/webrtc/modules/audio_coding/neteq/tools/audio_checksum.h b/webrtc/modules/audio_coding/neteq/tools/audio_checksum.h new file mode 100644 index 000000000..ac5682651 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/audio_checksum.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_CHECKSUM_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_CHECKSUM_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/md5digest.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_sink.h" +#include "webrtc/system_wrappers/interface/compile_assert.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class AudioChecksum : public AudioSink { + public: + AudioChecksum() : finished_(false) {} + + virtual bool WriteArray(const int16_t* audio, size_t num_samples) OVERRIDE { + if (finished_) + return false; + +#ifndef WEBRTC_ARCH_LITTLE_ENDIAN +#error "Big-endian gives a different checksum" +#endif + checksum_.Update(audio, num_samples * sizeof(*audio)); + return true; + } + + // Finalizes the computations, and returns the checksum. + std::string Finish() { + if (!finished_) { + finished_ = true; + checksum_.Finish(checksum_result_, rtc::Md5Digest::kSize); + } + return rtc::hex_encode(checksum_result_, rtc::Md5Digest::kSize); + } + + private: + rtc::Md5Digest checksum_; + char checksum_result_[rtc::Md5Digest::kSize]; + bool finished_; + + DISALLOW_COPY_AND_ASSIGN(AudioChecksum); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_CHECKSUM_H_ diff --git a/webrtc/modules/audio_coding/neteq4/tools/audio_loop.cc b/webrtc/modules/audio_coding/neteq/tools/audio_loop.cc similarity index 96% rename from webrtc/modules/audio_coding/neteq4/tools/audio_loop.cc rename to webrtc/modules/audio_coding/neteq/tools/audio_loop.cc index 94ea5bef0..2d2a7e3dd 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/audio_loop.cc +++ b/webrtc/modules/audio_coding/neteq/tools/audio_loop.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/tools/audio_loop.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h" #include #include diff --git a/webrtc/modules/audio_coding/neteq4/tools/audio_loop.h b/webrtc/modules/audio_coding/neteq/tools/audio_loop.h similarity index 82% rename from webrtc/modules/audio_coding/neteq4/tools/audio_loop.h rename to webrtc/modules/audio_coding/neteq/tools/audio_loop.h index 038ca370e..9647d827a 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/audio_loop.h +++ b/webrtc/modules/audio_coding/neteq/tools/audio_loop.h @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_AUDIO_LOOP_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_AUDIO_LOOP_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_LOOP_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_LOOP_H_ #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" @@ -27,8 +27,7 @@ class AudioLoop { AudioLoop() : next_index_(0), loop_length_samples_(0), - block_length_samples_(0), - audio_array_(NULL) { + block_length_samples_(0) { } virtual ~AudioLoop() {} @@ -50,11 +49,11 @@ class AudioLoop { size_t next_index_; size_t loop_length_samples_; size_t block_length_samples_; - scoped_array audio_array_; + scoped_ptr audio_array_; DISALLOW_COPY_AND_ASSIGN(AudioLoop); }; } // namespace test } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_AUDIO_LOOP_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_LOOP_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/audio_sink.h b/webrtc/modules/audio_coding/neteq/tools/audio_sink.h new file mode 100644 index 000000000..5743c3641 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/audio_sink.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_SINK_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_SINK_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/interface/module_common_types.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +// Interface class for an object receiving raw output audio from test +// applications. +class AudioSink { + public: + AudioSink() {} + virtual ~AudioSink() {} + + // Writes |num_samples| from |audio| to the AudioSink. Returns true if + // successful, otherwise false. + virtual bool WriteArray(const int16_t* audio, size_t num_samples) = 0; + + // Writes |audio_frame| to the AudioSink. Returns true if successful, + // otherwise false. + bool WriteAudioFrame(const AudioFrame& audio_frame) { + return WriteArray( + audio_frame.data_, + audio_frame.samples_per_channel_ * audio_frame.num_channels_); + } + + private: + DISALLOW_COPY_AND_ASSIGN(AudioSink); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_SINK_H_ diff --git a/webrtc/modules/audio_coding/neteq4/tools/input_audio_file.cc b/webrtc/modules/audio_coding/neteq/tools/input_audio_file.cc similarity index 95% rename from webrtc/modules/audio_coding/neteq4/tools/input_audio_file.cc rename to webrtc/modules/audio_coding/neteq/tools/input_audio_file.cc index 62692e27d..806317320 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/input_audio_file.cc +++ b/webrtc/modules/audio_coding/neteq/tools/input_audio_file.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" namespace webrtc { namespace test { diff --git a/webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h b/webrtc/modules/audio_coding/neteq/tools/input_audio_file.h similarity index 85% rename from webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h rename to webrtc/modules/audio_coding/neteq/tools/input_audio_file.h index de51ff88b..274f8ea07 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h +++ b/webrtc/modules/audio_coding/neteq/tools/input_audio_file.h @@ -8,14 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_INPUT_AUDIO_FILE_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_INPUT_AUDIO_FILE_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_INPUT_AUDIO_FILE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_INPUT_AUDIO_FILE_H_ #include #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -48,4 +48,4 @@ class InputAudioFile { } // namespace test } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_INPUT_AUDIO_FILE_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_INPUT_AUDIO_FILE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.cc similarity index 92% rename from webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.cc rename to webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.cc index 203ea040c..433546fbc 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.cc +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.cc @@ -8,12 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.h" +#include "webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.h" #include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/tools/audio_loop.h" -#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h" #include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/typedefs.h" @@ -36,7 +36,9 @@ int64_t NetEqPerformanceTest::Run(int runtime_ms, const int kPayloadType = 95; // Initialize NetEq instance. - NetEq* neteq = NetEq::Create(kSampRateHz); + NetEq::Config config; + config.sample_rate_hz = kSampRateHz; + NetEq* neteq = NetEq::Create(config); // Register decoder in |neteq|. if (neteq->RegisterPayloadType(kDecoderType, kPayloadType) != 0) return -1; diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.h b/webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.h similarity index 80% rename from webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.h rename to webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.h index 1b205c03c..d094db0f9 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.h +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_performance_test.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_PERFORMANCE_TEST_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_PERFORMANCE_TEST_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_PERFORMANCE_TEST_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_PERFORMANCE_TEST_H_ #include "webrtc/typedefs.h" @@ -29,4 +29,4 @@ class NetEqPerformanceTest { } // namespace test } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_PERFORMANCE_TEST_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_PERFORMANCE_TEST_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.cc new file mode 100644 index 000000000..a80b1f887 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.cc @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h" + +namespace webrtc { +namespace test { + +const uint8_t kPayloadType = 95; +const int kOutputSizeMs = 10; +const int kInitSeed = 0x12345678; +const int kPacketLossTimeUnitMs = 10; + +// Define switch for packet loss rate. +static bool ValidatePacketLossRate(const char* /* flag_name */, int32_t value) { + if (value >= 0 && value <= 100) + return true; + printf("Invalid packet loss percentile, should be between 0 and 100."); + return false; +} + +DEFINE_int32(packet_loss_rate, 10, "Percentile of packet loss."); + +static const bool packet_loss_rate_dummy = + RegisterFlagValidator(&FLAGS_packet_loss_rate, &ValidatePacketLossRate); + +// Define switch for random loss mode. +static bool ValidateRandomLossMode(const char* /* flag_name */, int32_t value) { + if (value >= 0 && value <= 2) + return true; + printf("Invalid random packet loss mode, should be between 0 and 2."); + return false; +} + +DEFINE_int32(random_loss_mode, 1, + "Random loss mode: 0--no loss, 1--uniform loss, 2--Gilbert Elliot loss."); +static const bool random_loss_mode_dummy = + RegisterFlagValidator(&FLAGS_random_loss_mode, &ValidateRandomLossMode); + +// Define switch for burst length. +static bool ValidateBurstLength(const char* /* flag_name */, int32_t value) { + if (value >= kPacketLossTimeUnitMs) + return true; + printf("Invalid burst length, should be greater than %d ms.", + kPacketLossTimeUnitMs); + return false; +} + +DEFINE_int32(burst_length, 30, + "Burst length in milliseconds, only valid for Gilbert Elliot loss."); + +static const bool burst_length_dummy = + RegisterFlagValidator(&FLAGS_burst_length, &ValidateBurstLength); + +// Define switch for drift factor. +static bool ValidateDriftFactor(const char* /* flag_name */, double value) { + if (value > -0.1) + return true; + printf("Invalid drift factor, should be greater than -0.1."); + return false; +} + +DEFINE_double(drift_factor, 0.0, "Time drift factor."); + +static const bool drift_factor_dummy = + RegisterFlagValidator(&FLAGS_drift_factor, &ValidateDriftFactor); + +// ProbTrans00Solver() is to calculate the transition probability from no-loss +// state to itself in a modified Gilbert Elliot packet loss model. The result is +// to achieve the target packet loss rate |loss_rate|, when a packet is not +// lost only if all |units| drawings within the duration of the packet result in +// no-loss. +static double ProbTrans00Solver(int units, double loss_rate, + double prob_trans_10) { + if (units == 1) + return prob_trans_10 / (1.0f - loss_rate) - prob_trans_10; +// 0 == prob_trans_00 ^ (units - 1) + (1 - loss_rate) / prob_trans_10 * +// prob_trans_00 - (1 - loss_rate) * (1 + 1 / prob_trans_10). +// There is a unique solution between 0.0 and 1.0, due to the monotonicity and +// an opposite sign at 0.0 and 1.0. +// For simplicity, we reformulate the equation as +// f(x) = x ^ (units - 1) + a x + b. +// Its derivative is +// f'(x) = (units - 1) x ^ (units - 2) + a. +// The derivative is strictly greater than 0 when x is between 0 and 1. +// We use Newton's method to solve the equation, iteration is +// x(k+1) = x(k) - f(x) / f'(x); + const double kPrecision = 0.001f; + const int kIterations = 100; + const double a = (1.0f - loss_rate) / prob_trans_10; + const double b = (loss_rate - 1.0f) * (1.0f + 1.0f / prob_trans_10); + double x = 0.0f; // Starting point; + double f = b; + double f_p; + int iter = 0; + while ((f >= kPrecision || f <= -kPrecision) && iter < kIterations) { + f_p = (units - 1.0f) * pow(x, units - 2) + a; + x -= f / f_p; + if (x > 1.0f) { + x = 1.0f; + } else if (x < 0.0f) { + x = 0.0f; + } + f = pow(x, units - 1) + a * x + b; + iter ++; + } + return x; +} + +NetEqQualityTest::NetEqQualityTest(int block_duration_ms, + int in_sampling_khz, + int out_sampling_khz, + enum NetEqDecoder decoder_type, + int channels, + std::string in_filename, + std::string out_filename) + : decoded_time_ms_(0), + decodable_time_ms_(0), + drift_factor_(FLAGS_drift_factor), + packet_loss_rate_(FLAGS_packet_loss_rate), + block_duration_ms_(block_duration_ms), + in_sampling_khz_(in_sampling_khz), + out_sampling_khz_(out_sampling_khz), + decoder_type_(decoder_type), + channels_(channels), + in_filename_(in_filename), + out_filename_(out_filename), + log_filename_(out_filename + ".log"), + in_size_samples_(in_sampling_khz_ * block_duration_ms_), + out_size_samples_(out_sampling_khz_ * kOutputSizeMs), + payload_size_bytes_(0), + max_payload_bytes_(0), + in_file_(new InputAudioFile(in_filename_)), + out_file_(NULL), + log_file_(NULL), + rtp_generator_(new RtpGenerator(in_sampling_khz_, 0, 0, + decodable_time_ms_)), + total_payload_size_bytes_(0) { + NetEq::Config config; + config.sample_rate_hz = out_sampling_khz_ * 1000; + neteq_.reset(NetEq::Create(config)); + max_payload_bytes_ = in_size_samples_ * channels_ * sizeof(int16_t); + in_data_.reset(new int16_t[in_size_samples_ * channels_]); + payload_.reset(new uint8_t[max_payload_bytes_]); + out_data_.reset(new int16_t[out_size_samples_ * channels_]); +} + +bool NoLoss::Lost() { + return false; +} + +UniformLoss::UniformLoss(int loss_rate) + : loss_rate_(loss_rate) { +} + +bool UniformLoss::Lost() { + int drop_this = rand(); + return (drop_this < loss_rate_ * RAND_MAX); +} + +GilbertElliotLoss::GilbertElliotLoss(double prob_trans_11, double prob_trans_01) + : prob_trans_11_(prob_trans_11), + prob_trans_01_(prob_trans_01), + lost_last_(false), + uniform_loss_model_(new UniformLoss(0)) { +} + +bool GilbertElliotLoss::Lost() { + // Simulate bursty channel (Gilbert model). + // (1st order) Markov chain model with memory of the previous/last + // packet state (lost or received). + if (lost_last_) { + // Previous packet was not received. + uniform_loss_model_->set_loss_rate(prob_trans_11_); + return lost_last_ = uniform_loss_model_->Lost(); + } else { + uniform_loss_model_->set_loss_rate(prob_trans_01_); + return lost_last_ = uniform_loss_model_->Lost(); + } +} + +void NetEqQualityTest::SetUp() { + out_file_ = fopen(out_filename_.c_str(), "wb"); + log_file_ = fopen(log_filename_.c_str(), "wt"); + ASSERT_TRUE(out_file_ != NULL); + ASSERT_EQ(0, neteq_->RegisterPayloadType(decoder_type_, kPayloadType)); + rtp_generator_->set_drift_factor(drift_factor_); + + int units = block_duration_ms_ / kPacketLossTimeUnitMs; + switch (FLAGS_random_loss_mode) { + case 1: { + // |unit_loss_rate| is the packet loss rate for each unit time interval + // (kPacketLossTimeUnitMs). Since a packet loss event is generated if any + // of |block_duration_ms_ / kPacketLossTimeUnitMs| unit time intervals of + // a full packet duration is drawn with a loss, |unit_loss_rate| fulfills + // (1 - unit_loss_rate) ^ (block_duration_ms_ / kPacketLossTimeUnitMs) == + // 1 - packet_loss_rate. + // |unit_loss_rate| is usually small. To increase its resolution, we + // magnify it by |RAND_MAX|. + double unit_loss_rate = (1.0f - pow(1.0f - 0.01f * packet_loss_rate_, + 1.0f / units)); + loss_model_.reset(new UniformLoss(unit_loss_rate)); + break; + } + case 2: { + // |FLAGS_burst_length| should be integer times of kPacketLossTimeUnitMs. + ASSERT_EQ(0, FLAGS_burst_length % kPacketLossTimeUnitMs); + + // We do not allow 100 percent packet loss in Gilbert Elliot model, which + // makes no sense. + ASSERT_GT(100, packet_loss_rate_); + + // To guarantee the overall packet loss rate, transition probabilities + // need to satisfy: + // pi_0 * (1 - prob_trans_01_) ^ units + + // pi_1 * prob_trans_10_ ^ (units - 1) == 1 - loss_rate + // pi_0 = prob_trans_10 / (prob_trans_10 + prob_trans_01_) + // is the stationary state probability of no-loss + // pi_1 = prob_trans_01_ / (prob_trans_10 + prob_trans_01_) + // is the stationary state probability of loss + // After a derivation prob_trans_00 should satisfy: + // prob_trans_00 ^ (units - 1) = (loss_rate - 1) / prob_trans_10 * + // prob_trans_00 + (1 - loss_rate) * (1 + 1 / prob_trans_10). + double loss_rate = 0.01f * packet_loss_rate_; + double prob_trans_10 = 1.0f * kPacketLossTimeUnitMs / FLAGS_burst_length; + double prob_trans_00 = ProbTrans00Solver(units, loss_rate, prob_trans_10); + loss_model_.reset(new GilbertElliotLoss(1.0f - prob_trans_10, + 1.0f - prob_trans_00)); + break; + } + default: { + loss_model_.reset(new NoLoss); + break; + } + } + + // Make sure that the packet loss profile is same for all derived tests. + srand(kInitSeed); +} + +void NetEqQualityTest::TearDown() { + fclose(out_file_); +} + +bool NetEqQualityTest::PacketLost() { + int cycles = block_duration_ms_ / kPacketLossTimeUnitMs; + + // The loop is to make sure that codecs with different block lengths share the + // same packet loss profile. + bool lost = false; + for (int idx = 0; idx < cycles; idx ++) { + if (loss_model_->Lost()) { + // The packet will be lost if any of the drawings indicates a loss, but + // the loop has to go on to make sure that codecs with different block + // lengths keep the same pace. + lost = true; + } + } + return lost; +} + +int NetEqQualityTest::Transmit() { + int packet_input_time_ms = + rtp_generator_->GetRtpHeader(kPayloadType, in_size_samples_, + &rtp_header_); + if (payload_size_bytes_ > 0) { + fprintf(log_file_, "Packet at %d ms", packet_input_time_ms); + if (!PacketLost()) { + int ret = neteq_->InsertPacket(rtp_header_, &payload_[0], + payload_size_bytes_, + packet_input_time_ms * in_sampling_khz_); + if (ret != NetEq::kOK) + return -1; + fprintf(log_file_, " OK.\n"); + } else { + fprintf(log_file_, " Lost.\n"); + } + } + return packet_input_time_ms; +} + +int NetEqQualityTest::DecodeBlock() { + int channels; + int samples; + int ret = neteq_->GetAudio(out_size_samples_ * channels_, &out_data_[0], + &samples, &channels, NULL); + + if (ret != NetEq::kOK) { + return -1; + } else { + assert(channels == channels_); + assert(samples == kOutputSizeMs * out_sampling_khz_); + fwrite(&out_data_[0], sizeof(int16_t), samples * channels, out_file_); + return samples; + } +} + +void NetEqQualityTest::Simulate(int end_time_ms) { + int audio_size_samples; + + while (decoded_time_ms_ < end_time_ms) { + // Assume 10 packets in packets buffer. + while (decodable_time_ms_ - 10 * block_duration_ms_ < decoded_time_ms_) { + ASSERT_TRUE(in_file_->Read(in_size_samples_ * channels_, &in_data_[0])); + payload_size_bytes_ = EncodeBlock(&in_data_[0], + in_size_samples_, &payload_[0], + max_payload_bytes_); + total_payload_size_bytes_ += payload_size_bytes_; + decodable_time_ms_ = Transmit() + block_duration_ms_; + } + audio_size_samples = DecodeBlock(); + if (audio_size_samples > 0) { + decoded_time_ms_ += audio_size_samples / out_sampling_khz_; + } + } + fprintf(log_file_, "%f", 8.0f * total_payload_size_bytes_ / end_time_ms); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h b/webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h similarity index 65% rename from webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h rename to webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h index 03aabc884..75d19ae6c 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_quality_test.h @@ -8,20 +8,56 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_QUALITY_TEST_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_QUALITY_TEST_H_ +#include #include #include "testing/gtest/include/gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h" -#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" +using google::RegisterFlagValidator; + namespace webrtc { namespace test { +class LossModel { + public: + virtual ~LossModel() {}; + virtual bool Lost() = 0; +}; + +class NoLoss : public LossModel { + public: + virtual bool Lost() OVERRIDE; +}; + +class UniformLoss : public LossModel { + public: + UniformLoss(int loss_rate); + virtual bool Lost() OVERRIDE; + void set_loss_rate(double loss_rate) { loss_rate_ = loss_rate; } + private: + double loss_rate_; +}; + +class GilbertElliotLoss : public LossModel { + public: + GilbertElliotLoss(double prob_trans_11, double prob_trans_01); + virtual bool Lost() OVERRIDE; + private: + // Prob. of losing current packet, when previous packet is lost. + double prob_trans_11_; + // Prob. of losing current packet, when previous packet is not lost. + double prob_trans_01_; + bool lost_last_; + scoped_ptr uniform_loss_model_; +}; + class NetEqQualityTest : public ::testing::Test { protected: NetEqQualityTest(int block_duration_ms, @@ -29,7 +65,6 @@ class NetEqQualityTest : public ::testing::Test { int out_sampling_khz, enum NetEqDecoder decoder_type, int channels, - double drift_factor, std::string in_filename, std::string out_filename); virtual void SetUp() OVERRIDE; @@ -43,9 +78,9 @@ class NetEqQualityTest : public ::testing::Test { virtual int EncodeBlock(int16_t* in_data, int block_size_samples, uint8_t* payload, int max_bytes) = 0; - // PacketLoss(...) determines weather a packet sent at an indicated time gets + // PacketLost(...) determines weather a packet sent at an indicated time gets // lost or not. - virtual bool PacketLost(int packet_input_time_ms) { return false; } + bool PacketLost(); // DecodeBlock() decodes a block of audio using the payload stored in // |payload_| with the length of |payload_size_bytes_| (bytes). The decoded @@ -65,6 +100,7 @@ class NetEqQualityTest : public ::testing::Test { int decoded_time_ms_; int decodable_time_ms_; double drift_factor_; + int packet_loss_rate_; const int block_duration_ms_; const int in_sampling_khz_; const int out_sampling_khz_; @@ -72,6 +108,7 @@ class NetEqQualityTest : public ::testing::Test { const int channels_; const std::string in_filename_; const std::string out_filename_; + const std::string log_filename_; // Number of samples per channel in a frame. const int in_size_samples_; @@ -84,17 +121,21 @@ class NetEqQualityTest : public ::testing::Test { scoped_ptr in_file_; FILE* out_file_; + FILE* log_file_; scoped_ptr rtp_generator_; scoped_ptr neteq_; + scoped_ptr loss_model_; scoped_ptr in_data_; scoped_ptr payload_; scoped_ptr out_data_; WebRtcRTPHeader rtp_header_; + + long total_payload_size_bytes_; }; } // namespace test } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_QUALITY_TEST_H_ diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_rtpplay.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc similarity index 98% rename from webrtc/modules/audio_coding/neteq4/tools/neteq_rtpplay.cc rename to webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc index d2c4a5cb4..3e3540a8b 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/neteq_rtpplay.cc +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc @@ -20,10 +20,10 @@ #include "google/gflags.h" #include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.h" -#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h" +#include "webrtc/modules/audio_coding/neteq/interface/neteq.h" +#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" +#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_DummyRTPpacket.h" +#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/system_wrappers/interface/trace.h" @@ -176,7 +176,9 @@ int main(int argc, char* argv[]) { // Initialize NetEq instance. int sample_rate_hz = 16000; - NetEq* neteq = NetEq::Create(sample_rate_hz); + NetEq::Config config; + config.sample_rate_hz = sample_rate_hz; + NetEq* neteq = NetEq::Create(config); RegisterPayloadTypes(neteq); // Read first packet. @@ -573,7 +575,6 @@ size_t ReplacePayload(webrtc::test::InputAudioFile* replacement_audio_file, " not supported or unknown." << std::endl; webrtc::Trace::ReturnTrace(); exit(1); - assert(false); } } return payload_len; diff --git a/webrtc/modules/audio_coding/neteq/tools/output_audio_file.h b/webrtc/modules/audio_coding/neteq/tools/output_audio_file.h new file mode 100644 index 000000000..1d6128076 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/output_audio_file.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_CODING_NETEQ_TOOLS_OUTPUT_AUDIO_FILE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_OUTPUT_AUDIO_FILE_H_ + +#include +#include +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_coding/neteq/tools/audio_sink.h" + +namespace webrtc { +namespace test { + +class OutputAudioFile : public AudioSink { + public: + // Creates an OutputAudioFile, opening a file named |file_name| for writing. + // The file format is 16-bit signed host-endian PCM. + explicit OutputAudioFile(const std::string& file_name) { + out_file_ = fopen(file_name.c_str(), "wb"); + } + + virtual ~OutputAudioFile() { + if (out_file_) + fclose(out_file_); + } + + virtual bool WriteArray(const int16_t* audio, size_t num_samples) OVERRIDE { + assert(out_file_); + return fwrite(audio, sizeof(*audio), num_samples, out_file_) == num_samples; + } + + private: + FILE* out_file_; + + DISALLOW_COPY_AND_ASSIGN(OutputAudioFile); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_OUTPUT_AUDIO_FILE_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/packet.cc b/webrtc/modules/audio_coding/neteq/tools/packet.cc new file mode 100644 index 000000000..d8fb7134f --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/packet.cc @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/neteq/tools/packet.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" + +namespace webrtc { +namespace test { + +Packet::Packet(uint8_t* packet_memory, + size_t allocated_bytes, + double time_ms, + const RtpHeaderParser& parser) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(allocated_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + valid_header_ = ParseHeader(parser); +} + +Packet::Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms, + const RtpHeaderParser& parser) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(virtual_packet_length_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + valid_header_ = ParseHeader(parser); +} + +Packet::Packet(uint8_t* packet_memory, size_t allocated_bytes, double time_ms) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(allocated_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + scoped_ptr parser(RtpHeaderParser::Create()); + valid_header_ = ParseHeader(*parser); +} + +Packet::Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(virtual_packet_length_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + scoped_ptr parser(RtpHeaderParser::Create()); + valid_header_ = ParseHeader(*parser); +} + +bool Packet::ExtractRedHeaders(std::list* headers) const { + // + // 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 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |1| block PT | timestamp offset | block length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |1| ... | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |0| block PT | + // +-+-+-+-+-+-+-+-+ + // + + assert(payload_); + const uint8_t* payload_ptr = payload_; + const uint8_t* payload_end_ptr = payload_ptr + payload_length_bytes_; + + // Find all RED headers with the extension bit set to 1. That is, all headers + // but the last one. + while ((payload_ptr < payload_end_ptr) && (*payload_ptr & 0x80)) { + RTPHeader* header = new RTPHeader; + CopyToHeader(header); + header->payloadType = payload_ptr[0] & 0x7F; + uint32_t offset = (payload_ptr[1] << 6) + ((payload_ptr[2] & 0xFC) >> 2); + header->timestamp -= offset; + headers->push_front(header); + payload_ptr += 4; + } + // Last header. + assert(payload_ptr < payload_end_ptr); + if (payload_ptr >= payload_end_ptr) { + return false; // Payload too short. + } + RTPHeader* header = new RTPHeader; + CopyToHeader(header); + header->payloadType = payload_ptr[0] & 0x7F; + headers->push_front(header); + return true; +} + +void Packet::DeleteRedHeaders(std::list* headers) { + while (!headers->empty()) { + delete headers->front(); + headers->pop_front(); + } +} + +bool Packet::ParseHeader(const RtpHeaderParser& parser) { + bool valid_header = parser.Parse( + payload_memory_.get(), static_cast(packet_length_bytes_), &header_); + assert(valid_header); + if (!valid_header) { + return false; + } + assert(header_.headerLength <= packet_length_bytes_); + payload_ = &payload_memory_[header_.headerLength]; + assert(packet_length_bytes_ >= header_.headerLength); + payload_length_bytes_ = packet_length_bytes_ - header_.headerLength; + assert(virtual_packet_length_bytes_ >= header_.headerLength); + virtual_payload_length_bytes_ = + virtual_packet_length_bytes_ - header_.headerLength; + return true; +} + +void Packet::CopyToHeader(RTPHeader* destination) const { + destination->markerBit = header_.markerBit; + destination->payloadType = header_.payloadType; + destination->sequenceNumber = header_.sequenceNumber; + destination->timestamp = header_.timestamp; + destination->ssrc = header_.ssrc; + destination->numCSRCs = header_.numCSRCs; + destination->paddingLength = header_.paddingLength; + destination->headerLength = header_.headerLength; + destination->payload_type_frequency = header_.payload_type_frequency; + memcpy(&destination->arrOfCSRCs, + &header_.arrOfCSRCs, + sizeof(header_.arrOfCSRCs)); + memcpy( + &destination->extension, &header_.extension, sizeof(header_.extension)); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/packet.h b/webrtc/modules/audio_coding/neteq/tools/packet.h new file mode 100644 index 000000000..eb8ce28a2 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/packet.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/common_types.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class RtpHeaderParser; + +namespace test { + +// Class for handling RTP packets in test applications. +class Packet { + public: + // Creates a packet, with the packet payload (including header bytes) in + // |packet_memory|. The length of |packet_memory| is |allocated_bytes|. + // The new object assumes ownership of |packet_memory| and will delete it + // when the Packet object is deleted. The |time_ms| is an extra time + // associated with this packet, typically used to denote arrival time. + // The first bytes in |packet_memory| will be parsed using |parser|. + Packet(uint8_t* packet_memory, + size_t allocated_bytes, + double time_ms, + const RtpHeaderParser& parser); + + // Same as above, but with the extra argument |virtual_packet_length_bytes|. + // This is typically used when reading RTP dump files that only contain the + // RTP headers, and no payload (a.k.a RTP dummy files or RTP light). The + // |virtual_packet_length_bytes| tells what size the packet had on wire, + // including the now discarded payload, whereas |allocated_bytes| is the + // length of the remaining payload (typically only the RTP header). + Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms, + const RtpHeaderParser& parser); + + // The following two constructors are the same as above, but without a + // parser. Note that when the object is constructed using any of these + // methods, the header will be parsed using a default RtpHeaderParser object. + // In particular, RTP header extensions won't be parsed. + Packet(uint8_t* packet_memory, size_t allocated_bytes, double time_ms); + + Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms); + + virtual ~Packet() {} + + // Parses the first bytes of the RTP payload, interpreting them as RED headers + // according to RFC 2198. The headers will be inserted into |headers|. The + // caller of the method assumes ownership of the objects in the list, and + // must delete them properly. + bool ExtractRedHeaders(std::list* headers) const; + + // Deletes all RTPHeader objects in |headers|, but does not delete |headers| + // itself. + static void DeleteRedHeaders(std::list* headers); + + const uint8_t* payload() const { return payload_; } + + size_t packet_length_bytes() const { return packet_length_bytes_; } + + size_t payload_length_bytes() const { return payload_length_bytes_; } + + size_t virtual_packet_length_bytes() const { + return virtual_packet_length_bytes_; + } + + size_t virtual_payload_length_bytes() const { + return virtual_payload_length_bytes_; + } + + const RTPHeader& header() const { return header_; } + + void set_time_ms(double time) { time_ms_ = time; } + double time_ms() const { return time_ms_; } + bool valid_header() const { return valid_header_; } + + private: + bool ParseHeader(const RtpHeaderParser& parser); + void CopyToHeader(RTPHeader* destination) const; + + RTPHeader header_; + scoped_ptr payload_memory_; + const uint8_t* payload_; // First byte after header. + const size_t packet_length_bytes_; // Total length of packet. + size_t payload_length_bytes_; // Length of the payload, after RTP header. + // Zero for dummy RTP packets. + // Virtual lengths are used when parsing RTP header files (dummy RTP files). + const size_t virtual_packet_length_bytes_; + size_t virtual_payload_length_bytes_; + double time_ms_; // Used to denote a packet's arrival time. + bool valid_header_; // Set by the RtpHeaderParser. + + DISALLOW_COPY_AND_ASSIGN(Packet); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/packet_source.h b/webrtc/modules/audio_coding/neteq/tools/packet_source.h new file mode 100644 index 000000000..669bc14e4 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/packet_source.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_SOURCE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_SOURCE_H_ + +#include "webrtc/base/constructormagic.h" + +namespace webrtc { +namespace test { + +class Packet; + +// Interface class for an object delivering RTP packets to test applications. +class PacketSource { + public: + PacketSource() {} + virtual ~PacketSource() {} + + // Returns a pointer to the next packet. Returns NULL if the source is + // depleted, or if an error occurred. + virtual Packet* NextPacket() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(PacketSource); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_SOURCE_H_ diff --git a/webrtc/modules/audio_coding/neteq/tools/packet_unittest.cc b/webrtc/modules/audio_coding/neteq/tools/packet_unittest.cc new file mode 100644 index 000000000..df844ee84 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/packet_unittest.cc @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2014 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. + */ + +// Unit tests for test Packet class. + +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" + +#include "gtest/gtest.h" + +namespace webrtc { +namespace test { + +namespace { +const int kHeaderLengthBytes = 12; + +void MakeRtpHeader(int payload_type, + int seq_number, + uint32_t timestamp, + uint32_t ssrc, + uint8_t* rtp_data) { + rtp_data[0] = 0x80; + rtp_data[1] = payload_type & 0xFF; + rtp_data[2] = (seq_number >> 8) & 0xFF; + rtp_data[3] = (seq_number) & 0xFF; + rtp_data[4] = (timestamp >> 24) & 0xFF; + rtp_data[5] = (timestamp >> 16) & 0xFF; + rtp_data[6] = (timestamp >> 8) & 0xFF; + rtp_data[7] = timestamp & 0xFF; + rtp_data[8] = (ssrc >> 24) & 0xFF; + rtp_data[9] = (ssrc >> 16) & 0xFF; + rtp_data[10] = (ssrc >> 8) & 0xFF; + rtp_data[11] = ssrc & 0xFF; +} +} // namespace + +TEST(TestPacket, RegularPacket) { + const size_t kPacketLengthBytes = 100; + uint8_t* packet_memory = new uint8_t[kPacketLengthBytes]; + const uint8_t kPayloadType = 17; + const uint16_t kSequenceNumber = 4711; + const uint32_t kTimestamp = 47114711; + const uint32_t kSsrc = 0x12345678; + MakeRtpHeader( + kPayloadType, kSequenceNumber, kTimestamp, kSsrc, packet_memory); + const double kPacketTime = 1.0; + // Hand over ownership of |packet_memory| to |packet|. + Packet packet(packet_memory, kPacketLengthBytes, kPacketTime); + ASSERT_TRUE(packet.valid_header()); + EXPECT_EQ(kPayloadType, packet.header().payloadType); + EXPECT_EQ(kSequenceNumber, packet.header().sequenceNumber); + EXPECT_EQ(kTimestamp, packet.header().timestamp); + EXPECT_EQ(kSsrc, packet.header().ssrc); + EXPECT_EQ(0, packet.header().numCSRCs); + EXPECT_EQ(kPacketLengthBytes, packet.packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.payload_length_bytes()); + EXPECT_EQ(kPacketLengthBytes, packet.virtual_packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.virtual_payload_length_bytes()); + EXPECT_EQ(kPacketTime, packet.time_ms()); +} + +TEST(TestPacket, DummyPacket) { + const size_t kPacketLengthBytes = kHeaderLengthBytes; // Only RTP header. + const size_t kVirtualPacketLengthBytes = 100; + uint8_t* packet_memory = new uint8_t[kPacketLengthBytes]; + const uint8_t kPayloadType = 17; + const uint16_t kSequenceNumber = 4711; + const uint32_t kTimestamp = 47114711; + const uint32_t kSsrc = 0x12345678; + MakeRtpHeader( + kPayloadType, kSequenceNumber, kTimestamp, kSsrc, packet_memory); + const double kPacketTime = 1.0; + // Hand over ownership of |packet_memory| to |packet|. + Packet packet(packet_memory, + kPacketLengthBytes, + kVirtualPacketLengthBytes, + kPacketTime); + ASSERT_TRUE(packet.valid_header()); + EXPECT_EQ(kPayloadType, packet.header().payloadType); + EXPECT_EQ(kSequenceNumber, packet.header().sequenceNumber); + EXPECT_EQ(kTimestamp, packet.header().timestamp); + EXPECT_EQ(kSsrc, packet.header().ssrc); + EXPECT_EQ(0, packet.header().numCSRCs); + EXPECT_EQ(kPacketLengthBytes, packet.packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.payload_length_bytes()); + EXPECT_EQ(kVirtualPacketLengthBytes, packet.virtual_packet_length_bytes()); + EXPECT_EQ(kVirtualPacketLengthBytes - kHeaderLengthBytes, + packet.virtual_payload_length_bytes()); + EXPECT_EQ(kPacketTime, packet.time_ms()); +} + +namespace { +// Writes one RED block header starting at |rtp_data|, according to RFC 2198. +// returns the number of bytes written (1 or 4). +// +// Format if |last_payoad| is false: +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |1| block PT | timestamp offset | block length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Format if |last_payoad| is true: +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |0| Block PT | +// +-+-+-+-+-+-+-+-+ + +int MakeRedHeader(int payload_type, + uint32_t timestamp_offset, + int block_length, + bool last_payload, + uint8_t* rtp_data) { + rtp_data[0] = 0x80 | (payload_type & 0x7F); // Set the first bit to 1. + if (last_payload) { + rtp_data[0] &= 0x7F; // Reset the first but to 0 to indicate last block. + return 1; + } + rtp_data[1] = timestamp_offset >> 6; + rtp_data[2] = (timestamp_offset & 0x3F) << 2; + rtp_data[2] |= block_length >> 8; + rtp_data[3] = block_length & 0xFF; + return 4; +} +} // namespace + +TEST(TestPacket, RED) { + const size_t kPacketLengthBytes = 100; + uint8_t* packet_memory = new uint8_t[kPacketLengthBytes]; + const uint8_t kRedPayloadType = 17; + const uint16_t kSequenceNumber = 4711; + const uint32_t kTimestamp = 47114711; + const uint32_t kSsrc = 0x12345678; + MakeRtpHeader( + kRedPayloadType, kSequenceNumber, kTimestamp, kSsrc, packet_memory); + // Create four RED headers. + // Payload types are just the same as the block index the offset is 100 times + // the block index. + const int kRedBlocks = 4; + uint8_t* payload_ptr = + &packet_memory[kHeaderLengthBytes]; // First byte after header. + for (int i = 0; i < kRedBlocks; ++i) { + int payload_type = i; + // Offset value is not used for the last block. + uint32_t timestamp_offset = 100 * i; + int block_length = 10 * i; + bool last_block = (i == kRedBlocks - 1) ? true : false; + payload_ptr += MakeRedHeader( + payload_type, timestamp_offset, block_length, last_block, payload_ptr); + } + const double kPacketTime = 1.0; + // Hand over ownership of |packet_memory| to |packet|. + Packet packet(packet_memory, kPacketLengthBytes, kPacketTime); + ASSERT_TRUE(packet.valid_header()); + EXPECT_EQ(kRedPayloadType, packet.header().payloadType); + EXPECT_EQ(kSequenceNumber, packet.header().sequenceNumber); + EXPECT_EQ(kTimestamp, packet.header().timestamp); + EXPECT_EQ(kSsrc, packet.header().ssrc); + EXPECT_EQ(0, packet.header().numCSRCs); + EXPECT_EQ(kPacketLengthBytes, packet.packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.payload_length_bytes()); + EXPECT_EQ(kPacketLengthBytes, packet.virtual_packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.virtual_payload_length_bytes()); + EXPECT_EQ(kPacketTime, packet.time_ms()); + std::list red_headers; + EXPECT_TRUE(packet.ExtractRedHeaders(&red_headers)); + EXPECT_EQ(kRedBlocks, static_cast(red_headers.size())); + int block_index = 0; + for (std::list::reverse_iterator it = red_headers.rbegin(); + it != red_headers.rend(); + ++it) { + // Reading list from the back, since the extraction puts the main payload + // (which is the last one on wire) first. + RTPHeader* red_block = *it; + EXPECT_EQ(block_index, red_block->payloadType); + EXPECT_EQ(kSequenceNumber, red_block->sequenceNumber); + if (block_index == kRedBlocks - 1) { + // Last block has zero offset per definition. + EXPECT_EQ(kTimestamp, red_block->timestamp); + } else { + EXPECT_EQ(kTimestamp - 100 * block_index, red_block->timestamp); + } + EXPECT_EQ(kSsrc, red_block->ssrc); + EXPECT_EQ(0, red_block->numCSRCs); + ++block_index; + } + Packet::DeleteRedHeaders(&red_headers); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/rtp_analyze.cc b/webrtc/modules/audio_coding/neteq/tools/rtp_analyze.cc new file mode 100644 index 000000000..773cc2c89 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/rtp_analyze.cc @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2012 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 "google/gflags.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +// Flag validator. +static bool ValidatePayloadType(const char* flagname, int32_t value) { + if (value >= 0 && value <= 127) // Value is ok. + return true; + printf("Invalid value for --%s: %d\n", flagname, static_cast(value)); + return false; +} +static bool ValidateExtensionId(const char* flagname, int32_t value) { + if (value > 0 && value <= 255) // Value is ok. + return true; + printf("Invalid value for --%s: %d\n", flagname, static_cast(value)); + return false; +} + +// Define command line flags. +DEFINE_int32(red, 117, "RTP payload type for RED"); +static const bool red_dummy = + google::RegisterFlagValidator(&FLAGS_red, &ValidatePayloadType); +DEFINE_int32(audio_level, 1, "Extension ID for audio level (RFC 6464)"); +static const bool audio_level_dummy = + google::RegisterFlagValidator(&FLAGS_audio_level, &ValidateExtensionId); + +int main(int argc, char* argv[]) { + std::string program_name = argv[0]; + std::string usage = + "Tool for parsing an RTP dump file to text output.\n" + "Run " + + program_name + + " --helpshort for usage.\n" + "Example usage:\n" + + program_name + " input.rtp output.txt\n\n" + + "Output is sent to stdout if no output file is given." + + "Note that this tool can read files with our without payloads."; + google::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + if (argc != 2 && argc != 3) { + // Print usage information. + printf("%s", google::ProgramUsage()); + return 0; + } + + FILE* in_file = fopen(argv[1], "rb"); + if (!in_file) { + printf("Cannot open input file %s\n", argv[1]); + return -1; + } + printf("Input file: %s\n", argv[1]); + webrtc::scoped_ptr file_source( + webrtc::test::RtpFileSource::Create(argv[1])); + assert(file_source.get()); + // Set RTP extension ID. + bool print_audio_level = false; + if (!google::GetCommandLineFlagInfoOrDie("audio_level").is_default) { + print_audio_level = true; + file_source->RegisterRtpHeaderExtension(webrtc::kRtpExtensionAudioLevel, + FLAGS_audio_level); + } + + FILE* out_file; + if (argc == 3) { + out_file = fopen(argv[2], "wt"); + if (!out_file) { + printf("Cannot open output file %s\n", argv[2]); + return -1; + } + printf("Output file: %s\n\n", argv[2]); + } else { + out_file = stdout; + } + + // Print file header. + fprintf(out_file, "SeqNo TimeStamp SendTime Size PT M SSRC"); + if (print_audio_level) { + fprintf(out_file, " AuLvl (V)"); + } + fprintf(out_file, "\n"); + + webrtc::scoped_ptr packet; + while (!file_source->EndOfFile()) { + packet.reset(file_source->NextPacket()); + if (!packet.get()) { + // This is probably an RTCP packet. Move on to the next one. + continue; + } + assert(packet.get()); + // Write packet data to file. + fprintf(out_file, + "%5u %10u %10u %5i %5i %2i %#08X", + packet->header().sequenceNumber, + packet->header().timestamp, + static_cast(packet->time_ms()), + static_cast(packet->packet_length_bytes()), + packet->header().payloadType, + packet->header().markerBit, + packet->header().ssrc); + if (print_audio_level && packet->header().extension.hasAudioLevel) { + // |audioLevel| consists of one bit for "V" and then 7 bits level. + fprintf(out_file, + " %5u (%1i)", + packet->header().extension.audioLevel & 0x7F, + (packet->header().extension.audioLevel & 0x80) == 0 ? 0 : 1); + } + fprintf(out_file, "\n"); + + if (packet->header().payloadType == FLAGS_red) { + std::list red_headers; + packet->ExtractRedHeaders(&red_headers); + while (!red_headers.empty()) { + webrtc::RTPHeader* red = red_headers.front(); + assert(red); + fprintf(out_file, + "* %5u %10u %10u %5i\n", + red->sequenceNumber, + red->timestamp, + static_cast(packet->time_ms()), + red->payloadType); + red_headers.pop_front(); + delete red; + } + } + } + + fclose(in_file); + fclose(out_file); + + return 0; +} diff --git a/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.cc b/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.cc new file mode 100644 index 000000000..6490d4685 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.cc @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h" + +#include +#include +#ifdef WIN32 +#include +#else +#include +#endif + +#include "webrtc/modules/audio_coding/neteq/tools/packet.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" + +namespace webrtc { +namespace test { + +RtpFileSource* RtpFileSource::Create(const std::string& file_name) { + RtpFileSource* source = new RtpFileSource; + assert(source); + if (!source->OpenFile(file_name) || !source->SkipFileHeader()) { + assert(false); + delete source; + return NULL; + } + return source; +} + +RtpFileSource::~RtpFileSource() { + if (in_file_) + fclose(in_file_); +} + +bool RtpFileSource::RegisterRtpHeaderExtension(RTPExtensionType type, + uint8_t id) { + assert(parser_.get()); + return parser_->RegisterRtpHeaderExtension(type, id); +} + +Packet* RtpFileSource::NextPacket() { + while (!EndOfFile()) { + uint16_t length; + if (fread(&length, sizeof(length), 1, in_file_) == 0) { + assert(false); + return NULL; + } + length = ntohs(length); + + uint16_t plen; + if (fread(&plen, sizeof(plen), 1, in_file_) == 0) { + assert(false); + return NULL; + } + plen = ntohs(plen); + + uint32_t offset; + if (fread(&offset, sizeof(offset), 1, in_file_) == 0) { + assert(false); + return NULL; + } + offset = ntohl(offset); + + // Use length here because a plen of 0 specifies RTCP. + assert(length >= kPacketHeaderSize); + size_t packet_size_bytes = length - kPacketHeaderSize; + if (packet_size_bytes == 0) { + // May be an RTCP packet. + // Read the next one. + continue; + } + scoped_ptr packet_memory(new uint8_t[packet_size_bytes]); + if (fread(packet_memory.get(), 1, packet_size_bytes, in_file_) != + packet_size_bytes) { + assert(false); + return NULL; + } + scoped_ptr packet(new Packet(packet_memory.release(), + packet_size_bytes, + plen, + offset, + *parser_.get())); + if (!packet->valid_header()) { + assert(false); + return NULL; + } + return packet.release(); + } + return NULL; +} + +bool RtpFileSource::EndOfFile() const { + assert(in_file_); + return ftell(in_file_) >= file_end_; +} + +RtpFileSource::RtpFileSource() + : PacketSource(), + in_file_(NULL), + file_end_(-1), + parser_(RtpHeaderParser::Create()) {} + +bool RtpFileSource::OpenFile(const std::string& file_name) { + in_file_ = fopen(file_name.c_str(), "rb"); + assert(in_file_); + if (in_file_ == NULL) { + return false; + } + + // Find out how long the file is. + fseek(in_file_, 0, SEEK_END); + file_end_ = ftell(in_file_); + rewind(in_file_); + return true; +} + +bool RtpFileSource::SkipFileHeader() { + char firstline[kFirstLineLength]; + assert(in_file_); + if (fgets(firstline, kFirstLineLength, in_file_) == NULL) { + assert(false); + return false; + } + // Check that the first line is ok. + if ((strncmp(firstline, "#!rtpplay1.0", 12) != 0) && + (strncmp(firstline, "#!RTPencode1.0", 14) != 0)) { + assert(false); + return false; + } + // Skip the file header. + if (fseek(in_file_, kRtpFileHeaderSize, SEEK_CUR) != 0) { + assert(false); + return false; + } + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h b/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h new file mode 100644 index 000000000..6b92a8869 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTP_FILE_SOURCE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTP_FILE_SOURCE_H_ + +#include +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/common_types.h" +#include "webrtc/modules/audio_coding/neteq/tools/packet_source.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class RtpHeaderParser; + +namespace test { + +class RtpFileSource : public PacketSource { + public: + // Creates an RtpFileSource reading from |file_name|. If the file cannot be + // opened, or has the wrong format, NULL will be returned. + static RtpFileSource* Create(const std::string& file_name); + + virtual ~RtpFileSource(); + + // Registers an RTP header extension and binds it to |id|. + virtual bool RegisterRtpHeaderExtension(RTPExtensionType type, uint8_t id); + + // Returns a pointer to the next packet. Returns NULL if end of file was + // reached, or if a the data was corrupt. + virtual Packet* NextPacket(); + + // Returns true if the end of file has been reached. + virtual bool EndOfFile() const; + + private: + static const int kFirstLineLength = 40; + static const int kRtpFileHeaderSize = 4 + 4 + 4 + 2 + 2; + static const size_t kPacketHeaderSize = 8; + + RtpFileSource(); + + bool OpenFile(const std::string& file_name); + + bool SkipFileHeader(); + + FILE* in_file_; + int64_t file_end_; + scoped_ptr parser_; + + DISALLOW_COPY_AND_ASSIGN(RtpFileSource); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTP_FILE_SOURCE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/tools/rtp_generator.cc b/webrtc/modules/audio_coding/neteq/tools/rtp_generator.cc similarity index 95% rename from webrtc/modules/audio_coding/neteq4/tools/rtp_generator.cc rename to webrtc/modules/audio_coding/neteq/tools/rtp_generator.cc index 8d9a89d54..17ac209f1 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/rtp_generator.cc +++ b/webrtc/modules/audio_coding/neteq/tools/rtp_generator.cc @@ -10,7 +10,7 @@ #include -#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h" +#include "webrtc/modules/audio_coding/neteq/tools/rtp_generator.h" namespace webrtc { namespace test { diff --git a/webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h b/webrtc/modules/audio_coding/neteq/tools/rtp_generator.h similarity index 86% rename from webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h rename to webrtc/modules/audio_coding/neteq/tools/rtp_generator.h index ece7ef298..d3824c8d2 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h +++ b/webrtc/modules/audio_coding/neteq/tools/rtp_generator.h @@ -8,11 +8,11 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_RTP_GENERATOR_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_RTP_GENERATOR_H_ +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTP_GENERATOR_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTP_GENERATOR_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/interface/module_common_types.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -54,4 +54,4 @@ class RtpGenerator { } // namespace test } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_RTP_GENERATOR_H_ +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_RTP_GENERATOR_H_ diff --git a/webrtc/modules/audio_coding/neteq/unmute_signal.c b/webrtc/modules/audio_coding/neteq/unmute_signal.c deleted file mode 100644 index 3128f21f4..000000000 --- a/webrtc/modules/audio_coding/neteq/unmute_signal.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 function "unmutes" a vector on a sample by sample basis. - */ - -#include "dsp_helpfunctions.h" - -#include "signal_processing_library.h" - - -void WebRtcNetEQ_UnmuteSignal(int16_t *pw16_inVec, int16_t *startMuteFact, - int16_t *pw16_outVec, int16_t unmuteFact, - int16_t N) -{ - int i; - uint16_t w16_tmp; - int32_t w32_tmp; - - w16_tmp = (uint16_t) *startMuteFact; - w32_tmp = WEBRTC_SPL_LSHIFT_W32((int32_t)w16_tmp,6) + 32; - for (i = 0; i < N; i++) - { - pw16_outVec[i] - = (int16_t) ((WEBRTC_SPL_MUL_16_16(w16_tmp, pw16_inVec[i]) + 8192) >> 14); - w32_tmp += unmuteFact; - w32_tmp = WEBRTC_SPL_MAX(0, w32_tmp); - w16_tmp = (uint16_t) WEBRTC_SPL_RSHIFT_W32(w32_tmp, 6); /* 20 - 14 = 6 */ - w16_tmp = WEBRTC_SPL_MIN(16384, w16_tmp); - } - *startMuteFact = (int16_t) w16_tmp; -} - diff --git a/webrtc/modules/audio_coding/neteq/webrtc_neteq.c b/webrtc/modules/audio_coding/neteq/webrtc_neteq.c deleted file mode 100644 index fad690d08..000000000 --- a/webrtc/modules/audio_coding/neteq/webrtc_neteq.c +++ /dev/null @@ -1,1769 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* - * Implementation of main NetEQ API. - */ - -#include "webrtc_neteq.h" -#include "webrtc_neteq_internal.h" - -#include -#include - -#include "typedefs.h" -#include "signal_processing_library.h" - -#include "neteq_error_codes.h" -#include "mcu_dsp_common.h" -#include "rtcp.h" - -#define RETURN_ON_ERROR( macroExpr, macroInstPtr ) { \ - if ((macroExpr) != 0) { \ - if ((macroExpr) == -1) { \ - (macroInstPtr)->ErrorCode = - (NETEQ_OTHER_ERROR); \ - } else { \ - (macroInstPtr)->ErrorCode = -((int16_t) (macroExpr)); \ - } \ - return(-1); \ - } } - -int WebRtcNetEQ_strncpy(char *strDest, int numberOfElements, - const char *strSource, int count) -{ - /* check vector lengths */ - if (count > numberOfElements) - { - strDest[0] = '\0'; - return (-1); - } - else - { - strncpy(strDest, strSource, count); - return (0); - } -} - -/********************************************************** - * NETEQ Functions - */ - -/***************************************** - * Error functions - */ - -int WebRtcNetEQ_GetErrorCode(void *inst) -{ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - return (NetEqMainInst->ErrorCode); -} - -int WebRtcNetEQ_GetErrorName(int errorCode, char *errorName, int maxStrLen) -{ - if ((errorName == NULL) || (maxStrLen <= 0)) - { - return (-1); - } - - if (errorCode < 0) - { - errorCode = -errorCode; // absolute value - } - - switch (errorCode) - { - case 1: // could be -1 - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "OTHER_ERROR", maxStrLen); - break; - } - case 1001: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "FAULTY_INSTRUCTION", maxStrLen); - break; - } - case 1002: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "FAULTY_NETWORK_TYPE", maxStrLen); - break; - } - case 1003: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "FAULTY_DELAYVALUE", maxStrLen); - break; - } - case 1004: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "FAULTY_PLAYOUTMODE", maxStrLen); - break; - } - case 1005: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "CORRUPT_INSTANCE", maxStrLen); - break; - } - case 1006: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "ILLEGAL_MASTER_SLAVE_SWITCH", maxStrLen); - break; - } - case 1007: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "MASTER_SLAVE_ERROR", maxStrLen); - break; - } - case 2001: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "UNKNOWN_BUFSTAT_DECISION", maxStrLen); - break; - } - case 2002: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RECOUT_ERROR_DECODING", maxStrLen); - break; - } - case 2003: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RECOUT_ERROR_SAMPLEUNDERRUN", maxStrLen); - break; - } - case 2004: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RECOUT_ERROR_DECODED_TOO_MUCH", - maxStrLen); - break; - } - case 3001: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RECIN_CNG_ERROR", maxStrLen); - break; - } - case 3002: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RECIN_UNKNOWNPAYLOAD", maxStrLen); - break; - } - case 3003: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RECIN_BUFFERINSERT_ERROR", maxStrLen); - break; - } - case 4001: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "PBUFFER_INIT_ERROR", maxStrLen); - break; - } - case 4002: - case 4003: - case 4004: - case 4005: - case 4006: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "PBUFFER_INSERT_ERROR1", maxStrLen); - break; - } - case 4007: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "UNKNOWN_G723_HEADER", maxStrLen); - break; - } - case 4008: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "PBUFFER_NONEXISTING_PACKET", maxStrLen); - break; - } - case 4009: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "PBUFFER_NOT_INITIALIZED", maxStrLen); - break; - } - case 4010: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "AMBIGUOUS_ILBC_FRAME_SIZE", maxStrLen); - break; - } - case 5001: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "CODEC_DB_FULL", maxStrLen); - break; - } - case 5002: - case 5003: - case 5004: - case 5005: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "CODEC_DB_NOT_EXIST", maxStrLen); - break; - } - case 5006: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "CODEC_DB_UNKNOWN_CODEC", maxStrLen); - break; - } - case 5007: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "CODEC_DB_PAYLOAD_TAKEN", maxStrLen); - break; - } - case 5008: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "CODEC_DB_UNSUPPORTED_CODEC", maxStrLen); - break; - } - case 5009: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "CODEC_DB_UNSUPPORTED_FS", maxStrLen); - break; - } - case 6001: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "DTMF_DEC_PARAMETER_ERROR", maxStrLen); - break; - } - case 6002: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "DTMF_INSERT_ERROR", maxStrLen); - break; - } - case 6003: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "DTMF_GEN_UNKNOWN_SAMP_FREQ", maxStrLen); - break; - } - case 6004: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "DTMF_NOT_SUPPORTED", maxStrLen); - break; - } - case 7001: - case 7002: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RED_SPLIT_ERROR", maxStrLen); - break; - } - case 7003: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RTP_TOO_SHORT_PACKET", maxStrLen); - break; - } - case 7004: - { - WebRtcNetEQ_strncpy(errorName, maxStrLen, "RTP_CORRUPT_PACKET", maxStrLen); - break; - } - default: - { - /* check for decoder error ranges */ - if (errorCode >= 6010 && errorCode <= 6810) - { - /* iSAC error code */ - WebRtcNetEQ_strncpy(errorName, maxStrLen, "iSAC ERROR", maxStrLen); - break; - } - - WebRtcNetEQ_strncpy(errorName, maxStrLen, "UNKNOWN_ERROR", maxStrLen); - return (-1); - } - } - - return (0); -} - -/* Assign functions (create not allowed in order to avoid malloc in lib) */ -int WebRtcNetEQ_AssignSize(int *sizeinbytes) -{ - *sizeinbytes = (sizeof(MainInst_t) * 2) / sizeof(int16_t); - return (0); -} - -int WebRtcNetEQ_Assign(void **inst, void *NETEQ_inst_Addr) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) NETEQ_inst_Addr; - *inst = NETEQ_inst_Addr; - if (*inst == NULL) return (-1); - - WebRtcSpl_Init(); - - /* Clear memory */ - WebRtcSpl_MemSetW16((int16_t*) NetEqMainInst, 0, - (sizeof(MainInst_t) / sizeof(int16_t))); - ok = WebRtcNetEQ_McuReset(&NetEqMainInst->MCUinst); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (0); -} - -int WebRtcNetEQ_GetRecommendedBufferSize(void *inst, const enum WebRtcNetEQDecoder *codec, - int noOfCodecs, enum WebRtcNetEQNetworkType nwType, - int *MaxNoOfPackets, int *sizeinbytes, - int* per_packet_overhead_bytes) -{ - int ok = 0; - int multiplier; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - *MaxNoOfPackets = 0; - *sizeinbytes = 0; - ok = WebRtcNetEQ_GetDefaultCodecSettings(codec, noOfCodecs, sizeinbytes, - MaxNoOfPackets, - per_packet_overhead_bytes); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - if (nwType == kUDPNormal) - { - multiplier = 1; - } - else if (nwType == kUDPVideoSync) - { - multiplier = 4; - } - else if (nwType == kTCPNormal) - { - multiplier = 4; - } - else if (nwType == kTCPLargeJitter) - { - multiplier = 8; - } - else if (nwType == kTCPXLargeJitter) - { - multiplier = 12; - } - else - { - NetEqMainInst->ErrorCode = -FAULTY_NETWORK_TYPE; - return (-1); - } - *MaxNoOfPackets = (*MaxNoOfPackets) * multiplier; - *sizeinbytes = (*sizeinbytes) * multiplier; - return 0; -} - -int WebRtcNetEQ_AssignBuffer(void *inst, int MaxNoOfPackets, void *NETEQ_Buffer_Addr, - int sizeinbytes) -{ - int ok; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - ok = WebRtcNetEQ_PacketBufferInit(&NetEqMainInst->MCUinst.PacketBuffer_inst, - MaxNoOfPackets, (int16_t*) NETEQ_Buffer_Addr, (sizeinbytes >> 1)); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -/************************************************ - * Init functions - */ - -/**************************************************************************** - * WebRtcNetEQ_Init(...) - * - * Initialize NetEQ. - * - * Input: - * - inst : NetEQ instance - * - fs : Initial sample rate in Hz (may change with payload) - * - * Output: - * - inst : Initialized NetEQ instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_Init(void *inst, uint16_t fs) -{ - int ok = 0; - - /* Typecast inst to internal instance format */ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - - if (NetEqMainInst == NULL) - { - return (-1); - } - -#ifdef NETEQ_VAD - /* Start out with no PostDecode VAD instance */ - NetEqMainInst->DSPinst.VADInst.VADState = NULL; - /* Also set all VAD function pointers to NULL */ - NetEqMainInst->DSPinst.VADInst.initFunction = NULL; - NetEqMainInst->DSPinst.VADInst.setmodeFunction = NULL; - NetEqMainInst->DSPinst.VADInst.VADFunction = NULL; -#endif /* NETEQ_VAD */ - - ok = WebRtcNetEQ_DSPinit(NetEqMainInst); /* Init addresses between MCU and DSP */ - RETURN_ON_ERROR(ok, NetEqMainInst); - - ok = WebRtcNetEQ_DSPInit(&NetEqMainInst->DSPinst, fs); /* Init dsp side */ - RETURN_ON_ERROR(ok, NetEqMainInst); - /* set BGN mode to default, since it is not cleared by DSP init function */ - NetEqMainInst->DSPinst.BGNInst.bgnMode = BGN_ON; - - /* init statistics functions and counters */ - ok = WebRtcNetEQ_ClearInCallStats(&NetEqMainInst->DSPinst); - RETURN_ON_ERROR(ok, NetEqMainInst); - ok = WebRtcNetEQ_ClearPostCallStats(&NetEqMainInst->DSPinst); - RETURN_ON_ERROR(ok, NetEqMainInst); - ok = WebRtcNetEQ_ResetMcuJitterStat(&NetEqMainInst->MCUinst); - RETURN_ON_ERROR(ok, NetEqMainInst); - - /* flush packet buffer */ - ok = WebRtcNetEQ_PacketBufferFlush(&NetEqMainInst->MCUinst.PacketBuffer_inst); - RETURN_ON_ERROR(ok, NetEqMainInst); - - /* set some variables to initial values */ - NetEqMainInst->MCUinst.current_Codec = -1; - NetEqMainInst->MCUinst.current_Payload = -1; - NetEqMainInst->MCUinst.first_packet = 1; - NetEqMainInst->MCUinst.one_desc = 0; - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.extraDelayMs = 0; - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.minimum_delay_ms = 0; - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.maximum_delay_ms = - 10000; - NetEqMainInst->MCUinst.NoOfExpandCalls = 0; - NetEqMainInst->MCUinst.fs = fs; - - /* Not in AV-sync by default. */ - NetEqMainInst->MCUinst.av_sync = 0; - -#ifdef NETEQ_ATEVENT_DECODE - /* init DTMF decoder */ - ok = WebRtcNetEQ_DtmfDecoderInit(&(NetEqMainInst->MCUinst.DTMF_inst),fs,560); - RETURN_ON_ERROR(ok, NetEqMainInst); -#endif - - /* init RTCP statistics */ - WebRtcNetEQ_RTCPInit(&(NetEqMainInst->MCUinst.RTCP_inst), 0); - - /* set BufferStat struct to zero */ - WebRtcSpl_MemSetW16((int16_t*) &(NetEqMainInst->MCUinst.BufferStat_inst), 0, - sizeof(BufstatsInst_t) / sizeof(int16_t)); - - /* reset automode */ - WebRtcNetEQ_ResetAutomode(&(NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst), - NetEqMainInst->MCUinst.PacketBuffer_inst.maxInsertPositions); - - NetEqMainInst->ErrorCode = 0; - -#ifdef NETEQ_STEREO - /* set master/slave info to undecided */ - NetEqMainInst->masterSlave = 0; -#endif - - /* Set to an invalid value. */ - NetEqMainInst->MCUinst.decoded_packet_sequence_number = -1; - NetEqMainInst->MCUinst.decoded_packet_timestamp = 0; - - return (ok); -} - -int WebRtcNetEQ_FlushBuffers(void *inst) -{ - int ok = 0; - - /* Typecast inst to internal instance format */ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - - if (NetEqMainInst == NULL) - { - return (-1); - } - - /* Flush packet buffer */ - ok = WebRtcNetEQ_PacketBufferFlush(&NetEqMainInst->MCUinst.PacketBuffer_inst); - RETURN_ON_ERROR(ok, NetEqMainInst); - - /* Set MCU to wait for new codec */ - NetEqMainInst->MCUinst.first_packet = 1; - - /* Flush speech buffer */ - ok = WebRtcNetEQ_FlushSpeechBuffer(&NetEqMainInst->DSPinst); - RETURN_ON_ERROR(ok, NetEqMainInst); - - return 0; -} - -int WebRtcNetEQ_SetAVTPlayout(void *inst, int PlayoutAVTon) -{ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); -#ifdef NETEQ_ATEVENT_DECODE - NetEqMainInst->MCUinst.AVT_PlayoutOn = PlayoutAVTon; - return(0); -#else - if (PlayoutAVTon != 0) - { - NetEqMainInst->ErrorCode = -DTMF_NOT_SUPPORTED; - return (-1); - } - else - { - return (0); - } -#endif -} - -int WebRtcNetEQ_SetExtraDelay(void *inst, int DelayInMs) { - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - if ((DelayInMs < 0) || (DelayInMs > 10000)) { - NetEqMainInst->ErrorCode = -FAULTY_DELAYVALUE; - return (-1); - } - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.extraDelayMs = DelayInMs; - return (0); -} - -int WebRtcNetEQ_SetMinimumDelay(void *inst, int minimum_delay_ms) { - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return -1; - if (minimum_delay_ms < 0 || minimum_delay_ms > 10000) { - NetEqMainInst->ErrorCode = -FAULTY_DELAYVALUE; - return -1; - } - if ((NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.maximum_delay_ms > - 0) && (minimum_delay_ms > - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.maximum_delay_ms)) { - NetEqMainInst->ErrorCode = -FAULTY_DELAYVALUE; - return -1; - } - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.minimum_delay_ms = - minimum_delay_ms; - return 0; -} - -int WebRtcNetEQ_SetMaximumDelay(void *inst, int maximum_delay_ms) { - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return -1; - if (maximum_delay_ms < 0 || maximum_delay_ms > 10000) { - NetEqMainInst->ErrorCode = -FAULTY_DELAYVALUE; - return -1; - } - if (maximum_delay_ms < - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.minimum_delay_ms) { - NetEqMainInst->ErrorCode = -FAULTY_DELAYVALUE; - return -1; - } - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.maximum_delay_ms = - maximum_delay_ms; - return 0; -} - -int WebRtcNetEQ_SetPlayoutMode(void *inst, enum WebRtcNetEQPlayoutMode playoutMode) -{ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - if ((playoutMode != kPlayoutOn) && (playoutMode != kPlayoutOff) && (playoutMode - != kPlayoutFax) && (playoutMode != kPlayoutStreaming)) - { - NetEqMainInst->ErrorCode = -FAULTY_PLAYOUTMODE; - return (-1); - } - else - { - NetEqMainInst->MCUinst.NetEqPlayoutMode = playoutMode; - return (0); - } -} - -int WebRtcNetEQ_SetBGNMode(void *inst, enum WebRtcNetEQBGNMode bgnMode) -{ - - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - - /* Instance sanity */ - if (NetEqMainInst == NULL) return (-1); - - /* Check for corrupt/cleared instance */ - if (NetEqMainInst->MCUinst.main_inst != NetEqMainInst) - { - /* Instance is corrupt */ - NetEqMainInst->ErrorCode = CORRUPT_INSTANCE; - return (-1); - } - - NetEqMainInst->DSPinst.BGNInst.bgnMode = (enum BGNMode) bgnMode; - - return (0); -} - -int WebRtcNetEQ_GetBGNMode(const void *inst, enum WebRtcNetEQBGNMode *bgnMode) -{ - - const MainInst_t *NetEqMainInst = (const MainInst_t*) inst; - - /* Instance sanity */ - if (NetEqMainInst == NULL) return (-1); - - *bgnMode = (enum WebRtcNetEQBGNMode) NetEqMainInst->DSPinst.BGNInst.bgnMode; - - return (0); -} - -/************************************************ - * CodecDB functions - */ - -int WebRtcNetEQ_CodecDbReset(void *inst) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - ok = WebRtcNetEQ_DbReset(&NetEqMainInst->MCUinst.codec_DB_inst); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - - /* set function pointers to NULL to prevent RecOut from using the codec */ - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecode = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecodeRCU = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcAddLatePkt = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecode = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecodeInit = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecodePLC = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcGetMDinfo = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcUpdBWEst = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcGetErrorCode = NULL; - - return (0); -} - -int WebRtcNetEQ_CodecDbGetSizeInfo(void *inst, int16_t *UsedEntries, - int16_t *MaxEntries) -{ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - *MaxEntries = NUM_CODECS; - *UsedEntries = NetEqMainInst->MCUinst.codec_DB_inst.nrOfCodecs; - return (0); -} - -int WebRtcNetEQ_CodecDbGetCodecInfo(void *inst, int16_t Entry, - enum WebRtcNetEQDecoder *codec) -{ - int i; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - *codec = (enum WebRtcNetEQDecoder) 0; - if ((Entry >= 0) && (Entry < NetEqMainInst->MCUinst.codec_DB_inst.nrOfCodecs)) - { - for (i = 0; i < NUM_TOTAL_CODECS; i++) - { - if (NetEqMainInst->MCUinst.codec_DB_inst.position[i] == Entry) - { - *codec = (enum WebRtcNetEQDecoder) i; - } - } - } - else - { - NetEqMainInst->ErrorCode = -(CODEC_DB_NOT_EXIST1); - return (-1); - } - return (0); -} - -int WebRtcNetEQ_CodecDbAdd(void *inst, WebRtcNetEQ_CodecDef *codecInst) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - ok = WebRtcNetEQ_DbAdd(&NetEqMainInst->MCUinst.codec_DB_inst, codecInst->codec, - codecInst->payloadType, codecInst->funcDecode, codecInst->funcDecodeRCU, - codecInst->funcDecodePLC, codecInst->funcDecodeInit, codecInst->funcAddLatePkt, - codecInst->funcGetMDinfo, codecInst->funcGetPitch, codecInst->funcUpdBWEst, - codecInst->funcDurationEst, codecInst->funcGetErrorCode, - codecInst->codec_state, codecInst->codec_fs); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -int WebRtcNetEQ_CodecDbRemove(void *inst, enum WebRtcNetEQDecoder codec) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - - /* check if currently used codec is being removed */ - if (NetEqMainInst->MCUinst.current_Codec == (int16_t) codec) - { - /* set function pointers to NULL to prevent RecOut from using the codec */ - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecode = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecodeRCU = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcAddLatePkt = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecode = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecodeInit = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcDecodePLC = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcGetMDinfo = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcUpdBWEst = NULL; - NetEqMainInst->DSPinst.codec_ptr_inst.funcGetErrorCode = NULL; - } - - ok = WebRtcNetEQ_DbRemove(&NetEqMainInst->MCUinst.codec_DB_inst, codec); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -/********************************* - * Real-time functions - */ - -int WebRtcNetEQ_RecIn(void *inst, int16_t *p_w16datagramstart, int16_t w16_RTPlen, - uint32_t uw32_timeRec) -{ - int ok = 0; - RTPPacket_t RTPpacket; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - - /* Check for corrupt/cleared instance */ - if (NetEqMainInst->MCUinst.main_inst != NetEqMainInst) - { - /* Instance is corrupt */ - NetEqMainInst->ErrorCode = CORRUPT_INSTANCE; - return (-1); - } - - /* Parse RTP header */ - ok = WebRtcNetEQ_RTPPayloadInfo(p_w16datagramstart, w16_RTPlen, &RTPpacket); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - - ok = WebRtcNetEQ_RecInInternal(&NetEqMainInst->MCUinst, &RTPpacket, uw32_timeRec); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -/**************************************************************************** - * WebRtcNetEQ_RecInRTPStruct(...) - * - * Alternative RecIn function, used when the RTP data has already been - * parsed into an RTP info struct (WebRtcNetEQ_RTPInfo). - * - * Input: - * - inst : NetEQ instance - * - rtpInfo : Pointer to RTP info - * - payloadPtr : Pointer to the RTP payload (first byte after header) - * - payloadLenBytes : Length (in bytes) of the payload in payloadPtr - * - timeRec : Receive time (in timestamps of the used codec) - * - * Return value : 0 - Ok - * -1 - Error - */ -int WebRtcNetEQ_RecInRTPStruct(void *inst, WebRtcNetEQ_RTPInfo *rtpInfo, - const uint8_t *payloadPtr, int16_t payloadLenBytes, - uint32_t uw32_timeRec) -{ - int ok = 0; - RTPPacket_t RTPpacket; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) - { - return (-1); - } - - /* Check for corrupt/cleared instance */ - if (NetEqMainInst->MCUinst.main_inst != NetEqMainInst) - { - /* Instance is corrupt */ - NetEqMainInst->ErrorCode = CORRUPT_INSTANCE; - return (-1); - } - - /* Load NetEQ's RTP struct from Module RTP struct */ - RTPpacket.payloadType = rtpInfo->payloadType; - RTPpacket.seqNumber = rtpInfo->sequenceNumber; - RTPpacket.timeStamp = rtpInfo->timeStamp; - RTPpacket.ssrc = rtpInfo->SSRC; - RTPpacket.payload = (const int16_t*) payloadPtr; - RTPpacket.payloadLen = payloadLenBytes; - RTPpacket.starts_byte1 = 0; - - ok = WebRtcNetEQ_RecInInternal(&NetEqMainInst->MCUinst, &RTPpacket, uw32_timeRec); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -int WebRtcNetEQ_RecOut(void *inst, int16_t *pw16_outData, int16_t *pw16_len) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; -#ifdef NETEQ_STEREO - MasterSlaveInfo msInfo; - msInfo.msMode = NETEQ_MONO; -#endif - - if (NetEqMainInst == NULL) return (-1); - - /* Check for corrupt/cleared instance */ - if (NetEqMainInst->DSPinst.main_inst != NetEqMainInst) - { - /* Instance is corrupt */ - NetEqMainInst->ErrorCode = CORRUPT_INSTANCE; - return (-1); - } - -#ifdef NETEQ_STEREO - NetEqMainInst->DSPinst.msInfo = &msInfo; -#endif - - ok = WebRtcNetEQ_RecOutInternal(&NetEqMainInst->DSPinst, pw16_outData, - pw16_len, 0 /* not BGN only */, NetEqMainInst->MCUinst.av_sync); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -/**************************************************************************** - * WebRtcNetEQ_RecOutMasterSlave(...) - * - * RecOut function for running several NetEQ instances in master/slave mode. - * One master can be used to control several slaves. - * - * Input: - * - inst : NetEQ instance - * - isMaster : Non-zero indicates that this is the master channel - * - msInfo : (slave only) Information from master - * - * Output: - * - inst : Updated NetEQ instance - * - pw16_outData : Pointer to vector where output should be written - * - pw16_len : Pointer to variable where output length is returned - * - msInfo : (master only) Information to slave(s) - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_RecOutMasterSlave(void *inst, int16_t *pw16_outData, - int16_t *pw16_len, void *msInfo, - int16_t isMaster) -{ -#ifndef NETEQ_STEREO - /* Stereo not supported */ - return(-1); -#else - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - - if (NetEqMainInst == NULL) return (-1); - - /* Check for corrupt/cleared instance */ - if (NetEqMainInst->DSPinst.main_inst != NetEqMainInst) - { - /* Instance is corrupt */ - NetEqMainInst->ErrorCode = CORRUPT_INSTANCE; - return (-1); - } - - if (msInfo == NULL) - { - /* msInfo not provided */ - NetEqMainInst->ErrorCode = NETEQ_OTHER_ERROR; - return (-1); - } - - /* translate from external to internal Master/Slave information */ - NetEqMainInst->DSPinst.msInfo = (MasterSlaveInfo *) msInfo; - - /* check that we have not done a master/slave switch without first re-initializing */ - if ((NetEqMainInst->masterSlave == 1 && !isMaster) || /* switch from master to slave */ - (NetEqMainInst->masterSlave == 2 && isMaster)) /* switch from slave to master */ - { - NetEqMainInst->ErrorCode = ILLEGAL_MASTER_SLAVE_SWITCH; - return (-1); - } - - if (!isMaster) - { - /* this is the slave */ - NetEqMainInst->masterSlave = 2; - NetEqMainInst->DSPinst.msInfo->msMode = NETEQ_SLAVE; - } - else - { - NetEqMainInst->DSPinst.msInfo->msMode = NETEQ_MASTER; - } - - ok = WebRtcNetEQ_RecOutInternal(&NetEqMainInst->DSPinst, pw16_outData, - pw16_len, 0 /* not BGN only */, NetEqMainInst->MCUinst.av_sync); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - - if (isMaster) - { - /* this is the master */ - NetEqMainInst->masterSlave = 1; - } - - return (ok); -#endif -} - -int WebRtcNetEQ_GetMasterSlaveInfoSize() -{ -#ifdef NETEQ_STEREO - return (sizeof(MasterSlaveInfo)); -#else - return(-1); -#endif -} - -/* Special RecOut that does not do any decoding. */ -int WebRtcNetEQ_RecOutNoDecode(void *inst, int16_t *pw16_outData, - int16_t *pw16_len) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; -#ifdef NETEQ_STEREO - MasterSlaveInfo msInfo; -#endif - - if (NetEqMainInst == NULL) return (-1); - - /* Check for corrupt/cleared instance */ - if (NetEqMainInst->DSPinst.main_inst != NetEqMainInst) - { - /* Instance is corrupt */ - NetEqMainInst->ErrorCode = CORRUPT_INSTANCE; - return (-1); - } - -#ifdef NETEQ_STEREO - /* keep same mode as before */ - switch (NetEqMainInst->masterSlave) - { - case 1: - { - msInfo.msMode = NETEQ_MASTER; - break; - } - case 2: - { - msInfo.msMode = NETEQ_SLAVE; - break; - } - default: - { - msInfo.msMode = NETEQ_MONO; - break; - } - } - - NetEqMainInst->DSPinst.msInfo = &msInfo; -#endif - - ok = WebRtcNetEQ_RecOutInternal(&NetEqMainInst->DSPinst, pw16_outData, - pw16_len, 1 /* BGN only */, NetEqMainInst->MCUinst.av_sync); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -int WebRtcNetEQ_GetRTCPStats(void *inst, WebRtcNetEQ_RTCPStat *RTCP_inst) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - ok = WebRtcNetEQ_RTCPGetStats(&NetEqMainInst->MCUinst.RTCP_inst, - &RTCP_inst->fraction_lost, &RTCP_inst->cum_lost, &RTCP_inst->ext_max, - &RTCP_inst->jitter, 0); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -int WebRtcNetEQ_GetRTCPStatsNoReset(void *inst, WebRtcNetEQ_RTCPStat *RTCP_inst) -{ - int ok = 0; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - ok = WebRtcNetEQ_RTCPGetStats(&NetEqMainInst->MCUinst.RTCP_inst, - &RTCP_inst->fraction_lost, &RTCP_inst->cum_lost, &RTCP_inst->ext_max, - &RTCP_inst->jitter, 1); - if (ok != 0) - { - NetEqMainInst->ErrorCode = -ok; - return (-1); - } - return (ok); -} - -int WebRtcNetEQ_GetSpeechTimeStamp(void *inst, uint32_t *timestamp) -{ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - - if (NetEqMainInst->MCUinst.TSscalingInitialized) - { - *timestamp = WebRtcNetEQ_ScaleTimestampInternalToExternal(&NetEqMainInst->MCUinst, - NetEqMainInst->DSPinst.videoSyncTimestamp); - } - else - { - *timestamp = NetEqMainInst->DSPinst.videoSyncTimestamp; - } - - return (0); -} - -/**************************************************************************** - * WebRtcNetEQ_GetSpeechOutputType(...) - * - * Get the output type for the audio provided by the latest call to - * WebRtcNetEQ_RecOut(). - * - * kOutputNormal = normal audio (possibly processed) - * kOutputPLC = loss concealment through stretching audio - * kOutputCNG = comfort noise (codec-internal or RFC3389) - * kOutputPLCtoCNG = background noise only due to long expand or error - * kOutputVADPassive = PostDecode VAD signalling passive speaker - * - * Input: - * - inst : NetEQ instance - * - * Output: - * - outputType : Output type from enum list WebRtcNetEQOutputType - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_GetSpeechOutputType(void *inst, enum WebRtcNetEQOutputType *outputType) -{ - /* Typecast to internal instance type */ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - - if (NetEqMainInst == NULL) - { - return (-1); - } - - if ((NetEqMainInst->DSPinst.w16_mode & MODE_BGN_ONLY) != 0) - { - /* If last mode was background noise only */ - *outputType = kOutputPLCtoCNG; - - } - else if ((NetEqMainInst->DSPinst.w16_mode == MODE_CODEC_INTERNAL_CNG) - || (NetEqMainInst->DSPinst.w16_mode == MODE_RFC3389CNG)) - { - /* If CN or internal CNG */ - *outputType = kOutputCNG; - - } - else if ((NetEqMainInst->DSPinst.w16_mode == MODE_EXPAND) - && (NetEqMainInst->DSPinst.ExpandInst.w16_expandMuteFactor == 0)) - { - /* Expand mode has faded down to background noise only (very long expand) */ - *outputType = kOutputPLCtoCNG; - - } - else if (NetEqMainInst->DSPinst.w16_mode == MODE_EXPAND) - { - /* PLC mode */ - *outputType = kOutputPLC; - -#ifdef NETEQ_VAD - } - else if ( NetEqMainInst->DSPinst.VADInst.VADDecision == 0 ) - { - /* post-decode VAD says passive speaker */ - *outputType = kOutputVADPassive; -#endif /* NETEQ_VAD */ - - } - else - { - /* Normal speech output type (can still be manipulated, e.g., accelerated) */ - *outputType = kOutputNormal; - } - - return (0); -} - -/********************************** - * Functions related to VQmon - */ - -#define WEBRTC_NETEQ_CONCEALMENTFLAG_LOST 0x01 -#define WEBRTC_NETEQ_CONCEALMENTFLAG_DISCARDED 0x02 -#define WEBRTC_NETEQ_CONCEALMENTFLAG_SUPRESS 0x04 -#define WEBRTC_NETEQ_CONCEALMENTFLAG_CNGACTIVE 0x80 - -int WebRtcNetEQ_VQmonRecOutStatistics(void *inst, uint16_t *validVoiceDurationMs, - uint16_t *concealedVoiceDurationMs, - uint8_t *concealedVoiceFlags) -{ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - int16_t fs_mult; - int16_t ms_lost; - if (NetEqMainInst == NULL) return (-1); - fs_mult = WebRtcSpl_DivW32W16ResW16(NetEqMainInst->MCUinst.fs, 8000); - - ms_lost = WebRtcSpl_DivW32W16ResW16( - (int32_t) NetEqMainInst->DSPinst.w16_concealedTS, (int16_t) (8 * fs_mult)); - if (ms_lost > NetEqMainInst->DSPinst.millisecondsPerCall) ms_lost - = NetEqMainInst->DSPinst.millisecondsPerCall; - - *validVoiceDurationMs = NetEqMainInst->DSPinst.millisecondsPerCall - ms_lost; - *concealedVoiceDurationMs = ms_lost; - if (ms_lost > 0) - { - *concealedVoiceFlags = WEBRTC_NETEQ_CONCEALMENTFLAG_LOST; - } - else - { - *concealedVoiceFlags = 0; - } - NetEqMainInst->DSPinst.w16_concealedTS -= ms_lost * (8 * fs_mult); - - return (0); -} - -int WebRtcNetEQ_VQmonGetConfiguration(void *inst, uint16_t *absMaxDelayMs, - uint8_t *adaptationRate) -{ - /* Dummy check the inst, just to avoid compiler warnings. */ - if (inst == NULL) - { - /* Do nothing. */ - } - - /* Hardcoded variables that are used for VQmon as jitter buffer parameters */ - *absMaxDelayMs = 240; - *adaptationRate = 1; - return (0); -} - -int WebRtcNetEQ_VQmonGetRxStatistics(void *inst, uint16_t *avgDelayMs, - uint16_t *maxDelayMs) -{ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) return (-1); - *avgDelayMs = (uint16_t) (NetEqMainInst->MCUinst.BufferStat_inst.avgDelayMsQ8 >> 8); - *maxDelayMs = (uint16_t) NetEqMainInst->MCUinst.BufferStat_inst.maxDelayMs; - return (0); -} - -/************************************* - * Statistics functions - */ - -/* Get the "in-call" statistics from NetEQ. - * The statistics are reset after the query. */ -int WebRtcNetEQ_GetNetworkStatistics(void *inst, WebRtcNetEQ_NetworkStatistics *stats) - -{ - - uint16_t tempU16; - uint32_t tempU32, tempU32_2; - int numShift; - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - - /* Instance sanity */ - if (NetEqMainInst == NULL) return (-1); - - stats->addedSamples = NetEqMainInst->DSPinst.statInst.addedSamples; - - /*******************/ - /* Get buffer size */ - /*******************/ - - if (NetEqMainInst->MCUinst.fs != 0) - { - int32_t temp32; - /* Query packet buffer for number of samples. */ - temp32 = WebRtcNetEQ_PacketBufferGetSize( - &NetEqMainInst->MCUinst.PacketBuffer_inst, - &NetEqMainInst->MCUinst.codec_DB_inst, - NetEqMainInst->MCUinst.av_sync); - - /* Divide by sample rate. - * Calculate temp32 * 1000 / fs to get result in ms. */ - stats->currentBufferSize = (uint16_t) - WebRtcSpl_DivU32U16(temp32 * 1000, NetEqMainInst->MCUinst.fs); - - /* Add number of samples yet to play in sync buffer. */ - temp32 = (int32_t) (NetEqMainInst->DSPinst.endPosition - - NetEqMainInst->DSPinst.curPosition); - stats->currentBufferSize += (uint16_t) - WebRtcSpl_DivU32U16(temp32 * 1000, NetEqMainInst->MCUinst.fs); - } - else - { - /* Sample rate not initialized. */ - stats->currentBufferSize = 0; - } - - /***************************/ - /* Get optimal buffer size */ - /***************************/ - - if (NetEqMainInst->MCUinst.fs != 0) - { - /* preferredBufferSize = Bopt * packSizeSamples / (fs/1000) */ - stats->preferredBufferSize - = (uint16_t) WEBRTC_SPL_MUL_16_16( - (int16_t) ((NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.optBufLevel) >> 8), /* optimal buffer level in packets shifted to Q0 */ - WebRtcSpl_DivW32W16ResW16( - (int32_t) NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.packetSpeechLenSamp, /* samples per packet */ - WebRtcSpl_DivW32W16ResW16( (int32_t) NetEqMainInst->MCUinst.fs, (int16_t) 1000 ) /* samples per ms */ - ) ); - - /* add extra delay */ - if (NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.extraDelayMs > 0) - { - stats->preferredBufferSize - += NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.extraDelayMs; - } - } - else - { - /* sample rate not initialized */ - stats->preferredBufferSize = 0; - } - - /***********************************/ - /* Check if jitter peaks are found */ - /***********************************/ - - stats->jitterPeaksFound = - NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.peakFound; - - /***********************/ - /* Calculate loss rate */ - /***********************/ - - /* timestamps elapsed since last report */ - tempU32 = NetEqMainInst->MCUinst.lastReportTS; - - if (NetEqMainInst->MCUinst.lostTS == 0) - { - /* no losses */ - stats->currentPacketLossRate = 0; - } - else if (NetEqMainInst->MCUinst.lostTS < tempU32) - { - /* calculate shifts; we want the result in Q14 */ - numShift = WebRtcSpl_NormU32(NetEqMainInst->MCUinst.lostTS); /* numerator shift for normalize */ - - if (numShift < 14) - { - /* cannot shift numerator 14 steps; shift denominator too */ - tempU32 = WEBRTC_SPL_RSHIFT_U32(tempU32, 14-numShift); /* right-shift */ - } - else - { - /* shift no more than 14 steps */ - numShift = 14; - } - - if (tempU32 == 0) - { - /* check for zero denominator; result should be zero in this case */ - stats->currentPacketLossRate = 0; - } - else - { - /* check that denominator fits in signed 16-bit */ - while (tempU32 > WEBRTC_SPL_WORD16_MAX) - { - tempU32 >>= 1; /* right-shift 1 step */ - numShift--; /* compensate in numerator */ - } - tempU16 = (uint16_t) tempU32; - - /* do the shift of numerator */ - tempU32 - = WEBRTC_SPL_SHIFT_W32( (uint32_t) NetEqMainInst->MCUinst.lostTS, numShift); - - stats->currentPacketLossRate = (uint16_t) WebRtcSpl_DivU32U16(tempU32, - tempU16); - } - } - else - { - /* lost count is larger than elapsed time count; probably timestamp wrap-around or something else wrong */ - /* set loss rate = 1 */ - stats->currentPacketLossRate = 1 << 14; /* 1 in Q14 */ - } - - /**************************/ - /* Calculate discard rate */ - /**************************/ - - /* timestamps elapsed since last report */ - tempU32 = NetEqMainInst->MCUinst.lastReportTS; - - /* number of discarded samples */ - tempU32_2 - = WEBRTC_SPL_MUL_16_U16( (int16_t) NetEqMainInst->MCUinst.PacketBuffer_inst.packSizeSamples, - NetEqMainInst->MCUinst.PacketBuffer_inst.discardedPackets); - - if (tempU32_2 == 0) - { - /* no discarded samples */ - stats->currentDiscardRate = 0; - } - else if (tempU32_2 < tempU32) - { - /* calculate shifts; we want the result in Q14 */ - numShift = WebRtcSpl_NormU32(tempU32_2); /* numerator shift for normalize */ - - if (numShift < 14) - { - /* cannot shift numerator 14 steps; shift denominator too */ - tempU32 = WEBRTC_SPL_RSHIFT_U32(tempU32, 14-numShift); /* right-shift */ - } - else - { - /* shift no more than 14 steps */ - numShift = 14; - } - - if (tempU32 == 0) - { - /* check for zero denominator; result should be zero in this case */ - stats->currentDiscardRate = 0; - } - else - { - /* check that denominator fits in signed 16-bit */ - while (tempU32 > WEBRTC_SPL_WORD16_MAX) - { - tempU32 >>= 1; /* right-shift 1 step */ - numShift--; /* compensate in numerator */ - } - tempU16 = (uint16_t) tempU32; - - /* do the shift of numerator */ - tempU32 = WEBRTC_SPL_SHIFT_W32( tempU32_2, numShift); - - stats->currentDiscardRate = (uint16_t) WebRtcSpl_DivU32U16(tempU32, tempU16); - } - } - else - { - /* lost count is larger than elapsed time count; probably timestamp wrap-around or something else wrong */ - /* set loss rate = 1 */ - stats->currentDiscardRate = 1 << 14; /* 1 in Q14 */ - } - - /*************************************************************/ - /* Calculate Accelerate, Expand and Pre-emptive Expand rates */ - /*************************************************************/ - - /* timestamps elapsed since last report */ - tempU32 = NetEqMainInst->MCUinst.lastReportTS; - - if (NetEqMainInst->DSPinst.statInst.accelerateLength == 0) - { - /* no accelerate */ - stats->currentAccelerateRate = 0; - } - else if (NetEqMainInst->DSPinst.statInst.accelerateLength < tempU32) - { - /* calculate shifts; we want the result in Q14 */ - numShift = WebRtcSpl_NormU32(NetEqMainInst->DSPinst.statInst.accelerateLength); /* numerator shift for normalize */ - - if (numShift < 14) - { - /* cannot shift numerator 14 steps; shift denominator too */ - tempU32 = WEBRTC_SPL_RSHIFT_U32(tempU32, 14-numShift); /* right-shift */ - } - else - { - /* shift no more than 14 steps */ - numShift = 14; - } - - if (tempU32 == 0) - { - /* check for zero denominator; result should be zero in this case */ - stats->currentAccelerateRate = 0; - } - else - { - /* check that denominator fits in signed 16-bit */ - while (tempU32 > WEBRTC_SPL_WORD16_MAX) - { - tempU32 >>= 1; /* right-shift 1 step */ - numShift--; /* compensate in numerator */ - } - tempU16 = (uint16_t) tempU32; - - /* do the shift of numerator */ - tempU32 - = WEBRTC_SPL_SHIFT_W32( NetEqMainInst->DSPinst.statInst.accelerateLength, numShift); - - stats->currentAccelerateRate = (uint16_t) WebRtcSpl_DivU32U16(tempU32, - tempU16); - } - } - else - { - /* lost count is larger than elapsed time count; probably timestamp wrap-around or something else wrong */ - /* set loss rate = 1 */ - stats->currentAccelerateRate = 1 << 14; /* 1 in Q14 */ - } - - /* timestamps elapsed since last report */ - tempU32 = NetEqMainInst->MCUinst.lastReportTS; - - if (NetEqMainInst->DSPinst.statInst.expandLength == 0) - { - /* no expand */ - stats->currentExpandRate = 0; - } - else if (NetEqMainInst->DSPinst.statInst.expandLength < tempU32) - { - /* calculate shifts; we want the result in Q14 */ - numShift = WebRtcSpl_NormU32(NetEqMainInst->DSPinst.statInst.expandLength); /* numerator shift for normalize */ - - if (numShift < 14) - { - /* cannot shift numerator 14 steps; shift denominator too */ - tempU32 = WEBRTC_SPL_RSHIFT_U32(tempU32, 14-numShift); /* right-shift */ - } - else - { - /* shift no more than 14 steps */ - numShift = 14; - } - - if (tempU32 == 0) - { - /* check for zero denominator; result should be zero in this case */ - stats->currentExpandRate = 0; - } - else - { - /* check that denominator fits in signed 16-bit */ - while (tempU32 > WEBRTC_SPL_WORD16_MAX) - { - tempU32 >>= 1; /* right-shift 1 step */ - numShift--; /* compensate in numerator */ - } - tempU16 = (uint16_t) tempU32; - - /* do the shift of numerator */ - tempU32 - = WEBRTC_SPL_SHIFT_W32( NetEqMainInst->DSPinst.statInst.expandLength, numShift); - - stats->currentExpandRate = (uint16_t) WebRtcSpl_DivU32U16(tempU32, tempU16); - } - } - else - { - /* lost count is larger than elapsed time count; probably timestamp wrap-around or something else wrong */ - /* set loss rate = 1 */ - stats->currentExpandRate = 1 << 14; /* 1 in Q14 */ - } - - /* timestamps elapsed since last report */ - tempU32 = NetEqMainInst->MCUinst.lastReportTS; - - if (NetEqMainInst->DSPinst.statInst.preemptiveLength == 0) - { - /* no pre-emptive expand */ - stats->currentPreemptiveRate = 0; - } - else if (NetEqMainInst->DSPinst.statInst.preemptiveLength < tempU32) - { - /* calculate shifts; we want the result in Q14 */ - numShift = WebRtcSpl_NormU32(NetEqMainInst->DSPinst.statInst.preemptiveLength); /* numerator shift for normalize */ - - if (numShift < 14) - { - /* cannot shift numerator 14 steps; shift denominator too */ - tempU32 = WEBRTC_SPL_RSHIFT_U32(tempU32, 14-numShift); /* right-shift */ - } - else - { - /* shift no more than 14 steps */ - numShift = 14; - } - - if (tempU32 == 0) - { - /* check for zero denominator; result should be zero in this case */ - stats->currentPreemptiveRate = 0; - } - else - { - /* check that denominator fits in signed 16-bit */ - while (tempU32 > WEBRTC_SPL_WORD16_MAX) - { - tempU32 >>= 1; /* right-shift 1 step */ - numShift--; /* compensate in numerator */ - } - tempU16 = (uint16_t) tempU32; - - /* do the shift of numerator */ - tempU32 - = WEBRTC_SPL_SHIFT_W32( NetEqMainInst->DSPinst.statInst.preemptiveLength, numShift); - - stats->currentPreemptiveRate = (uint16_t) WebRtcSpl_DivU32U16(tempU32, - tempU16); - } - } - else - { - /* lost count is larger than elapsed time count; probably timestamp wrap-around or something else wrong */ - /* set loss rate = 1 */ - stats->currentPreemptiveRate = 1 << 14; /* 1 in Q14 */ - } - - stats->clockDriftPPM = WebRtcNetEQ_AverageIAT( - &NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst); - - /* reset counters */ - WebRtcNetEQ_ResetMcuInCallStats(&(NetEqMainInst->MCUinst)); - WebRtcNetEQ_ClearInCallStats(&(NetEqMainInst->DSPinst)); - - return (0); -} - -int WebRtcNetEQ_GetRawFrameWaitingTimes(void *inst, - int max_length, - int* waiting_times_ms) { - int i = 0; - MainInst_t *main_inst = (MainInst_t*) inst; - if (main_inst == NULL) return -1; - - while ((i < max_length) && (i < main_inst->MCUinst.len_waiting_times)) { - waiting_times_ms[i] = main_inst->MCUinst.waiting_times[i] * - main_inst->DSPinst.millisecondsPerCall; - ++i; - } - assert(i <= kLenWaitingTimes); - WebRtcNetEQ_ResetWaitingTimeStats(&main_inst->MCUinst); - return i; -} - -/**************************************************************************** - * WebRtcNetEQ_SetVADInstance(...) - * - * Provide a pointer to an allocated VAD instance. If function is never - * called or it is called with NULL pointer as VAD_inst, the post-decode - * VAD functionality is disabled. Also provide pointers to init, setmode - * and VAD functions. These are typically pointers to WebRtcVad_Init, - * WebRtcVad_set_mode and WebRtcVad_Process, respectively, all found in the - * interface file webrtc_vad.h. - * - * Input: - * - NetEQ_inst : NetEQ instance - * - VADinst : VAD instance - * - initFunction : Pointer to VAD init function - * - setmodeFunction : Pointer to VAD setmode function - * - VADfunction : Pointer to VAD function - * - * Output: - * - NetEQ_inst : Updated NetEQ instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_SetVADInstance(void *NetEQ_inst, void *VAD_inst, - WebRtcNetEQ_VADInitFunction initFunction, - WebRtcNetEQ_VADSetmodeFunction setmodeFunction, - WebRtcNetEQ_VADFunction VADFunction) -{ - - /* Typecast to internal instance type */ - MainInst_t *NetEqMainInst = (MainInst_t*) NetEQ_inst; - if (NetEqMainInst == NULL) - { - return (-1); - } - -#ifdef NETEQ_VAD - - /* Store pointer in PostDecode VAD struct */ - NetEqMainInst->DSPinst.VADInst.VADState = VAD_inst; - - /* Store function pointers */ - NetEqMainInst->DSPinst.VADInst.initFunction = initFunction; - NetEqMainInst->DSPinst.VADInst.setmodeFunction = setmodeFunction; - NetEqMainInst->DSPinst.VADInst.VADFunction = VADFunction; - - /* Call init function and return the result (ok or fail) */ - return(WebRtcNetEQ_InitVAD(&NetEqMainInst->DSPinst.VADInst, NetEqMainInst->DSPinst.fs)); - -#else /* NETEQ_VAD not defined */ - return (-1); -#endif /* NETEQ_VAD */ - -} - -/**************************************************************************** - * WebRtcNetEQ_SetVADMode(...) - * - * Pass an aggressiveness mode parameter to the post-decode VAD instance. - * If this function is never called, mode 0 (quality mode) is used as default. - * - * Input: - * - inst : NetEQ instance - * - mode : mode parameter (same range as WebRtc VAD mode) - * - * Output: - * - inst : Updated NetEQ instance - * - * Return value : 0 - Ok - * -1 - Error - */ - -int WebRtcNetEQ_SetVADMode(void *inst, int mode) -{ - - /* Typecast to internal instance type */ - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst == NULL) - { - return (-1); - } - -#ifdef NETEQ_VAD - - /* Set mode and return result */ - return(WebRtcNetEQ_SetVADModeInternal(&NetEqMainInst->DSPinst.VADInst, mode)); - -#else /* NETEQ_VAD not defined */ - return (-1); -#endif /* NETEQ_VAD */ - -} - -void WebRtcNetEQ_GetProcessingActivity(void *inst, - WebRtcNetEQ_ProcessingActivity *stats) { - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - - stats->accelerate_bgn_samples = - NetEqMainInst->DSPinst.activity_stats.accelerate_bgn_samples; - stats->accelerate_normal_samples = - NetEqMainInst->DSPinst.activity_stats.accelarate_normal_samples; - - stats->expand_bgn_sampels = - NetEqMainInst->DSPinst.activity_stats.expand_bgn_samples; - stats->expand_normal_samples = - NetEqMainInst->DSPinst.activity_stats.expand_normal_samples; - - stats->preemptive_expand_bgn_samples = - NetEqMainInst->DSPinst.activity_stats.preemptive_expand_bgn_samples; - stats->preemptive_expand_normal_samples = - NetEqMainInst->DSPinst.activity_stats.preemptive_expand_normal_samples; - - stats->merge_expand_bgn_samples = - NetEqMainInst->DSPinst.activity_stats.merge_expand_bgn_samples; - stats->merge_expand_normal_samples = - NetEqMainInst->DSPinst.activity_stats.merge_expand_normal_samples; - - WebRtcNetEQ_ClearActivityStats(&NetEqMainInst->DSPinst); -} - -void WebRtcNetEQ_EnableAVSync(void* inst, int enable) { - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - NetEqMainInst->MCUinst.av_sync = (enable != 0) ? 1 : 0; -} - -int WebRtcNetEQ_RecInSyncRTP(void* inst, WebRtcNetEQ_RTPInfo* rtp_info, - uint32_t receive_timestamp) { - MainInst_t *NetEqMainInst = (MainInst_t*) inst; - if (NetEqMainInst->MCUinst.av_sync == 0) - return -1; - if (WebRtcNetEQ_RecInRTPStruct(inst, rtp_info, kSyncPayload, - SYNC_PAYLOAD_LEN_BYTES, - receive_timestamp) < 0) { - return -1; - } - return SYNC_PAYLOAD_LEN_BYTES; -} - -int WebRtcNetEQ_GetRequiredDelayMs(const void* inst) { - const MainInst_t* NetEqMainInst = (MainInst_t*)inst; - const AutomodeInst_t* auto_mode = (NetEqMainInst == NULL) ? NULL : - &NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst; - - /* Instance sanity */ - if (NetEqMainInst == NULL || auto_mode == NULL) - return 0; - - if (NetEqMainInst->MCUinst.fs == 0) - return 0; // Sampling rate not initialized. - - /* |required_delay_q8| has the unit of packets in Q8 domain, therefore, - * the corresponding delay is - * required_delay_ms = (1000 * required_delay_q8 * samples_per_packet / - * sample_rate_hz) / 256; - */ - return (auto_mode->required_delay_q8 * - ((auto_mode->packetSpeechLenSamp * 1000) / NetEqMainInst->MCUinst.fs) + - 128) >> 8; -} - -int WebRtcNetEQ_DecodedRtpInfo(const void* inst, - int* sequence_number, - uint32_t* timestamp) { - const MainInst_t *NetEqMainInst = (inst == NULL) ? NULL : - (const MainInst_t*) inst; - if (NetEqMainInst->MCUinst.decoded_packet_sequence_number < 0) - return -1; - *sequence_number = NetEqMainInst->MCUinst.decoded_packet_sequence_number; - *timestamp = NetEqMainInst->MCUinst.decoded_packet_timestamp; - return 0; -} diff --git a/webrtc/modules/audio_coding/neteq/webrtc_neteq_unittest.cc b/webrtc/modules/audio_coding/neteq/webrtc_neteq_unittest.cc deleted file mode 100644 index c6adf0365..000000000 --- a/webrtc/modules/audio_coding/neteq/webrtc_neteq_unittest.cc +++ /dev/null @@ -1,779 +0,0 @@ -/* - * 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 file includes unit tests for NetEQ. - */ - -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq.h" - -#include -#include // memset - -#include -#include -#include -#include -#include - -#include "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_help_macros.h" -#include "webrtc/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h" -#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_CodecClass.h" -#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_NetEQClass.h" -#include "webrtc/modules/audio_coding/neteq/test/NETEQTEST_RTPpacket.h" -#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h" -#include "webrtc/test/testsupport/fileutils.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -class RefFiles { - public: - RefFiles(const std::string& input_file, const std::string& output_file); - ~RefFiles(); - template void ProcessReference(const T& test_results); - template void ProcessReference( - const T (&test_results)[n], - size_t length); - template void WriteToFile( - const T (&test_results)[n], - size_t length); - template void ReadFromFileAndCompare( - const T (&test_results)[n], - size_t length); - void WriteToFile(const WebRtcNetEQ_NetworkStatistics& stats); - void ReadFromFileAndCompare(const WebRtcNetEQ_NetworkStatistics& stats); - void WriteToFile(const WebRtcNetEQ_RTCPStat& stats); - void ReadFromFileAndCompare(const WebRtcNetEQ_RTCPStat& stats); - - FILE* input_fp_; - FILE* output_fp_; -}; - -RefFiles::RefFiles(const std::string &input_file, - const std::string &output_file) - : input_fp_(NULL), - output_fp_(NULL) { - if (!input_file.empty()) { - input_fp_ = fopen(input_file.c_str(), "rb"); - EXPECT_TRUE(input_fp_ != NULL); - } - if (!output_file.empty()) { - output_fp_ = fopen(output_file.c_str(), "wb"); - EXPECT_TRUE(output_fp_ != NULL); - } -} - -RefFiles::~RefFiles() { - if (input_fp_) { - EXPECT_EQ(EOF, fgetc(input_fp_)); // Make sure that we reached the end. - fclose(input_fp_); - } - if (output_fp_) fclose(output_fp_); -} - -template -void RefFiles::ProcessReference(const T& test_results) { - WriteToFile(test_results); - ReadFromFileAndCompare(test_results); -} - -template -void RefFiles::ProcessReference(const T (&test_results)[n], size_t length) { - WriteToFile(test_results, length); - ReadFromFileAndCompare(test_results, length); -} - -template -void RefFiles::WriteToFile(const T (&test_results)[n], size_t length) { - if (output_fp_) { - ASSERT_EQ(length, fwrite(&test_results, sizeof(T), length, output_fp_)); - } -} - -template -void RefFiles::ReadFromFileAndCompare(const T (&test_results)[n], - size_t length) { - if (input_fp_) { - // Read from ref file. - T* ref = new T[length]; - ASSERT_EQ(length, fread(ref, sizeof(T), length, input_fp_)); - // Compare - ASSERT_EQ(0, memcmp(&test_results, ref, sizeof(T) * length)); - delete [] ref; - } -} - -void RefFiles::WriteToFile(const WebRtcNetEQ_NetworkStatistics& stats) { - if (output_fp_) { - ASSERT_EQ(1u, fwrite(&stats, sizeof(WebRtcNetEQ_NetworkStatistics), 1, - output_fp_)); - } -} - -void RefFiles::ReadFromFileAndCompare( - const WebRtcNetEQ_NetworkStatistics& stats) { - if (input_fp_) { - // Read from ref file. - size_t stat_size = sizeof(WebRtcNetEQ_NetworkStatistics); - WebRtcNetEQ_NetworkStatistics ref_stats; - ASSERT_EQ(1u, fread(&ref_stats, stat_size, 1, input_fp_)); - // Compare - EXPECT_EQ(0, memcmp(&stats, &ref_stats, stat_size)); - } -} - -void RefFiles::WriteToFile(const WebRtcNetEQ_RTCPStat& stats) { - if (output_fp_) { - ASSERT_EQ(1u, fwrite(&(stats.fraction_lost), sizeof(stats.fraction_lost), 1, - output_fp_)); - ASSERT_EQ(1u, fwrite(&(stats.cum_lost), sizeof(stats.cum_lost), 1, - output_fp_)); - ASSERT_EQ(1u, fwrite(&(stats.ext_max), sizeof(stats.ext_max), 1, - output_fp_)); - ASSERT_EQ(1u, fwrite(&(stats.jitter), sizeof(stats.jitter), 1, - output_fp_)); - } -} - -void RefFiles::ReadFromFileAndCompare( - const WebRtcNetEQ_RTCPStat& stats) { - if (input_fp_) { - // Read from ref file. - WebRtcNetEQ_RTCPStat ref_stats; - ASSERT_EQ(1u, fread(&(ref_stats.fraction_lost), - sizeof(ref_stats.fraction_lost), 1, input_fp_)); - ASSERT_EQ(1u, fread(&(ref_stats.cum_lost), sizeof(ref_stats.cum_lost), 1, - input_fp_)); - ASSERT_EQ(1u, fread(&(ref_stats.ext_max), sizeof(ref_stats.ext_max), 1, - input_fp_)); - ASSERT_EQ(1u, fread(&(ref_stats.jitter), sizeof(ref_stats.jitter), 1, - input_fp_)); - // Compare - EXPECT_EQ(ref_stats.fraction_lost, stats.fraction_lost); - EXPECT_EQ(ref_stats.cum_lost, stats.cum_lost); - EXPECT_EQ(ref_stats.ext_max, stats.ext_max); - EXPECT_EQ(ref_stats.jitter, stats.jitter); - } -} - -class NetEqDecodingTest : public ::testing::Test { - protected: - // NetEQ must be polled for data once every 10 ms. Thus, neither of the - // constants below can be changed. - static const int kTimeStepMs = 10; - static const int kBlockSize8kHz = kTimeStepMs * 8; - static const int kBlockSize16kHz = kTimeStepMs * 16; - static const int kBlockSize32kHz = kTimeStepMs * 32; - static const int kMaxBlockSize = kBlockSize32kHz; - - NetEqDecodingTest(); - virtual void SetUp(); - virtual void TearDown(); - void SelectDecoders(WebRtcNetEQDecoder* used_codec); - void LoadDecoders(); - void OpenInputFile(const std::string &rtp_file); - void Process(NETEQTEST_RTPpacket* rtp_ptr, int16_t* out_len); - void DecodeAndCompare(const std::string &rtp_file, - const std::string &ref_file); - void DecodeAndCheckStats(const std::string &rtp_file, - const std::string &stat_ref_file, - const std::string &rtcp_ref_file); - static void PopulateRtpInfo(int frame_index, - int timestamp, - WebRtcNetEQ_RTPInfo* rtp_info); - static void PopulateCng(int frame_index, - int timestamp, - WebRtcNetEQ_RTPInfo* rtp_info, - uint8_t* payload, - int* payload_len); - void WrapTest(uint16_t start_seq_no, uint32_t start_timestamp, - const std::set& drop_seq_numbers); - - NETEQTEST_NetEQClass* neteq_inst_; - std::vector dec_; - FILE* rtp_fp_; - unsigned int sim_clock_; - int16_t out_data_[kMaxBlockSize]; -}; - -NetEqDecodingTest::NetEqDecodingTest() - : neteq_inst_(NULL), - rtp_fp_(NULL), - sim_clock_(0) { - memset(out_data_, 0, sizeof(out_data_)); -} - -void NetEqDecodingTest::SetUp() { - WebRtcNetEQDecoder usedCodec[kDecoderReservedEnd - 1]; - - SelectDecoders(usedCodec); - neteq_inst_ = new NETEQTEST_NetEQClass(usedCodec, dec_.size(), 8000, - kTCPLargeJitter); - ASSERT_TRUE(neteq_inst_); - LoadDecoders(); -} - -void NetEqDecodingTest::TearDown() { - if (neteq_inst_) - delete neteq_inst_; - for (size_t i = 0; i < dec_.size(); ++i) { - if (dec_[i]) - delete dec_[i]; - } - if (rtp_fp_) - fclose(rtp_fp_); -} - -void NetEqDecodingTest::SelectDecoders(WebRtcNetEQDecoder* used_codec) { - *used_codec++ = kDecoderPCMu; - dec_.push_back(new decoder_PCMU(0)); - *used_codec++ = kDecoderPCMa; - dec_.push_back(new decoder_PCMA(8)); - *used_codec++ = kDecoderILBC; - dec_.push_back(new decoder_ILBC(102)); - *used_codec++ = kDecoderISAC; - dec_.push_back(new decoder_iSAC(103)); - *used_codec++ = kDecoderISACswb; - dec_.push_back(new decoder_iSACSWB(104)); - *used_codec++ = kDecoderISACfb; - dec_.push_back(new decoder_iSACFB(105)); - *used_codec++ = kDecoderPCM16B; - dec_.push_back(new decoder_PCM16B_NB(93)); - *used_codec++ = kDecoderPCM16Bwb; - dec_.push_back(new decoder_PCM16B_WB(94)); - *used_codec++ = kDecoderPCM16Bswb32kHz; - dec_.push_back(new decoder_PCM16B_SWB32(95)); - *used_codec++ = kDecoderCNG; - dec_.push_back(new decoder_CNG(13, 8000)); - *used_codec++ = kDecoderCNG; - dec_.push_back(new decoder_CNG(98, 16000)); -} - -void NetEqDecodingTest::LoadDecoders() { - for (size_t i = 0; i < dec_.size(); ++i) { - ASSERT_EQ(0, dec_[i]->loadToNetEQ(*neteq_inst_)); - } -} - -void NetEqDecodingTest::OpenInputFile(const std::string &rtp_file) { - rtp_fp_ = fopen(rtp_file.c_str(), "rb"); - ASSERT_TRUE(rtp_fp_ != NULL); - ASSERT_EQ(0, NETEQTEST_RTPpacket::skipFileHeader(rtp_fp_)); -} - -void NetEqDecodingTest::Process(NETEQTEST_RTPpacket* rtp, int16_t* out_len) { - // Check if time to receive. - while ((sim_clock_ >= rtp->time()) && - (rtp->dataLen() >= 0)) { - if (rtp->dataLen() > 0) { - ASSERT_EQ(0, neteq_inst_->recIn(*rtp)); - } - // Get next packet. - ASSERT_NE(-1, rtp->readFromFile(rtp_fp_)); - } - - // RecOut - *out_len = neteq_inst_->recOut(out_data_); - ASSERT_TRUE((*out_len == kBlockSize8kHz) || - (*out_len == kBlockSize16kHz) || - (*out_len == kBlockSize32kHz)); - - // Increase time. - sim_clock_ += kTimeStepMs; -} - -void NetEqDecodingTest::DecodeAndCompare(const std::string &rtp_file, - const std::string &ref_file) { - OpenInputFile(rtp_file); - - std::string ref_out_file = ""; - if (ref_file.empty()) { - ref_out_file = webrtc::test::OutputPath() + "neteq_out.pcm"; - } - RefFiles ref_files(ref_file, ref_out_file); - - NETEQTEST_RTPpacket rtp; - ASSERT_GT(rtp.readFromFile(rtp_fp_), 0); - int i = 0; - while (rtp.dataLen() >= 0) { - std::ostringstream ss; - ss << "Lap number " << i++ << " in DecodeAndCompare while loop"; - SCOPED_TRACE(ss.str()); // Print out the parameter values on failure. - int16_t out_len; - ASSERT_NO_FATAL_FAILURE(Process(&rtp, &out_len)); - ASSERT_NO_FATAL_FAILURE(ref_files.ProcessReference(out_data_, out_len)); - } -} - -void NetEqDecodingTest::DecodeAndCheckStats(const std::string &rtp_file, - const std::string &stat_ref_file, - const std::string &rtcp_ref_file) { - OpenInputFile(rtp_file); - std::string stat_out_file = ""; - if (stat_ref_file.empty()) { - stat_out_file = webrtc::test::OutputPath() + - "neteq_network_stats.dat"; - } - RefFiles network_stat_files(stat_ref_file, stat_out_file); - - std::string rtcp_out_file = ""; - if (rtcp_ref_file.empty()) { - rtcp_out_file = webrtc::test::OutputPath() + - "neteq_rtcp_stats.dat"; - } - RefFiles rtcp_stat_files(rtcp_ref_file, rtcp_out_file); - - NETEQTEST_RTPpacket rtp; - ASSERT_GT(rtp.readFromFile(rtp_fp_), 0); - while (rtp.dataLen() >= 0) { - int16_t out_len; - Process(&rtp, &out_len); - - // Query the network statistics API once per second - if (sim_clock_ % 1000 == 0) { - // Process NetworkStatistics. - WebRtcNetEQ_NetworkStatistics network_stats; - ASSERT_EQ(0, WebRtcNetEQ_GetNetworkStatistics(neteq_inst_->instance(), - &network_stats)); - network_stat_files.ProcessReference(network_stats); - - // Process RTCPstat. - WebRtcNetEQ_RTCPStat rtcp_stats; - ASSERT_EQ(0, WebRtcNetEQ_GetRTCPStats(neteq_inst_->instance(), - &rtcp_stats)); - rtcp_stat_files.ProcessReference(rtcp_stats); - } - } -} - -void NetEqDecodingTest::PopulateRtpInfo(int frame_index, - int timestamp, - WebRtcNetEQ_RTPInfo* rtp_info) { - rtp_info->sequenceNumber = frame_index; - rtp_info->timeStamp = timestamp; - rtp_info->SSRC = 0x1234; // Just an arbitrary SSRC. - rtp_info->payloadType = 94; // PCM16b WB codec. - rtp_info->markerBit = 0; -} - -void NetEqDecodingTest::PopulateCng(int frame_index, - int timestamp, - WebRtcNetEQ_RTPInfo* rtp_info, - uint8_t* payload, - int* payload_len) { - rtp_info->sequenceNumber = frame_index; - rtp_info->timeStamp = timestamp; - rtp_info->SSRC = 0x1234; // Just an arbitrary SSRC. - rtp_info->payloadType = 98; // WB CNG. - rtp_info->markerBit = 0; - payload[0] = 64; // Noise level -64 dBov, quite arbitrarily chosen. - *payload_len = 1; // Only noise level, no spectral parameters. -} - -#if (defined(_WIN32) && defined(WEBRTC_ARCH_64_BITS)) || defined(WEBRTC_ANDROID) -// Disabled for Windows 64-bit until webrtc:1460 is fixed. -#define MAYBE_TestBitExactness DISABLED_TestBitExactness -#else -#define MAYBE_TestBitExactness TestBitExactness -#endif - -TEST_F(NetEqDecodingTest, MAYBE_TestBitExactness) { - const std::string kInputRtpFile = webrtc::test::ProjectRootPath() + - "resources/audio_coding/neteq_universal.rtp"; -#if defined(_MSC_VER) && (_MSC_VER >= 1700) - // For Visual Studio 2012 and later, we will have to use the generic reference - // file, rather than the windows-specific one. - const std::string kInputRefFile = webrtc::test::ProjectRootPath() + - "resources/audio_coding/neteq_universal_ref.pcm"; -#else - const std::string kInputRefFile = - webrtc::test::ResourcePath("audio_coding/neteq_universal_ref", "pcm"); -#endif - DecodeAndCompare(kInputRtpFile, kInputRefFile); -} - -TEST_F(NetEqDecodingTest, TestNetworkStatistics) { - const std::string kInputRtpFile = webrtc::test::ProjectRootPath() + - "resources/audio_coding/neteq_universal.rtp"; -#if defined(_MSC_VER) && (_MSC_VER >= 1700) - // For Visual Studio 2012 and later, we will have to use the generic reference - // file, rather than the windows-specific one. - const std::string kNetworkStatRefFile = webrtc::test::ProjectRootPath() + - "resources/audio_coding/neteq_network_stats.dat"; -#else - const std::string kNetworkStatRefFile = - webrtc::test::ResourcePath("audio_coding/neteq_network_stats", "dat"); -#endif - const std::string kRtcpStatRefFile = - webrtc::test::ResourcePath("audio_coding/neteq_rtcp_stats", "dat"); - DecodeAndCheckStats(kInputRtpFile, kNetworkStatRefFile, kRtcpStatRefFile); -} - -TEST_F(NetEqDecodingTest, TestFrameWaitingTimeStatistics) { - // Use fax mode to avoid time-scaling. This is to simplify the testing of - // packet waiting times in the packet buffer. - ASSERT_EQ(0, - WebRtcNetEQ_SetPlayoutMode(neteq_inst_->instance(), kPlayoutFax)); - // Insert 30 dummy packets at once. Each packet contains 10 ms 16 kHz audio. - int num_frames = 30; - const int kSamples = 10 * 16; - const int kPayloadBytes = kSamples * 2; - for (int i = 0; i < num_frames; ++i) { - uint16_t payload[kSamples] = {0}; - WebRtcNetEQ_RTPInfo rtp_info; - rtp_info.sequenceNumber = i; - rtp_info.timeStamp = i * kSamples; - rtp_info.SSRC = 0x1234; // Just an arbitrary SSRC. - rtp_info.payloadType = 94; // PCM16b WB codec. - rtp_info.markerBit = 0; - ASSERT_EQ(0, WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), &rtp_info, - reinterpret_cast(payload), - kPayloadBytes, 0)); - } - // Pull out all data. - for (int i = 0; i < num_frames; ++i) { - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - } - const int kVecLen = 110; // More than kLenWaitingTimes in mcu.h. - int waiting_times[kVecLen]; - int len = WebRtcNetEQ_GetRawFrameWaitingTimes(neteq_inst_->instance(), - kVecLen, waiting_times); - EXPECT_EQ(num_frames, len); - // Since all frames are dumped into NetEQ at once, but pulled out with 10 ms - // spacing (per definition), we expect the delay to increase with 10 ms for - // each packet. - for (int i = 0; i < len; ++i) { - EXPECT_EQ((i + 1) * 10, waiting_times[i]); - } - - // Check statistics again and make sure it's been reset. - EXPECT_EQ(0, WebRtcNetEQ_GetRawFrameWaitingTimes(neteq_inst_->instance(), - kVecLen, waiting_times)); - - // Process > 100 frames, and make sure that that we get statistics - // only for 100 frames. Note the new SSRC, causing NetEQ to reset. - num_frames = 110; - for (int i = 0; i < num_frames; ++i) { - uint16_t payload[kSamples] = {0}; - WebRtcNetEQ_RTPInfo rtp_info; - rtp_info.sequenceNumber = i; - rtp_info.timeStamp = i * kSamples; - rtp_info.SSRC = 0x1235; // Just an arbitrary SSRC. - rtp_info.payloadType = 94; // PCM16b WB codec. - rtp_info.markerBit = 0; - ASSERT_EQ(0, WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), &rtp_info, - reinterpret_cast(payload), - kPayloadBytes, 0)); - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - } - - len = WebRtcNetEQ_GetRawFrameWaitingTimes(neteq_inst_->instance(), - kVecLen, waiting_times); - EXPECT_EQ(100, len); -} - -TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimeNegative) { - const int kNumFrames = 3000; // Needed for convergence. - int frame_index = 0; - const int kSamples = 10 * 16; - const int kPayloadBytes = kSamples * 2; - while (frame_index < kNumFrames) { - // Insert one packet each time, except every 10th time where we insert two - // packets at once. This will create a negative clock-drift of approx. 10%. - int num_packets = (frame_index % 10 == 0 ? 2 : 1); - for (int n = 0; n < num_packets; ++n) { - uint8_t payload[kPayloadBytes] = {0}; - WebRtcNetEQ_RTPInfo rtp_info; - PopulateRtpInfo(frame_index, frame_index * kSamples, &rtp_info); - ASSERT_EQ(0, - WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), - &rtp_info, - payload, - kPayloadBytes, 0)); - ++frame_index; - } - - // Pull out data once. - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - } - - WebRtcNetEQ_NetworkStatistics network_stats; - ASSERT_EQ(0, WebRtcNetEQ_GetNetworkStatistics(neteq_inst_->instance(), - &network_stats)); - EXPECT_EQ(-103196, network_stats.clockDriftPPM); -} - -TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimePositive) { - const int kNumFrames = 5000; // Needed for convergence. - int frame_index = 0; - const int kSamples = 10 * 16; - const int kPayloadBytes = kSamples * 2; - for (int i = 0; i < kNumFrames; ++i) { - // Insert one packet each time, except every 10th time where we don't insert - // any packet. This will create a positive clock-drift of approx. 11%. - int num_packets = (i % 10 == 9 ? 0 : 1); - for (int n = 0; n < num_packets; ++n) { - uint8_t payload[kPayloadBytes] = {0}; - WebRtcNetEQ_RTPInfo rtp_info; - PopulateRtpInfo(frame_index, frame_index * kSamples, &rtp_info); - ASSERT_EQ(0, - WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), - &rtp_info, - payload, - kPayloadBytes, 0)); - ++frame_index; - } - - // Pull out data once. - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - } - - WebRtcNetEQ_NetworkStatistics network_stats; - ASSERT_EQ(0, WebRtcNetEQ_GetNetworkStatistics(neteq_inst_->instance(), - &network_stats)); - EXPECT_EQ(110946, network_stats.clockDriftPPM); -} - -TEST_F(NetEqDecodingTest, LongCngWithClockDrift) { - uint16_t seq_no = 0; - uint32_t timestamp = 0; - const int kFrameSizeMs = 30; - const int kSamples = kFrameSizeMs * 16; - const int kPayloadBytes = kSamples * 2; - // Apply a clock drift of -25 ms / s (sender faster than receiver). - const double kDriftFactor = 1000.0 / (1000.0 + 25.0); - double next_input_time_ms = 0.0; - double t_ms; - - // Insert speech for 5 seconds. - const int kSpeechDurationMs = 5000; - for (t_ms = 0; t_ms < kSpeechDurationMs; t_ms += 10) { - // Each turn in this for loop is 10 ms. - while (next_input_time_ms <= t_ms) { - // Insert one 30 ms speech frame. - uint8_t payload[kPayloadBytes] = {0}; - WebRtcNetEQ_RTPInfo rtp_info; - PopulateRtpInfo(seq_no, timestamp, &rtp_info); - ASSERT_EQ(0, - WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), - &rtp_info, - payload, - kPayloadBytes, 0)); - ++seq_no; - timestamp += kSamples; - next_input_time_ms += static_cast(kFrameSizeMs) * kDriftFactor; - } - // Pull out data once. - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - } - - EXPECT_EQ(kOutputNormal, neteq_inst_->getOutputType()); - int32_t delay_before = timestamp - neteq_inst_->getSpeechTimeStamp(); - - // Insert CNG for 1 minute (= 60000 ms). - const int kCngPeriodMs = 100; - const int kCngPeriodSamples = kCngPeriodMs * 16; // Period in 16 kHz samples. - const int kCngDurationMs = 60000; - for (; t_ms < kSpeechDurationMs + kCngDurationMs; t_ms += 10) { - // Each turn in this for loop is 10 ms. - while (next_input_time_ms <= t_ms) { - // Insert one CNG frame each 100 ms. - uint8_t payload[kPayloadBytes]; - int payload_len; - WebRtcNetEQ_RTPInfo rtp_info; - PopulateCng(seq_no, timestamp, &rtp_info, payload, &payload_len); - ASSERT_EQ(0, - WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), - &rtp_info, - payload, - payload_len, 0)); - ++seq_no; - timestamp += kCngPeriodSamples; - next_input_time_ms += static_cast(kCngPeriodMs) * kDriftFactor; - } - // Pull out data once. - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - } - - EXPECT_EQ(kOutputCNG, neteq_inst_->getOutputType()); - - // Insert speech again until output type is speech. - while (neteq_inst_->getOutputType() != kOutputNormal) { - // Each turn in this for loop is 10 ms. - while (next_input_time_ms <= t_ms) { - // Insert one 30 ms speech frame. - uint8_t payload[kPayloadBytes] = {0}; - WebRtcNetEQ_RTPInfo rtp_info; - PopulateRtpInfo(seq_no, timestamp, &rtp_info); - ASSERT_EQ(0, - WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), - &rtp_info, - payload, - kPayloadBytes, 0)); - ++seq_no; - timestamp += kSamples; - next_input_time_ms += static_cast(kFrameSizeMs) * kDriftFactor; - } - // Pull out data once. - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - // Increase clock. - t_ms += 10; - } - - int32_t delay_after = timestamp - neteq_inst_->getSpeechTimeStamp(); - // Compare delay before and after, and make sure it differs less than 20 ms. - EXPECT_LE(delay_after, delay_before + 20 * 16); - EXPECT_GE(delay_after, delay_before - 20 * 16); -} - -TEST_F(NetEqDecodingTest, NoInputDataStereo) { - void *ms_info; - ms_info = malloc(WebRtcNetEQ_GetMasterSlaveInfoSize()); - neteq_inst_->setMaster(); - - // Slave instance without decoders (because it is easier). - WebRtcNetEQDecoder usedCodec[kDecoderReservedEnd - 1]; - usedCodec[0] = kDecoderPCMu; - NETEQTEST_NetEQClass* slave_inst = - new NETEQTEST_NetEQClass(usedCodec, 1, 8000, kTCPLargeJitter); - ASSERT_TRUE(slave_inst); - NETEQTEST_Decoder* dec = new decoder_PCMU(0); - ASSERT_TRUE(dec != NULL); - dec->loadToNetEQ(*slave_inst); - slave_inst->setSlave(); - - // Pull out data. - const int kNumFrames = 100; - for (int i = 0; i < kNumFrames; ++i) { - ASSERT_TRUE(kBlockSize8kHz == neteq_inst_->recOut(out_data_, ms_info)); - ASSERT_TRUE(kBlockSize8kHz == slave_inst->recOut(out_data_, ms_info)); - } - - delete dec; - delete slave_inst; - free(ms_info); -} - -TEST_F(NetEqDecodingTest, TestExtraDelay) { - static const int kNumFrames = 120000; // Needed for convergence. - int frame_index = 0; - static const int kFrameSizeSamples = 30 * 16; - static const int kPayloadBytes = kFrameSizeSamples * 2; - test::InputAudioFile input_file( - webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm")); - int16_t input[kFrameSizeSamples]; - // Buffers of NetEq cannot accommodate larger delays for PCM16. - static const int kExtraDelayMs = 3200; - ASSERT_EQ(0, WebRtcNetEQ_SetExtraDelay(neteq_inst_->instance(), - kExtraDelayMs)); - for (int i = 0; i < kNumFrames; ++i) { - ASSERT_TRUE(input_file.Read(kFrameSizeSamples, input)); - WebRtcNetEQ_RTPInfo rtp_info; - PopulateRtpInfo(frame_index, frame_index * kFrameSizeSamples, &rtp_info); - uint8_t* payload = reinterpret_cast(input); - ASSERT_EQ(0, - WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), - &rtp_info, - payload, - kPayloadBytes, 0)); - ++frame_index; - // Pull out data. - for (int j = 0; j < 3; ++j) { - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - } - if (i % 100 == 0) { - WebRtcNetEQ_NetworkStatistics network_stats; - ASSERT_EQ(0, WebRtcNetEQ_GetNetworkStatistics(neteq_inst_->instance(), - &network_stats)); - const int expected_lower_limit = - std::min(i * 0.083 - 210, 0.9 * network_stats.preferredBufferSize); - EXPECT_GE(network_stats.currentBufferSize, expected_lower_limit); - const int expected_upper_limit = - std::min(i * 0.083 + 255, 1.2 * network_stats.preferredBufferSize); - EXPECT_LE(network_stats.currentBufferSize, expected_upper_limit); - } - } -} - -void NetEqDecodingTest::WrapTest(uint16_t start_seq_no, - uint32_t start_timestamp, - const std::set& drop_seq_numbers) { - uint16_t seq_no = start_seq_no; - uint32_t timestamp = start_timestamp; - const int kFrameSizeMs = 30; - const int kSamples = kFrameSizeMs * 16; - const int kPayloadBytes = kSamples * 2; - double next_input_time_ms = 0.0; - - // Insert speech for 1 second. - const int kSpeechDurationMs = 1000; - for (double t_ms = 0; t_ms < kSpeechDurationMs; t_ms += 10) { - // Each turn in this for loop is 10 ms. - while (next_input_time_ms <= t_ms) { - // Insert one 30 ms speech frame. - uint8_t payload[kPayloadBytes] = {0}; - WebRtcNetEQ_RTPInfo rtp_info; - PopulateRtpInfo(seq_no, timestamp, &rtp_info); - if (drop_seq_numbers.find(seq_no) == drop_seq_numbers.end()) { - // This sequence number was not in the set to drop. Insert it. - ASSERT_EQ(0, - WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(), - &rtp_info, - payload, - kPayloadBytes, 0)); - } - ++seq_no; - timestamp += kSamples; - next_input_time_ms += static_cast(kFrameSizeMs); - WebRtcNetEQ_NetworkStatistics network_stats; - ASSERT_EQ(0, WebRtcNetEQ_GetNetworkStatistics(neteq_inst_->instance(), - &network_stats)); - // Expect preferred and actual buffer size to be no more than 2 frames. - EXPECT_LE(network_stats.preferredBufferSize, kFrameSizeMs * 2); - EXPECT_LE(network_stats.currentBufferSize, kFrameSizeMs * 2); - } - // Pull out data once. - ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_)); - // Expect delay (in samples) to be less than 2 packets. - EXPECT_LE(timestamp - neteq_inst_->getSpeechTimeStamp(), - static_cast(kSamples * 2)); - } -} - -TEST_F(NetEqDecodingTest, SequenceNumberWrap) { - // Start with a sequence number that will soon wrap. - std::set drop_seq_numbers; // Don't drop any packets. - WrapTest(0xFFFF - 5, 0, drop_seq_numbers); -} - -TEST_F(NetEqDecodingTest, SequenceNumberWrapAndDrop) { - // Start with a sequence number that will soon wrap. - std::set drop_seq_numbers; - drop_seq_numbers.insert(0xFFFF); - drop_seq_numbers.insert(0x0); - WrapTest(0xFFFF - 5, 0, drop_seq_numbers); -} - -TEST_F(NetEqDecodingTest, TimestampWrap) { - // Start with a timestamp that will soon wrap. - std::set drop_seq_numbers; - WrapTest(0, 0xFFFFFFFF - 1000, drop_seq_numbers); -} - -TEST_F(NetEqDecodingTest, TimestampAndSequenceNumberWrap) { - // Start with a timestamp and a sequence number that will wrap at the same - // time. - std::set drop_seq_numbers; - WrapTest(0xFFFF - 2, 0xFFFFFFFF - 1000, drop_seq_numbers); -} - -} // namespace diff --git a/webrtc/modules/audio_coding/neteq4/OWNERS b/webrtc/modules/audio_coding/neteq4/OWNERS deleted file mode 100644 index d54559c4a..000000000 --- a/webrtc/modules/audio_coding/neteq4/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -henrik.lundin@webrtc.org -tina.legrand@webrtc.org -turaj@webrtc.org -minyue@webrtc.org diff --git a/webrtc/modules/audio_coding/neteq4/dtmf_buffer.h b/webrtc/modules/audio_coding/neteq4/dtmf_buffer.h deleted file mode 100644 index d08b64f49..000000000 --- a/webrtc/modules/audio_coding/neteq4/dtmf_buffer.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_NETEQ4_DTMF_BUFFER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DTMF_BUFFER_H_ - -#include -#include // size_t - -#include "webrtc/system_wrappers/interface/constructor_magic.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -struct DtmfEvent { - uint32_t timestamp; - int event_no; - int volume; - int duration; - bool end_bit; - - // Constructors - DtmfEvent() - : timestamp(0), - event_no(0), - volume(0), - duration(0), - end_bit(false) { - } - DtmfEvent(uint32_t ts, int ev, int vol, int dur, bool end) - : timestamp(ts), - event_no(ev), - volume(vol), - duration(dur), - end_bit(end) { - } -}; - -// This is the buffer holding DTMF events while waiting for them to be played. -class DtmfBuffer { - public: - enum BufferReturnCodes { - kOK = 0, - kInvalidPointer, - kPayloadTooShort, - kInvalidEventParameters, - kInvalidSampleRate - }; - - // Set up the buffer for use at sample rate |fs_hz|. - explicit DtmfBuffer(int fs_hz) { - SetSampleRate(fs_hz); - } - - virtual ~DtmfBuffer() {} - - // Flushes the buffer. - virtual void Flush() { buffer_.clear(); } - - // Static method to parse 4 bytes from |payload| as a DTMF event (RFC 4733) - // and write the parsed information into the struct |event|. Input variable - // |rtp_timestamp| is simply copied into the struct. - static int ParseEvent(uint32_t rtp_timestamp, - const uint8_t* payload, - int payload_length_bytes, - DtmfEvent* event); - - // Inserts |event| into the buffer. The method looks for a matching event and - // merges the two if a match is found. - virtual int InsertEvent(const DtmfEvent& event); - - // Checks if a DTMF event should be played at time |current_timestamp|. If so, - // the method returns true; otherwise false. The parameters of the event to - // play will be written to |event|. - virtual bool GetEvent(uint32_t current_timestamp, DtmfEvent* event); - - // Number of events in the buffer. - virtual size_t Length() const { return buffer_.size(); } - - virtual bool Empty() const { return buffer_.empty(); } - - // Set a new sample rate. - virtual int SetSampleRate(int fs_hz); - - private: - typedef std::list DtmfList; - - int max_extrapolation_samples_; - int frame_len_samples_; // TODO(hlundin): Remove this later. - - // Compares two events and returns true if they are the same. - static bool SameEvent(const DtmfEvent& a, const DtmfEvent& b); - - // Merges |event| to the event pointed out by |it|. The method checks that - // the two events are the same (using the SameEvent method), and merges them - // if that was the case, returning true. If the events are not the same, false - // is returned. - bool MergeEvents(DtmfList::iterator it, const DtmfEvent& event); - - // Method used by the sort algorithm to rank events in the buffer. - static bool CompareEvents(const DtmfEvent& a, const DtmfEvent& b); - - DtmfList buffer_; - - DISALLOW_COPY_AND_ASSIGN(DtmfBuffer); -}; - -} // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_DTMF_BUFFER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/neteq.gypi b/webrtc/modules/audio_coding/neteq4/neteq.gypi deleted file mode 100644 index 3e7ede4e6..000000000 --- a/webrtc/modules/audio_coding/neteq4/neteq.gypi +++ /dev/null @@ -1,232 +0,0 @@ -# Copyright (c) 2012 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. - -{ - 'variables': { - 'neteq_dependencies': [ - 'G711', - 'G722', - 'PCM16B', - 'iLBC', - 'iSAC', - 'iSACFix', - 'CNG', - '<(DEPTH)/third_party/opus/opus.gyp:opus', - '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', - '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', - ], - 'neteq_defines': [], - 'conditions': [ - ['include_opus==1', { - 'neteq_dependencies': ['webrtc_opus',], - 'neteq_defines': ['WEBRTC_CODEC_OPUS',], - }], - ], - }, - 'targets': [ - { - 'target_name': 'NetEq4', - 'type': 'static_library', - 'dependencies': [ - '<@(neteq_dependencies)', - ], - 'defines': [ - '<@(neteq_defines)', - ], - 'include_dirs': [ - # Need Opus header files for the audio classifier. - '<(DEPTH)/third_party/opus/src/celt', - '<(DEPTH)/third_party/opus/src/src', - ], - 'direct_dependent_settings': { - 'include_dirs': [ - # Need Opus header files for the audio classifier. - '<(DEPTH)/third_party/opus/src/celt', - '<(DEPTH)/third_party/opus/src/src', - ], - }, - 'export_dependent_settings': [ - '<(DEPTH)/third_party/opus/opus.gyp:opus', - ], - 'sources': [ - 'interface/audio_decoder.h', - 'interface/neteq.h', - 'accelerate.cc', - 'accelerate.h', - 'audio_classifier.cc', - 'audio_classifier.h', - 'audio_decoder_impl.cc', - 'audio_decoder_impl.h', - 'audio_decoder.cc', - 'audio_multi_vector.cc', - 'audio_multi_vector.h', - 'audio_vector.cc', - 'audio_vector.h', - 'background_noise.cc', - 'background_noise.h', - 'buffer_level_filter.cc', - 'buffer_level_filter.h', - 'comfort_noise.cc', - 'comfort_noise.h', - 'decision_logic.cc', - 'decision_logic.h', - 'decision_logic_fax.cc', - 'decision_logic_fax.h', - 'decision_logic_normal.cc', - 'decision_logic_normal.h', - 'decoder_database.cc', - 'decoder_database.h', - 'defines.h', - 'delay_manager.cc', - 'delay_manager.h', - 'delay_peak_detector.cc', - 'delay_peak_detector.h', - 'dsp_helper.cc', - 'dsp_helper.h', - 'dtmf_buffer.cc', - 'dtmf_buffer.h', - 'dtmf_tone_generator.cc', - 'dtmf_tone_generator.h', - 'expand.cc', - 'expand.h', - 'merge.cc', - 'merge.h', - 'neteq_impl.cc', - 'neteq_impl.h', - 'neteq.cc', - 'statistics_calculator.cc', - 'statistics_calculator.h', - 'normal.cc', - 'normal.h', - 'packet_buffer.cc', - 'packet_buffer.h', - 'payload_splitter.cc', - 'payload_splitter.h', - 'post_decode_vad.cc', - 'post_decode_vad.h', - 'preemptive_expand.cc', - 'preemptive_expand.h', - 'random_vector.cc', - 'random_vector.h', - 'rtcp.cc', - 'rtcp.h', - 'sync_buffer.cc', - 'sync_buffer.h', - 'timestamp_scaler.cc', - 'timestamp_scaler.h', - 'time_stretch.cc', - 'time_stretch.h', - ], - }, - ], # targets - 'conditions': [ - ['include_tests==1', { - 'includes': ['neteq_tests.gypi',], - 'targets': [ - { - 'target_name': 'audio_decoder_unittests', - 'type': '<(gtest_target_type)', - 'dependencies': [ - '<@(neteq_dependencies)', - '<(DEPTH)/testing/gtest.gyp:gtest', - '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', - '<(webrtc_root)/test/test.gyp:test_support_main', - ], - 'defines': [ - 'AUDIO_DECODER_UNITTEST', - 'WEBRTC_CODEC_G722', - 'WEBRTC_CODEC_ILBC', - 'WEBRTC_CODEC_ISACFX', - 'WEBRTC_CODEC_ISAC', - 'WEBRTC_CODEC_PCM16', - '<@(neteq_defines)', - ], - 'sources': [ - 'audio_decoder_impl.cc', - 'audio_decoder_impl.h', - 'audio_decoder_unittest.cc', - 'audio_decoder.cc', - 'interface/audio_decoder.h', - ], - 'conditions': [ - # TODO(henrike): remove build_with_chromium==1 when the bots are - # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { - 'dependencies': [ - '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', - ], - }], - ], - }, # audio_decoder_unittests - - { - 'target_name': 'neteq_unittest_tools', - 'type': 'static_library', - 'dependencies': [ - '<(DEPTH)/testing/gmock.gyp:gmock', - '<(DEPTH)/testing/gtest.gyp:gtest', - 'PCM16B', # Needed by neteq_performance_test. - ], - 'direct_dependent_settings': { - 'include_dirs': [ - 'tools', - ], - }, - 'include_dirs': [ - 'tools', - ], - 'sources': [ - 'tools/audio_loop.cc', - 'tools/audio_loop.h', - 'tools/input_audio_file.cc', - 'tools/input_audio_file.h', - 'tools/neteq_performance_test.cc', - 'tools/neteq_performance_test.h', - 'tools/rtp_generator.cc', - 'tools/rtp_generator.h', - 'tools/neteq_quality_test.cc', - 'tools/neteq_quality_test.h', - ], - }, # neteq_unittest_tools - ], # targets - 'conditions': [ - # TODO(henrike): remove build_with_chromium==1 when the bots are using - # Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { - 'targets': [ - { - 'target_name': 'audio_decoder_unittests_apk_target', - 'type': 'none', - 'dependencies': [ - '<(apk_tests_path):audio_decoder_unittests_apk', - ], - }, - ], - }], - ['test_isolation_mode != "noop"', { - 'targets': [ - { - 'target_name': 'audio_decoder_unittests_run', - 'type': 'none', - 'dependencies': [ - 'audio_decoder_unittests', - ], - 'includes': [ - '../../../build/isolate.gypi', - 'audio_decoder_unittests.isolate', - ], - 'sources': [ - 'audio_decoder_unittests.isolate', - ], - }, - ], - }], - ], - }], # include_tests - ], # conditions -} diff --git a/webrtc/modules/audio_coding/neteq4/packet_buffer.h b/webrtc/modules/audio_coding/neteq4/packet_buffer.h deleted file mode 100644 index e964c28f2..000000000 --- a/webrtc/modules/audio_coding/neteq4/packet_buffer.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2012 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_MODULES_AUDIO_CODING_NETEQ4_PACKET_BUFFER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PACKET_BUFFER_H_ - -#include "webrtc/modules/audio_coding/neteq4/packet.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -// Forward declaration. -class DecoderDatabase; - -// This is the actual buffer holding the packets before decoding. -class PacketBuffer { - public: - enum BufferReturnCodes { - kOK = 0, - kFlushed, - kNotFound, - kBufferEmpty, - kInvalidPacket, - kInvalidPointer, - kOversizePacket - }; - - // Constructor creates a buffer which can hold a maximum of - // |max_number_of_packets| packets and |max_payload_memory| bytes of payload, - // excluding RTP headers. - PacketBuffer(size_t max_number_of_packets, size_t max_payload_memory); - - // Deletes all packets in the buffer before destroying the buffer. - virtual ~PacketBuffer(); - - // Flushes the buffer and deletes all packets in it. - virtual void Flush(); - - // Returns true for an empty buffer. - virtual bool Empty() const { return buffer_.empty(); } - - // Inserts |packet| into the buffer. The buffer will take over ownership of - // the packet object. - // Returns PacketBuffer::kOK on success, PacketBuffer::kFlushed if the buffer - // was flushed due to overfilling. - virtual int InsertPacket(Packet* packet); - - // Inserts a list of packets into the buffer. The buffer will take over - // ownership of the packet objects. - // Returns PacketBuffer::kOK if all packets were inserted successfully. - // If the buffer was flushed due to overfilling, only a subset of the list is - // inserted, and PacketBuffer::kFlushed is returned. - // The last three parameters are included for legacy compatibility. - // TODO(hlundin): Redesign to not use current_*_payload_type and - // decoder_database. - virtual int InsertPacketList(PacketList* packet_list, - const DecoderDatabase& decoder_database, - uint8_t* current_rtp_payload_type, - uint8_t* current_cng_rtp_payload_type); - - // Gets the timestamp for the first packet in the buffer and writes it to the - // output variable |next_timestamp|. - // Returns PacketBuffer::kBufferEmpty if the buffer is empty, - // PacketBuffer::kOK otherwise. - virtual int NextTimestamp(uint32_t* next_timestamp) const; - - // Gets the timestamp for the first packet in the buffer with a timestamp no - // lower than the input limit |timestamp|. The result is written to the output - // variable |next_timestamp|. - // Returns PacketBuffer::kBufferEmpty if the buffer is empty, - // PacketBuffer::kOK otherwise. - virtual int NextHigherTimestamp(uint32_t timestamp, - uint32_t* next_timestamp) const; - - // Returns a (constant) pointer the RTP header of the first packet in the - // buffer. Returns NULL if the buffer is empty. - virtual const RTPHeader* NextRtpHeader() const; - - // Extracts the first packet in the buffer and returns a pointer to it. - // Returns NULL if the buffer is empty. The caller is responsible for deleting - // the packet. - // Subsequent packets with the same timestamp as the one extracted will be - // discarded and properly deleted. The number of discarded packets will be - // written to the output variable |discard_count|. - virtual Packet* GetNextPacket(int* discard_count); - - // Discards the first packet in the buffer. The packet is deleted. - // Returns PacketBuffer::kBufferEmpty if the buffer is empty, - // PacketBuffer::kOK otherwise. - virtual int DiscardNextPacket(); - - // Discards all packets that are (strictly) older than |timestamp_limit|. - // Returns number of packets discarded. - virtual int DiscardOldPackets(uint32_t timestamp_limit); - - // Returns the number of packets in the buffer, including duplicates and - // redundant packets. - virtual int NumPacketsInBuffer() const { - return static_cast(buffer_.size()); - } - - // Returns the number of samples in the buffer, including samples carried in - // duplicate and redundant packets. - virtual int NumSamplesInBuffer(DecoderDatabase* decoder_database, - int last_decoded_length) const; - - // Increase the waiting time counter for every packet in the buffer by |inc|. - // The default value for |inc| is 1. - virtual void IncrementWaitingTimes(int inc = 1); - - virtual void BufferStat(int* num_packets, - int* max_num_packets, - int* current_memory_bytes, - int* max_memory_bytes) const; - - virtual int current_memory_bytes() const { return current_memory_bytes_; } - - // Static method that properly deletes the first packet, and its payload - // array, in |packet_list|. Returns false if |packet_list| already was empty, - // otherwise true. - static bool DeleteFirstPacket(PacketList* packet_list); - - // Static method that properly deletes all packets, and their payload arrays, - // in |packet_list|. - static void DeleteAllPackets(PacketList* packet_list); - - private: - size_t max_number_of_packets_; - size_t max_memory_bytes_; - int current_memory_bytes_; - PacketList buffer_; - DISALLOW_COPY_AND_ASSIGN(PacketBuffer); -}; - -} // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_PACKET_BUFFER_H_ diff --git a/webrtc/modules/audio_coding/neteq4/rtcp.h b/webrtc/modules/audio_coding/neteq4/rtcp.h deleted file mode 100644 index 00cbbd158..000000000 --- a/webrtc/modules/audio_coding/neteq4/rtcp.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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_MODULES_AUDIO_CODING_NETEQ4_RTCP_H_ -#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_RTCP_H_ - -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" -#include "webrtc/typedefs.h" - -namespace webrtc { - -// Forward declaration. -struct RTPHeader; - -class Rtcp { - public: - Rtcp() { - Init(0); - } - - ~Rtcp() {} - - // Resets the RTCP statistics, and sets the first received sequence number. - void Init(uint16_t start_sequence_number); - - // Updates the RTCP statistics with a new received packet. - void Update(const RTPHeader& rtp_header, uint32_t receive_timestamp); - - // Returns the current RTCP statistics. If |no_reset| is true, the statistics - // are not reset, otherwise they are. - void GetStatistics(bool no_reset, RtcpStatistics* stats); - - private: - uint16_t cycles_; // The number of wrap-arounds for the sequence number. - uint16_t max_seq_no_; // The maximum sequence number received. Starts over - // from 0 after wrap-around. - uint16_t base_seq_no_; // The sequence number of the first received packet. - uint32_t received_packets_; // The number of packets that have been received. - uint32_t received_packets_prior_; // Number of packets received when last - // report was generated. - uint32_t expected_prior_; // Expected number of packets, at the time of the - // last report. - uint32_t jitter_; // Current jitter value. - int32_t transit_; // Clock difference for previous packet. - - DISALLOW_COPY_AND_ASSIGN(Rtcp); -}; - -} // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_RTCP_H_ diff --git a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.cc b/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.cc deleted file mode 100644 index e1750912c..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.cc +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2012 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 "NETEQTEST_DummyRTPpacket.h" - -#include -#include -#include - -#ifdef WIN32 -#include -#else -#include // for htons, htonl, etc -#endif - -int NETEQTEST_DummyRTPpacket::readFromFile(FILE *fp) -{ - if (!fp) - { - return -1; - } - - uint16_t length, plen; - uint32_t offset; - int packetLen; - - bool readNextPacket = true; - while (readNextPacket) { - readNextPacket = false; - if (fread(&length, 2, 1, fp) == 0) - { - reset(); - return -2; - } - length = ntohs(length); - - if (fread(&plen, 2, 1, fp) == 0) - { - reset(); - return -1; - } - packetLen = ntohs(plen); - - if (fread(&offset, 4, 1, fp) == 0) - { - reset(); - return -1; - } - // Store in local variable until we have passed the reset below. - uint32_t receiveTime = ntohl(offset); - - // Use length here because a plen of 0 specifies rtcp. - length = (uint16_t) (length - _kRDHeaderLen); - - // check buffer size - if (_datagram && _memSize < length + 1) - { - reset(); - } - - if (!_datagram) - { - // Add one extra byte, to be able to fake a dummy payload of 1 byte. - _datagram = new uint8_t[length + 1]; - _memSize = length + 1; - } - memset(_datagram, 0, length + 1); - - if (length == 0) - { - _datagramLen = 0; - _rtpParsed = false; - return packetLen; - } - - // Read basic header - if (fread((unsigned short *) _datagram, 1, _kBasicHeaderLen, fp) - != (size_t)_kBasicHeaderLen) - { - reset(); - return -1; - } - _receiveTime = receiveTime; - _datagramLen = _kBasicHeaderLen; - - // Parse the basic header - webrtc::WebRtcRTPHeader tempRTPinfo; - int P, X, CC; - parseBasicHeader(&tempRTPinfo, &P, &X, &CC); - - // Check if we have to extend the header - if (X != 0 || CC != 0) - { - int newLen = _kBasicHeaderLen + CC * 4 + X * 4; - assert(_memSize >= newLen); - - // Read extension from file - size_t readLen = newLen - _kBasicHeaderLen; - if (fread(&_datagram[_kBasicHeaderLen], 1, readLen, fp) != readLen) - { - reset(); - return -1; - } - _datagramLen = newLen; - - if (X != 0) - { - int totHdrLen = calcHeaderLength(X, CC); - assert(_memSize >= totHdrLen); - - // Read extension from file - size_t readLen = totHdrLen - newLen; - if (fread(&_datagram[newLen], 1, readLen, fp) != readLen) - { - reset(); - return -1; - } - _datagramLen = totHdrLen; - } - } - _datagramLen = length; - - if (!_blockList.empty() && _blockList.count(payloadType()) > 0) - { - readNextPacket = true; - } - } - - _rtpParsed = false; - assert(_memSize > _datagramLen); - _payloadLen = 1; // Set the length to 1 byte. - return packetLen; - -} - -int NETEQTEST_DummyRTPpacket::writeToFile(FILE *fp) -{ - if (!fp) - { - return -1; - } - - uint16_t length, plen; - uint32_t offset; - - // length including RTPplay header - length = htons(_datagramLen + _kRDHeaderLen); - if (fwrite(&length, 2, 1, fp) != 1) - { - return -1; - } - - // payload length - plen = htons(_datagramLen); - if (fwrite(&plen, 2, 1, fp) != 1) - { - return -1; - } - - // offset (=receive time) - offset = htonl(_receiveTime); - if (fwrite(&offset, 4, 1, fp) != 1) - { - return -1; - } - - // Figure out the length of the RTP header. - int headerLen; - if (_datagramLen == 0) - { - // No payload at all; we are done writing to file. - headerLen = 0; - } - else - { - parseHeader(); - headerLen = _payloadPtr - _datagram; - assert(headerLen >= 0); - } - - // write RTP header - if (fwrite((unsigned short *) _datagram, 1, headerLen, fp) != - static_cast(headerLen)) - { - return -1; - } - - return (headerLen + _kRDHeaderLen); // total number of bytes written - -} - -void NETEQTEST_DummyRTPpacket::parseHeader() { - NETEQTEST_RTPpacket::parseHeader(); - // Change _payloadLen to 1 byte. The memory should always be big enough. - assert(_memSize > _datagramLen); - _payloadLen = 1; -} diff --git a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.h b/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.h deleted file mode 100644 index 9f09c9482..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2012 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 NETEQTEST_DUMMYRTPPACKET_H -#define NETEQTEST_DUMMYRTPPACKET_H - -#include "NETEQTEST_RTPpacket.h" - -class NETEQTEST_DummyRTPpacket : public NETEQTEST_RTPpacket { - public: - virtual int readFromFile(FILE* fp) OVERRIDE; - virtual int writeToFile(FILE* fp) OVERRIDE; - virtual void parseHeader() OVERRIDE; -}; - -#endif // NETEQTEST_DUMMYRTPPACKET_H diff --git a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.cc b/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.cc deleted file mode 100644 index 22f18efde..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.cc +++ /dev/null @@ -1,875 +0,0 @@ -/* - * Copyright (c) 2012 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 "NETEQTEST_RTPpacket.h" - -#include -#include // rand -#include - -#ifdef WIN32 -#include -#else -#include // for htons, htonl, etc -#endif - -const int NETEQTEST_RTPpacket::_kRDHeaderLen = 8; -const int NETEQTEST_RTPpacket::_kBasicHeaderLen = 12; - -NETEQTEST_RTPpacket::NETEQTEST_RTPpacket() -: -_datagram(NULL), -_payloadPtr(NULL), -_memSize(0), -_datagramLen(-1), -_payloadLen(0), -_rtpParsed(false), -_receiveTime(0), -_lost(false) -{ - memset(&_rtpInfo, 0, sizeof(_rtpInfo)); - _blockList.clear(); -} - -NETEQTEST_RTPpacket::~NETEQTEST_RTPpacket() -{ - if(_datagram) - { - delete [] _datagram; - } -} - -void NETEQTEST_RTPpacket::reset() -{ - if(_datagram) { - delete [] _datagram; - } - _datagram = NULL; - _memSize = 0; - _datagramLen = -1; - _payloadLen = 0; - _payloadPtr = NULL; - _receiveTime = 0; - memset(&_rtpInfo, 0, sizeof(_rtpInfo)); - _rtpParsed = false; - -} - -int NETEQTEST_RTPpacket::skipFileHeader(FILE *fp) -{ - if (!fp) { - return -1; - } - - const int kFirstLineLength = 40; - char firstline[kFirstLineLength]; - if (fgets(firstline, kFirstLineLength, fp) == NULL) { - return -1; - } - if (strncmp(firstline, "#!rtpplay", 9) == 0) { - if (strncmp(firstline, "#!rtpplay1.0", 12) != 0) { - return -1; - } - } - else if (strncmp(firstline, "#!RTPencode", 11) == 0) { - if (strncmp(firstline, "#!RTPencode1.0", 14) != 0) { - return -1; - } - } - else - { - return -1; - } - - const int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; - if (fseek(fp, kRtpDumpHeaderSize, SEEK_CUR) != 0) - { - return -1; - } - return 0; -} - -int NETEQTEST_RTPpacket::readFromFile(FILE *fp) -{ - if(!fp) - { - return(-1); - } - - uint16_t length, plen; - uint32_t offset; - int packetLen; - - bool readNextPacket = true; - while (readNextPacket) { - readNextPacket = false; - if (fread(&length,2,1,fp)==0) - { - reset(); - return(-2); - } - length = ntohs(length); - - if (fread(&plen,2,1,fp)==0) - { - reset(); - return(-1); - } - packetLen = ntohs(plen); - - if (fread(&offset,4,1,fp)==0) - { - reset(); - return(-1); - } - // store in local variable until we have passed the reset below - uint32_t receiveTime = ntohl(offset); - - // Use length here because a plen of 0 specifies rtcp - length = (uint16_t) (length - _kRDHeaderLen); - - // check buffer size - if (_datagram && _memSize < length) - { - reset(); - } - - if (!_datagram) - { - _datagram = new uint8_t[length]; - _memSize = length; - } - - if (fread((unsigned short *) _datagram,1,length,fp) != length) - { - reset(); - return(-1); - } - - _datagramLen = length; - _receiveTime = receiveTime; - - if (!_blockList.empty() && _blockList.count(payloadType()) > 0) - { - readNextPacket = true; - } - } - - _rtpParsed = false; - return(packetLen); - -} - - -int NETEQTEST_RTPpacket::readFixedFromFile(FILE *fp, size_t length) -{ - if (!fp) - { - return -1; - } - - // check buffer size - if (_datagram && _memSize < static_cast(length)) - { - reset(); - } - - if (!_datagram) - { - _datagram = new uint8_t[length]; - _memSize = length; - } - - if (fread(_datagram, 1, length, fp) != length) - { - reset(); - return -1; - } - - _datagramLen = length; - _receiveTime = 0; - - if (!_blockList.empty() && _blockList.count(payloadType()) > 0) - { - // discard this payload - return readFromFile(fp); - } - - _rtpParsed = false; - return length; - -} - - -int NETEQTEST_RTPpacket::writeToFile(FILE *fp) -{ - if (!fp) - { - return -1; - } - - uint16_t length, plen; - uint32_t offset; - - // length including RTPplay header - length = htons(_datagramLen + _kRDHeaderLen); - if (fwrite(&length, 2, 1, fp) != 1) - { - return -1; - } - - // payload length - plen = htons(_datagramLen); - if (fwrite(&plen, 2, 1, fp) != 1) - { - return -1; - } - - // offset (=receive time) - offset = htonl(_receiveTime); - if (fwrite(&offset, 4, 1, fp) != 1) - { - return -1; - } - - - // write packet data - if (fwrite(_datagram, 1, _datagramLen, fp) != - static_cast(_datagramLen)) - { - return -1; - } - - return _datagramLen + _kRDHeaderLen; // total number of bytes written - -} - - -void NETEQTEST_RTPpacket::blockPT(uint8_t pt) -{ - _blockList[pt] = true; -} - - -void NETEQTEST_RTPpacket::parseHeader() -{ - if (_rtpParsed) - { - // nothing to do - return; - } - - if (_datagramLen < _kBasicHeaderLen) - { - // corrupt packet? - return; - } - - _payloadLen = parseRTPheader(&_payloadPtr); - - _rtpParsed = true; - - return; - -} - -void NETEQTEST_RTPpacket::parseHeader(webrtc::WebRtcRTPHeader* rtp_header) { - if (!_rtpParsed) { - parseHeader(); - } - if (rtp_header) { - rtp_header->header.markerBit = _rtpInfo.header.markerBit; - rtp_header->header.payloadType = _rtpInfo.header.payloadType; - rtp_header->header.sequenceNumber = _rtpInfo.header.sequenceNumber; - rtp_header->header.timestamp = _rtpInfo.header.timestamp; - rtp_header->header.ssrc = _rtpInfo.header.ssrc; - } -} - -const webrtc::WebRtcRTPHeader* NETEQTEST_RTPpacket::RTPinfo() const -{ - if (_rtpParsed) - { - return &_rtpInfo; - } - else - { - return NULL; - } -} - -uint8_t * NETEQTEST_RTPpacket::datagram() const -{ - if (_datagramLen > 0) - { - return _datagram; - } - else - { - return NULL; - } -} - -uint8_t * NETEQTEST_RTPpacket::payload() const -{ - if (_payloadLen > 0) - { - return _payloadPtr; - } - else - { - return NULL; - } -} - -int16_t NETEQTEST_RTPpacket::payloadLen() -{ - parseHeader(); - return _payloadLen; -} - -int16_t NETEQTEST_RTPpacket::dataLen() const -{ - return _datagramLen; -} - -bool NETEQTEST_RTPpacket::isParsed() const -{ - return _rtpParsed; -} - -bool NETEQTEST_RTPpacket::isLost() const -{ - return _lost; -} - -uint8_t NETEQTEST_RTPpacket::payloadType() const -{ - webrtc::WebRtcRTPHeader tempRTPinfo; - - if(_datagram && _datagramLen >= _kBasicHeaderLen) - { - parseRTPheader(&tempRTPinfo); - } - else - { - return 0; - } - - return tempRTPinfo.header.payloadType; -} - -uint16_t NETEQTEST_RTPpacket::sequenceNumber() const -{ - webrtc::WebRtcRTPHeader tempRTPinfo; - - if(_datagram && _datagramLen >= _kBasicHeaderLen) - { - parseRTPheader(&tempRTPinfo); - } - else - { - return 0; - } - - return tempRTPinfo.header.sequenceNumber; -} - -uint32_t NETEQTEST_RTPpacket::timeStamp() const -{ - webrtc::WebRtcRTPHeader tempRTPinfo; - - if(_datagram && _datagramLen >= _kBasicHeaderLen) - { - parseRTPheader(&tempRTPinfo); - } - else - { - return 0; - } - - return tempRTPinfo.header.timestamp; -} - -uint32_t NETEQTEST_RTPpacket::SSRC() const -{ - webrtc::WebRtcRTPHeader tempRTPinfo; - - if(_datagram && _datagramLen >= _kBasicHeaderLen) - { - parseRTPheader(&tempRTPinfo); - } - else - { - return 0; - } - - return tempRTPinfo.header.ssrc; -} - -uint8_t NETEQTEST_RTPpacket::markerBit() const -{ - webrtc::WebRtcRTPHeader tempRTPinfo; - - if(_datagram && _datagramLen >= _kBasicHeaderLen) - { - parseRTPheader(&tempRTPinfo); - } - else - { - return 0; - } - - return tempRTPinfo.header.markerBit; -} - - - -int NETEQTEST_RTPpacket::setPayloadType(uint8_t pt) -{ - - if (_datagramLen < 12) - { - return -1; - } - - if (!_rtpParsed) - { - _rtpInfo.header.payloadType = pt; - } - - _datagram[1]=(unsigned char)(pt & 0xFF); - - return 0; - -} - -int NETEQTEST_RTPpacket::setSequenceNumber(uint16_t sn) -{ - - if (_datagramLen < 12) - { - return -1; - } - - if (!_rtpParsed) - { - _rtpInfo.header.sequenceNumber = sn; - } - - _datagram[2]=(unsigned char)((sn>>8)&0xFF); - _datagram[3]=(unsigned char)((sn)&0xFF); - - return 0; - -} - -int NETEQTEST_RTPpacket::setTimeStamp(uint32_t ts) -{ - - if (_datagramLen < 12) - { - return -1; - } - - if (!_rtpParsed) - { - _rtpInfo.header.timestamp = ts; - } - - _datagram[4]=(unsigned char)((ts>>24)&0xFF); - _datagram[5]=(unsigned char)((ts>>16)&0xFF); - _datagram[6]=(unsigned char)((ts>>8)&0xFF); - _datagram[7]=(unsigned char)(ts & 0xFF); - - return 0; - -} - -int NETEQTEST_RTPpacket::setSSRC(uint32_t ssrc) -{ - - if (_datagramLen < 12) - { - return -1; - } - - if (!_rtpParsed) - { - _rtpInfo.header.ssrc = ssrc; - } - - _datagram[8]=(unsigned char)((ssrc>>24)&0xFF); - _datagram[9]=(unsigned char)((ssrc>>16)&0xFF); - _datagram[10]=(unsigned char)((ssrc>>8)&0xFF); - _datagram[11]=(unsigned char)(ssrc & 0xFF); - - return 0; - -} - -int NETEQTEST_RTPpacket::setMarkerBit(uint8_t mb) -{ - - if (_datagramLen < 12) - { - return -1; - } - - if (_rtpParsed) - { - _rtpInfo.header.markerBit = mb; - } - - if (mb) - { - _datagram[0] |= 0x01; - } - else - { - _datagram[0] &= 0xFE; - } - - return 0; - -} - -int NETEQTEST_RTPpacket::setRTPheader(const webrtc::WebRtcRTPHeader* RTPinfo) -{ - if (_datagramLen < 12) - { - // this packet is not ok - return -1; - } - - makeRTPheader(_datagram, - RTPinfo->header.payloadType, - RTPinfo->header.sequenceNumber, - RTPinfo->header.timestamp, - RTPinfo->header.ssrc, - RTPinfo->header.markerBit); - - return 0; -} - - -int NETEQTEST_RTPpacket::splitStereo(NETEQTEST_RTPpacket* slaveRtp, - enum stereoModes mode) -{ - // if mono, do nothing - if (mode == stereoModeMono) - { - return 0; - } - - // check that the RTP header info is parsed - parseHeader(); - - // start by copying the main rtp packet - *slaveRtp = *this; - - if(_payloadLen == 0) - { - // do no more - return 0; - } - - if(_payloadLen%2 != 0) - { - // length must be a factor of 2 - return -1; - } - - switch(mode) - { - case stereoModeSample1: - { - // sample based codec with 1-byte samples - splitStereoSample(slaveRtp, 1 /* 1 byte/sample */); - break; - } - case stereoModeSample2: - { - // sample based codec with 2-byte samples - splitStereoSample(slaveRtp, 2 /* 2 bytes/sample */); - break; - } - case stereoModeFrame: - { - // frame based codec - splitStereoFrame(slaveRtp); - break; - } - case stereoModeDuplicate: - { - // frame based codec, send the whole packet to both master and slave - splitStereoDouble(slaveRtp); - break; - } - case stereoModeMono: - { - assert(false); - return -1; - } - } - - return 0; -} - - -void NETEQTEST_RTPpacket::makeRTPheader(unsigned char* rtp_data, uint8_t payloadType, uint16_t seqNo, uint32_t timestamp, uint32_t ssrc, uint8_t markerBit) const -{ - rtp_data[0]=(unsigned char)0x80; - if (markerBit) - { - rtp_data[0] |= 0x01; - } - else - { - rtp_data[0] &= 0xFE; - } - rtp_data[1]=(unsigned char)(payloadType & 0xFF); - rtp_data[2]=(unsigned char)((seqNo>>8)&0xFF); - rtp_data[3]=(unsigned char)((seqNo)&0xFF); - rtp_data[4]=(unsigned char)((timestamp>>24)&0xFF); - rtp_data[5]=(unsigned char)((timestamp>>16)&0xFF); - - rtp_data[6]=(unsigned char)((timestamp>>8)&0xFF); - rtp_data[7]=(unsigned char)(timestamp & 0xFF); - - rtp_data[8]=(unsigned char)((ssrc>>24)&0xFF); - rtp_data[9]=(unsigned char)((ssrc>>16)&0xFF); - - rtp_data[10]=(unsigned char)((ssrc>>8)&0xFF); - rtp_data[11]=(unsigned char)(ssrc & 0xFF); -} - -uint16_t - NETEQTEST_RTPpacket::parseRTPheader(webrtc::WebRtcRTPHeader* RTPinfo, - uint8_t **payloadPtr) const -{ - int16_t *rtp_data = (int16_t *) _datagram; - int i_P, i_X, i_CC; - - assert(_datagramLen >= 12); - parseBasicHeader(RTPinfo, &i_P, &i_X, &i_CC); - - int i_startPosition = calcHeaderLength(i_X, i_CC); - - int i_padlength = calcPadLength(i_P); - - if (payloadPtr) - { - *payloadPtr = (uint8_t*) &rtp_data[i_startPosition >> 1]; - } - - return (uint16_t) (_datagramLen - i_startPosition - i_padlength); -} - - -void NETEQTEST_RTPpacket::parseBasicHeader(webrtc::WebRtcRTPHeader* RTPinfo, - int *i_P, int *i_X, int *i_CC) const -{ - int16_t *rtp_data = (int16_t *) _datagram; - if (_datagramLen < 12) - { - assert(false); - return; - } - - *i_P=(((uint16_t)(rtp_data[0] & 0x20))>>5); /* Extract the P bit */ - *i_X=(((uint16_t)(rtp_data[0] & 0x10))>>4); /* Extract the X bit */ - *i_CC=(uint16_t)(rtp_data[0] & 0xF); /* Get the CC number */ - /* Get the marker bit */ - RTPinfo->header.markerBit = (uint8_t) ((rtp_data[0] >> 15) & 0x01); - /* Get the coder type */ - RTPinfo->header.payloadType = (uint8_t) ((rtp_data[0] >> 8) & 0x7F); - /* Get the packet number */ - RTPinfo->header.sequenceNumber = - ((( ((uint16_t)rtp_data[1]) >> 8) & 0xFF) | - ( ((uint16_t)(rtp_data[1] & 0xFF)) << 8)); - /* Get timestamp */ - RTPinfo->header.timestamp = ((((uint16_t)rtp_data[2]) & 0xFF) << 24) | - ((((uint16_t)rtp_data[2]) & 0xFF00) << 8) | - ((((uint16_t)rtp_data[3]) >> 8) & 0xFF) | - ((((uint16_t)rtp_data[3]) & 0xFF) << 8); - /* Get the SSRC */ - RTPinfo->header.ssrc = ((((uint16_t)rtp_data[4]) & 0xFF) << 24) | - ((((uint16_t)rtp_data[4]) & 0xFF00) << 8) | - ((((uint16_t)rtp_data[5]) >> 8) & 0xFF) | - ((((uint16_t)rtp_data[5]) & 0xFF) << 8); -} - -int NETEQTEST_RTPpacket::calcHeaderLength(int i_X, int i_CC) const -{ - int i_extlength = 0; - int16_t *rtp_data = (int16_t *) _datagram; - - if (i_X == 1) - { - // Extension header exists. - // Find out how many int32_t it consists of. - assert(_datagramLen > 2 * (7 + 2 * i_CC)); - if (_datagramLen > 2 * (7 + 2 * i_CC)) - { - i_extlength = (((((uint16_t) rtp_data[7 + 2 * i_CC]) >> 8) - & 0xFF) | (((uint16_t) (rtp_data[7 + 2 * i_CC] & 0xFF)) - << 8)) + 1; - } - } - - return 12 + 4 * i_extlength + 4 * i_CC; -} - -int NETEQTEST_RTPpacket::calcPadLength(int i_P) const -{ - int16_t *rtp_data = (int16_t *) _datagram; - if (i_P == 1) - { - /* Padding exists. Find out how many bytes the padding consists of. */ - if (_datagramLen & 0x1) - { - /* odd number of bytes => last byte in higher byte */ - return rtp_data[_datagramLen >> 1] & 0xFF; - } - else - { - /* even number of bytes => last byte in lower byte */ - return ((uint16_t) rtp_data[(_datagramLen >> 1) - 1]) >> 8; - } - } - return 0; -} - -void NETEQTEST_RTPpacket::splitStereoSample(NETEQTEST_RTPpacket* slaveRtp, - int stride) -{ - if(!_payloadPtr || !slaveRtp || !slaveRtp->_payloadPtr - || _payloadLen <= 0 || slaveRtp->_memSize < _memSize) - { - return; - } - - uint8_t *readDataPtr = _payloadPtr; - uint8_t *writeDataPtr = _payloadPtr; - uint8_t *slaveData = slaveRtp->_payloadPtr; - - while (readDataPtr - _payloadPtr < _payloadLen) - { - // master data - for (int ix = 0; ix < stride; ix++) { - *writeDataPtr = *readDataPtr; - writeDataPtr++; - readDataPtr++; - } - - // slave data - for (int ix = 0; ix < stride; ix++) { - *slaveData = *readDataPtr; - slaveData++; - readDataPtr++; - } - } - - _payloadLen /= 2; - slaveRtp->_payloadLen = _payloadLen; -} - - -void NETEQTEST_RTPpacket::splitStereoFrame(NETEQTEST_RTPpacket* slaveRtp) -{ - if(!_payloadPtr || !slaveRtp || !slaveRtp->_payloadPtr - || _payloadLen <= 0 || slaveRtp->_memSize < _memSize) - { - return; - } - - memmove(slaveRtp->_payloadPtr, _payloadPtr + _payloadLen/2, _payloadLen/2); - - _payloadLen /= 2; - slaveRtp->_payloadLen = _payloadLen; -} -void NETEQTEST_RTPpacket::splitStereoDouble(NETEQTEST_RTPpacket* slaveRtp) -{ - if(!_payloadPtr || !slaveRtp || !slaveRtp->_payloadPtr - || _payloadLen <= 0 || slaveRtp->_memSize < _memSize) - { - return; - } - - memcpy(slaveRtp->_payloadPtr, _payloadPtr, _payloadLen); - slaveRtp->_payloadLen = _payloadLen; -} - -// Get the RTP header for the RED payload indicated by argument index. -// The first RED payload is index = 0. -int NETEQTEST_RTPpacket::extractRED(int index, webrtc::WebRtcRTPHeader& red) -{ -// -// 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 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |1| block PT | timestamp offset | block length | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |1| ... | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |0| block PT | -// +-+-+-+-+-+-+-+-+ -// - - parseHeader(); - - uint8_t* ptr = payload(); - uint8_t* payloadEndPtr = ptr + payloadLen(); - int num_encodings = 0; - int total_len = 0; - - while ((ptr < payloadEndPtr) && (*ptr & 0x80)) - { - int len = ((ptr[2] & 0x03) << 8) + ptr[3]; - if (num_encodings == index) - { - // Header found. - red.header.payloadType = ptr[0] & 0x7F; - uint32_t offset = (ptr[1] << 6) + ((ptr[2] & 0xFC) >> 2); - red.header.sequenceNumber = sequenceNumber(); - red.header.timestamp = timeStamp() - offset; - red.header.markerBit = markerBit(); - red.header.ssrc = SSRC(); - return len; - } - ++num_encodings; - total_len += len; - ptr += 4; - } - if ((ptr < payloadEndPtr) && (num_encodings == index)) - { - // Last header. - red.header.payloadType = ptr[0] & 0x7F; - red.header.sequenceNumber = sequenceNumber(); - red.header.timestamp = timeStamp(); - red.header.markerBit = markerBit(); - red.header.ssrc = SSRC(); - ++ptr; - return payloadLen() - (ptr - payload()) - total_len; - } - return -1; -} - -// Randomize the payload, not the RTP header. -void NETEQTEST_RTPpacket::scramblePayload(void) -{ - parseHeader(); - - for (int i = 0; i < _payloadLen; ++i) - { - _payloadPtr[i] = static_cast(rand()); - } -} diff --git a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h b/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h deleted file mode 100644 index 8a31274ab..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2012 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 NETEQTEST_RTPPACKET_H -#define NETEQTEST_RTPPACKET_H - -#include -#include -#include "webrtc/typedefs.h" -#include "webrtc/modules/interface/module_common_types.h" - -enum stereoModes { - stereoModeMono, - stereoModeSample1, - stereoModeSample2, - stereoModeFrame, - stereoModeDuplicate -}; - -class NETEQTEST_RTPpacket -{ -public: - NETEQTEST_RTPpacket(); - bool operator !() const { return (dataLen() < 0); }; - virtual ~NETEQTEST_RTPpacket(); - void reset(); - static int skipFileHeader(FILE *fp); - virtual int readFromFile(FILE *fp); - int readFixedFromFile(FILE *fp, size_t len); - virtual int writeToFile(FILE *fp); - void blockPT(uint8_t pt); - //int16_t payloadType(); - virtual void parseHeader(); - void parseHeader(webrtc::WebRtcRTPHeader* rtp_header); - const webrtc::WebRtcRTPHeader* RTPinfo() const; - uint8_t * datagram() const; - uint8_t * payload() const; - int16_t payloadLen(); - int16_t dataLen() const; - bool isParsed() const; - bool isLost() const; - uint32_t time() const { return _receiveTime; }; - - uint8_t payloadType() const; - uint16_t sequenceNumber() const; - uint32_t timeStamp() const; - uint32_t SSRC() const; - uint8_t markerBit() const; - - int setPayloadType(uint8_t pt); - int setSequenceNumber(uint16_t sn); - int setTimeStamp(uint32_t ts); - int setSSRC(uint32_t ssrc); - int setMarkerBit(uint8_t mb); - void setTime(uint32_t receiveTime) { _receiveTime = receiveTime; }; - - int setRTPheader(const webrtc::WebRtcRTPHeader* RTPinfo); - - int splitStereo(NETEQTEST_RTPpacket* slaveRtp, enum stereoModes mode); - - int extractRED(int index, webrtc::WebRtcRTPHeader& red); - - void scramblePayload(void); - - uint8_t * _datagram; - uint8_t * _payloadPtr; - int _memSize; - int16_t _datagramLen; - int16_t _payloadLen; - webrtc::WebRtcRTPHeader _rtpInfo; - bool _rtpParsed; - uint32_t _receiveTime; - bool _lost; - std::map _blockList; - -protected: - static const int _kRDHeaderLen; - static const int _kBasicHeaderLen; - - void parseBasicHeader(webrtc::WebRtcRTPHeader* RTPinfo, int *i_P, int *i_X, - int *i_CC) const; - int calcHeaderLength(int i_X, int i_CC) const; - -private: - void makeRTPheader(unsigned char* rtp_data, uint8_t payloadType, - uint16_t seqNo, uint32_t timestamp, - uint32_t ssrc, uint8_t markerBit) const; - uint16_t parseRTPheader(webrtc::WebRtcRTPHeader* RTPinfo, - uint8_t **payloadPtr = NULL) const; - uint16_t parseRTPheader(uint8_t **payloadPtr = NULL) - { return parseRTPheader(&_rtpInfo, payloadPtr);}; - int calcPadLength(int i_P) const; - void splitStereoSample(NETEQTEST_RTPpacket* slaveRtp, int stride); - void splitStereoFrame(NETEQTEST_RTPpacket* slaveRtp); - void splitStereoDouble(NETEQTEST_RTPpacket* slaveRtp); -}; - -#endif //NETEQTEST_RTPPACKET_H diff --git a/webrtc/modules/audio_coding/neteq4/test/PayloadTypes.h b/webrtc/modules/audio_coding/neteq4/test/PayloadTypes.h deleted file mode 100644 index f6cc3da80..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/PayloadTypes.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* PayloadTypes.h */ -/* Used by NetEqRTPplay application */ - -/* RTP defined codepoints */ -#define NETEQ_CODEC_PCMU_PT 0 -#define NETEQ_CODEC_GSMFR_PT 3 -#define NETEQ_CODEC_G723_PT 4 -#define NETEQ_CODEC_DVI4_PT 125 // 8 kHz version -//#define NETEQ_CODEC_DVI4_16_PT 6 // 16 kHz version -#define NETEQ_CODEC_PCMA_PT 8 -#define NETEQ_CODEC_G722_PT 9 -#define NETEQ_CODEC_CN_PT 13 -//#define NETEQ_CODEC_G728_PT 15 -//#define NETEQ_CODEC_DVI4_11_PT 16 // 11.025 kHz version -//#define NETEQ_CODEC_DVI4_22_PT 17 // 22.050 kHz version -#define NETEQ_CODEC_G729_PT 18 - -/* Dynamic RTP codepoints as defined in VoiceEngine (file VEAPI.cpp) */ -#define NETEQ_CODEC_IPCMWB_PT 97 -#define NETEQ_CODEC_SPEEX8_PT 98 -#define NETEQ_CODEC_SPEEX16_PT 99 -#define NETEQ_CODEC_EG711U_PT 100 -#define NETEQ_CODEC_EG711A_PT 101 -#define NETEQ_CODEC_ILBC_PT 102 -#define NETEQ_CODEC_ISAC_PT 103 -#define NETEQ_CODEC_ISACLC_PT 119 -#define NETEQ_CODEC_ISACSWB_PT 104 -#define NETEQ_CODEC_AVT_PT 106 -#define NETEQ_CODEC_G722_1_16_PT 108 -#define NETEQ_CODEC_G722_1_24_PT 109 -#define NETEQ_CODEC_G722_1_32_PT 110 -#define NETEQ_CODEC_SC3_PT 111 -#define NETEQ_CODEC_AMR_PT 112 -#define NETEQ_CODEC_GSMEFR_PT 113 -//#define NETEQ_CODEC_ILBCRCU_PT 114 -#define NETEQ_CODEC_G726_16_PT 115 -#define NETEQ_CODEC_G726_24_PT 116 -#define NETEQ_CODEC_G726_32_PT 121 -#define NETEQ_CODEC_RED_PT 117 -#define NETEQ_CODEC_G726_40_PT 118 -//#define NETEQ_CODEC_ENERGY_PT 120 -#define NETEQ_CODEC_CN_WB_PT 105 -#define NETEQ_CODEC_CN_SWB_PT 126 -#define NETEQ_CODEC_G729_1_PT 107 -#define NETEQ_CODEC_G729D_PT 123 -#define NETEQ_CODEC_MELPE_PT 124 -#define NETEQ_CODEC_CELT32_PT 114 - -/* Extra dynamic codepoints */ -#define NETEQ_CODEC_AMRWB_PT 120 -#define NETEQ_CODEC_PCM16B_PT 93 -#define NETEQ_CODEC_PCM16B_WB_PT 94 -#define NETEQ_CODEC_PCM16B_SWB32KHZ_PT 95 -#define NETEQ_CODEC_PCM16B_SWB48KHZ_PT 96 -#define NETEQ_CODEC_MPEG4AAC_PT 122 - - -/* Not default in VoiceEngine */ -#define NETEQ_CODEC_G722_1C_24_PT 84 -#define NETEQ_CODEC_G722_1C_32_PT 85 -#define NETEQ_CODEC_G722_1C_48_PT 86 - -#define NETEQ_CODEC_SILK_8_PT 80 -#define NETEQ_CODEC_SILK_12_PT 81 -#define NETEQ_CODEC_SILK_16_PT 82 -#define NETEQ_CODEC_SILK_24_PT 83 - diff --git a/webrtc/modules/audio_coding/neteq4/test/RTPcat.cc b/webrtc/modules/audio_coding/neteq4/test/RTPcat.cc deleted file mode 100644 index 87189cfe9..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/RTPcat.cc +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 "gtest/gtest.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h" - -#define FIRSTLINELEN 40 - -int main(int argc, char* argv[]) { - if (argc < 3) { - printf("Usage: RTPcat in1.rtp int2.rtp [...] out.rtp\n"); - exit(1); - } - - FILE* in_file = fopen(argv[1], "rb"); - if (!in_file) { - printf("Cannot open input file %s\n", argv[1]); - return -1; - } - - FILE* out_file = fopen(argv[argc - 1], "wb"); // Last parameter is out file. - if (!out_file) { - printf("Cannot open output file %s\n", argv[argc - 1]); - return -1; - } - printf("Output RTP file: %s\n\n", argv[argc - 1]); - - // Read file header and write directly to output file. - char firstline[FIRSTLINELEN]; - const unsigned int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; - EXPECT_TRUE(fgets(firstline, FIRSTLINELEN, in_file) != NULL); - EXPECT_GT(fputs(firstline, out_file), 0); - EXPECT_EQ(kRtpDumpHeaderSize, fread(firstline, 1, kRtpDumpHeaderSize, - in_file)); - EXPECT_EQ(kRtpDumpHeaderSize, fwrite(firstline, 1, kRtpDumpHeaderSize, - out_file)); - - // Close input file and re-open it later (easier to write the loop below). - fclose(in_file); - - for (int i = 1; i < argc - 1; i++) { - in_file = fopen(argv[i], "rb"); - if (!in_file) { - printf("Cannot open input file %s\n", argv[i]); - return -1; - } - printf("Input RTP file: %s\n", argv[i]); - - NETEQTEST_RTPpacket::skipFileHeader(in_file); - NETEQTEST_RTPpacket packet; - int pack_len = packet.readFromFile(in_file); - if (pack_len < 0) { - exit(1); - } - while (pack_len >= 0) { - packet.writeToFile(out_file); - pack_len = packet.readFromFile(in_file); - } - fclose(in_file); - } - fclose(out_file); - return 0; -} diff --git a/webrtc/modules/audio_coding/neteq4/test/RTPchange.cc b/webrtc/modules/audio_coding/neteq4/test/RTPchange.cc deleted file mode 100644 index 30bee86a6..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/RTPchange.cc +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2012 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 "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h" - -#define FIRSTLINELEN 40 -//#define WEBRTC_DUMMY_RTP - -static bool pktCmp(NETEQTEST_RTPpacket *a, NETEQTEST_RTPpacket *b) { - return (a->time() < b->time()); -} - -int main(int argc, char* argv[]) { - FILE* in_file = fopen(argv[1], "rb"); - if (!in_file) { - printf("Cannot open input file %s\n", argv[1]); - return -1; - } - printf("Input RTP file: %s\n", argv[1]); - - FILE* stat_file = fopen(argv[2], "rt"); - if (!stat_file) { - printf("Cannot open timing file %s\n", argv[2]); - return -1; - } - printf("Timing file: %s\n", argv[2]); - - FILE* out_file = fopen(argv[3], "wb"); - if (!out_file) { - printf("Cannot open output file %s\n", argv[3]); - return -1; - } - printf("Output RTP file: %s\n\n", argv[3]); - - // Read all statistics and insert into map. - // Read first line. - char temp_str[100]; - if (fgets(temp_str, 100, stat_file) == NULL) { - printf("Failed to read timing file %s\n", argv[2]); - return -1; - } - // Define map. - std::map, uint32_t> packet_stats; - uint16_t seq_no; - uint32_t ts; - uint32_t send_time; - - while (fscanf(stat_file, - "%hu %u %u %*i %*i\n", &seq_no, &ts, &send_time) == 3) { - std::pair - temp_pair = std::pair(seq_no, ts); - - packet_stats[temp_pair] = send_time; - } - - fclose(stat_file); - - // Read file header and write directly to output file. - char first_line[FIRSTLINELEN]; - if (fgets(first_line, FIRSTLINELEN, in_file) == NULL) { - printf("Failed to read first line of input file %s\n", argv[1]); - return -1; - } - fputs(first_line, out_file); - // start_sec + start_usec + source + port + padding - const unsigned int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; - if (fread(first_line, 1, kRtpDumpHeaderSize, in_file) - != kRtpDumpHeaderSize) { - printf("Failed to read RTP dump header from input file %s\n", argv[1]); - return -1; - } - if (fwrite(first_line, 1, kRtpDumpHeaderSize, out_file) - != kRtpDumpHeaderSize) { - printf("Failed to write RTP dump header to output file %s\n", argv[3]); - return -1; - } - - std::vector packet_vec; - - while (1) { - // Insert in vector. -#ifdef WEBRTC_DUMMY_RTP - NETEQTEST_RTPpacket *new_packet = new NETEQTEST_DummyRTPpacket(); -#else - NETEQTEST_RTPpacket *new_packet = new NETEQTEST_RTPpacket(); -#endif - if (new_packet->readFromFile(in_file) < 0) { - // End of file. - break; - } - - // Look for new send time in statistics vector. - std::pair temp_pair = - std::pair(new_packet->sequenceNumber(), - new_packet->timeStamp()); - - uint32_t new_send_time = packet_stats[temp_pair]; - new_packet->setTime(new_send_time); // Set new send time. - packet_vec.push_back(new_packet); // Insert in vector. - } - - // Sort the vector according to send times. - std::sort(packet_vec.begin(), packet_vec.end(), pktCmp); - - std::vector::iterator it; - for (it = packet_vec.begin(); it != packet_vec.end(); it++) { - // Write to out file. - if ((*it)->writeToFile(out_file) < 0) { - printf("Error writing to file\n"); - return -1; - } - // Delete packet. - delete *it; - } - - fclose(in_file); - fclose(out_file); - - return 0; -} diff --git a/webrtc/modules/audio_coding/neteq4/test/RTPencode.cc b/webrtc/modules/audio_coding/neteq4/test/RTPencode.cc deleted file mode 100644 index bc806091b..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/RTPencode.cc +++ /dev/null @@ -1,1826 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -//TODO(hlundin): Reformat file to meet style guide. - -/* header includes */ -#include -#include -#include -#ifdef WIN32 -#include -#endif -#ifdef WEBRTC_LINUX -#include -#endif - -#include - -#include "webrtc/typedefs.h" -// needed for NetEqDecoder -#include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" -#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" - -/************************/ -/* Define payload types */ -/************************/ - -#include "PayloadTypes.h" - - - -/*********************/ -/* Misc. definitions */ -/*********************/ - -#define STOPSENDTIME 3000 -#define RESTARTSENDTIME 0 //162500 -#define FIRSTLINELEN 40 -#define CHECK_NOT_NULL(a) if((a)==0){printf("\n %s \n line: %d \nerror at %s\n",__FILE__,__LINE__,#a );return(-1);} - -//#define MULTIPLE_SAME_TIMESTAMP -#define REPEAT_PACKET_DISTANCE 17 -#define REPEAT_PACKET_COUNT 1 // number of extra packets to send - -//#define INSERT_OLD_PACKETS -#define OLD_PACKET 5 // how many seconds too old should the packet be? - -//#define TIMESTAMP_WRAPAROUND - -//#define RANDOM_DATA -//#define RANDOM_PAYLOAD_DATA -#define RANDOM_SEED 10 - -//#define INSERT_DTMF_PACKETS -//#define NO_DTMF_OVERDUB -#define DTMF_PACKET_INTERVAL 2000 -#define DTMF_DURATION 500 - -#define STEREO_MODE_FRAME 0 -#define STEREO_MODE_SAMPLE_1 1 //1 octet per sample -#define STEREO_MODE_SAMPLE_2 2 //2 octets per sample - -/*************************/ -/* Function declarations */ -/*************************/ - -void NetEQTest_GetCodec_and_PT(char * name, webrtc::NetEqDecoder *codec, int *PT, int frameLen, int *fs, int *bitrate, int *useRed); -int NetEQTest_init_coders(webrtc::NetEqDecoder coder, int enc_frameSize, int bitrate, int sampfreq , int vad, int numChannels); -void defineCodecs(webrtc::NetEqDecoder *usedCodec, int *noOfCodecs ); -int NetEQTest_free_coders(webrtc::NetEqDecoder coder, int numChannels); -int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * encoded,int sampleRate , int * vad, int useVAD, int bitrate, int numChannels); -void makeRTPheader(unsigned char* rtp_data, int payloadType, int seqNo, uint32_t timestamp, uint32_t ssrc); -int makeRedundantHeader(unsigned char* rtp_data, int *payloadType, int numPayloads, uint32_t *timestamp, uint16_t *blockLen, - int seqNo, uint32_t ssrc); -int makeDTMFpayload(unsigned char* payload_data, int Event, int End, int Volume, int Duration); -void stereoDeInterleave(int16_t* audioSamples, int numSamples); -void stereoInterleave(unsigned char* data, int dataLen, int stride); - -/*********************/ -/* Codec definitions */ -/*********************/ - -#include "webrtc_vad.h" - -#if ((defined CODEC_PCM16B)||(defined NETEQ_ARBITRARY_CODEC)) - #include "pcm16b.h" -#endif -#ifdef CODEC_G711 - #include "g711_interface.h" -#endif -#ifdef CODEC_G729 - #include "G729Interface.h" -#endif -#ifdef CODEC_G729_1 - #include "G729_1Interface.h" -#endif -#ifdef CODEC_AMR - #include "AMRInterface.h" - #include "AMRCreation.h" -#endif -#ifdef CODEC_AMRWB - #include "AMRWBInterface.h" - #include "AMRWBCreation.h" -#endif -#ifdef CODEC_ILBC - #include "ilbc.h" -#endif -#if (defined CODEC_ISAC || defined CODEC_ISAC_SWB) - #include "isac.h" -#endif -#ifdef NETEQ_ISACFIX_CODEC - #include "isacfix.h" - #ifdef CODEC_ISAC - #error Cannot have both ISAC and ISACfix defined. Please de-select one in the beginning of RTPencode.cpp - #endif -#endif -#ifdef CODEC_G722 - #include "g722_interface.h" -#endif -#ifdef CODEC_G722_1_24 - #include "G722_1Interface.h" -#endif -#ifdef CODEC_G722_1_32 - #include "G722_1Interface.h" -#endif -#ifdef CODEC_G722_1_16 - #include "G722_1Interface.h" -#endif -#ifdef CODEC_G722_1C_24 - #include "G722_1Interface.h" -#endif -#ifdef CODEC_G722_1C_32 - #include "G722_1Interface.h" -#endif -#ifdef CODEC_G722_1C_48 - #include "G722_1Interface.h" -#endif -#ifdef CODEC_G726 - #include "G726Creation.h" - #include "G726Interface.h" -#endif -#ifdef CODEC_GSMFR - #include "GSMFRInterface.h" - #include "GSMFRCreation.h" -#endif -#if (defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) - #include "webrtc_cng.h" -#endif -#if ((defined CODEC_SPEEX_8)||(defined CODEC_SPEEX_16)) - #include "SpeexInterface.h" -#endif -#ifdef CODEC_CELT_32 -#include "celt_interface.h" -#endif - - -/***********************************/ -/* Global codec instance variables */ -/***********************************/ - -WebRtcVadInst *VAD_inst[2]; - -#ifdef CODEC_G722 - G722EncInst *g722EncState[2]; -#endif - -#ifdef CODEC_G722_1_24 - G722_1_24_encinst_t *G722_1_24enc_inst[2]; -#endif -#ifdef CODEC_G722_1_32 - G722_1_32_encinst_t *G722_1_32enc_inst[2]; -#endif -#ifdef CODEC_G722_1_16 - G722_1_16_encinst_t *G722_1_16enc_inst[2]; -#endif -#ifdef CODEC_G722_1C_24 - G722_1C_24_encinst_t *G722_1C_24enc_inst[2]; -#endif -#ifdef CODEC_G722_1C_32 - G722_1C_32_encinst_t *G722_1C_32enc_inst[2]; -#endif -#ifdef CODEC_G722_1C_48 - G722_1C_48_encinst_t *G722_1C_48enc_inst[2]; -#endif -#ifdef CODEC_G726 - G726_encinst_t *G726enc_inst[2]; -#endif -#ifdef CODEC_G729 - G729_encinst_t *G729enc_inst[2]; -#endif -#ifdef CODEC_G729_1 - G729_1_inst_t *G729_1_inst[2]; -#endif -#ifdef CODEC_AMR - AMR_encinst_t *AMRenc_inst[2]; - int16_t AMR_bitrate; -#endif -#ifdef CODEC_AMRWB - AMRWB_encinst_t *AMRWBenc_inst[2]; - int16_t AMRWB_bitrate; -#endif -#ifdef CODEC_ILBC - iLBC_encinst_t *iLBCenc_inst[2]; -#endif -#ifdef CODEC_ISAC - ISACStruct *ISAC_inst[2]; -#endif -#ifdef NETEQ_ISACFIX_CODEC - ISACFIX_MainStruct *ISAC_inst[2]; -#endif -#ifdef CODEC_ISAC_SWB - ISACStruct *ISACSWB_inst[2]; -#endif -#ifdef CODEC_GSMFR - GSMFR_encinst_t *GSMFRenc_inst[2]; -#endif -#if (defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) - CNG_enc_inst *CNGenc_inst[2]; -#endif -#ifdef CODEC_SPEEX_8 - SPEEX_encinst_t *SPEEX8enc_inst[2]; -#endif -#ifdef CODEC_SPEEX_16 - SPEEX_encinst_t *SPEEX16enc_inst[2]; -#endif -#ifdef CODEC_CELT_32 - CELT_encinst_t *CELT32enc_inst[2]; -#endif -#ifdef CODEC_G711 - void *G711state[2]={NULL, NULL}; -#endif - - -int main(int argc, char* argv[]) -{ - int packet_size, fs; - webrtc::NetEqDecoder usedCodec; - int payloadType; - int bitrate = 0; - int useVAD, vad; - int useRed=0; - int len, enc_len; - int16_t org_data[4000]; - unsigned char rtp_data[8000]; - int16_t seqNo=0xFFF; - uint32_t ssrc=1235412312; - uint32_t timestamp=0xAC1245; - uint16_t length, plen; - uint32_t offset; - double sendtime = 0; - int red_PT[2] = {0}; - uint32_t red_TS[2] = {0}; - uint16_t red_len[2] = {0}; - int RTPheaderLen=12; - unsigned char red_data[8000]; -#ifdef INSERT_OLD_PACKETS - uint16_t old_length, old_plen; - int old_enc_len; - int first_old_packet=1; - unsigned char old_rtp_data[8000]; - int packet_age=0; -#endif -#ifdef INSERT_DTMF_PACKETS - int NTone = 1; - int DTMFfirst = 1; - uint32_t DTMFtimestamp; - bool dtmfSent = false; -#endif - bool usingStereo = false; - int stereoMode = 0; - int numChannels = 1; - - /* check number of parameters */ - if ((argc != 6) && (argc != 7)) { - /* print help text and exit */ - printf("Application to encode speech into an RTP stream.\n"); - printf("The program reads a PCM file and encodes is using the specified codec.\n"); - printf("The coded speech is packetized in RTP packest and written to the output file.\n"); - printf("The format of the RTP stream file is simlilar to that of rtpplay,\n"); - printf("but with the receive time euqal to 0 for all packets.\n"); - printf("Usage:\n\n"); - printf("%s PCMfile RTPfile frameLen codec useVAD bitrate\n", argv[0]); - printf("where:\n"); - - printf("PCMfile : PCM speech input file\n\n"); - - printf("RTPfile : RTP stream output file\n\n"); - - printf("frameLen : 80...960... Number of samples per packet (limit depends on codec)\n\n"); - - printf("codecName\n"); -#ifdef CODEC_PCM16B - printf(" : pcm16b 16 bit PCM (8kHz)\n"); -#endif -#ifdef CODEC_PCM16B_WB - printf(" : pcm16b_wb 16 bit PCM (16kHz)\n"); -#endif -#ifdef CODEC_PCM16B_32KHZ - printf(" : pcm16b_swb32 16 bit PCM (32kHz)\n"); -#endif -#ifdef CODEC_PCM16B_48KHZ - printf(" : pcm16b_swb48 16 bit PCM (48kHz)\n"); -#endif -#ifdef CODEC_G711 - printf(" : pcma g711 A-law (8kHz)\n"); -#endif -#ifdef CODEC_G711 - printf(" : pcmu g711 u-law (8kHz)\n"); -#endif -#ifdef CODEC_G729 - printf(" : g729 G729 (8kHz and 8kbps) CELP (One-Three frame(s)/packet)\n"); -#endif -#ifdef CODEC_G729_1 - printf(" : g729.1 G729.1 (16kHz) variable rate (8--32 kbps)\n"); -#endif -#ifdef CODEC_G722_1_16 - printf(" : g722.1_16 G722.1 coder (16kHz) (g722.1 with 16kbps)\n"); -#endif -#ifdef CODEC_G722_1_24 - printf(" : g722.1_24 G722.1 coder (16kHz) (the 24kbps version)\n"); -#endif -#ifdef CODEC_G722_1_32 - printf(" : g722.1_32 G722.1 coder (16kHz) (the 32kbps version)\n"); -#endif -#ifdef CODEC_G722_1C_24 - printf(" : g722.1C_24 G722.1 C coder (32kHz) (the 24kbps version)\n"); -#endif -#ifdef CODEC_G722_1C_32 - printf(" : g722.1C_32 G722.1 C coder (32kHz) (the 32kbps version)\n"); -#endif -#ifdef CODEC_G722_1C_48 - printf(" : g722.1C_48 G722.1 C coder (32kHz) (the 48kbps)\n"); -#endif - -#ifdef CODEC_G726 - printf(" : g726_16 G726 coder (8kHz) 16kbps\n"); - printf(" : g726_24 G726 coder (8kHz) 24kbps\n"); - printf(" : g726_32 G726 coder (8kHz) 32kbps\n"); - printf(" : g726_40 G726 coder (8kHz) 40kbps\n"); -#endif -#ifdef CODEC_AMR - printf(" : AMRXk Adaptive Multi Rate CELP codec (8kHz)\n"); - printf(" X = 4.75, 5.15, 5.9, 6.7, 7.4, 7.95, 10.2 or 12.2\n"); -#endif -#ifdef CODEC_AMRWB - printf(" : AMRwbXk Adaptive Multi Rate Wideband CELP codec (16kHz)\n"); - printf(" X = 7, 9, 12, 14, 16, 18, 20, 23 or 24\n"); -#endif -#ifdef CODEC_ILBC - printf(" : ilbc iLBC codec (8kHz and 13.8kbps)\n"); -#endif -#ifdef CODEC_ISAC - printf(" : isac iSAC (16kHz and 32.0 kbps). To set rate specify a rate parameter as last parameter\n"); -#endif -#ifdef CODEC_ISAC_SWB - printf(" : isacswb iSAC SWB (32kHz and 32.0-52.0 kbps). To set rate specify a rate parameter as last parameter\n"); -#endif -#ifdef CODEC_GSMFR - printf(" : gsmfr GSM FR codec (8kHz and 13kbps)\n"); -#endif -#ifdef CODEC_G722 - printf(" : g722 g722 coder (16kHz) (the 64kbps version)\n"); -#endif -#ifdef CODEC_SPEEX_8 - printf(" : speex8 speex coder (8 kHz)\n"); -#endif -#ifdef CODEC_SPEEX_16 - printf(" : speex16 speex coder (16 kHz)\n"); -#endif -#ifdef CODEC_CELT_32 - printf(" : celt32 celt coder (32 kHz)\n"); -#endif -#ifdef CODEC_RED -#ifdef CODEC_G711 - printf(" : red_pcm Redundancy RTP packet with 2*G711A frames\n"); -#endif -#ifdef CODEC_ISAC - printf(" : red_isac Redundancy RTP packet with 2*iSAC frames\n"); -#endif -#endif - printf("\n"); - -#if (defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) - printf("useVAD : 0 Voice Activity Detection is switched off\n"); - printf(" : 1 Voice Activity Detection is switched on\n\n"); -#else - printf("useVAD : 0 Voice Activity Detection switched off (on not supported)\n\n"); -#endif - printf("bitrate : Codec bitrate in bps (only applies to vbr codecs)\n\n"); - - return(0); - } - - FILE* in_file=fopen(argv[1],"rb"); - CHECK_NOT_NULL(in_file); - printf("Input file: %s\n",argv[1]); - FILE* out_file=fopen(argv[2],"wb"); - CHECK_NOT_NULL(out_file); - printf("Output file: %s\n\n",argv[2]); - packet_size=atoi(argv[3]); - CHECK_NOT_NULL(packet_size); - printf("Packet size: %i\n",packet_size); - - // check for stereo - if(argv[4][strlen(argv[4])-1] == '*') { - // use stereo - usingStereo = true; - numChannels = 2; - argv[4][strlen(argv[4])-1] = '\0'; - } - - NetEQTest_GetCodec_and_PT(argv[4], &usedCodec, &payloadType, packet_size, &fs, &bitrate, &useRed); - - if(useRed) { - RTPheaderLen = 12 + 4 + 1; /* standard RTP = 12; 4 bytes per redundant payload, except last one which is 1 byte */ - } - - useVAD=atoi(argv[5]); -#if !(defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) - if (useVAD!=0) { - printf("Error: this simulation does not support VAD/DTX/CNG\n"); - } -#endif - - // check stereo type - if(usingStereo) - { - switch(usedCodec) - { - // sample based codecs - case webrtc::kDecoderPCMu: - case webrtc::kDecoderPCMa: - case webrtc::kDecoderG722: - { - // 1 octet per sample - stereoMode = STEREO_MODE_SAMPLE_1; - break; - } - case webrtc::kDecoderPCM16B: - case webrtc::kDecoderPCM16Bwb: - case webrtc::kDecoderPCM16Bswb32kHz: - case webrtc::kDecoderPCM16Bswb48kHz: - { - // 2 octets per sample - stereoMode = STEREO_MODE_SAMPLE_2; - break; - } - - // fixed-rate frame codecs (with internal VAD) - default: - { - printf("Cannot use codec %s as stereo codec\n", argv[4]); - exit(0); - } - } - } - - if ((usedCodec == webrtc::kDecoderISAC) || (usedCodec == webrtc::kDecoderISACswb)) - { - if (argc != 7) - { - if (usedCodec == webrtc::kDecoderISAC) - { - bitrate = 32000; - printf( - "Running iSAC at default bitrate of 32000 bps (to specify explicitly add the bps as last parameter)\n"); - } - else // (usedCodec==webrtc::kDecoderISACswb) - { - bitrate = 56000; - printf( - "Running iSAC at default bitrate of 56000 bps (to specify explicitly add the bps as last parameter)\n"); - } - } - else - { - bitrate = atoi(argv[6]); - if (usedCodec == webrtc::kDecoderISAC) - { - if ((bitrate < 10000) || (bitrate > 32000)) - { - printf( - "Error: iSAC bitrate must be between 10000 and 32000 bps (%i is invalid)\n", - bitrate); - exit(0); - } - printf("Running iSAC at bitrate of %i bps\n", bitrate); - } - else // (usedCodec==webrtc::kDecoderISACswb) - { - if ((bitrate < 32000) || (bitrate > 56000)) - { - printf( - "Error: iSAC SWB bitrate must be between 32000 and 56000 bps (%i is invalid)\n", - bitrate); - exit(0); - } - } - } - } - else - { - if (argc == 7) - { - printf( - "Error: Bitrate parameter can only be specified for iSAC, G.723, and G.729.1\n"); - exit(0); - } - } - - if(useRed) { - printf("Redundancy engaged. "); - } - printf("Used codec: %i\n",usedCodec); - printf("Payload type: %i\n",payloadType); - - NetEQTest_init_coders(usedCodec, packet_size, bitrate, fs, useVAD, numChannels); - - /* write file header */ - //fprintf(out_file, "#!RTPencode%s\n", "1.0"); - fprintf(out_file, "#!rtpplay%s \n", "1.0"); // this is the string that rtpplay needs - uint32_t dummy_variable = 0; // should be converted to network endian format, but does not matter when 0 - if (fwrite(&dummy_variable, 4, 1, out_file) != 1) { - return -1; - } - if (fwrite(&dummy_variable, 4, 1, out_file) != 1) { - return -1; - } - if (fwrite(&dummy_variable, 4, 1, out_file) != 1) { - return -1; - } - if (fwrite(&dummy_variable, 2, 1, out_file) != 1) { - return -1; - } - if (fwrite(&dummy_variable, 2, 1, out_file) != 1) { - return -1; - } - -#ifdef TIMESTAMP_WRAPAROUND - timestamp = 0xFFFFFFFF - fs*10; /* should give wrap-around in 10 seconds */ -#endif -#if defined(RANDOM_DATA) | defined(RANDOM_PAYLOAD_DATA) - srand(RANDOM_SEED); -#endif - - /* if redundancy is used, the first redundant payload is zero length */ - red_len[0] = 0; - - /* read first frame */ - len=fread(org_data,2,packet_size * numChannels,in_file) / numChannels; - - /* de-interleave if stereo */ - if ( usingStereo ) - { - stereoDeInterleave(org_data, len * numChannels); - } - - while (len==packet_size) { - -#ifdef INSERT_DTMF_PACKETS - dtmfSent = false; - - if ( sendtime >= NTone * DTMF_PACKET_INTERVAL ) { - if ( sendtime < NTone * DTMF_PACKET_INTERVAL + DTMF_DURATION ) { - // tone has not ended - if (DTMFfirst==1) { - DTMFtimestamp = timestamp; // save this timestamp - DTMFfirst=0; - } - makeRTPheader(rtp_data, NETEQ_CODEC_AVT_PT, seqNo,DTMFtimestamp, ssrc); - enc_len = makeDTMFpayload(&rtp_data[12], NTone % 12, 0, 4, (int) (sendtime - NTone * DTMF_PACKET_INTERVAL)*(fs/1000) + len); - } - else { - // tone has ended - makeRTPheader(rtp_data, NETEQ_CODEC_AVT_PT, seqNo,DTMFtimestamp, ssrc); - enc_len = makeDTMFpayload(&rtp_data[12], NTone % 12, 1, 4, DTMF_DURATION*(fs/1000)); - NTone++; - DTMFfirst=1; - } - - /* write RTP packet to file */ - length = htons(12 + enc_len + 8); - plen = htons(12 + enc_len); - offset = (uint32_t) sendtime; //(timestamp/(fs/1000)); - offset = htonl(offset); - if (fwrite(&length, 2, 1, out_file) != 1) { - return -1; - } - if (fwrite(&plen, 2, 1, out_file) != 1) { - return -1; - } - if (fwrite(&offset, 4, 1, out_file) != 1) { - return -1; - } - if (fwrite(rtp_data, 12 + enc_len, 1, out_file) != 1) { - return -1; - } - - dtmfSent = true; - } -#endif - -#ifdef NO_DTMF_OVERDUB - /* If DTMF is sent, we should not send any speech packets during the same time */ - if (dtmfSent) { - enc_len = 0; - } - else { -#endif - /* encode frame */ - enc_len=NetEQTest_encode(usedCodec, org_data, packet_size, &rtp_data[12] ,fs,&vad, useVAD, bitrate, numChannels); - if (enc_len==-1) { - printf("Error encoding frame\n"); - exit(0); - } - - if ( usingStereo && - stereoMode != STEREO_MODE_FRAME && - vad == 1 ) - { - // interleave the encoded payload for sample-based codecs (not for CNG) - stereoInterleave(&rtp_data[12], enc_len, stereoMode); - } -#ifdef NO_DTMF_OVERDUB - } -#endif - - if (enc_len > 0 && (sendtime <= STOPSENDTIME || sendtime > RESTARTSENDTIME)) { - if(useRed) { - if(red_len[0] > 0) { - memmove(&rtp_data[RTPheaderLen+red_len[0]], &rtp_data[12], enc_len); - memcpy(&rtp_data[RTPheaderLen], red_data, red_len[0]); - - red_len[1] = enc_len; - red_TS[1] = timestamp; - if(vad) - red_PT[1] = payloadType; - else - red_PT[1] = NETEQ_CODEC_CN_PT; - - makeRedundantHeader(rtp_data, red_PT, 2, red_TS, red_len, seqNo++, ssrc); - - - enc_len += red_len[0] + RTPheaderLen - 12; - } - else { // do not use redundancy payload for this packet, i.e., only last payload - memmove(&rtp_data[RTPheaderLen-4], &rtp_data[12], enc_len); - //memcpy(&rtp_data[RTPheaderLen], red_data, red_len[0]); - - red_len[1] = enc_len; - red_TS[1] = timestamp; - if(vad) - red_PT[1] = payloadType; - else - red_PT[1] = NETEQ_CODEC_CN_PT; - - makeRedundantHeader(rtp_data, red_PT, 2, red_TS, red_len, seqNo++, ssrc); - - - enc_len += red_len[0] + RTPheaderLen - 4 - 12; // 4 is length of redundancy header (not used) - } - } - else { - - /* make RTP header */ - if (vad) // regular speech data - makeRTPheader(rtp_data, payloadType, seqNo++,timestamp, ssrc); - else // CNG data - makeRTPheader(rtp_data, NETEQ_CODEC_CN_PT, seqNo++,timestamp, ssrc); - - } -#ifdef MULTIPLE_SAME_TIMESTAMP - int mult_pack=0; - do { -#endif //MULTIPLE_SAME_TIMESTAMP - /* write RTP packet to file */ - length = htons(12 + enc_len + 8); - plen = htons(12 + enc_len); - offset = (uint32_t) sendtime; - //(timestamp/(fs/1000)); - offset = htonl(offset); - if (fwrite(&length, 2, 1, out_file) != 1) { - return -1; - } - if (fwrite(&plen, 2, 1, out_file) != 1) { - return -1; - } - if (fwrite(&offset, 4, 1, out_file) != 1) { - return -1; - } -#ifdef RANDOM_DATA - for (int k=0; k<12+enc_len; k++) { - rtp_data[k] = rand() + rand(); - } -#endif -#ifdef RANDOM_PAYLOAD_DATA - for (int k=12; k<12+enc_len; k++) { - rtp_data[k] = rand() + rand(); - } -#endif - if (fwrite(rtp_data, 12 + enc_len, 1, out_file) != 1) { - return -1; - } -#ifdef MULTIPLE_SAME_TIMESTAMP - } while ( (seqNo%REPEAT_PACKET_DISTANCE == 0) && (mult_pack++ < REPEAT_PACKET_COUNT) ); -#endif //MULTIPLE_SAME_TIMESTAMP - -#ifdef INSERT_OLD_PACKETS - if (packet_age >= OLD_PACKET*fs) { - if (!first_old_packet) { - // send the old packet - if (fwrite(&old_length, 2, 1, - out_file) != 1) { - return -1; - } - if (fwrite(&old_plen, 2, 1, - out_file) != 1) { - return -1; - } - if (fwrite(&offset, 4, 1, - out_file) != 1) { - return -1; - } - if (fwrite(old_rtp_data, 12 + old_enc_len, - 1, out_file) != 1) { - return -1; - } - } - // store current packet as old - old_length=length; - old_plen=plen; - memcpy(old_rtp_data,rtp_data,12+enc_len); - old_enc_len=enc_len; - first_old_packet=0; - packet_age=0; - - } - packet_age += packet_size; -#endif - - if(useRed) { - /* move data to redundancy store */ -#ifdef CODEC_ISAC - if(usedCodec==webrtc::kDecoderISAC) - { - assert(!usingStereo); // Cannot handle stereo yet - red_len[0] = WebRtcIsac_GetRedPayload(ISAC_inst[0], (int16_t*)red_data); - } - else - { -#endif - memcpy(red_data, &rtp_data[RTPheaderLen+red_len[0]], enc_len); - red_len[0]=red_len[1]; -#ifdef CODEC_ISAC - } -#endif - red_TS[0]=red_TS[1]; - red_PT[0]=red_PT[1]; - } - - } - - /* read next frame */ - len=fread(org_data,2,packet_size * numChannels,in_file) / numChannels; - /* de-interleave if stereo */ - if ( usingStereo ) - { - stereoDeInterleave(org_data, len * numChannels); - } - - if (payloadType==NETEQ_CODEC_G722_PT) - timestamp+=len>>1; - else - timestamp+=len; - - sendtime += (double) len/(fs/1000); - } - - NetEQTest_free_coders(usedCodec, numChannels); - fclose(in_file); - fclose(out_file); - printf("Done!\n"); - - return(0); -} - - - - -/****************/ -/* Subfunctions */ -/****************/ - -void NetEQTest_GetCodec_and_PT(char * name, webrtc::NetEqDecoder *codec, int *PT, int frameLen, int *fs, int *bitrate, int *useRed) { - - *bitrate = 0; /* Default bitrate setting */ - *useRed = 0; /* Default no redundancy */ - - if(!strcmp(name,"pcmu")){ - *codec=webrtc::kDecoderPCMu; - *PT=NETEQ_CODEC_PCMU_PT; - *fs=8000; - } - else if(!strcmp(name,"pcma")){ - *codec=webrtc::kDecoderPCMa; - *PT=NETEQ_CODEC_PCMA_PT; - *fs=8000; - } - else if(!strcmp(name,"pcm16b")){ - *codec=webrtc::kDecoderPCM16B; - *PT=NETEQ_CODEC_PCM16B_PT; - *fs=8000; - } - else if(!strcmp(name,"pcm16b_wb")){ - *codec=webrtc::kDecoderPCM16Bwb; - *PT=NETEQ_CODEC_PCM16B_WB_PT; - *fs=16000; - } - else if(!strcmp(name,"pcm16b_swb32")){ - *codec=webrtc::kDecoderPCM16Bswb32kHz; - *PT=NETEQ_CODEC_PCM16B_SWB32KHZ_PT; - *fs=32000; - } - else if(!strcmp(name,"pcm16b_swb48")){ - *codec=webrtc::kDecoderPCM16Bswb48kHz; - *PT=NETEQ_CODEC_PCM16B_SWB48KHZ_PT; - *fs=48000; - } - else if(!strcmp(name,"g722")){ - *codec=webrtc::kDecoderG722; - *PT=NETEQ_CODEC_G722_PT; - *fs=16000; - } - else if((!strcmp(name,"ilbc"))&&((frameLen%240==0)||(frameLen%160==0))){ - *fs=8000; - *codec=webrtc::kDecoderILBC; - *PT=NETEQ_CODEC_ILBC_PT; - } - else if(!strcmp(name,"isac")){ - *fs=16000; - *codec=webrtc::kDecoderISAC; - *PT=NETEQ_CODEC_ISAC_PT; - } - else if(!strcmp(name,"isacswb")){ - *fs=32000; - *codec=webrtc::kDecoderISACswb; - *PT=NETEQ_CODEC_ISACSWB_PT; - } - else if(!strcmp(name,"celt32")){ - *fs=32000; - *codec=webrtc::kDecoderCELT_32; - *PT=NETEQ_CODEC_CELT32_PT; - } - else if(!strcmp(name,"red_pcm")){ - *codec=webrtc::kDecoderPCMa; - *PT=NETEQ_CODEC_PCMA_PT; /* this will be the PT for the sub-headers */ - *fs=8000; - *useRed = 1; - } else if(!strcmp(name,"red_isac")){ - *codec=webrtc::kDecoderISAC; - *PT=NETEQ_CODEC_ISAC_PT; /* this will be the PT for the sub-headers */ - *fs=16000; - *useRed = 1; - } else { - printf("Error: Not a supported codec (%s)\n", name); - exit(0); - } - -} - - - - -int NetEQTest_init_coders(webrtc::NetEqDecoder coder, int enc_frameSize, int bitrate, int sampfreq , int vad, int numChannels){ - - int ok=0; - - for (int k = 0; k < numChannels; k++) - { - ok=WebRtcVad_Create(&VAD_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for VAD instance\n"); - exit(0); - } - ok=WebRtcVad_Init(VAD_inst[k]); - if (ok==-1) { - printf("Error: Initialization of VAD struct failed\n"); - exit(0); - } - - -#if (defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) - ok=WebRtcCng_CreateEnc(&CNGenc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for CNG encoding instance\n"); - exit(0); - } - if(sampfreq <= 16000) { - ok=WebRtcCng_InitEnc(CNGenc_inst[k],sampfreq, 200, 5); - if (ok==-1) { - printf("Error: Initialization of CNG struct failed. Error code %d\n", - WebRtcCng_GetErrorCodeEnc(CNGenc_inst[k])); - exit(0); - } - } -#endif - - switch (coder) { -#ifdef CODEC_PCM16B - case webrtc::kDecoderPCM16B : -#endif -#ifdef CODEC_PCM16B_WB - case webrtc::kDecoderPCM16Bwb : -#endif -#ifdef CODEC_PCM16B_32KHZ - case webrtc::kDecoderPCM16Bswb32kHz : -#endif -#ifdef CODEC_PCM16B_48KHZ - case webrtc::kDecoderPCM16Bswb48kHz : -#endif -#ifdef CODEC_G711 - case webrtc::kDecoderPCMu : - case webrtc::kDecoderPCMa : -#endif - // do nothing - break; -#ifdef CODEC_G729 - case webrtc::kDecoderG729: - if (sampfreq==8000) { - if ((enc_frameSize==80)||(enc_frameSize==160)||(enc_frameSize==240)||(enc_frameSize==320)||(enc_frameSize==400)||(enc_frameSize==480)) { - ok=WebRtcG729_CreateEnc(&G729enc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G729 encoding instance\n"); - exit(0); - } - } else { - printf("\nError: g729 only supports 10, 20, 30, 40, 50 or 60 ms!!\n\n"); - exit(0); - } - WebRtcG729_EncoderInit(G729enc_inst[k], vad); - if ((vad==1)&&(enc_frameSize!=80)) { - printf("\nError - This simulation only supports VAD for G729 at 10ms packets (not %dms)\n", (enc_frameSize>>3)); - } - } else { - printf("\nError - g729 is only developed for 8kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_G729_1 - case webrtc::kDecoderG729_1: - if (sampfreq==16000) { - if ((enc_frameSize==320)||(enc_frameSize==640)||(enc_frameSize==960) - ) { - ok=WebRtcG7291_Create(&G729_1_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G.729.1 codec instance\n"); - exit(0); - } - } else { - printf("\nError: G.729.1 only supports 20, 40 or 60 ms!!\n\n"); - exit(0); - } - if (!(((bitrate >= 12000) && (bitrate <= 32000) && (bitrate%2000 == 0)) || (bitrate == 8000))) { - /* must be 8, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, or 32 kbps */ - printf("\nError: G.729.1 bitrate must be 8000 or 12000--32000 in steps of 2000 bps\n"); - exit(0); - } - WebRtcG7291_EncoderInit(G729_1_inst[k], bitrate, 0 /* flag8kHz*/, 0 /*flagG729mode*/); - } else { - printf("\nError - G.729.1 input is always 16 kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_SPEEX_8 - case webrtc::kDecoderSPEEX_8 : - if (sampfreq==8000) { - if ((enc_frameSize==160)||(enc_frameSize==320)||(enc_frameSize==480)) { - ok=WebRtcSpeex_CreateEnc(&SPEEX8enc_inst[k], sampfreq); - if (ok!=0) { - printf("Error: Couldn't allocate memory for Speex encoding instance\n"); - exit(0); - } - } else { - printf("\nError: Speex only supports 20, 40, and 60 ms!!\n\n"); - exit(0); - } - if ((vad==1)&&(enc_frameSize!=160)) { - printf("\nError - This simulation only supports VAD for Speex at 20ms packets (not %dms)\n", (enc_frameSize>>3)); - vad=0; - } - ok=WebRtcSpeex_EncoderInit(SPEEX8enc_inst[k], 0/*vbr*/, 3 /*complexity*/, vad); - if (ok!=0) exit(0); - } else { - printf("\nError - Speex8 called with sample frequency other than 8 kHz.\n\n"); - } - break; -#endif -#ifdef CODEC_SPEEX_16 - case webrtc::kDecoderSPEEX_16 : - if (sampfreq==16000) { - if ((enc_frameSize==320)||(enc_frameSize==640)||(enc_frameSize==960)) { - ok=WebRtcSpeex_CreateEnc(&SPEEX16enc_inst[k], sampfreq); - if (ok!=0) { - printf("Error: Couldn't allocate memory for Speex encoding instance\n"); - exit(0); - } - } else { - printf("\nError: Speex only supports 20, 40, and 60 ms!!\n\n"); - exit(0); - } - if ((vad==1)&&(enc_frameSize!=320)) { - printf("\nError - This simulation only supports VAD for Speex at 20ms packets (not %dms)\n", (enc_frameSize>>4)); - vad=0; - } - ok=WebRtcSpeex_EncoderInit(SPEEX16enc_inst[k], 0/*vbr*/, 3 /*complexity*/, vad); - if (ok!=0) exit(0); - } else { - printf("\nError - Speex16 called with sample frequency other than 16 kHz.\n\n"); - } - break; -#endif -#ifdef CODEC_CELT_32 - case webrtc::kDecoderCELT_32 : - if (sampfreq==32000) { - if (enc_frameSize==320) { - ok=WebRtcCelt_CreateEnc(&CELT32enc_inst[k], 1 /*mono*/); - if (ok!=0) { - printf("Error: Couldn't allocate memory for Celt encoding instance\n"); - exit(0); - } - } else { - printf("\nError: Celt only supports 10 ms!!\n\n"); - exit(0); - } - ok=WebRtcCelt_EncoderInit(CELT32enc_inst[k], 1 /*mono*/, 48000 /*bitrate*/); - if (ok!=0) exit(0); - } else { - printf("\nError - Celt32 called with sample frequency other than 32 kHz.\n\n"); - } - break; -#endif - -#ifdef CODEC_G722_1_16 - case webrtc::kDecoderG722_1_16 : - if (sampfreq==16000) { - ok=WebRtcG7221_CreateEnc16(&G722_1_16enc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G.722.1 instance\n"); - exit(0); - } - if (enc_frameSize==320) { - } else { - printf("\nError: G722.1 only supports 20 ms!!\n\n"); - exit(0); - } - WebRtcG7221_EncoderInit16((G722_1_16_encinst_t*)G722_1_16enc_inst[k]); - } else { - printf("\nError - G722.1 is only developed for 16kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_G722_1_24 - case webrtc::kDecoderG722_1_24 : - if (sampfreq==16000) { - ok=WebRtcG7221_CreateEnc24(&G722_1_24enc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G.722.1 instance\n"); - exit(0); - } - if (enc_frameSize==320) { - } else { - printf("\nError: G722.1 only supports 20 ms!!\n\n"); - exit(0); - } - WebRtcG7221_EncoderInit24((G722_1_24_encinst_t*)G722_1_24enc_inst[k]); - } else { - printf("\nError - G722.1 is only developed for 16kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_G722_1_32 - case webrtc::kDecoderG722_1_32 : - if (sampfreq==16000) { - ok=WebRtcG7221_CreateEnc32(&G722_1_32enc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G.722.1 instance\n"); - exit(0); - } - if (enc_frameSize==320) { - } else { - printf("\nError: G722.1 only supports 20 ms!!\n\n"); - exit(0); - } - WebRtcG7221_EncoderInit32((G722_1_32_encinst_t*)G722_1_32enc_inst[k]); - } else { - printf("\nError - G722.1 is only developed for 16kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_G722_1C_24 - case webrtc::kDecoderG722_1C_24 : - if (sampfreq==32000) { - ok=WebRtcG7221C_CreateEnc24(&G722_1C_24enc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G.722.1C instance\n"); - exit(0); - } - if (enc_frameSize==640) { - } else { - printf("\nError: G722.1 C only supports 20 ms!!\n\n"); - exit(0); - } - WebRtcG7221C_EncoderInit24((G722_1C_24_encinst_t*)G722_1C_24enc_inst[k]); - } else { - printf("\nError - G722.1 C is only developed for 32kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_G722_1C_32 - case webrtc::kDecoderG722_1C_32 : - if (sampfreq==32000) { - ok=WebRtcG7221C_CreateEnc32(&G722_1C_32enc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G.722.1C instance\n"); - exit(0); - } - if (enc_frameSize==640) { - } else { - printf("\nError: G722.1 C only supports 20 ms!!\n\n"); - exit(0); - } - WebRtcG7221C_EncoderInit32((G722_1C_32_encinst_t*)G722_1C_32enc_inst[k]); - } else { - printf("\nError - G722.1 C is only developed for 32kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_G722_1C_48 - case webrtc::kDecoderG722_1C_48 : - if (sampfreq==32000) { - ok=WebRtcG7221C_CreateEnc48(&G722_1C_48enc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for G.722.1C instance\n"); - exit(0); - } - if (enc_frameSize==640) { - } else { - printf("\nError: G722.1 C only supports 20 ms!!\n\n"); - exit(0); - } - WebRtcG7221C_EncoderInit48((G722_1C_48_encinst_t*)G722_1C_48enc_inst[k]); - } else { - printf("\nError - G722.1 C is only developed for 32kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_G722 - case webrtc::kDecoderG722 : - if (sampfreq==16000) { - if (enc_frameSize%2==0) { - } else { - printf("\nError - g722 frames must have an even number of enc_frameSize\n"); - exit(0); - } - WebRtcG722_CreateEncoder(&g722EncState[k]); - WebRtcG722_EncoderInit(g722EncState[k]); - } else { - printf("\nError - g722 is only developed for 16kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_AMR - case webrtc::kDecoderAMR : - if (sampfreq==8000) { - ok=WebRtcAmr_CreateEnc(&AMRenc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for AMR encoding instance\n"); - exit(0); - }if ((enc_frameSize==160)||(enc_frameSize==320)||(enc_frameSize==480)) { - } else { - printf("\nError - AMR must have a multiple of 160 enc_frameSize\n"); - exit(0); - } - WebRtcAmr_EncoderInit(AMRenc_inst[k], vad); - WebRtcAmr_EncodeBitmode(AMRenc_inst[k], AMRBandwidthEfficient); - AMR_bitrate = bitrate; - } else { - printf("\nError - AMR is only developed for 8kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_AMRWB - case webrtc::kDecoderAMRWB : - if (sampfreq==16000) { - ok=WebRtcAmrWb_CreateEnc(&AMRWBenc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for AMRWB encoding instance\n"); - exit(0); - } - if (((enc_frameSize/320)<0)||((enc_frameSize/320)>3)||((enc_frameSize%320)!=0)) { - printf("\nError - AMRwb must have frameSize of 20, 40 or 60ms\n"); - exit(0); - } - WebRtcAmrWb_EncoderInit(AMRWBenc_inst[k], vad); - if (bitrate==7000) { - AMRWB_bitrate = AMRWB_MODE_7k; - } else if (bitrate==9000) { - AMRWB_bitrate = AMRWB_MODE_9k; - } else if (bitrate==12000) { - AMRWB_bitrate = AMRWB_MODE_12k; - } else if (bitrate==14000) { - AMRWB_bitrate = AMRWB_MODE_14k; - } else if (bitrate==16000) { - AMRWB_bitrate = AMRWB_MODE_16k; - } else if (bitrate==18000) { - AMRWB_bitrate = AMRWB_MODE_18k; - } else if (bitrate==20000) { - AMRWB_bitrate = AMRWB_MODE_20k; - } else if (bitrate==23000) { - AMRWB_bitrate = AMRWB_MODE_23k; - } else if (bitrate==24000) { - AMRWB_bitrate = AMRWB_MODE_24k; - } - WebRtcAmrWb_EncodeBitmode(AMRWBenc_inst[k], AMRBandwidthEfficient); - - } else { - printf("\nError - AMRwb is only developed for 16kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_ILBC - case webrtc::kDecoderILBC : - if (sampfreq==8000) { - ok=WebRtcIlbcfix_EncoderCreate(&iLBCenc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for iLBC encoding instance\n"); - exit(0); - } - if ((enc_frameSize==160)||(enc_frameSize==240)||(enc_frameSize==320)||(enc_frameSize==480)) { - } else { - printf("\nError - iLBC only supports 160, 240, 320 and 480 enc_frameSize (20, 30, 40 and 60 ms)\n"); - exit(0); - } - if ((enc_frameSize==160)||(enc_frameSize==320)) { - /* 20 ms version */ - WebRtcIlbcfix_EncoderInit(iLBCenc_inst[k], 20); - } else { - /* 30 ms version */ - WebRtcIlbcfix_EncoderInit(iLBCenc_inst[k], 30); - } - } else { - printf("\nError - iLBC is only developed for 8kHz \n"); - exit(0); - } - break; -#endif -#ifdef CODEC_ISAC - case webrtc::kDecoderISAC: - if (sampfreq==16000) { - ok=WebRtcIsac_Create(&ISAC_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for iSAC instance\n"); - exit(0); - }if ((enc_frameSize==480)||(enc_frameSize==960)) { - } else { - printf("\nError - iSAC only supports frameSize (30 and 60 ms)\n"); - exit(0); - } - WebRtcIsac_EncoderInit(ISAC_inst[k],1); - if ((bitrate<10000)||(bitrate>32000)) { - printf("\nError - iSAC bitrate has to be between 10000 and 32000 bps (not %i)\n", bitrate); - exit(0); - } - WebRtcIsac_Control(ISAC_inst[k], bitrate, enc_frameSize>>4); - } else { - printf("\nError - iSAC only supports 480 or 960 enc_frameSize (30 or 60 ms)\n"); - exit(0); - } - break; -#endif -#ifdef NETEQ_ISACFIX_CODEC - case webrtc::kDecoderISAC: - if (sampfreq==16000) { - ok=WebRtcIsacfix_Create(&ISAC_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for iSAC instance\n"); - exit(0); - }if ((enc_frameSize==480)||(enc_frameSize==960)) { - } else { - printf("\nError - iSAC only supports frameSize (30 and 60 ms)\n"); - exit(0); - } - WebRtcIsacfix_EncoderInit(ISAC_inst[k],1); - if ((bitrate<10000)||(bitrate>32000)) { - printf("\nError - iSAC bitrate has to be between 10000 and 32000 bps (not %i)\n", bitrate); - exit(0); - } - WebRtcIsacfix_Control(ISAC_inst[k], bitrate, enc_frameSize>>4); - } else { - printf("\nError - iSAC only supports 480 or 960 enc_frameSize (30 or 60 ms)\n"); - exit(0); - } - break; -#endif -#ifdef CODEC_ISAC_SWB - case webrtc::kDecoderISACswb: - if (sampfreq==32000) { - ok=WebRtcIsac_Create(&ISACSWB_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for iSAC SWB instance\n"); - exit(0); - }if (enc_frameSize==960) { - } else { - printf("\nError - iSAC SWB only supports frameSize 30 ms\n"); - exit(0); - } - ok = WebRtcIsac_SetEncSampRate(ISACSWB_inst[k], 32000); - if (ok!=0) { - printf("Error: Couldn't set sample rate for iSAC SWB instance\n"); - exit(0); - } - WebRtcIsac_EncoderInit(ISACSWB_inst[k],1); - if ((bitrate<32000)||(bitrate>56000)) { - printf("\nError - iSAC SWB bitrate has to be between 32000 and 56000 bps (not %i)\n", bitrate); - exit(0); - } - WebRtcIsac_Control(ISACSWB_inst[k], bitrate, enc_frameSize>>5); - } else { - printf("\nError - iSAC SWB only supports 960 enc_frameSize (30 ms)\n"); - exit(0); - } - break; -#endif -#ifdef CODEC_GSMFR - case webrtc::kDecoderGSMFR: - if (sampfreq==8000) { - ok=WebRtcGSMFR_CreateEnc(&GSMFRenc_inst[k]); - if (ok!=0) { - printf("Error: Couldn't allocate memory for GSM FR encoding instance\n"); - exit(0); - } - if ((enc_frameSize==160)||(enc_frameSize==320)||(enc_frameSize==480)) { - } else { - printf("\nError - GSM FR must have a multiple of 160 enc_frameSize\n"); - exit(0); - } - WebRtcGSMFR_EncoderInit(GSMFRenc_inst[k], 0); - } else { - printf("\nError - GSM FR is only developed for 8kHz \n"); - exit(0); - } - break; -#endif - default : - printf("Error: unknown codec in call to NetEQTest_init_coders.\n"); - exit(0); - break; - } - - if (ok != 0) { - return(ok); - } - } // end for - - return(0); -} - - - - -int NetEQTest_free_coders(webrtc::NetEqDecoder coder, int numChannels) { - - for (int k = 0; k < numChannels; k++) - { - WebRtcVad_Free(VAD_inst[k]); -#if (defined(CODEC_CNGCODEC8) || defined(CODEC_CNGCODEC16) || \ - defined(CODEC_CNGCODEC32) || defined(CODEC_CNGCODEC48)) - WebRtcCng_FreeEnc(CNGenc_inst[k]); -#endif - - switch (coder) - { -#ifdef CODEC_PCM16B - case webrtc::kDecoderPCM16B : -#endif -#ifdef CODEC_PCM16B_WB - case webrtc::kDecoderPCM16Bwb : -#endif -#ifdef CODEC_PCM16B_32KHZ - case webrtc::kDecoderPCM16Bswb32kHz : -#endif -#ifdef CODEC_PCM16B_48KHZ - case webrtc::kDecoderPCM16Bswb48kHz : -#endif -#ifdef CODEC_G711 - case webrtc::kDecoderPCMu : - case webrtc::kDecoderPCMa : -#endif - // do nothing - break; -#ifdef CODEC_G729 - case webrtc::kDecoderG729: - WebRtcG729_FreeEnc(G729enc_inst[k]); - break; -#endif -#ifdef CODEC_G729_1 - case webrtc::kDecoderG729_1: - WebRtcG7291_Free(G729_1_inst[k]); - break; -#endif -#ifdef CODEC_SPEEX_8 - case webrtc::kDecoderSPEEX_8 : - WebRtcSpeex_FreeEnc(SPEEX8enc_inst[k]); - break; -#endif -#ifdef CODEC_SPEEX_16 - case webrtc::kDecoderSPEEX_16 : - WebRtcSpeex_FreeEnc(SPEEX16enc_inst[k]); - break; -#endif -#ifdef CODEC_CELT_32 - case webrtc::kDecoderCELT_32 : - WebRtcCelt_FreeEnc(CELT32enc_inst[k]); - break; -#endif - -#ifdef CODEC_G722_1_16 - case webrtc::kDecoderG722_1_16 : - WebRtcG7221_FreeEnc16(G722_1_16enc_inst[k]); - break; -#endif -#ifdef CODEC_G722_1_24 - case webrtc::kDecoderG722_1_24 : - WebRtcG7221_FreeEnc24(G722_1_24enc_inst[k]); - break; -#endif -#ifdef CODEC_G722_1_32 - case webrtc::kDecoderG722_1_32 : - WebRtcG7221_FreeEnc32(G722_1_32enc_inst[k]); - break; -#endif -#ifdef CODEC_G722_1C_24 - case webrtc::kDecoderG722_1C_24 : - WebRtcG7221C_FreeEnc24(G722_1C_24enc_inst[k]); - break; -#endif -#ifdef CODEC_G722_1C_32 - case webrtc::kDecoderG722_1C_32 : - WebRtcG7221C_FreeEnc32(G722_1C_32enc_inst[k]); - break; -#endif -#ifdef CODEC_G722_1C_48 - case webrtc::kDecoderG722_1C_48 : - WebRtcG7221C_FreeEnc48(G722_1C_48enc_inst[k]); - break; -#endif -#ifdef CODEC_G722 - case webrtc::kDecoderG722 : - WebRtcG722_FreeEncoder(g722EncState[k]); - break; -#endif -#ifdef CODEC_AMR - case webrtc::kDecoderAMR : - WebRtcAmr_FreeEnc(AMRenc_inst[k]); - break; -#endif -#ifdef CODEC_AMRWB - case webrtc::kDecoderAMRWB : - WebRtcAmrWb_FreeEnc(AMRWBenc_inst[k]); - break; -#endif -#ifdef CODEC_ILBC - case webrtc::kDecoderILBC : - WebRtcIlbcfix_EncoderFree(iLBCenc_inst[k]); - break; -#endif -#ifdef CODEC_ISAC - case webrtc::kDecoderISAC: - WebRtcIsac_Free(ISAC_inst[k]); - break; -#endif -#ifdef NETEQ_ISACFIX_CODEC - case webrtc::kDecoderISAC: - WebRtcIsacfix_Free(ISAC_inst[k]); - break; -#endif -#ifdef CODEC_ISAC_SWB - case webrtc::kDecoderISACswb: - WebRtcIsac_Free(ISACSWB_inst[k]); - break; -#endif -#ifdef CODEC_GSMFR - case webrtc::kDecoderGSMFR: - WebRtcGSMFR_FreeEnc(GSMFRenc_inst[k]); - break; -#endif - default : - printf("Error: unknown codec in call to NetEQTest_init_coders.\n"); - exit(0); - break; - } - } - - return(0); -} - - - - - - -int NetEQTest_encode(int coder, int16_t *indata, int frameLen, unsigned char * encoded,int sampleRate , - int * vad, int useVAD, int bitrate, int numChannels){ - - short cdlen = 0; - int16_t *tempdata; - static int first_cng=1; - int16_t tempLen; - - *vad =1; - - // check VAD first - if(useVAD) - { - *vad = 0; - - for (int k = 0; k < numChannels; k++) - { - tempLen = frameLen; - tempdata = &indata[k*frameLen]; - int localVad=0; - /* Partition the signal and test each chunk for VAD. - All chunks must be VAD=0 to produce a total VAD=0. */ - while (tempLen >= 10*sampleRate/1000) { - if ((tempLen % 30*sampleRate/1000) == 0) { // tempLen is multiple of 30ms - localVad |= WebRtcVad_Process(VAD_inst[k] ,sampleRate, tempdata, 30*sampleRate/1000); - tempdata += 30*sampleRate/1000; - tempLen -= 30*sampleRate/1000; - } - else if (tempLen >= 20*sampleRate/1000) { // tempLen >= 20ms - localVad |= WebRtcVad_Process(VAD_inst[k] ,sampleRate, tempdata, 20*sampleRate/1000); - tempdata += 20*sampleRate/1000; - tempLen -= 20*sampleRate/1000; - } - else { // use 10ms - localVad |= WebRtcVad_Process(VAD_inst[k] ,sampleRate, tempdata, 10*sampleRate/1000); - tempdata += 10*sampleRate/1000; - tempLen -= 10*sampleRate/1000; - } - } - - // aggregate all VAD decisions over all channels - *vad |= localVad; - } - - if(!*vad){ - // all channels are silent - cdlen = 0; - for (int k = 0; k < numChannels; k++) - { - WebRtcCng_Encode(CNGenc_inst[k],&indata[k*frameLen], (frameLen <= 640 ? frameLen : 640) /* max 640 */, - encoded,&tempLen,first_cng); - encoded += tempLen; - cdlen += tempLen; - } - *vad=0; - first_cng=0; - return(cdlen); - } - } - - - // loop over all channels - int totalLen = 0; - - for (int k = 0; k < numChannels; k++) - { - /* Encode with the selected coder type */ - if (coder==webrtc::kDecoderPCMu) { /*g711 u-law */ -#ifdef CODEC_G711 - cdlen = WebRtcG711_EncodeU(G711state[k], indata, frameLen, (int16_t*) encoded); -#endif - } - else if (coder==webrtc::kDecoderPCMa) { /*g711 A-law */ -#ifdef CODEC_G711 - cdlen = WebRtcG711_EncodeA(G711state[k], indata, frameLen, (int16_t*) encoded); - } -#endif -#ifdef CODEC_PCM16B - else if ((coder==webrtc::kDecoderPCM16B)||(coder==webrtc::kDecoderPCM16Bwb)|| - (coder==webrtc::kDecoderPCM16Bswb32kHz)||(coder==webrtc::kDecoderPCM16Bswb48kHz)) { /*pcm16b (8kHz, 16kHz, 32kHz or 48kHz) */ - cdlen = WebRtcPcm16b_EncodeW16(indata, frameLen, (int16_t*) encoded); - } -#endif -#ifdef CODEC_G722 - else if (coder==webrtc::kDecoderG722) { /*g722 */ - cdlen=WebRtcG722_Encode(g722EncState[k], indata, frameLen, (int16_t*)encoded); - assert(cdlen == frameLen>>1); - } -#endif -#ifdef CODEC_ILBC - else if (coder==webrtc::kDecoderILBC) { /*iLBC */ - cdlen=WebRtcIlbcfix_Encode(iLBCenc_inst[k], indata,frameLen,(int16_t*)encoded); - } -#endif -#if (defined(CODEC_ISAC) || defined(NETEQ_ISACFIX_CODEC)) // TODO(hlundin): remove all NETEQ_ISACFIX_CODEC - else if (coder==webrtc::kDecoderISAC) { /*iSAC */ - int noOfCalls=0; - cdlen=0; - while (cdlen<=0) { -#ifdef CODEC_ISAC /* floating point */ - cdlen=WebRtcIsac_Encode(ISAC_inst[k],&indata[noOfCalls*160],(int16_t*)encoded); -#else /* fixed point */ - cdlen=WebRtcIsacfix_Encode(ISAC_inst[k],&indata[noOfCalls*160],(int16_t*)encoded); -#endif - noOfCalls++; - } - } -#endif -#ifdef CODEC_ISAC_SWB - else if (coder==webrtc::kDecoderISACswb) { /* iSAC SWB */ - int noOfCalls=0; - cdlen=0; - while (cdlen<=0) { - cdlen=WebRtcIsac_Encode(ISACSWB_inst[k],&indata[noOfCalls*320],(int16_t*)encoded); - noOfCalls++; - } - } -#endif -#ifdef CODEC_CELT_32 - else if (coder==webrtc::kDecoderCELT_32) { /* Celt */ - int encodedLen = 0; - cdlen = 0; - while (cdlen <= 0) { - cdlen = WebRtcCelt_Encode(CELT32enc_inst[k], &indata[encodedLen], encoded); - encodedLen += 10*32; /* 10 ms */ - } - if( (encodedLen != frameLen) || cdlen < 0) { - printf("Error encoding Celt frame!\n"); - exit(0); - } - } -#endif - - indata += frameLen; - encoded += cdlen; - totalLen += cdlen; - - } // end for - - first_cng=1; - return(totalLen); -} - - - -void makeRTPheader(unsigned char* rtp_data, int payloadType, int seqNo, uint32_t timestamp, uint32_t ssrc){ - - rtp_data[0]=(unsigned char)0x80; - rtp_data[1]=(unsigned char)(payloadType & 0xFF); - rtp_data[2]=(unsigned char)((seqNo>>8)&0xFF); - rtp_data[3]=(unsigned char)((seqNo)&0xFF); - rtp_data[4]=(unsigned char)((timestamp>>24)&0xFF); - rtp_data[5]=(unsigned char)((timestamp>>16)&0xFF); - - rtp_data[6]=(unsigned char)((timestamp>>8)&0xFF); - rtp_data[7]=(unsigned char)(timestamp & 0xFF); - - rtp_data[8]=(unsigned char)((ssrc>>24)&0xFF); - rtp_data[9]=(unsigned char)((ssrc>>16)&0xFF); - - rtp_data[10]=(unsigned char)((ssrc>>8)&0xFF); - rtp_data[11]=(unsigned char)(ssrc & 0xFF); -} - - -int makeRedundantHeader(unsigned char* rtp_data, int *payloadType, int numPayloads, uint32_t *timestamp, uint16_t *blockLen, - int seqNo, uint32_t ssrc) -{ - - int i; - unsigned char *rtpPointer; - uint16_t offset; - - /* first create "standard" RTP header */ - makeRTPheader(rtp_data, NETEQ_CODEC_RED_PT, seqNo, timestamp[numPayloads-1], ssrc); - - rtpPointer = &rtp_data[12]; - - /* add one sub-header for each redundant payload (not the primary) */ - for(i=0; i 0) { - offset = (uint16_t) (timestamp[numPayloads-1] - timestamp[i]); - - rtpPointer[0] = (unsigned char) ( 0x80 | (0x7F & payloadType[i]) ); /* |F| block PT | */ - rtpPointer[1] = (unsigned char) ((offset >> 6) & 0xFF); /* | timestamp- | */ - rtpPointer[2] = (unsigned char) ( ((offset & 0x3F)<<2) | - ( (blockLen[i]>>8) & 0x03 ) ); /* | -offset |bl-| */ - rtpPointer[3] = (unsigned char) ( blockLen[i] & 0xFF ); /* | -ock length | */ - - rtpPointer += 4; - } - } - - /* last sub-header */ - rtpPointer[0]= (unsigned char) (0x00 | (0x7F&payloadType[numPayloads-1]));/* |F| block PT | */ - rtpPointer += 1; - - return(rtpPointer - rtp_data); /* length of header in bytes */ -} - - - -int makeDTMFpayload(unsigned char* payload_data, int Event, int End, int Volume, int Duration) { - unsigned char E,R,V; - R=0; - V=(unsigned char)Volume; - if (End==0) { - E = 0x00; - } else { - E = 0x80; - } - payload_data[0]=(unsigned char)Event; - payload_data[1]=(unsigned char)(E|R|V); - //Duration equals 8 times time_ms, default is 8000 Hz. - payload_data[2]=(unsigned char)((Duration>>8)&0xFF); - payload_data[3]=(unsigned char)(Duration&0xFF); - return(4); -} - -void stereoDeInterleave(int16_t* audioSamples, int numSamples) -{ - - int16_t *tempVec; - int16_t *readPtr, *writeL, *writeR; - - if (numSamples <= 0) - return; - - tempVec = (int16_t *) malloc(sizeof(int16_t) * numSamples); - if (tempVec == NULL) { - printf("Error allocating memory\n"); - exit(0); - } - - memcpy(tempVec, audioSamples, numSamples*sizeof(int16_t)); - - writeL = audioSamples; - writeR = &audioSamples[numSamples/2]; - readPtr = tempVec; - - for (int k = 0; k < numSamples; k += 2) - { - *writeL = *readPtr; - readPtr++; - *writeR = *readPtr; - readPtr++; - writeL++; - writeR++; - } - - free(tempVec); - -} - - -void stereoInterleave(unsigned char* data, int dataLen, int stride) -{ - - unsigned char *ptrL, *ptrR; - unsigned char temp[10]; - - if (stride > 10) - { - exit(0); - } - - if (dataLen%1 != 0) - { - // must be even number of samples - printf("Error: cannot interleave odd sample number\n"); - exit(0); - } - - ptrL = data + stride; - ptrR = &data[dataLen/2]; - - while (ptrL < ptrR) { - // copy from right pointer to temp - memcpy(temp, ptrR, stride); - - // shift data between pointers - memmove(ptrL + stride, ptrL, ptrR - ptrL); - - // copy from temp to left pointer - memcpy(ptrL, temp, stride); - - // advance pointers - ptrL += stride*2; - ptrR += stride; - } - -} diff --git a/webrtc/modules/audio_coding/neteq4/test/RTPjitter.cc b/webrtc/modules/audio_coding/neteq4/test/RTPjitter.cc deleted file mode 100644 index eeb4c9014..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/RTPjitter.cc +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -//TODO(hlundin): Reformat file to meet style guide. - -/* header includes */ -#include -#include -#include -#include -#ifdef WIN32 -#include -#include -#endif -#ifdef WEBRTC_LINUX -#include -#endif - -#include - -#include "gtest/gtest.h" -#include "webrtc/typedefs.h" - -/*********************/ -/* Misc. definitions */ -/*********************/ - -#define FIRSTLINELEN 40 -#define CHECK_NOT_NULL(a) if((a)==NULL){fprintf(stderr,"\n %s \n line: %d \nerror at %s\n",__FILE__,__LINE__,#a );return(-1);} - -struct arr_time { - float time; - uint32_t ix; -}; - -int filelen(FILE *fid) -{ - fpos_t cur_pos; - int len; - - if (!fid || fgetpos(fid, &cur_pos)) { - return(-1); - } - - fseek(fid, 0, SEEK_END); - len = ftell(fid); - - fsetpos(fid, &cur_pos); - - return (len); -} - -int compare_arr_time(const void *x, const void *y); - -int main(int argc, char* argv[]) -{ - unsigned int dat_len, rtp_len, Npack, k; - arr_time *time_vec; - char firstline[FIRSTLINELEN]; - unsigned char* rtp_vec = NULL; - unsigned char** packet_ptr = NULL; - unsigned char* temp_packet = NULL; - const unsigned int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; - uint16_t len; - uint32_t *offset; - -/* check number of parameters */ - if (argc != 4) { - /* print help text and exit */ - printf("Apply jitter on RTP stream.\n"); - printf("The program reads an RTP stream and packet timing from two files.\n"); - printf("The RTP stream is modified to have the same jitter as described in the timing files.\n"); - printf("The format of the RTP stream file should be the same as for rtpplay,\n"); - printf("and can be obtained e.g., from Ethereal by using\n"); - printf("Statistics -> RTP -> Show All Streams -> [select a stream] -> Save As\n\n"); - printf("Usage:\n\n"); - printf("%s RTP_infile dat_file RTP_outfile\n", argv[0]); - printf("where:\n"); - - printf("RTP_infile : RTP stream input file\n\n"); - - printf("dat_file : file with packet arrival times in ms\n\n"); - - printf("RTP_outfile : RTP stream output file\n\n"); - - return(0); - } - - FILE* in_file=fopen(argv[1],"rb"); - CHECK_NOT_NULL(in_file); - printf("Input file: %s\n",argv[1]); - FILE* dat_file=fopen(argv[2],"rb"); - CHECK_NOT_NULL(dat_file); - printf("Dat-file: %s\n",argv[2]); - FILE* out_file=fopen(argv[3],"wb"); - CHECK_NOT_NULL(out_file); - printf("Output file: %s\n\n",argv[3]); - - time_vec = (arr_time *) malloc(sizeof(arr_time)*(filelen(dat_file)/sizeof(float)) + 1000); // add 1000 bytes to avoid (rare) strange error - if (time_vec==NULL) { - fprintf(stderr, "Error: could not allocate memory for reading dat file\n"); - goto closing; - } - - dat_len=0; - while(fread(&(time_vec[dat_len].time),sizeof(float),1,dat_file)>0) { - time_vec[dat_len].ix=dat_len; - dat_len++; - } - - if (dat_len == 0) { - fprintf(stderr, "Error: dat_file is empty, no arrival time is given.\n"); - goto closing; - } - - qsort(time_vec,dat_len,sizeof(arr_time),compare_arr_time); - - - rtp_vec = (unsigned char *) malloc(sizeof(unsigned char)*filelen(in_file)); - if (rtp_vec==NULL) { - fprintf(stderr,"Error: could not allocate memory for reading rtp file\n"); - goto closing; - } - - // read file header and write directly to output file - EXPECT_TRUE(fgets(firstline, FIRSTLINELEN, in_file) != NULL); - EXPECT_GT(fputs(firstline, out_file), 0); - EXPECT_EQ(kRtpDumpHeaderSize, fread(firstline, 1, kRtpDumpHeaderSize, - in_file)); - EXPECT_EQ(kRtpDumpHeaderSize, fwrite(firstline, 1, kRtpDumpHeaderSize, - out_file)); - - // read all RTP packets into vector - rtp_len=0; - Npack=0; - len=(uint16_t) fread(&rtp_vec[rtp_len], sizeof(unsigned char), 2, in_file); // read length of first packet - while(len==2) { - len = ntohs(*((uint16_t *)(rtp_vec + rtp_len))); - rtp_len += 2; - if(fread(&rtp_vec[rtp_len], sizeof(unsigned char), len-2, in_file)!=(unsigned) (len-2)) { - fprintf(stderr,"Error: currupt packet length\n"); - goto closing; - } - rtp_len += len-2; - Npack++; - len=(uint16_t) fread(&rtp_vec[rtp_len], sizeof(unsigned char), 2, in_file); // read length of next packet - } - - if (Npack == 0) { - fprintf(stderr, "Error: No RTP packet found.\n"); - goto closing; - } - - packet_ptr = (unsigned char **) malloc(Npack*sizeof(unsigned char*)); - - packet_ptr[0]=rtp_vec; - k=1; - while(k= 0 ) { - *offset = htonl((uint32_t) time_vec[k].time); - } - else { - *offset = htonl((uint32_t) 0); - fprintf(stderr, "Warning: negative receive time in dat file transformed to 0.\n"); - } - - // write packet to file - if (fwrite(temp_packet, sizeof(unsigned char), - ntohs(*((uint16_t*) temp_packet)), - out_file) != - ntohs(*((uint16_t*) temp_packet))) { - return -1; - } - } - } - - -closing: - free(time_vec); - free(rtp_vec); - if (packet_ptr != NULL) { - free(packet_ptr); - } - fclose(in_file); - fclose(dat_file); - fclose(out_file); - - return(0); -} - - - -int compare_arr_time(const void *xp, const void *yp) { - - if(((arr_time *)xp)->time == ((arr_time *)yp)->time) - return(0); - else if(((arr_time *)xp)->time > ((arr_time *)yp)->time) - return(1); - - return(-1); -} diff --git a/webrtc/modules/audio_coding/neteq4/test/RTPtimeshift.cc b/webrtc/modules/audio_coding/neteq4/test/RTPtimeshift.cc deleted file mode 100644 index 15ffdf6a5..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/RTPtimeshift.cc +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 "NETEQTEST_RTPpacket.h" -#include "gtest/gtest.h" - -/*********************/ -/* Misc. definitions */ -/*********************/ - -#define FIRSTLINELEN 40 - - -int main(int argc, char* argv[]) -{ - if(argc < 4 || argc > 6) - { - printf("Usage: RTPtimeshift in.rtp out.rtp newStartTS [newStartSN [newStartArrTime]]\n"); - exit(1); - } - - FILE *inFile=fopen(argv[1],"rb"); - if (!inFile) - { - printf("Cannot open input file %s\n", argv[1]); - return(-1); - } - printf("Input RTP file: %s\n",argv[1]); - - FILE *outFile=fopen(argv[2],"wb"); - if (!outFile) - { - printf("Cannot open output file %s\n", argv[2]); - return(-1); - } - printf("Output RTP file: %s\n\n",argv[2]); - - // read file header and write directly to output file - const unsigned int kRtpDumpHeaderSize = 4 + 4 + 4 + 2 + 2; - char firstline[FIRSTLINELEN]; - EXPECT_TRUE(fgets(firstline, FIRSTLINELEN, inFile) != NULL); - EXPECT_GT(fputs(firstline, outFile), 0); - EXPECT_EQ(kRtpDumpHeaderSize, - fread(firstline, 1, kRtpDumpHeaderSize, inFile)); - EXPECT_EQ(kRtpDumpHeaderSize, - fwrite(firstline, 1, kRtpDumpHeaderSize, outFile)); - NETEQTEST_RTPpacket packet; - int packLen = packet.readFromFile(inFile); - if (packLen < 0) - { - exit(1); - } - - // get new start TS and start SeqNo from arguments - uint32_t TSdiff = atoi(argv[3]) - packet.timeStamp(); - uint16_t SNdiff = 0; - uint32_t ATdiff = 0; - if (argc > 4) - { - int startSN = atoi(argv[4]); - if (startSN >= 0) - SNdiff = startSN - packet.sequenceNumber(); - if (argc > 5) - { - int startTS = atoi(argv[5]); - if (startTS >= 0) - ATdiff = startTS - packet.time(); - } - } - - while (packLen >= 0) - { - - packet.setTimeStamp(packet.timeStamp() + TSdiff); - packet.setSequenceNumber(packet.sequenceNumber() + SNdiff); - packet.setTime(packet.time() + ATdiff); - - packet.writeToFile(outFile); - - packLen = packet.readFromFile(inFile); - - } - - fclose(inFile); - fclose(outFile); - - return 0; -} diff --git a/webrtc/modules/audio_coding/neteq4/test/delay_tool/parse_delay_file.m b/webrtc/modules/audio_coding/neteq4/test/delay_tool/parse_delay_file.m deleted file mode 100644 index 77b394f41..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/delay_tool/parse_delay_file.m +++ /dev/null @@ -1,191 +0,0 @@ -function outStruct = parse_delay_file(file) - -fid = fopen(file, 'rb'); -if fid == -1 - error('Cannot open file %s', file); -end - -textline = fgetl(fid); -if ~strncmp(textline, '#!NetEQ_Delay_Logging', 21) - error('Wrong file format'); -end - -ver = sscanf(textline, '#!NetEQ_Delay_Logging%d.%d'); -if ~all(ver == [2; 0]) - error('Wrong version of delay logging function') -end - - -start_pos = ftell(fid); -fseek(fid, -12, 'eof'); -textline = fgetl(fid); -if ~strncmp(textline, 'End of file', 21) - error('File ending is not correct. Seems like the simulation ended abnormally.'); -end - -fseek(fid,-12-4, 'eof'); -Npackets = fread(fid, 1, 'int32'); -fseek(fid, start_pos, 'bof'); - -rtpts = zeros(Npackets, 1); -seqno = zeros(Npackets, 1); -pt = zeros(Npackets, 1); -plen = zeros(Npackets, 1); -recin_t = nan*ones(Npackets, 1); -decode_t = nan*ones(Npackets, 1); -playout_delay = zeros(Npackets, 1); -optbuf = zeros(Npackets, 1); - -fs_ix = 1; -clock = 0; -ts_ix = 1; -ended = 0; -late_packets = 0; -fs_now = 8000; -last_decode_k = 0; -tot_expand = 0; -tot_accelerate = 0; -tot_preemptive = 0; - -while not(ended) - signal = fread(fid, 1, '*int32'); - - switch signal - case 3 % NETEQ_DELAY_LOGGING_SIGNAL_CLOCK - clock = fread(fid, 1, '*float32'); - - % keep on reading batches of M until the signal is no longer "3" - % read int32 + float32 in one go - % this is to save execution time - temp = [3; 0]; - M = 120; - while all(temp(1,:) == 3) - fp = ftell(fid); - temp = fread(fid, [2 M], '*int32'); - end - - % back up to last clock event - fseek(fid, fp - ftell(fid) + ... - (find(temp(1,:) ~= 3, 1 ) - 2) * 2 * 4 + 4, 'cof'); - % read the last clock value - clock = fread(fid, 1, '*float32'); - - case 1 % NETEQ_DELAY_LOGGING_SIGNAL_RECIN - temp_ts = fread(fid, 1, 'uint32'); - - if late_packets > 0 - temp_ix = ts_ix - 1; - while (temp_ix >= 1) && (rtpts(temp_ix) ~= temp_ts) - % TODO(hlundin): use matlab vector search instead? - temp_ix = temp_ix - 1; - end - - if temp_ix >= 1 - % the ts was found in the vector - late_packets = late_packets - 1; - else - temp_ix = ts_ix; - ts_ix = ts_ix + 1; - end - else - temp_ix = ts_ix; - ts_ix = ts_ix + 1; - end - - rtpts(temp_ix) = temp_ts; - seqno(temp_ix) = fread(fid, 1, 'uint16'); - pt(temp_ix) = fread(fid, 1, 'int32'); - plen(temp_ix) = fread(fid, 1, 'int16'); - recin_t(temp_ix) = clock; - - case 2 % NETEQ_DELAY_LOGGING_SIGNAL_FLUSH - % do nothing - - case 4 % NETEQ_DELAY_LOGGING_SIGNAL_EOF - ended = 1; - - case 5 % NETEQ_DELAY_LOGGING_SIGNAL_DECODE - last_decode_ts = fread(fid, 1, 'uint32'); - temp_delay = fread(fid, 1, 'uint16'); - - k = find(rtpts(1:(ts_ix - 1))==last_decode_ts,1,'last'); - if ~isempty(k) - decode_t(k) = clock; - playout_delay(k) = temp_delay + ... - 5 * fs_now / 8000; % add overlap length - last_decode_k = k; - end - - case 6 % NETEQ_DELAY_LOGGING_SIGNAL_CHANGE_FS - fsvec(fs_ix) = fread(fid, 1, 'uint16'); - fschange_ts(fs_ix) = last_decode_ts; - fs_now = fsvec(fs_ix); - fs_ix = fs_ix + 1; - - case 7 % NETEQ_DELAY_LOGGING_SIGNAL_MERGE_INFO - playout_delay(last_decode_k) = playout_delay(last_decode_k) ... - + fread(fid, 1, 'int32'); - - case 8 % NETEQ_DELAY_LOGGING_SIGNAL_EXPAND_INFO - temp = fread(fid, 1, 'int32'); - if last_decode_k ~= 0 - tot_expand = tot_expand + temp / (fs_now / 1000); - end - - case 9 % NETEQ_DELAY_LOGGING_SIGNAL_ACCELERATE_INFO - temp = fread(fid, 1, 'int32'); - if last_decode_k ~= 0 - tot_accelerate = tot_accelerate + temp / (fs_now / 1000); - end - - case 10 % NETEQ_DELAY_LOGGING_SIGNAL_PREEMPTIVE_INFO - temp = fread(fid, 1, 'int32'); - if last_decode_k ~= 0 - tot_preemptive = tot_preemptive + temp / (fs_now / 1000); - end - - case 11 % NETEQ_DELAY_LOGGING_SIGNAL_OPTBUF - optbuf(last_decode_k) = fread(fid, 1, 'int32'); - - case 12 % NETEQ_DELAY_LOGGING_SIGNAL_DECODE_ONE_DESC - last_decode_ts = fread(fid, 1, 'uint32'); - k = ts_ix - 1; - - while (k >= 1) && (rtpts(k) ~= last_decode_ts) - % TODO(hlundin): use matlab vector search instead? - k = k - 1; - end - - if k < 1 - % packet not received yet - k = ts_ix; - rtpts(ts_ix) = last_decode_ts; - late_packets = late_packets + 1; - end - - decode_t(k) = clock; - playout_delay(k) = fread(fid, 1, 'uint16') + ... - 5 * fs_now / 8000; % add overlap length - last_decode_k = k; - - end - -end - - -fclose(fid); - -outStruct = struct(... - 'ts', rtpts, ... - 'sn', seqno, ... - 'pt', pt,... - 'plen', plen,... - 'arrival', recin_t,... - 'decode', decode_t,... - 'fs', fsvec(:),... - 'fschange_ts', fschange_ts(:),... - 'playout_delay', playout_delay,... - 'tot_expand', tot_expand,... - 'tot_accelerate', tot_accelerate,... - 'tot_preemptive', tot_preemptive,... - 'optbuf', optbuf); diff --git a/webrtc/modules/audio_coding/neteq4/test/delay_tool/plot_neteq_delay.m b/webrtc/modules/audio_coding/neteq4/test/delay_tool/plot_neteq_delay.m deleted file mode 100644 index bc1c85a20..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/delay_tool/plot_neteq_delay.m +++ /dev/null @@ -1,187 +0,0 @@ -function [delay_struct, delayvalues] = plot_neteq_delay(delayfile, varargin) - -% InfoStruct = plot_neteq_delay(delayfile) -% InfoStruct = plot_neteq_delay(delayfile, 'skipdelay', skip_seconds) -% -% Henrik Lundin, 2006-11-17 -% Henrik Lundin, 2011-05-17 -% - -try - s = parse_delay_file(delayfile); -catch - error(lasterr); -end - -delayskip=0; -noplot=0; -arg_ptr=1; -delaypoints=[]; - -s.sn=unwrap_seqno(s.sn); - -while arg_ptr+1 <= nargin - switch lower(varargin{arg_ptr}) - case {'skipdelay', 'delayskip'} - % skip a number of seconds in the beginning when calculating delays - delayskip = varargin{arg_ptr+1}; - arg_ptr = arg_ptr + 2; - case 'noplot' - noplot=1; - arg_ptr = arg_ptr + 1; - case {'get_delay', 'getdelay'} - % return a vector of delay values for the points in the given vector - delaypoints = varargin{arg_ptr+1}; - arg_ptr = arg_ptr + 2; - otherwise - warning('Unknown switch %s\n', varargin{arg_ptr}); - arg_ptr = arg_ptr + 1; - end -end - -% find lost frames that were covered by one-descriptor decoding -one_desc_ix=find(isnan(s.arrival)); -for k=1:length(one_desc_ix) - ix=find(s.ts==max(s.ts(s.ts(one_desc_ix(k))>s.ts))); - s.sn(one_desc_ix(k))=s.sn(ix)+1; - s.pt(one_desc_ix(k))=s.pt(ix); - s.arrival(one_desc_ix(k))=s.arrival(ix)+s.decode(one_desc_ix(k))-s.decode(ix); -end - -% remove duplicate received frames that were never decoded (RED codec) -if length(unique(s.ts(isfinite(s.ts)))) < length(s.ts(isfinite(s.ts))) - ix=find(isfinite(s.decode)); - s.sn=s.sn(ix); - s.ts=s.ts(ix); - s.arrival=s.arrival(ix); - s.playout_delay=s.playout_delay(ix); - s.pt=s.pt(ix); - s.optbuf=s.optbuf(ix); - plen=plen(ix); - s.decode=s.decode(ix); -end - -% find non-unique sequence numbers -[~,un_ix]=unique(s.sn); -nonun_ix=setdiff(1:length(s.sn),un_ix); -if ~isempty(nonun_ix) - warning('RTP sequence numbers are in error'); -end - -% sort vectors -[s.sn,sort_ix]=sort(s.sn); -s.ts=s.ts(sort_ix); -s.arrival=s.arrival(sort_ix); -s.decode=s.decode(sort_ix); -s.playout_delay=s.playout_delay(sort_ix); -s.pt=s.pt(sort_ix); - -send_t=s.ts-s.ts(1); -if length(s.fs)<1 - warning('No info about sample rate found in file. Using default 8000.'); - s.fs(1)=8000; - s.fschange_ts(1)=min(s.ts); -elseif s.fschange_ts(1)>min(s.ts) - s.fschange_ts(1)=min(s.ts); -end - -end_ix=length(send_t); -for k=length(s.fs):-1:1 - start_ix=find(s.ts==s.fschange_ts(k)); - send_t(start_ix:end_ix)=send_t(start_ix:end_ix)/s.fs(k)*1000; - s.playout_delay(start_ix:end_ix)=s.playout_delay(start_ix:end_ix)/s.fs(k)*1000; - s.optbuf(start_ix:end_ix)=s.optbuf(start_ix:end_ix)/s.fs(k)*1000; - end_ix=start_ix-1; -end - -tot_time=max(send_t)-min(send_t); - -seq_ix=s.sn-min(s.sn)+1; -send_t=send_t+max(min(s.arrival-send_t),0); - -plot_send_t=nan*ones(max(seq_ix),1); -plot_send_t(seq_ix)=send_t; -plot_nw_delay=nan*ones(max(seq_ix),1); -plot_nw_delay(seq_ix)=s.arrival-send_t; - -cng_ix=find(s.pt~=13); % find those packets that are not CNG/SID - -if noplot==0 - h=plot(plot_send_t/1000,plot_nw_delay); - set(h,'color',0.75*[1 1 1]); - hold on - if any(s.optbuf~=0) - peak_ix=find(s.optbuf(cng_ix)<0); % peak mode is labeled with negative values - no_peak_ix=find(s.optbuf(cng_ix)>0); %setdiff(1:length(cng_ix),peak_ix); - h1=plot(send_t(cng_ix(peak_ix))/1000,... - s.arrival(cng_ix(peak_ix))+abs(s.optbuf(cng_ix(peak_ix)))-send_t(cng_ix(peak_ix)),... - 'r.'); - h2=plot(send_t(cng_ix(no_peak_ix))/1000,... - s.arrival(cng_ix(no_peak_ix))+abs(s.optbuf(cng_ix(no_peak_ix)))-send_t(cng_ix(no_peak_ix)),... - 'g.'); - set([h1, h2],'markersize',1) - end - %h=plot(send_t(seq_ix)/1000,s.decode+s.playout_delay-send_t(seq_ix)); - h=plot(send_t(cng_ix)/1000,s.decode(cng_ix)+s.playout_delay(cng_ix)-send_t(cng_ix)); - set(h,'linew',1.5); - hold off - ax1=axis; - axis tight - ax2=axis; - axis([ax2(1:3) ax1(4)]) -end - - -% calculate delays and other parameters - -delayskip_ix = find(send_t-send_t(1)>=delayskip*1000, 1 ); - -use_ix = intersect(cng_ix,... % use those that are not CNG/SID frames... - intersect(find(isfinite(s.decode)),... % ... that did arrive ... - (delayskip_ix:length(s.decode))')); % ... and are sent after delayskip seconds - -mean_delay = mean(s.decode(use_ix)+s.playout_delay(use_ix)-send_t(use_ix)); -neteq_delay = mean(s.decode(use_ix)+s.playout_delay(use_ix)-s.arrival(use_ix)); - -Npack=max(s.sn(delayskip_ix:end))-min(s.sn(delayskip_ix:end))+1; -nw_lossrate=(Npack-length(s.sn(delayskip_ix:end)))/Npack; -neteq_lossrate=(length(s.sn(delayskip_ix:end))-length(use_ix))/Npack; - -delay_struct=struct('mean_delay',mean_delay,'neteq_delay',neteq_delay,... - 'nw_lossrate',nw_lossrate,'neteq_lossrate',neteq_lossrate,... - 'tot_expand',round(s.tot_expand),'tot_accelerate',round(s.tot_accelerate),... - 'tot_preemptive',round(s.tot_preemptive),'tot_time',tot_time,... - 'filename',delayfile,'units','ms','fs',unique(s.fs)); - -if not(isempty(delaypoints)) - delayvalues=interp1(send_t(cng_ix),... - s.decode(cng_ix)+s.playout_delay(cng_ix)-send_t(cng_ix),... - delaypoints,'nearest',NaN); -else - delayvalues=[]; -end - - - -% SUBFUNCTIONS % - -function y=unwrap_seqno(x) - -jumps=find(abs((diff(x)-1))>65000); - -while ~isempty(jumps) - n=jumps(1); - if x(n+1)-x(n) < 0 - % negative jump - x(n+1:end)=x(n+1:end)+65536; - else - % positive jump - x(n+1:end)=x(n+1:end)-65536; - end - - jumps=find(abs((diff(x(n+1:end))-1))>65000); -end - -y=x; - -return; diff --git a/webrtc/modules/audio_coding/neteq4/test/neteq_speed_test.cc b/webrtc/modules/audio_coding/neteq4/test/neteq_speed_test.cc deleted file mode 100644 index cecd48b70..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/neteq_speed_test.cc +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2013 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 "gflags/gflags.h" -#include "webrtc/modules/audio_coding/neteq4/tools/neteq_performance_test.h" -#include "webrtc/typedefs.h" - -// Flag validators. -static bool ValidateRuntime(const char* flagname, int value) { - if (value > 0) // Value is ok. - return true; - printf("Invalid value for --%s: %d\n", flagname, static_cast(value)); - return false; -} -static bool ValidateLossrate(const char* flagname, int value) { - if (value >= 0) // Value is ok. - return true; - printf("Invalid value for --%s: %d\n", flagname, static_cast(value)); - return false; -} -static bool ValidateDriftfactor(const char* flagname, double value) { - if (value >= 0.0 && value < 1.0) // Value is ok. - return true; - printf("Invalid value for --%s: %f\n", flagname, value); - return false; -} - -// Define command line flags. -DEFINE_int32(runtime_ms, 10000, "Simulated runtime in ms."); -static const bool runtime_ms_dummy = - google::RegisterFlagValidator(&FLAGS_runtime_ms, &ValidateRuntime); -DEFINE_int32(lossrate, 10, - "Packet lossrate; drop every N packets."); -static const bool lossrate_dummy = - google::RegisterFlagValidator(&FLAGS_lossrate, &ValidateLossrate); -DEFINE_double(drift, 0.1, - "Clockdrift factor."); -static const bool drift_dummy = - google::RegisterFlagValidator(&FLAGS_drift, &ValidateDriftfactor); - -int main(int argc, char* argv[]) { - std::string program_name = argv[0]; - std::string usage = "Tool for measuring the speed of NetEq.\n" - "Usage: " + program_name + " [options]\n\n" - " --runtime_ms=N runtime in ms; default is 10000 ms\n" - " --lossrate=N drop every N packets; default is 10\n" - " --drift=F clockdrift factor between 0.0 and 1.0; " - "default is 0.1\n"; - google::SetUsageMessage(usage); - google::ParseCommandLineFlags(&argc, &argv, true); - - if (argc != 1) { - // Print usage information. - std::cout << google::ProgramUsage(); - return 0; - } - - int64_t result = - webrtc::test::NetEqPerformanceTest::Run(FLAGS_runtime_ms, FLAGS_lossrate, - FLAGS_drift); - if (result <= 0) { - std::cout << "There was an error" << std::endl; - return -1; - } - - std::cout << "Simulation done" << std::endl; - std::cout << "Runtime = " << result << " ms" << std::endl; - return 0; -} diff --git a/webrtc/modules/audio_coding/neteq4/test/rtp_to_text.cc b/webrtc/modules/audio_coding/neteq4/test/rtp_to_text.cc deleted file mode 100644 index 1112d79c8..000000000 --- a/webrtc/modules/audio_coding/neteq4/test/rtp_to_text.cc +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2012 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. - */ - -/* - * Parses an rtpdump file and outputs a text table parsable by parseLog.m. - * The output file will have .txt appended to the specified base name. - * $ rtp_to_text [-d] - * - * -d RTP headers only - * - */ - -#include "data_log.h" -#include "NETEQTEST_DummyRTPpacket.h" -#include "NETEQTEST_RTPpacket.h" - -#include -#include - -#include -#include -#include - -/*********************/ -/* Misc. definitions */ -/*********************/ - -#define FIRSTLINELEN 40 - -using ::webrtc::DataLog; - -int main(int argc, char* argv[]) -{ - int arg_count = 1; - NETEQTEST_RTPpacket* packet; - - if (argc < 3) - { - printf("Usage: %s [-d] \n", argv[0]); - return -1; - } - - // Parse dummy option - if (argc >= 3 && strcmp(argv[arg_count], "-d") == 0) - { - packet = new NETEQTEST_DummyRTPpacket; - ++arg_count; - } - else - { - packet = new NETEQTEST_RTPpacket; - } - - std::string input_filename = argv[arg_count++]; - std::string table_name = argv[arg_count]; - - std::cout << "Input file: " << input_filename << std::endl; - std::cout << "Output file: " << table_name << ".txt" << std::endl; - - FILE *inFile=fopen(input_filename.c_str(),"rb"); - if (!inFile) - { - std::cout << "Cannot open input file " << input_filename << std::endl; - return -1; - } - - // Set up the DataLog and define the table - DataLog::CreateLog(); - if (DataLog::AddTable(table_name) < 0) - { - std::cout << "Error adding table " << table_name << ".txt" << std::endl; - return -1; - } - - DataLog::AddColumn(table_name, "seq", 1); - DataLog::AddColumn(table_name, "ssrc", 1); - DataLog::AddColumn(table_name, "payload type", 1); - DataLog::AddColumn(table_name, "length", 1); - DataLog::AddColumn(table_name, "timestamp", 1); - DataLog::AddColumn(table_name, "marker bit", 1); - DataLog::AddColumn(table_name, "arrival", 1); - - // read file header - char firstline[FIRSTLINELEN]; - if (fgets(firstline, FIRSTLINELEN, inFile) == NULL) - { - std::cout << "Error reading file " << input_filename << std::endl; - return -1; - } - - // start_sec + start_usec + source + port + padding - if (fread(firstline, 4+4+4+2+2, 1, inFile) != 1) - { - std::cout << "Error reading file " << input_filename << std::endl; - return -1; - } - - while (packet->readFromFile(inFile) >= 0) - { - // write packet headers to - DataLog::InsertCell(table_name, "seq", packet->sequenceNumber()); - DataLog::InsertCell(table_name, "ssrc", packet->SSRC()); - DataLog::InsertCell(table_name, "payload type", packet->payloadType()); - DataLog::InsertCell(table_name, "length", packet->dataLen()); - DataLog::InsertCell(table_name, "timestamp", packet->timeStamp()); - DataLog::InsertCell(table_name, "marker bit", packet->markerBit()); - DataLog::InsertCell(table_name, "arrival", packet->time()); - DataLog::NextRow(table_name); - return -1; - } - - DataLog::ReturnLog(); - - fclose(inFile); - - return 0; -} diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc deleted file mode 100644 index c56e5b98e..000000000 --- a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2014 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 "webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h" - -namespace webrtc { -namespace test { - -const uint8_t kPayloadType = 95; -const int kOutputSizeMs = 10; - -NetEqQualityTest::NetEqQualityTest(int block_duration_ms, - int in_sampling_khz, - int out_sampling_khz, - enum NetEqDecoder decoder_type, - int channels, - double drift_factor, - std::string in_filename, - std::string out_filename) - : decoded_time_ms_(0), - decodable_time_ms_(0), - drift_factor_(drift_factor), - block_duration_ms_(block_duration_ms), - in_sampling_khz_(in_sampling_khz), - out_sampling_khz_(out_sampling_khz), - decoder_type_(decoder_type), - channels_(channels), - in_filename_(in_filename), - out_filename_(out_filename), - in_size_samples_(in_sampling_khz_ * block_duration_ms_), - out_size_samples_(out_sampling_khz_ * kOutputSizeMs), - payload_size_bytes_(0), - max_payload_bytes_(0), - in_file_(new InputAudioFile(in_filename_)), - out_file_(NULL), - rtp_generator_(new RtpGenerator(in_sampling_khz_, 0, 0, - decodable_time_ms_)), - neteq_(NetEq::Create(out_sampling_khz_ * 1000)) { - max_payload_bytes_ = in_size_samples_ * channels_ * sizeof(int16_t); - in_data_.reset(new int16_t[in_size_samples_ * channels_]); - payload_.reset(new uint8_t[max_payload_bytes_]); - out_data_.reset(new int16_t[out_size_samples_ * channels_]); -} - -void NetEqQualityTest::SetUp() { - out_file_ = fopen(out_filename_.c_str(), "wb"); - ASSERT_TRUE(out_file_ != NULL); - ASSERT_EQ(0, neteq_->RegisterPayloadType(decoder_type_, kPayloadType)); - rtp_generator_->set_drift_factor(drift_factor_); -} - -void NetEqQualityTest::TearDown() { - fclose(out_file_); -} - -int NetEqQualityTest::Transmit() { - int packet_input_time_ms = - rtp_generator_->GetRtpHeader(kPayloadType, in_size_samples_, - &rtp_header_); - if (!PacketLost(packet_input_time_ms) && payload_size_bytes_ > 0) { - int ret = neteq_->InsertPacket(rtp_header_, &payload_[0], - payload_size_bytes_, - packet_input_time_ms * in_sampling_khz_); - if (ret != NetEq::kOK) - return -1; - } - return packet_input_time_ms; -} - -int NetEqQualityTest::DecodeBlock() { - int channels; - int samples; - int ret = neteq_->GetAudio(out_size_samples_ * channels_, &out_data_[0], - &samples, &channels, NULL); - - if (ret != NetEq::kOK) { - return -1; - } else { - assert(channels == channels_); - assert(samples == kOutputSizeMs * out_sampling_khz_); - fwrite(&out_data_[0], sizeof(int16_t), samples * channels, out_file_); - return samples; - } -} - -void NetEqQualityTest::Simulate(int end_time_ms) { - int audio_size_samples; - - while (decoded_time_ms_ < end_time_ms) { - while (decodable_time_ms_ - kOutputSizeMs < decoded_time_ms_) { - ASSERT_TRUE(in_file_->Read(in_size_samples_ * channels_, &in_data_[0])); - payload_size_bytes_ = EncodeBlock(&in_data_[0], - in_size_samples_, &payload_[0], - max_payload_bytes_); - decodable_time_ms_ = Transmit() + block_duration_ms_; - } - audio_size_samples = DecodeBlock(); - if (audio_size_samples > 0) { - decoded_time_ms_ += audio_size_samples / out_sampling_khz_; - } - } -} - -} // namespace test -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/tools/rtp_analyze.cc b/webrtc/modules/audio_coding/neteq4/tools/rtp_analyze.cc deleted file mode 100644 index 63786ec56..000000000 --- a/webrtc/modules/audio_coding/neteq4/tools/rtp_analyze.cc +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2012 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 "google/gflags.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h" - -// Flag validator. -static bool ValidatePayloadType(const char* flagname, int32_t value) { - if (value >= 0 && value <= 127) // Value is ok. - return true; - printf("Invalid value for --%s: %d\n", flagname, static_cast(value)); - return false; -} - -// Define command line flags. -DEFINE_int32(red, 117, "RTP payload type for RED"); -static const bool pcmu_dummy = - google::RegisterFlagValidator(&FLAGS_red, &ValidatePayloadType); - -int main(int argc, char* argv[]) { - std::string program_name = argv[0]; - std::string usage = - "Tool for parsing an RTP dump file to text output.\n" - "Run " + - program_name + - " --helpshort for usage.\n" - "Example usage:\n" + - program_name + " input.rtp output.txt\n\n" + - "Output is sent to stdout if no output file is given." + - "Note that this tool can read files with our without payloads."; - google::SetUsageMessage(usage); - google::ParseCommandLineFlags(&argc, &argv, true); - - if (argc != 2 && argc != 3) { - // Print usage information. - printf("%s", google::ProgramUsage()); - return 0; - } - - FILE* in_file = fopen(argv[1], "rb"); - if (!in_file) { - printf("Cannot open input file %s\n", argv[1]); - return -1; - } - printf("Input file: %s\n", argv[1]); - - FILE* out_file; - if (argc == 3) { - out_file = fopen(argv[2], "wt"); - if (!out_file) { - printf("Cannot open output file %s\n", argv[2]); - return -1; - } - printf("Output file: %s\n\n", argv[2]); - } else { - out_file = stdout; - } - - // Print file header. - fprintf(out_file, "SeqNo TimeStamp SendTime Size PT M SSRC\n"); - - // Read file header. - NETEQTEST_RTPpacket::skipFileHeader(in_file); - NETEQTEST_RTPpacket packet; - - while (packet.readFromFile(in_file) >= 0) { - // Write packet data to file. - fprintf(out_file, - "%5u %10u %10u %5i %5i %2i %#08X\n", - packet.sequenceNumber(), - packet.timeStamp(), - packet.time(), - packet.dataLen(), - packet.payloadType(), - packet.markerBit(), - packet.SSRC()); - if (packet.payloadType() == FLAGS_red) { - webrtc::WebRtcRTPHeader red_header; - int len; - int red_index = 0; - while ((len = packet.extractRED(red_index++, red_header)) >= 0) { - fprintf(out_file, - "* %5u %10u %10u %5i %5i\n", - red_header.header.sequenceNumber, - red_header.header.timestamp, - packet.time(), - len, - red_header.header.payloadType); - } - assert(red_index > 1); // We must get at least one payload. - } - } - - fclose(in_file); - fclose(out_file); - - return 0; -} diff --git a/webrtc/modules/audio_conference_mixer/BUILD.gn b/webrtc/modules/audio_conference_mixer/BUILD.gn new file mode 100644 index 000000000..a27bb84c6 --- /dev/null +++ b/webrtc/modules/audio_conference_mixer/BUILD.gn @@ -0,0 +1,41 @@ +# Copyright (c) 2014 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. + +config("internal_config") { + visibility = ":*" # Only targets in this file can depend on this. + include_dirs = [ + "interface", + "../interface", + ] +} + +source_set("audio_conference_mixer") { + sources = [ + "interface/audio_conference_mixer.h", + "interface/audio_conference_mixer_defines.h", + "source/audio_conference_mixer_impl.cc", + "source/audio_conference_mixer_impl.h", + "source/audio_frame_manipulator.cc", + "source/audio_frame_manipulator.h", + "source/level_indicator.cc", + "source/level_indicator.h", + "source/memory_pool.h", + "source/memory_pool_posix.h", + "source/memory_pool_win.h", + "source/time_scheduler.cc", + "source/time_scheduler.h", + ] + + direct_dependent_configs = [ ":internal_config" ] + + deps = [ + "../../system_wrappers", + "../audio_processing", + "../utility", + ] +} diff --git a/webrtc/modules/audio_conference_mixer/OWNERS b/webrtc/modules/audio_conference_mixer/OWNERS index 7dc791ef9..7d4cc6108 100644 --- a/webrtc/modules/audio_conference_mixer/OWNERS +++ b/webrtc/modules/audio_conference_mixer/OWNERS @@ -1,3 +1,5 @@ henrike@webrtc.org pwestin@webrtc.org -andrew@webrtc.org \ No newline at end of file +andrew@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/audio_conference_mixer/source/OWNERS b/webrtc/modules/audio_conference_mixer/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_conference_mixer/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_conference_mixer/source/audio_conference_mixer_impl.cc b/webrtc/modules/audio_conference_mixer/source/audio_conference_mixer_impl.cc index f3883c0b5..26ef3e881 100644 --- a/webrtc/modules/audio_conference_mixer/source/audio_conference_mixer_impl.cc +++ b/webrtc/modules/audio_conference_mixer/source/audio_conference_mixer_impl.cc @@ -651,6 +651,11 @@ void AudioConferenceMixerImpl::UpdateToMix( _audioFramePool->PushMemory(audioFrame); continue; } + if (_participantList.size() != 1) { + // TODO(wu): Issue 3390, add support for multiple participants case. + audioFrame->ntp_time_ms_ = -1; + } + // TODO(henrike): this assert triggers in some test cases where SRTP is // used which prevents NetEQ from making a VAD. Temporarily disable this // assert until the problem is fixed on a higher level. @@ -950,6 +955,16 @@ int32_t AudioConferenceMixerImpl::MixFromList( return 0; } + if (audioFrameList->size() == 1) { + mixedAudio.timestamp_ = audioFrameList->front()->timestamp_; + mixedAudio.elapsed_time_ms_ = audioFrameList->front()->elapsed_time_ms_; + } else { + // TODO(wu): Issue 3390. + // Audio frame timestamp is only supported in one channel case. + mixedAudio.timestamp_ = 0; + mixedAudio.elapsed_time_ms_ = -1; + } + for (AudioFrameList::const_iterator iter = audioFrameList->begin(); iter != audioFrameList->end(); ++iter) { diff --git a/webrtc/modules/audio_conference_mixer/source/audio_frame_manipulator.cc b/webrtc/modules/audio_conference_mixer/source/audio_frame_manipulator.cc index 679d608f5..3dce5c8be 100644 --- a/webrtc/modules/audio_conference_mixer/source/audio_frame_manipulator.cc +++ b/webrtc/modules/audio_conference_mixer/source/audio_frame_manipulator.cc @@ -41,10 +41,6 @@ const int rampSize = sizeof(rampArray)/sizeof(rampArray[0]); namespace webrtc { void CalculateEnergy(AudioFrame& audioFrame) { - if(audioFrame.energy_ != 0xffffffff) - { - return; - } audioFrame.energy_ = 0; for(int position = 0; position < audioFrame.samples_per_channel_; position++) diff --git a/webrtc/modules/audio_device/Android.mk b/webrtc/modules/audio_device/Android.mk index affa5e1c2..4b3b9124d 100644 --- a/webrtc/modules/audio_device/Android.mk +++ b/webrtc/modules/audio_device/Android.mk @@ -25,7 +25,8 @@ LOCAL_SRC_FILES := \ android/audio_device_android_opensles.cc \ android/audio_device_utility_android.cc \ dummy/audio_device_utility_dummy.cc \ - dummy/audio_device_dummy.cc + dummy/audio_device_dummy.cc \ + dummy/file_audio_device.cc # Flags passed to both C and C++ files. LOCAL_CFLAGS := \ diff --git a/webrtc/modules/audio_device/BUILD.gn b/webrtc/modules/audio_device/BUILD.gn new file mode 100644 index 000000000..d6481ad52 --- /dev/null +++ b/webrtc/modules/audio_device/BUILD.gn @@ -0,0 +1,13 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("audio_device") { + # TODO(henrike): Implement. +} diff --git a/webrtc/modules/audio_device/OWNERS b/webrtc/modules/audio_device/OWNERS index a07ced37b..1fe45940b 100644 --- a/webrtc/modules/audio_device/OWNERS +++ b/webrtc/modules/audio_device/OWNERS @@ -2,3 +2,12 @@ henrikg@webrtc.org henrika@webrtc.org niklas.enbom@webrtc.org xians@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/audio_device/android/fine_audio_buffer.h b/webrtc/modules/audio_device/android/fine_audio_buffer.h index 597b8aaa3..e577b72fd 100644 --- a/webrtc/modules/audio_device/android/fine_audio_buffer.h +++ b/webrtc/modules/audio_device/android/fine_audio_buffer.h @@ -56,7 +56,7 @@ class FineAudioBuffer { int bytes_per_10_ms_; // Storage for samples that are not yet asked for. - scoped_array cache_buffer_; + scoped_ptr cache_buffer_; int cached_buffer_start_; // Location of first unread sample. int cached_bytes_; // Number of bytes stored in cache. }; diff --git a/webrtc/modules/audio_device/android/fine_audio_buffer_unittest.cc b/webrtc/modules/audio_device/android/fine_audio_buffer_unittest.cc index 69ba741d1..e1f03f8f3 100644 --- a/webrtc/modules/audio_device/android/fine_audio_buffer_unittest.cc +++ b/webrtc/modules/audio_device/android/fine_audio_buffer_unittest.cc @@ -80,7 +80,7 @@ void RunFineBufferTest(int sample_rate, int frame_size_in_samples) { FineAudioBuffer fine_buffer(&audio_device_buffer, kFrameSizeBytes, sample_rate); - scoped_array out_buffer; + scoped_ptr out_buffer; out_buffer.reset( new int8_t[fine_buffer.RequiredBufferSizeBytes()]); for (int i = 0; i < kNumberOfFrames; ++i) { diff --git a/webrtc/modules/audio_device/android/opensles_input.cc b/webrtc/modules/audio_device/android/opensles_input.cc index 6b600c9fd..f22d8bf7e 100644 --- a/webrtc/modules/audio_device/android/opensles_input.cc +++ b/webrtc/modules/audio_device/android/opensles_input.cc @@ -289,7 +289,7 @@ void OpenSlesInput::AllocateBuffers() { fifo_.reset(new SingleRwFifo(num_fifo_buffers_needed_)); // Allocate the memory area to be used. - rec_buf_.reset(new scoped_array[TotalBuffersUsed()]); + rec_buf_.reset(new scoped_ptr[TotalBuffersUsed()]); for (int i = 0; i < TotalBuffersUsed(); ++i) { rec_buf_[i].reset(new int8_t[buffer_size_bytes()]); } diff --git a/webrtc/modules/audio_device/android/opensles_input.h b/webrtc/modules/audio_device/android/opensles_input.h index 48e4fd2df..d27d82435 100644 --- a/webrtc/modules/audio_device/android/opensles_input.h +++ b/webrtc/modules/audio_device/android/opensles_input.h @@ -205,7 +205,7 @@ class OpenSlesInput { // Audio buffers AudioDeviceBuffer* audio_buffer_; // Holds all allocated memory such that it is deallocated properly. - scoped_array > rec_buf_; + scoped_ptr[]> rec_buf_; // Index in |rec_buf_| pointing to the audio buffer that will be ready the // next time RecorderSimpleBufferQueueCallbackHandler is invoked. // Ready means buffer contains audio data from the device. diff --git a/webrtc/modules/audio_device/android/opensles_output.cc b/webrtc/modules/audio_device/android/opensles_output.cc index 6185b2ad2..377789b23 100644 --- a/webrtc/modules/audio_device/android/opensles_output.cc +++ b/webrtc/modules/audio_device/android/opensles_output.cc @@ -340,7 +340,7 @@ void OpenSlesOutput::AllocateBuffers() { fifo_.reset(new SingleRwFifo(num_fifo_buffers_needed_)); // Allocate the memory area to be used. - play_buf_.reset(new scoped_array[TotalBuffersUsed()]); + play_buf_.reset(new scoped_ptr[TotalBuffersUsed()]); int required_buffer_size = fine_buffer_->RequiredBufferSizeBytes(); for (int i = 0; i < TotalBuffersUsed(); ++i) { play_buf_[i].reset(new int8_t[required_buffer_size]); diff --git a/webrtc/modules/audio_device/android/opensles_output.h b/webrtc/modules/audio_device/android/opensles_output.h index 464a7e41a..aa9b5bf12 100644 --- a/webrtc/modules/audio_device/android/opensles_output.h +++ b/webrtc/modules/audio_device/android/opensles_output.h @@ -223,7 +223,7 @@ class OpenSlesOutput : public PlayoutDelayProvider { // Audio buffers AudioDeviceBuffer* audio_buffer_; scoped_ptr fine_buffer_; - scoped_array > play_buf_; + scoped_ptr[]> play_buf_; // Index in |rec_buf_| pointing to the audio buffer that will be ready the // next time PlayerSimpleBufferQueueCallbackHandler is invoked. // Ready means buffer is ready to be played out to device. diff --git a/webrtc/modules/audio_device/android/single_rw_fifo.cc b/webrtc/modules/audio_device/android/single_rw_fifo.cc index d65ab9fbb..73d4d61dd 100644 --- a/webrtc/modules/audio_device/android/single_rw_fifo.cc +++ b/webrtc/modules/audio_device/android/single_rw_fifo.cc @@ -10,6 +10,8 @@ #include "webrtc/modules/audio_device/android/single_rw_fifo.h" +#include + static int UpdatePos(int pos, int capacity) { return (pos + 1) % capacity; } @@ -18,14 +20,16 @@ namespace webrtc { namespace subtle { -#if defined(__ARMEL__) +#if defined(__aarch64__) +// From http://http://src.chromium.org/viewvc/chrome/trunk/src/base/atomicops_internals_arm64_gcc.h +inline void MemoryBarrier() { + __asm__ __volatile__ ("dmb ish" ::: "memory"); +} + +#elif defined(__ARMEL__) // From http://src.chromium.org/viewvc/chrome/trunk/src/base/atomicops_internals_arm_gcc.h -// Note that it is only the MemoryBarrier function that makes this class arm -// specific. Borrowing other MemoryBarrier implementations, this class could -// be extended to more platforms. inline void MemoryBarrier() { - // Note: This is a function call, which is also an implicit compiler - // barrier. + // Note: This is a function call, which is also an implicit compiler barrier. typedef void (*KernelMemoryBarrierFunc)(); ((KernelMemoryBarrierFunc)0xffff0fa0)(); } diff --git a/webrtc/modules/audio_device/android/single_rw_fifo.h b/webrtc/modules/audio_device/android/single_rw_fifo.h index a1fcfaab4..092b1d5e0 100644 --- a/webrtc/modules/audio_device/android/single_rw_fifo.h +++ b/webrtc/modules/audio_device/android/single_rw_fifo.h @@ -35,7 +35,7 @@ class SingleRwFifo { int capacity() const { return capacity_; } private: - scoped_array queue_; + scoped_ptr queue_; int capacity_; Atomic32 size_; diff --git a/webrtc/modules/audio_device/android/single_rw_fifo_unittest.cc b/webrtc/modules/audio_device/android/single_rw_fifo_unittest.cc index c722c2756..9925baaa8 100644 --- a/webrtc/modules/audio_device/android/single_rw_fifo_unittest.cc +++ b/webrtc/modules/audio_device/android/single_rw_fifo_unittest.cc @@ -90,7 +90,7 @@ class SingleRwFifoTest : public testing::Test { protected: SingleRwFifo fifo_; // Memory area for proper de-allocation. - scoped_array buffer_[kCapacity]; + scoped_ptr buffer_[kCapacity]; std::list memory_queue_; int pushed_; diff --git a/webrtc/modules/audio_device/audio_device.gypi b/webrtc/modules/audio_device/audio_device.gypi index 944f4222d..a64856b5d 100644 --- a/webrtc/modules/audio_device/audio_device.gypi +++ b/webrtc/modules/audio_device/audio_device.gypi @@ -20,7 +20,7 @@ '.', '../interface', 'include', - 'dummy', # dummy audio device + 'dummy', # Contains dummy audio device implementations. ], 'direct_dependent_settings': { 'include_dirs': [ @@ -45,6 +45,8 @@ 'dummy/audio_device_dummy.h', 'dummy/audio_device_utility_dummy.cc', 'dummy/audio_device_utility_dummy.h', + 'dummy/file_audio_device.cc', + 'dummy/file_audio_device.h', ], 'conditions': [ ['OS=="linux"', { @@ -77,6 +79,13 @@ 'WEBRTC_DUMMY_AUDIO_BUILD', ], }], + ['build_with_chromium==0', { + 'sources': [ + # Don't link these into Chrome since they contain static data. + 'dummy/file_audio_device_factory.cc', + 'dummy/file_audio_device_factory.h', + ], + }], ['include_internal_audio_device==1', { 'sources': [ 'linux/alsasymboltable_linux.cc', diff --git a/webrtc/modules/audio_device/audio_device_buffer.cc b/webrtc/modules/audio_device/audio_device_buffer.cc index db5cc322f..42fdaad22 100644 --- a/webrtc/modules/audio_device/audio_device_buffer.cc +++ b/webrtc/modules/audio_device/audio_device_buffer.cc @@ -548,13 +548,16 @@ int32_t AudioDeviceBuffer::RequestPlayoutData(uint32_t nSamples) if (_ptrCbAudioTransport) { uint32_t res(0); - + int64_t elapsed_time_ms = -1; + int64_t ntp_time_ms = -1; res = _ptrCbAudioTransport->NeedMorePlayData(_playSamples, playBytesPerSample, playChannels, playSampleRate, &_playBuffer[0], - nSamplesOut); + nSamplesOut, + &elapsed_time_ms, + &ntp_time_ms); if (res != 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "NeedMorePlayData() failed"); diff --git a/webrtc/modules/audio_device/audio_device_impl.cc b/webrtc/modules/audio_device/audio_device_impl.cc index a2e5cba78..58411e3b9 100644 --- a/webrtc/modules/audio_device/audio_device_impl.cc +++ b/webrtc/modules/audio_device/audio_device_impl.cc @@ -45,8 +45,14 @@ #include "audio_device_utility_mac.h" #include "audio_device_mac.h" #endif + +#if defined(WEBRTC_DUMMY_FILE_DEVICES) +#include "webrtc/modules/audio_device/dummy/file_audio_device_factory.h" +#endif + #include "webrtc/modules/audio_device/dummy/audio_device_dummy.h" #include "webrtc/modules/audio_device/dummy/audio_device_utility_dummy.h" +#include "webrtc/modules/audio_device/dummy/file_audio_device.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/trace.h" @@ -203,6 +209,14 @@ int32_t AudioDeviceModuleImpl::CreatePlatformSpecificObjects() { ptrAudioDeviceUtility = new AudioDeviceUtilityDummy(Id()); } +#elif defined(WEBRTC_DUMMY_FILE_DEVICES) + ptrAudioDevice = FileAudioDeviceFactory::CreateFileAudioDevice(Id()); + WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, + "Will use file-playing dummy device."); + if (ptrAudioDevice != NULL) + { + ptrAudioDeviceUtility = new AudioDeviceUtilityDummy(Id()); + } #else const AudioLayer audioLayer(PlatformAudioLayer()); diff --git a/webrtc/modules/audio_device/audio_device_tests.isolate b/webrtc/modules/audio_device/audio_device_tests.isolate index 69e877c14..ebe8bfb40 100644 --- a/webrtc/modules/audio_device/audio_device_tests.isolate +++ b/webrtc/modules/audio_device/audio_device_tests.isolate @@ -8,27 +8,25 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../../data/', - '../../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/audio_device_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/audio_device_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/modules/audio_device/dummy/file_audio_device.cc b/webrtc/modules/audio_device/dummy/file_audio_device.cc new file mode 100644 index 000000000..e7771c66d --- /dev/null +++ b/webrtc/modules/audio_device/dummy/file_audio_device.cc @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_device/dummy/file_audio_device.h" +#include "webrtc/system_wrappers/interface/sleep.h" +#include "webrtc/system_wrappers/interface/thread_wrapper.h" + +namespace webrtc { + +int kRecordingFixedSampleRate = 48000; +int kRecordingNumChannels = 2; +int kPlayoutFixedSampleRate = 48000; +int kPlayoutNumChannels = 2; +int kPlayoutBufferSize = kPlayoutFixedSampleRate / 100 + * kPlayoutNumChannels * 2; +int kRecordingBufferSize = kRecordingFixedSampleRate / 100 + * kRecordingNumChannels * 2; + +FileAudioDevice::FileAudioDevice(const int32_t id, + const char* inputFilename, + const char* outputFile): + _ptrAudioBuffer(NULL), + _recordingBuffer(NULL), + _playoutBuffer(NULL), + _recordingFramesLeft(0), + _playoutFramesLeft(0), + _critSect(*CriticalSectionWrapper::CreateCriticalSection()), + _recordingBufferSizeIn10MS(0), + _recordingFramesIn10MS(0), + _playoutFramesIn10MS(0), + _ptrThreadRec(NULL), + _ptrThreadPlay(NULL), + _recThreadID(0), + _playThreadID(0), + _playing(false), + _recording(false), + _lastCallPlayoutMillis(0), + _lastCallRecordMillis(0), + _outputFile(*FileWrapper::Create()), + _inputFile(*FileWrapper::Create()), + _outputFilename(outputFile), + _inputFilename(inputFilename), + _clock(Clock::GetRealTimeClock()) { +} + +FileAudioDevice::~FileAudioDevice() { + _outputFile.Flush(); + _outputFile.CloseFile(); + delete &_outputFile; + _inputFile.Flush(); + _inputFile.CloseFile(); + delete &_inputFile; +} + +int32_t FileAudioDevice::ActiveAudioLayer( + AudioDeviceModule::AudioLayer& audioLayer) const { + return -1; +} + +int32_t FileAudioDevice::Init() { return 0; } + +int32_t FileAudioDevice::Terminate() { return 0; } + +bool FileAudioDevice::Initialized() const { return true; } + +int16_t FileAudioDevice::PlayoutDevices() { + return 1; +} + +int16_t FileAudioDevice::RecordingDevices() { + return 1; +} + +int32_t FileAudioDevice::PlayoutDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) { + const char* kName = "dummy_device"; + const char* kGuid = "dummy_device_unique_id"; + if (index < 1) { + memset(name, 0, kAdmMaxDeviceNameSize); + memset(guid, 0, kAdmMaxGuidSize); + memcpy(name, kName, strlen(kName)); + memcpy(guid, kGuid, strlen(guid)); + return 0; + } + return -1; +} + +int32_t FileAudioDevice::RecordingDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) { + const char* kName = "dummy_device"; + const char* kGuid = "dummy_device_unique_id"; + if (index < 1) { + memset(name, 0, kAdmMaxDeviceNameSize); + memset(guid, 0, kAdmMaxGuidSize); + memcpy(name, kName, strlen(kName)); + memcpy(guid, kGuid, strlen(guid)); + return 0; + } + return -1; +} + +int32_t FileAudioDevice::SetPlayoutDevice(uint16_t index) { + if (index == 0) { + _playout_index = index; + return 0; + } + return -1; +} + +int32_t FileAudioDevice::SetPlayoutDevice( + AudioDeviceModule::WindowsDeviceType device) { + return -1; +} + +int32_t FileAudioDevice::SetRecordingDevice(uint16_t index) { + if (index == 0) { + _record_index = index; + return _record_index; + } + return -1; +} + +int32_t FileAudioDevice::SetRecordingDevice( + AudioDeviceModule::WindowsDeviceType device) { + return -1; +} + +int32_t FileAudioDevice::PlayoutIsAvailable(bool& available) { + if (_playout_index == 0) { + available = true; + return _playout_index; + } + available = false; + return -1; +} + +int32_t FileAudioDevice::InitPlayout() { + if (_ptrAudioBuffer) + { + // Update webrtc audio buffer with the selected parameters + _ptrAudioBuffer->SetPlayoutSampleRate(kPlayoutFixedSampleRate); + _ptrAudioBuffer->SetPlayoutChannels(kPlayoutNumChannels); + } + return 0; +} + +bool FileAudioDevice::PlayoutIsInitialized() const { + return true; +} + +int32_t FileAudioDevice::RecordingIsAvailable(bool& available) { + if (_record_index == 0) { + available = true; + return _record_index; + } + available = false; + return -1; +} + +int32_t FileAudioDevice::InitRecording() { + CriticalSectionScoped lock(&_critSect); + + if (_recording) { + return -1; + } + + _recordingFramesIn10MS = kRecordingFixedSampleRate/100; + + if (_ptrAudioBuffer) { + _ptrAudioBuffer->SetRecordingSampleRate(kRecordingFixedSampleRate); + _ptrAudioBuffer->SetRecordingChannels(kRecordingNumChannels); + } + return 0; +} + +bool FileAudioDevice::RecordingIsInitialized() const { + return true; +} + +int32_t FileAudioDevice::StartPlayout() { + if (_playing) + { + return 0; + } + + _playing = true; + _playoutFramesLeft = 0; + + if (!_playoutBuffer) + _playoutBuffer = new int8_t[2 * + kPlayoutNumChannels * + kPlayoutFixedSampleRate/100]; + if (!_playoutBuffer) + { + _playing = false; + return -1; + } + + // PLAYOUT + const char* threadName = "webrtc_audio_module_play_thread"; + _ptrThreadPlay = ThreadWrapper::CreateThread(PlayThreadFunc, + this, + kRealtimePriority, + threadName); + if (_ptrThreadPlay == NULL) + { + _playing = false; + delete [] _playoutBuffer; + _playoutBuffer = NULL; + return -1; + } + + if (_outputFile.OpenFile(_outputFilename.c_str(), + false, false, false) == -1) { + printf("Failed to open playout file %s!", _outputFilename.c_str()); + _playing = false; + delete [] _playoutBuffer; + _playoutBuffer = NULL; + return -1; + } + + unsigned int threadID(0); + if (!_ptrThreadPlay->Start(threadID)) + { + _playing = false; + delete _ptrThreadPlay; + _ptrThreadPlay = NULL; + delete [] _playoutBuffer; + _playoutBuffer = NULL; + return -1; + } + _playThreadID = threadID; + + return 0; +} + +int32_t FileAudioDevice::StopPlayout() { + { + CriticalSectionScoped lock(&_critSect); + _playing = false; + } + + // stop playout thread first + if (_ptrThreadPlay && !_ptrThreadPlay->Stop()) + { + return -1; + } + else { + delete _ptrThreadPlay; + _ptrThreadPlay = NULL; + } + + CriticalSectionScoped lock(&_critSect); + + _playoutFramesLeft = 0; + delete [] _playoutBuffer; + _playoutBuffer = NULL; + _outputFile.Flush(); + _outputFile.CloseFile(); + return 0; +} + +bool FileAudioDevice::Playing() const { + return true; +} + +int32_t FileAudioDevice::StartRecording() { + _recording = true; + + // Make sure we only create the buffer once. + _recordingBufferSizeIn10MS = _recordingFramesIn10MS * + kRecordingNumChannels * + 2; + if (!_recordingBuffer) { + _recordingBuffer = new int8_t[_recordingBufferSizeIn10MS]; + } + + if (_inputFile.OpenFile(_inputFilename.c_str(), true, + true, false) == -1) { + printf("Failed to open audio input file %s!\n", + _inputFilename.c_str()); + _recording = false; + delete[] _recordingBuffer; + _recordingBuffer = NULL; + return -1; + } + + const char* threadName = "webrtc_audio_module_capture_thread"; + _ptrThreadRec = ThreadWrapper::CreateThread(RecThreadFunc, + this, + kRealtimePriority, + threadName); + if (_ptrThreadRec == NULL) + { + _recording = false; + delete [] _recordingBuffer; + _recordingBuffer = NULL; + return -1; + } + + unsigned int threadID(0); + if (!_ptrThreadRec->Start(threadID)) + { + _recording = false; + delete _ptrThreadRec; + _ptrThreadRec = NULL; + delete [] _recordingBuffer; + _recordingBuffer = NULL; + return -1; + } + _recThreadID = threadID; + + return 0; +} + + +int32_t FileAudioDevice::StopRecording() { + { + CriticalSectionScoped lock(&_critSect); + _recording = false; + } + + if (_ptrThreadRec && !_ptrThreadRec->Stop()) + { + return -1; + } + else { + delete _ptrThreadRec; + _ptrThreadRec = NULL; + } + + CriticalSectionScoped lock(&_critSect); + _recordingFramesLeft = 0; + if (_recordingBuffer) + { + delete [] _recordingBuffer; + _recordingBuffer = NULL; + } + return 0; +} + +bool FileAudioDevice::Recording() const { + return _recording; +} + +int32_t FileAudioDevice::SetAGC(bool enable) { return -1; } + +bool FileAudioDevice::AGC() const { return false; } + +int32_t FileAudioDevice::SetWaveOutVolume(uint16_t volumeLeft, + uint16_t volumeRight) { + return -1; +} + +int32_t FileAudioDevice::WaveOutVolume(uint16_t& volumeLeft, + uint16_t& volumeRight) const { + return -1; +} + +int32_t FileAudioDevice::InitSpeaker() { return -1; } + +bool FileAudioDevice::SpeakerIsInitialized() const { return false; } + +int32_t FileAudioDevice::InitMicrophone() { return 0; } + +bool FileAudioDevice::MicrophoneIsInitialized() const { return true; } + +int32_t FileAudioDevice::SpeakerVolumeIsAvailable(bool& available) { + return -1; +} + +int32_t FileAudioDevice::SetSpeakerVolume(uint32_t volume) { return -1; } + +int32_t FileAudioDevice::SpeakerVolume(uint32_t& volume) const { return -1; } + +int32_t FileAudioDevice::MaxSpeakerVolume(uint32_t& maxVolume) const { + return -1; +} + +int32_t FileAudioDevice::MinSpeakerVolume(uint32_t& minVolume) const { + return -1; +} + +int32_t FileAudioDevice::SpeakerVolumeStepSize(uint16_t& stepSize) const { + return -1; +} + +int32_t FileAudioDevice::MicrophoneVolumeIsAvailable(bool& available) { + return -1; +} + +int32_t FileAudioDevice::SetMicrophoneVolume(uint32_t volume) { return -1; } + +int32_t FileAudioDevice::MicrophoneVolume(uint32_t& volume) const { + return -1; +} + +int32_t FileAudioDevice::MaxMicrophoneVolume(uint32_t& maxVolume) const { + return -1; +} + +int32_t FileAudioDevice::MinMicrophoneVolume(uint32_t& minVolume) const { + return -1; +} + +int32_t FileAudioDevice::MicrophoneVolumeStepSize(uint16_t& stepSize) const { + return -1; +} + +int32_t FileAudioDevice::SpeakerMuteIsAvailable(bool& available) { return -1; } + +int32_t FileAudioDevice::SetSpeakerMute(bool enable) { return -1; } + +int32_t FileAudioDevice::SpeakerMute(bool& enabled) const { return -1; } + +int32_t FileAudioDevice::MicrophoneMuteIsAvailable(bool& available) { + return -1; +} + +int32_t FileAudioDevice::SetMicrophoneMute(bool enable) { return -1; } + +int32_t FileAudioDevice::MicrophoneMute(bool& enabled) const { return -1; } + +int32_t FileAudioDevice::MicrophoneBoostIsAvailable(bool& available) { + return -1; +} + +int32_t FileAudioDevice::SetMicrophoneBoost(bool enable) { return -1; } + +int32_t FileAudioDevice::MicrophoneBoost(bool& enabled) const { return -1; } + +int32_t FileAudioDevice::StereoPlayoutIsAvailable(bool& available) { + available = true; + return 0; +} +int32_t FileAudioDevice::SetStereoPlayout(bool enable) { + return 0; +} + +int32_t FileAudioDevice::StereoPlayout(bool& enabled) const { + enabled = true; + return 0; +} + +int32_t FileAudioDevice::StereoRecordingIsAvailable(bool& available) { + available = true; + return 0; +} + +int32_t FileAudioDevice::SetStereoRecording(bool enable) { + return 0; +} + +int32_t FileAudioDevice::StereoRecording(bool& enabled) const { + enabled = true; + return 0; +} + +int32_t FileAudioDevice::SetPlayoutBuffer( + const AudioDeviceModule::BufferType type, + uint16_t sizeMS) { + return 0; +} + +int32_t FileAudioDevice::PlayoutBuffer(AudioDeviceModule::BufferType& type, + uint16_t& sizeMS) const { + type = _playBufType; + return 0; +} + +int32_t FileAudioDevice::PlayoutDelay(uint16_t& delayMS) const { + return 0; +} + +int32_t FileAudioDevice::RecordingDelay(uint16_t& delayMS) const { return -1; } + +int32_t FileAudioDevice::CPULoad(uint16_t& load) const { return -1; } + +bool FileAudioDevice::PlayoutWarning() const { return false; } + +bool FileAudioDevice::PlayoutError() const { return false; } + +bool FileAudioDevice::RecordingWarning() const { return false; } + +bool FileAudioDevice::RecordingError() const { return false; } + +void FileAudioDevice::ClearPlayoutWarning() {} + +void FileAudioDevice::ClearPlayoutError() {} + +void FileAudioDevice::ClearRecordingWarning() {} + +void FileAudioDevice::ClearRecordingError() {} + +void FileAudioDevice::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { + CriticalSectionScoped lock(&_critSect); + + _ptrAudioBuffer = audioBuffer; + + // Inform the AudioBuffer about default settings for this implementation. + // Set all values to zero here since the actual settings will be done by + // InitPlayout and InitRecording later. + _ptrAudioBuffer->SetRecordingSampleRate(0); + _ptrAudioBuffer->SetPlayoutSampleRate(0); + _ptrAudioBuffer->SetRecordingChannels(0); + _ptrAudioBuffer->SetPlayoutChannels(0); +} + +bool FileAudioDevice::PlayThreadFunc(void* pThis) +{ + return (static_cast(pThis)->PlayThreadProcess()); +} + +bool FileAudioDevice::RecThreadFunc(void* pThis) +{ + return (static_cast(pThis)->RecThreadProcess()); +} + +bool FileAudioDevice::PlayThreadProcess() +{ + if(!_playing) + return false; + + uint64_t currentTime = _clock->CurrentNtpInMilliseconds(); + _critSect.Enter(); + + if (_lastCallPlayoutMillis == 0 || + currentTime - _lastCallPlayoutMillis >= 10) + { + _critSect.Leave(); + _ptrAudioBuffer->RequestPlayoutData(_playoutFramesIn10MS); + _critSect.Enter(); + + _playoutFramesLeft = _ptrAudioBuffer->GetPlayoutData(_playoutBuffer); + assert(_playoutFramesLeft == _playoutFramesIn10MS); + if (_outputFile.Open()) { + _outputFile.Write(_playoutBuffer, kPlayoutBufferSize); + _outputFile.Flush(); + } + _lastCallPlayoutMillis = currentTime; + } + _playoutFramesLeft = 0; + _critSect.Leave(); + SleepMs(10 - (_clock->CurrentNtpInMilliseconds() - currentTime)); + return true; +} + +bool FileAudioDevice::RecThreadProcess() +{ + if (!_recording) + return false; + + uint64_t currentTime = _clock->CurrentNtpInMilliseconds(); + _critSect.Enter(); + + if (_lastCallRecordMillis == 0 || + currentTime - _lastCallRecordMillis >= 10) { + if (_inputFile.Open()) { + if (_inputFile.Read(_recordingBuffer, kRecordingBufferSize) > 0) { + _ptrAudioBuffer->SetRecordedBuffer(_recordingBuffer, + _recordingFramesIn10MS); + } else { + _inputFile.Rewind(); + } + _lastCallRecordMillis = currentTime; + _critSect.Leave(); + _ptrAudioBuffer->DeliverRecordedData(); + _critSect.Enter(); + } + } + + _critSect.Leave(); + SleepMs(10 - (_clock->CurrentNtpInMilliseconds() - currentTime)); + return true; +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_device/dummy/file_audio_device.h b/webrtc/modules/audio_device/dummy/file_audio_device.h new file mode 100644 index 000000000..6f417eb2e --- /dev/null +++ b/webrtc/modules/audio_device/dummy/file_audio_device.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2014 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_AUDIO_DEVICE_FILE_AUDIO_DEVICE_H +#define WEBRTC_AUDIO_DEVICE_FILE_AUDIO_DEVICE_H + +#include + +#include + +#include "webrtc/modules/audio_device/audio_device_generic.h" +#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" +#include "webrtc/system_wrappers/interface/file_wrapper.h" +#include "webrtc/system_wrappers/interface/clock.h" + +namespace webrtc { +class EventWrapper; +class ThreadWrapper; + +// This is a fake audio device which plays audio from a file as its microphone +// and plays out into a file. +class FileAudioDevice : public AudioDeviceGeneric { + public: + // Constructs a file audio device with |id|. It will read audio from + // |inputFilename| and record output audio to |outputFilename|. + // + // The input file should be a readable 48k stereo raw file, and the output + // file should point to a writable location. The output format will also be + // 48k stereo raw audio. + FileAudioDevice(const int32_t id, + const char* inputFilename, + const char* outputFilename); + virtual ~FileAudioDevice(); + + // Retrieve the currently utilized audio layer + virtual int32_t ActiveAudioLayer( + AudioDeviceModule::AudioLayer& audioLayer) const OVERRIDE; + + // Main initializaton and termination + virtual int32_t Init() OVERRIDE; + virtual int32_t Terminate() OVERRIDE; + virtual bool Initialized() const OVERRIDE; + + // Device enumeration + virtual int16_t PlayoutDevices() OVERRIDE; + virtual int16_t RecordingDevices() OVERRIDE; + virtual int32_t PlayoutDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) OVERRIDE; + virtual int32_t RecordingDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) OVERRIDE; + + // Device selection + virtual int32_t SetPlayoutDevice(uint16_t index) OVERRIDE; + virtual int32_t SetPlayoutDevice( + AudioDeviceModule::WindowsDeviceType device) OVERRIDE; + virtual int32_t SetRecordingDevice(uint16_t index) OVERRIDE; + virtual int32_t SetRecordingDevice( + AudioDeviceModule::WindowsDeviceType device) OVERRIDE; + + // Audio transport initialization + virtual int32_t PlayoutIsAvailable(bool& available) OVERRIDE; + virtual int32_t InitPlayout() OVERRIDE; + virtual bool PlayoutIsInitialized() const OVERRIDE; + virtual int32_t RecordingIsAvailable(bool& available) OVERRIDE; + virtual int32_t InitRecording() OVERRIDE; + virtual bool RecordingIsInitialized() const OVERRIDE; + + // Audio transport control + virtual int32_t StartPlayout() OVERRIDE; + virtual int32_t StopPlayout() OVERRIDE; + virtual bool Playing() const OVERRIDE; + virtual int32_t StartRecording() OVERRIDE; + virtual int32_t StopRecording() OVERRIDE; + virtual bool Recording() const OVERRIDE; + + // Microphone Automatic Gain Control (AGC) + virtual int32_t SetAGC(bool enable) OVERRIDE; + virtual bool AGC() const OVERRIDE; + + // Volume control based on the Windows Wave API (Windows only) + virtual int32_t SetWaveOutVolume(uint16_t volumeLeft, + uint16_t volumeRight) OVERRIDE; + virtual int32_t WaveOutVolume(uint16_t& volumeLeft, + uint16_t& volumeRight) const OVERRIDE; + + // Audio mixer initialization + virtual int32_t InitSpeaker() OVERRIDE; + virtual bool SpeakerIsInitialized() const OVERRIDE; + virtual int32_t InitMicrophone() OVERRIDE; + virtual bool MicrophoneIsInitialized() const OVERRIDE; + + // Speaker volume controls + virtual int32_t SpeakerVolumeIsAvailable(bool& available) OVERRIDE; + virtual int32_t SetSpeakerVolume(uint32_t volume) OVERRIDE; + virtual int32_t SpeakerVolume(uint32_t& volume) const OVERRIDE; + virtual int32_t MaxSpeakerVolume(uint32_t& maxVolume) const OVERRIDE; + virtual int32_t MinSpeakerVolume(uint32_t& minVolume) const OVERRIDE; + virtual int32_t SpeakerVolumeStepSize(uint16_t& stepSize) const OVERRIDE; + + // Microphone volume controls + virtual int32_t MicrophoneVolumeIsAvailable(bool& available) OVERRIDE; + virtual int32_t SetMicrophoneVolume(uint32_t volume) OVERRIDE; + virtual int32_t MicrophoneVolume(uint32_t& volume) const OVERRIDE; + virtual int32_t MaxMicrophoneVolume(uint32_t& maxVolume) const OVERRIDE; + virtual int32_t MinMicrophoneVolume(uint32_t& minVolume) const OVERRIDE; + virtual int32_t MicrophoneVolumeStepSize(uint16_t& stepSize) const OVERRIDE; + + // Speaker mute control + virtual int32_t SpeakerMuteIsAvailable(bool& available) OVERRIDE; + virtual int32_t SetSpeakerMute(bool enable) OVERRIDE; + virtual int32_t SpeakerMute(bool& enabled) const OVERRIDE; + + // Microphone mute control + virtual int32_t MicrophoneMuteIsAvailable(bool& available) OVERRIDE; + virtual int32_t SetMicrophoneMute(bool enable) OVERRIDE; + virtual int32_t MicrophoneMute(bool& enabled) const OVERRIDE; + + // Microphone boost control + virtual int32_t MicrophoneBoostIsAvailable(bool& available) OVERRIDE; + virtual int32_t SetMicrophoneBoost(bool enable) OVERRIDE; + virtual int32_t MicrophoneBoost(bool& enabled) const OVERRIDE; + + // Stereo support + virtual int32_t StereoPlayoutIsAvailable(bool& available) OVERRIDE; + virtual int32_t SetStereoPlayout(bool enable) OVERRIDE; + virtual int32_t StereoPlayout(bool& enabled) const OVERRIDE; + virtual int32_t StereoRecordingIsAvailable(bool& available) OVERRIDE; + virtual int32_t SetStereoRecording(bool enable) OVERRIDE; + virtual int32_t StereoRecording(bool& enabled) const OVERRIDE; + + // Delay information and control + virtual int32_t SetPlayoutBuffer(const AudioDeviceModule::BufferType type, + uint16_t sizeMS) OVERRIDE; + virtual int32_t PlayoutBuffer(AudioDeviceModule::BufferType& type, + uint16_t& sizeMS) const OVERRIDE; + virtual int32_t PlayoutDelay(uint16_t& delayMS) const OVERRIDE; + virtual int32_t RecordingDelay(uint16_t& delayMS) const OVERRIDE; + + // CPU load + virtual int32_t CPULoad(uint16_t& load) const OVERRIDE; + + virtual bool PlayoutWarning() const OVERRIDE; + virtual bool PlayoutError() const OVERRIDE; + virtual bool RecordingWarning() const OVERRIDE; + virtual bool RecordingError() const OVERRIDE; + virtual void ClearPlayoutWarning() OVERRIDE; + virtual void ClearPlayoutError() OVERRIDE; + virtual void ClearRecordingWarning() OVERRIDE; + virtual void ClearRecordingError() OVERRIDE; + + virtual void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) OVERRIDE; + + private: + static bool RecThreadFunc(void*); + static bool PlayThreadFunc(void*); + bool RecThreadProcess(); + bool PlayThreadProcess(); + + int32_t _playout_index; + int32_t _record_index; + AudioDeviceModule::BufferType _playBufType; + AudioDeviceBuffer* _ptrAudioBuffer; + int8_t* _recordingBuffer; // In bytes. + int8_t* _playoutBuffer; // In bytes. + uint32_t _recordingFramesLeft; + uint32_t _playoutFramesLeft; + CriticalSectionWrapper& _critSect; + + uint32_t _recordingBufferSizeIn10MS; + uint32_t _recordingFramesIn10MS; + uint32_t _playoutFramesIn10MS; + + ThreadWrapper* _ptrThreadRec; + ThreadWrapper* _ptrThreadPlay; + uint32_t _recThreadID; + uint32_t _playThreadID; + + bool _playing; + bool _recording; + uint64_t _lastCallPlayoutMillis; + uint64_t _lastCallRecordMillis; + + FileWrapper& _outputFile; + FileWrapper& _inputFile; + std::string _outputFilename; + std::string _inputFilename; + + Clock* _clock; +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_DEVICE_FILE_AUDIO_DEVICE_H diff --git a/webrtc/modules/audio_device/dummy/file_audio_device_factory.cc b/webrtc/modules/audio_device/dummy/file_audio_device_factory.cc new file mode 100644 index 000000000..db35bf111 --- /dev/null +++ b/webrtc/modules/audio_device/dummy/file_audio_device_factory.cc @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_device/dummy/file_audio_device_factory.h" + +#include + +#include "webrtc/modules/audio_device/dummy/file_audio_device.h" + +namespace webrtc { + +char FileAudioDeviceFactory::_inputAudioFilename[MAX_FILENAME_LEN] = ""; +char FileAudioDeviceFactory::_outputAudioFilename[MAX_FILENAME_LEN] = ""; + +FileAudioDevice* FileAudioDeviceFactory::CreateFileAudioDevice( + const int32_t id) { + // Bail out here if the files aren't set. + if (strlen(_inputAudioFilename) == 0 || strlen(_outputAudioFilename) == 0) { + printf("Was compiled with WEBRTC_DUMMY_AUDIO_PLAY_STATIC_FILE " + "but did not set input/output files to use. Bailing out.\n"); + exit(1); + } + return new FileAudioDevice(id, _inputAudioFilename, _outputAudioFilename); +} + +void FileAudioDeviceFactory::SetFilenamesToUse( + const char* inputAudioFilename, const char* outputAudioFilename) { + assert(strlen(inputAudioFilename) < MAX_FILENAME_LEN && + strlen(outputAudioFilename) < MAX_FILENAME_LEN); + + // Copy the strings since we don't know the lifetime of the input pointers. + strncpy(_inputAudioFilename, inputAudioFilename, MAX_FILENAME_LEN); + strncpy(_outputAudioFilename, outputAudioFilename, MAX_FILENAME_LEN); +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_device/dummy/file_audio_device_factory.h b/webrtc/modules/audio_device/dummy/file_audio_device_factory.h new file mode 100644 index 000000000..9975d7b90 --- /dev/null +++ b/webrtc/modules/audio_device/dummy/file_audio_device_factory.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014 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_AUDIO_DEVICE_FILE_AUDIO_DEVICE_FACTORY_H +#define WEBRTC_AUDIO_DEVICE_FILE_AUDIO_DEVICE_FACTORY_H + +#include "webrtc/common_types.h" + +namespace webrtc { + +class FileAudioDevice; + +// This class is used by audio_device_impl.cc when WebRTC is compiled with +// WEBRTC_DUMMY_FILE_DEVICES. The application must include this file and set the +// filenames to use before the audio device module is initialized. This is +// intended for test tools which use the audio device module. +class FileAudioDeviceFactory { + public: + static FileAudioDevice* CreateFileAudioDevice(const int32_t id); + + // The input file must be a readable 48k stereo raw file. The output + // file must be writable. The strings will be copied. + static void SetFilenamesToUse(const char* inputAudioFilename, + const char* outputAudioFilename); + + private: + static const uint32_t MAX_FILENAME_LEN = 256; + static char _inputAudioFilename[MAX_FILENAME_LEN]; + static char _outputAudioFilename[MAX_FILENAME_LEN]; +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_DEVICE_FILE_AUDIO_DEVICE_FACTORY_H diff --git a/webrtc/modules/audio_device/include/audio_device_defines.h b/webrtc/modules/audio_device/include/audio_device_defines.h index 9f3e24b10..56a584ef9 100644 --- a/webrtc/modules/audio_device/include/audio_device_defines.h +++ b/webrtc/modules/audio_device/include/audio_device_defines.h @@ -63,14 +63,16 @@ class AudioTransport const int32_t clockDrift, const uint32_t currentMicLevel, const bool keyPressed, - uint32_t& newMicLevel) = 0; + uint32_t& newMicLevel) = 0; virtual int32_t NeedMorePlayData(const uint32_t nSamples, const uint8_t nBytesPerSample, const uint8_t nChannels, const uint32_t samplesPerSec, void* audioSamples, - uint32_t& nSamplesOut) = 0; + uint32_t& nSamplesOut, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) = 0; // Method to pass captured data directly and unmixed to network channels. // |channel_ids| contains a list of VoE channels which are the @@ -101,13 +103,34 @@ class AudioTransport // Method to pass the captured audio data to the specific VoE channel. // |voe_channel| is the id of the VoE channel which is the sink to the // capture data. - // TODO(xians): Make the interface pure virtual after libjingle - // has its implementation. + // TODO(xians): Remove this interface after Libjingle switches to + // PushCaptureData(). virtual void OnData(int voe_channel, const void* audio_data, int bits_per_sample, int sample_rate, int number_of_channels, int number_of_frames) {} + // Method to push the captured audio data to the specific VoE channel. + // The data will not undergo audio processing. + // |voe_channel| is the id of the VoE channel which is the sink to the + // capture data. + // TODO(xians): Make the interface pure virtual after Libjingle + // has its implementation. + virtual void PushCaptureData(int voe_channel, const void* audio_data, + int bits_per_sample, int sample_rate, + int number_of_channels, + int number_of_frames) {} + + // Method to pull mixed render audio data from all active VoE channels. + // The data will not be passed as reference for audio processing internally. + // TODO(xians): Support getting the unmixed render data from specific VoE + // channel. + virtual void PullRenderData(int bits_per_sample, int sample_rate, + int number_of_channels, int number_of_frames, + void* audio_data, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) {} + protected: virtual ~AudioTransport() {} }; diff --git a/webrtc/modules/audio_device/ios/audio_device_ios.cc b/webrtc/modules/audio_device/ios/audio_device_ios.cc index b459a4644..7a7189a2b 100644 --- a/webrtc/modules/audio_device/ios/audio_device_ios.cc +++ b/webrtc/modules/audio_device/ios/audio_device_ios.cc @@ -1299,7 +1299,7 @@ int32_t AudioDeviceIPhone::InitPlayOrRecord() { // todo: Add 48 kHz (increase buffer sizes). Other fs? if ((playoutDesc.mSampleRate > 44090.0) && (playoutDesc.mSampleRate < 44110.0)) { - _adbSampFreq = 44000; + _adbSampFreq = 44100; } else if ((playoutDesc.mSampleRate > 15990.0) && (playoutDesc.mSampleRate < 16010.0)) { _adbSampFreq = 16000; @@ -1730,7 +1730,7 @@ void AudioDeviceIPhone::UpdatePlayoutDelay() { _playoutDelayMeasurementCounter = 0; } - // todo: Add playout buffer? (Only used for 44.1 kHz) + // todo: Add playout buffer? } void AudioDeviceIPhone::UpdateRecordingDelay() { diff --git a/webrtc/modules/audio_device/linux/latebindingsymboltable_linux.h b/webrtc/modules/audio_device/linux/latebindingsymboltable_linux.h index b5186fa7b..052390a65 100644 --- a/webrtc/modules/audio_device/linux/latebindingsymboltable_linux.h +++ b/webrtc/modules/audio_device/linux/latebindingsymboltable_linux.h @@ -32,7 +32,7 @@ #include // for NULL #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/trace.h" // This file provides macros for creating "symbol table" classes to simplify the diff --git a/webrtc/modules/audio_device/main/source/OWNERS b/webrtc/modules/audio_device/main/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/audio_device/main/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/audio_device/test/audio_device_test_api.cc b/webrtc/modules/audio_device/test/audio_device_test_api.cc index 7692b12a4..011fc1033 100644 --- a/webrtc/modules/audio_device/test/audio_device_test_api.cc +++ b/webrtc/modules/audio_device/test/audio_device_test_api.cc @@ -116,7 +116,9 @@ class AudioTransportAPI: public AudioTransport { const uint8_t nChannels, const uint32_t sampleRate, void* audioSamples, - uint32_t& nSamplesOut) { + uint32_t& nSamplesOut, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) { play_count_++; if (play_count_ % 100 == 0) { if (nChannels == 1) { @@ -142,10 +144,16 @@ class AudioTransportAPI: public AudioTransport { return 0; } - virtual void OnData(int voe_channel, const void* audio_data, - int bits_per_sample, int sample_rate, - int number_of_channels, - int number_of_frames) {} + virtual void PushCaptureData(int voe_channel, const void* audio_data, + int bits_per_sample, int sample_rate, + int number_of_channels, + int number_of_frames) {} + + virtual void PullRenderData(int bits_per_sample, int sample_rate, + int number_of_channels, int number_of_frames, + void* audio_data, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) {} private: uint32_t rec_count_; uint32_t play_count_; @@ -1788,19 +1796,19 @@ TEST_F(AudioDeviceAPITest, SetPlayoutSpeaker) { #if defined(WEBRTC_IOS) // Not playing or recording, should just return a success EXPECT_EQ(0, audio_device_->SetLoudspeakerStatus(true)); - EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(loudspeakerOn)); + EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(&loudspeakerOn)); EXPECT_TRUE(loudspeakerOn); EXPECT_EQ(0, audio_device_->SetLoudspeakerStatus(false)); - EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(loudspeakerOn)); + EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(&loudspeakerOn)); EXPECT_FALSE(loudspeakerOn); EXPECT_EQ(0, audio_device_->InitPlayout()); EXPECT_EQ(0, audio_device_->StartPlayout()); EXPECT_EQ(0, audio_device_->SetLoudspeakerStatus(true)); - EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(loudspeakerOn)); + EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(&loudspeakerOn)); EXPECT_TRUE(loudspeakerOn); EXPECT_EQ(0, audio_device_->SetLoudspeakerStatus(false)); - EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(loudspeakerOn)); + EXPECT_EQ(0, audio_device_->GetLoudspeakerStatus(&loudspeakerOn)); EXPECT_FALSE(loudspeakerOn); #else diff --git a/webrtc/modules/audio_device/test/func_test_manager.cc b/webrtc/modules/audio_device/test/func_test_manager.cc index 7024e2c6e..2a1928775 100644 --- a/webrtc/modules/audio_device/test/func_test_manager.cc +++ b/webrtc/modules/audio_device/test/func_test_manager.cc @@ -292,7 +292,9 @@ int32_t AudioTransportImpl::NeedMorePlayData( const uint8_t nChannels, const uint32_t samplesPerSec, void* audioSamples, - uint32_t& nSamplesOut) + uint32_t& nSamplesOut, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) { if (_fullDuplex) { @@ -542,11 +544,18 @@ int AudioTransportImpl::OnDataAvailable(const int voe_channels[], return 0; } -void AudioTransportImpl::OnData(int voe_channel, - const void* audio_data, - int bits_per_sample, int sample_rate, - int number_of_channels, - int number_of_frames) {} +void AudioTransportImpl::PushCaptureData(int voe_channel, + const void* audio_data, + int bits_per_sample, int sample_rate, + int number_of_channels, + int number_of_frames) {} + +void AudioTransportImpl::PullRenderData(int bits_per_sample, int sample_rate, + int number_of_channels, + int number_of_frames, + void* audio_data, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) {} FuncTestManager::FuncTestManager() : _processThread(NULL), @@ -2683,7 +2692,7 @@ int32_t FuncTestManager::TestAdvancedMBAPI() " from the loudspeaker.\n\ > Press any key to stop...\n \n"); PAUSE(DEFAULT_PAUSE_TIME); - EXPECT_EQ(0, audioDevice->GetLoudspeakerStatus(loudspeakerOn)); + EXPECT_EQ(0, audioDevice->GetLoudspeakerStatus(&loudspeakerOn)); EXPECT_TRUE(loudspeakerOn); TEST_LOG("Set to not use speaker\n"); @@ -2692,7 +2701,7 @@ int32_t FuncTestManager::TestAdvancedMBAPI() " from the loudspeaker.\n\ > Press any key to stop...\n \n"); PAUSE(DEFAULT_PAUSE_TIME); - EXPECT_EQ(0, audioDevice->GetLoudspeakerStatus(loudspeakerOn)); + EXPECT_EQ(0, audioDevice->GetLoudspeakerStatus(&loudspeakerOn)); EXPECT_FALSE(loudspeakerOn); #endif diff --git a/webrtc/modules/audio_device/test/func_test_manager.h b/webrtc/modules/audio_device/test/func_test_manager.h index 6e21466e6..5cb4f4610 100644 --- a/webrtc/modules/audio_device/test/func_test_manager.h +++ b/webrtc/modules/audio_device/test/func_test_manager.h @@ -118,7 +118,9 @@ class AudioTransportImpl: public AudioTransport const uint8_t nChannels, const uint32_t samplesPerSec, void* audioSamples, - uint32_t& nSamplesOut); + uint32_t& nSamplesOut, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms); virtual int OnDataAvailable(const int voe_channels[], int number_of_voe_channels, @@ -131,10 +133,16 @@ class AudioTransportImpl: public AudioTransport bool key_pressed, bool need_audio_processing); - virtual void OnData(int voe_channel, const void* audio_data, - int bits_per_sample, int sample_rate, - int number_of_channels, - int number_of_frames); + virtual void PushCaptureData(int voe_channel, const void* audio_data, + int bits_per_sample, int sample_rate, + int number_of_channels, + int number_of_frames); + + virtual void PullRenderData(int bits_per_sample, int sample_rate, + int number_of_channels, int number_of_frames, + void* audio_data, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms); AudioTransportImpl(AudioDeviceModule* audioDevice); ~AudioTransportImpl(); diff --git a/webrtc/modules/audio_processing/BUILD.gn b/webrtc/modules/audio_processing/BUILD.gn new file mode 100644 index 000000000..a727d54b2 --- /dev/null +++ b/webrtc/modules/audio_processing/BUILD.gn @@ -0,0 +1,13 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("audio_processing") { + # TODO(andrew): Implement. +} diff --git a/webrtc/modules/audio_processing/OWNERS b/webrtc/modules/audio_processing/OWNERS index 5a2563444..41a82af20 100644 --- a/webrtc/modules/audio_processing/OWNERS +++ b/webrtc/modules/audio_processing/OWNERS @@ -1,2 +1,10 @@ +aluebs@webrtc.org andrew@webrtc.org bjornv@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/audio_processing/aec/Android.mk b/webrtc/modules/audio_processing/aec/Android.mk index 3ad52b966..181e87d9a 100644 --- a/webrtc/modules/audio_processing/aec/Android.mk +++ b/webrtc/modules/audio_processing/aec/Android.mk @@ -47,3 +47,12 @@ ifndef NDK_ROOT include external/stlport/libstlport.mk endif include $(BUILD_STATIC_LIBRARY) + +######################### +# Build the neon library. +ifeq ($(WEBRTC_BUILD_NEON_LIBS),true) + +LOCAL_SRC_FILES += \ + aec_core_neon.c + +endif # ifeq ($(WEBRTC_BUILD_NEON_LIBS),true) diff --git a/webrtc/modules/audio_processing/aec/aec_core.c b/webrtc/modules/audio_processing/aec/aec_core.c index 3f3d2c088..207c6dc3b 100644 --- a/webrtc/modules/audio_processing/aec/aec_core.c +++ b/webrtc/modules/audio_processing/aec/aec_core.c @@ -67,7 +67,7 @@ static const float sqrtHanning[65] = { // Matlab code to produce table: // weightCurve = [0 ; 0.3 * sqrt(linspace(0,1,64))' + 0.1]; // fprintf(1, '\t%.4f, %.4f, %.4f, %.4f, %.4f, %.4f,\n', weightCurve); -const float WebRtcAec_weightCurve[65] = { +ALIGN16_BEG const float ALIGN16_END WebRtcAec_weightCurve[65] = { 0.0000f, 0.1000f, 0.1378f, 0.1535f, 0.1655f, 0.1756f, 0.1845f, 0.1926f, 0.2000f, 0.2069f, 0.2134f, 0.2195f, 0.2254f, 0.2309f, 0.2363f, 0.2414f, 0.2464f, 0.2512f, 0.2558f, 0.2604f, 0.2648f, 0.2690f, 0.2732f, 0.2773f, @@ -81,7 +81,7 @@ const float WebRtcAec_weightCurve[65] = { // Matlab code to produce table: // overDriveCurve = [sqrt(linspace(0,1,65))' + 1]; // fprintf(1, '\t%.4f, %.4f, %.4f, %.4f, %.4f, %.4f,\n', overDriveCurve); -const float WebRtcAec_overDriveCurve[65] = { +ALIGN16_BEG const float ALIGN16_END WebRtcAec_overDriveCurve[65] = { 1.0000f, 1.1250f, 1.1768f, 1.2165f, 1.2500f, 1.2795f, 1.3062f, 1.3307f, 1.3536f, 1.3750f, 1.3953f, 1.4146f, 1.4330f, 1.4507f, 1.4677f, 1.4841f, 1.5000f, 1.5154f, 1.5303f, 1.5449f, 1.5590f, 1.5728f, 1.5863f, 1.5995f, @@ -116,7 +116,7 @@ extern int webrtc_aec_instance_count; // "Private" function prototypes. static void ProcessBlock(AecCore* aec); -static void NonLinearProcessing(AecCore* aec, short* output, short* outputH); +static void NonLinearProcessing(AecCore* aec, float* output, float* outputH); static void GetHighbandGain(const float* lambda, float* nlpGainHband); @@ -160,28 +160,28 @@ int WebRtcAec_CreateAec(AecCore** aecInst) { return -1; } - aec->nearFrBuf = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(int16_t)); + aec->nearFrBuf = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(float)); if (!aec->nearFrBuf) { WebRtcAec_FreeAec(aec); aec = NULL; return -1; } - aec->outFrBuf = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(int16_t)); + aec->outFrBuf = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(float)); if (!aec->outFrBuf) { WebRtcAec_FreeAec(aec); aec = NULL; return -1; } - aec->nearFrBufH = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(int16_t)); + aec->nearFrBufH = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(float)); if (!aec->nearFrBufH) { WebRtcAec_FreeAec(aec); aec = NULL; return -1; } - aec->outFrBufH = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(int16_t)); + aec->outFrBufH = WebRtc_CreateBuffer(FRAME_LEN + PART_LEN, sizeof(float)); if (!aec->outFrBufH) { WebRtcAec_FreeAec(aec); aec = NULL; @@ -473,6 +473,7 @@ int WebRtcAec_InitAec(AecCore* aec, int sampFreq) { aec->delay_logging_enabled = 0; memset(aec->delay_histogram, 0, sizeof(aec->delay_histogram)); + aec->reported_delay_enabled = 1; aec->extended_filter_enabled = 0; aec->num_partitions = kNormalNumPartitions; @@ -581,6 +582,10 @@ int WebRtcAec_InitAec(AecCore* aec, int sampFreq) { WebRtcAec_InitAec_mips(); #endif +#if defined(WEBRTC_DETECT_ARM_NEON) || defined(WEBRTC_ARCH_ARM_NEON) + WebRtcAec_InitAec_neon(); +#endif + aec_rdft_init(); return 0; @@ -616,11 +621,11 @@ int WebRtcAec_MoveFarReadPtr(AecCore* aec, int elements) { } void WebRtcAec_ProcessFrame(AecCore* aec, - const short* nearend, - const short* nearendH, + const float* nearend, + const float* nearendH, int knownDelay, - int16_t* out, - int16_t* outH) { + float* out, + float* outH) { int out_elements = 0; // For each frame the process is as follows: @@ -785,6 +790,14 @@ void WebRtcAec_SetConfigCore(AecCore* self, } } +void WebRtcAec_enable_reported_delay(AecCore* self, int enable) { + self->reported_delay_enabled = enable; +} + +int WebRtcAec_reported_delay_enabled(AecCore* self) { + return self->reported_delay_enabled; +} + void WebRtcAec_enable_delay_correction(AecCore* self, int enable) { self->extended_filter_enabled = enable; self->num_partitions = enable ? kExtendedNumPartitions : kNormalNumPartitions; @@ -805,7 +818,7 @@ void WebRtcAec_SetSystemDelay(AecCore* self, int delay) { static void ProcessBlock(AecCore* aec) { int i; - float d[PART_LEN], y[PART_LEN], e[PART_LEN], dH[PART_LEN]; + float y[PART_LEN], e[PART_LEN]; float scale; float fft[PART_LEN2]; @@ -824,30 +837,22 @@ static void ProcessBlock(AecCore* aec) { const float ramp = 1.0002f; const float gInitNoise[2] = {0.999f, 0.001f}; - int16_t nearend[PART_LEN]; - int16_t* nearend_ptr = NULL; - int16_t output[PART_LEN]; - int16_t outputH[PART_LEN]; + float nearend[PART_LEN]; + float* nearend_ptr = NULL; + float output[PART_LEN]; + float outputH[PART_LEN]; float* xf_ptr = NULL; - memset(dH, 0, sizeof(dH)); + // Concatenate old and new nearend blocks. if (aec->sampFreq == 32000) { - // Get the upper band first so we can reuse |nearend|. WebRtc_ReadBuffer(aec->nearFrBufH, (void**)&nearend_ptr, nearend, PART_LEN); - for (i = 0; i < PART_LEN; i++) { - dH[i] = (float)(nearend_ptr[i]); - } - memcpy(aec->dBufH + PART_LEN, dH, sizeof(float) * PART_LEN); + memcpy(aec->dBufH + PART_LEN, nearend_ptr, sizeof(nearend)); } WebRtc_ReadBuffer(aec->nearFrBuf, (void**)&nearend_ptr, nearend, PART_LEN); + memcpy(aec->dBuf + PART_LEN, nearend_ptr, sizeof(nearend)); // ---------- Ooura fft ---------- - // Concatenate old and new nearend blocks. - for (i = 0; i < PART_LEN; i++) { - d[i] = (float)(nearend_ptr[i]); - } - memcpy(aec->dBuf + PART_LEN, d, sizeof(float) * PART_LEN); #ifdef WEBRTC_AEC_DEBUG_DUMP { @@ -959,7 +964,7 @@ static void ProcessBlock(AecCore* aec) { } for (i = 0; i < PART_LEN; i++) { - e[i] = d[i] - y[i]; + e[i] = nearend_ptr[i] - y[i]; } // Error fft @@ -1018,7 +1023,7 @@ static void ProcessBlock(AecCore* aec) { #endif } -static void NonLinearProcessing(AecCore* aec, short* output, short* outputH) { +static void NonLinearProcessing(AecCore* aec, float* output, float* outputH) { float efw[2][PART_LEN1], dfw[2][PART_LEN1], xfw[2][PART_LEN1]; complex_t comfortNoiseHband[PART_LEN1]; float fft[PART_LEN2]; @@ -1312,12 +1317,12 @@ static void NonLinearProcessing(AecCore* aec, short* output, short* outputH) { fft[i] *= scale; // fft scaling fft[i] = fft[i] * sqrtHanning[i] + aec->outBuf[i]; - // Saturation protection - output[i] = (short)WEBRTC_SPL_SAT( - WEBRTC_SPL_WORD16_MAX, fft[i], WEBRTC_SPL_WORD16_MIN); - fft[PART_LEN + i] *= scale; // fft scaling aec->outBuf[i] = fft[PART_LEN + i] * sqrtHanning[PART_LEN - i]; + + // Saturate output to keep it in the allowed range. + output[i] = WEBRTC_SPL_SAT( + WEBRTC_SPL_WORD16_MAX, fft[i], WEBRTC_SPL_WORD16_MIN); } // For H band @@ -1342,8 +1347,8 @@ static void NonLinearProcessing(AecCore* aec, short* output, short* outputH) { // compute gain factor for (i = 0; i < PART_LEN; i++) { - dtmp = (float)aec->dBufH[i]; - dtmp = (float)dtmp * nlpGainHband; // for variable gain + dtmp = aec->dBufH[i]; + dtmp = dtmp * nlpGainHband; // for variable gain // add some comfort noise where Hband is attenuated if (flagHbandCn == 1) { @@ -1351,8 +1356,8 @@ static void NonLinearProcessing(AecCore* aec, short* output, short* outputH) { dtmp += cnScaleHband * fft[i]; } - // Saturation protection - outputH[i] = (short)WEBRTC_SPL_SAT( + // Saturate output to keep it in the allowed range. + outputH[i] = WEBRTC_SPL_SAT( WEBRTC_SPL_WORD16_MAX, dtmp, WEBRTC_SPL_WORD16_MIN); } } diff --git a/webrtc/modules/audio_processing/aec/aec_core.h b/webrtc/modules/audio_processing/aec/aec_core.h index e1f6f903d..93bfed466 100644 --- a/webrtc/modules/audio_processing/aec/aec_core.h +++ b/webrtc/modules/audio_processing/aec/aec_core.h @@ -22,17 +22,6 @@ #define PART_LEN1 (PART_LEN + 1) // Unique fft coefficients #define PART_LEN2 (PART_LEN * 2) // Length of partition * 2 -// Delay estimator constants, used for logging. -enum { - kMaxDelayBlocks = 60 -}; -enum { - kLookaheadBlocks = 15 -}; -enum { - kHistorySizeBlocks = kMaxDelayBlocks + kLookaheadBlocks -}; - typedef float complex_t[2]; // For performance reasons, some arrays of complex numbers are replaced by twice // as long arrays of float, all the real parts followed by all the imaginary @@ -68,14 +57,17 @@ void WebRtcAec_InitAec_SSE2(void); #if defined(MIPS_FPU_LE) void WebRtcAec_InitAec_mips(void); #endif +#if defined(WEBRTC_DETECT_ARM_NEON) || defined(WEBRTC_ARCH_ARM_NEON) +void WebRtcAec_InitAec_neon(void); +#endif void WebRtcAec_BufferFarendPartition(AecCore* aec, const float* farend); void WebRtcAec_ProcessFrame(AecCore* aec, - const short* nearend, - const short* nearendH, + const float* nearend, + const float* nearendH, int knownDelay, - int16_t* out, - int16_t* outH); + float* out, + float* outH); // A helper function to call WebRtc_MoveReadPtr() for all far-end buffers. // Returns the number of elements moved, and adjusts |system_delay| by the @@ -104,6 +96,12 @@ void WebRtcAec_SetConfigCore(AecCore* self, int metrics_mode, int delay_logging); +// Non-zero enables, zero disables. +void WebRtcAec_enable_reported_delay(AecCore* self, int enable); + +// Returns non-zero if reported delay is enabled and zero if disabled. +int WebRtcAec_reported_delay_enabled(AecCore* self); + // We now interpret delay correction to mean an extended filter length feature. // We reuse the delay correction infrastructure to avoid changes through to // libjingle. See details along with |DelayCorrection| in diff --git a/webrtc/modules/audio_processing/aec/aec_core_internal.h b/webrtc/modules/audio_processing/aec/aec_core_internal.h index c6b762ab1..1c560f91c 100644 --- a/webrtc/modules/audio_processing/aec/aec_core_internal.h +++ b/webrtc/modules/audio_processing/aec/aec_core_internal.h @@ -26,6 +26,17 @@ enum { }; static const int kNormalNumPartitions = 12; +// Delay estimator constants, used for logging. +enum { + kMaxDelayBlocks = 60 +}; +enum { + kLookaheadBlocks = 15 +}; +enum { + kHistorySizeBlocks = kMaxDelayBlocks + kLookaheadBlocks +}; + // Extended filter adaptation parameters. // TODO(ajm): No narrowband tuning yet. static const float kExtendedMu = 0.4f; @@ -122,6 +133,7 @@ struct AecCore { void* delay_estimator_farend; void* delay_estimator; + int reported_delay_enabled; // 0 = disabled, otherwise enabled. // 1 = extended filter mode enabled, 0 = disabled. int extended_filter_enabled; // Runtime selection of number of filter partitions. diff --git a/webrtc/modules/audio_processing/aec/aec_core_neon.c b/webrtc/modules/audio_processing/aec/aec_core_neon.c new file mode 100644 index 000000000..13ca47af4 --- /dev/null +++ b/webrtc/modules/audio_processing/aec/aec_core_neon.c @@ -0,0 +1,451 @@ +/* + * Copyright (c) 2014 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. + */ + +/* + * The core AEC algorithm, neon version of speed-critical functions. + * + * Based on aec_core_sse2.c. + */ + +#include "webrtc/modules/audio_processing/aec/aec_core.h" + +#include +#include +#include // memset + +#include "webrtc/modules/audio_processing/aec/aec_core_internal.h" +#include "webrtc/modules/audio_processing/aec/aec_rdft.h" + +enum { kShiftExponentIntoTopMantissa = 8 }; +enum { kFloatExponentShift = 23 }; + +__inline static float MulRe(float aRe, float aIm, float bRe, float bIm) { + return aRe * bRe - aIm * bIm; +} + +__inline static float MulIm(float aRe, float aIm, float bRe, float bIm) { + return aRe * bIm + aIm * bRe; +} + +static void FilterFarNEON(AecCore* aec, float yf[2][PART_LEN1]) { + int i; + const int num_partitions = aec->num_partitions; + for (i = 0; i < num_partitions; i++) { + int j; + int xPos = (i + aec->xfBufBlockPos) * PART_LEN1; + int pos = i * PART_LEN1; + // Check for wrap + if (i + aec->xfBufBlockPos >= num_partitions) { + xPos -= num_partitions * PART_LEN1; + } + + // vectorized code (four at once) + for (j = 0; j + 3 < PART_LEN1; j += 4) { + const float32x4_t xfBuf_re = vld1q_f32(&aec->xfBuf[0][xPos + j]); + const float32x4_t xfBuf_im = vld1q_f32(&aec->xfBuf[1][xPos + j]); + const float32x4_t wfBuf_re = vld1q_f32(&aec->wfBuf[0][pos + j]); + const float32x4_t wfBuf_im = vld1q_f32(&aec->wfBuf[1][pos + j]); + const float32x4_t yf_re = vld1q_f32(&yf[0][j]); + const float32x4_t yf_im = vld1q_f32(&yf[1][j]); + const float32x4_t a = vmulq_f32(xfBuf_re, wfBuf_re); + const float32x4_t e = vmlsq_f32(a, xfBuf_im, wfBuf_im); + const float32x4_t c = vmulq_f32(xfBuf_re, wfBuf_im); + const float32x4_t f = vmlaq_f32(c, xfBuf_im, wfBuf_re); + const float32x4_t g = vaddq_f32(yf_re, e); + const float32x4_t h = vaddq_f32(yf_im, f); + vst1q_f32(&yf[0][j], g); + vst1q_f32(&yf[1][j], h); + } + // scalar code for the remaining items. + for (; j < PART_LEN1; j++) { + yf[0][j] += MulRe(aec->xfBuf[0][xPos + j], + aec->xfBuf[1][xPos + j], + aec->wfBuf[0][pos + j], + aec->wfBuf[1][pos + j]); + yf[1][j] += MulIm(aec->xfBuf[0][xPos + j], + aec->xfBuf[1][xPos + j], + aec->wfBuf[0][pos + j], + aec->wfBuf[1][pos + j]); + } + } +} + +static float32x4_t vdivq_f32(float32x4_t a, float32x4_t b) { + int i; + float32x4_t x = vrecpeq_f32(b); + // from arm documentation + // The Newton-Raphson iteration: + // x[n+1] = x[n] * (2 - d * x[n]) + // converges to (1/d) if x0 is the result of VRECPE applied to d. + // + // Note: The precision did not improve after 2 iterations. + for (i = 0; i < 2; i++) { + x = vmulq_f32(vrecpsq_f32(b, x), x); + } + // a/b = a*(1/b) + return vmulq_f32(a, x); +} + +static float32x4_t vsqrtq_f32(float32x4_t s) { + int i; + float32x4_t x = vrsqrteq_f32(s); + + // Code to handle sqrt(0). + // If the input to sqrtf() is zero, a zero will be returned. + // If the input to vrsqrteq_f32() is zero, positive infinity is returned. + const uint32x4_t vec_p_inf = vdupq_n_u32(0x7F800000); + // check for divide by zero + const uint32x4_t div_by_zero = vceqq_u32(vec_p_inf, vreinterpretq_u32_f32(x)); + // zero out the positive infinity results + x = vreinterpretq_f32_u32(vandq_u32(vmvnq_u32(div_by_zero), + vreinterpretq_u32_f32(x))); + // from arm documentation + // The Newton-Raphson iteration: + // x[n+1] = x[n] * (3 - d * (x[n] * x[n])) / 2) + // converges to (1/√d) if x0 is the result of VRSQRTE applied to d. + // + // Note: The precision did not improve after 2 iterations. + for (i = 0; i < 2; i++) { + x = vmulq_f32(vrsqrtsq_f32(vmulq_f32(x, x), s), x); + } + // sqrt(s) = s * 1/sqrt(s) + return vmulq_f32(s, x);; +} + +static void ScaleErrorSignalNEON(AecCore* aec, float ef[2][PART_LEN1]) { + const float mu = aec->extended_filter_enabled ? kExtendedMu : aec->normal_mu; + const float error_threshold = aec->extended_filter_enabled ? + kExtendedErrorThreshold : aec->normal_error_threshold; + const float32x4_t k1e_10f = vdupq_n_f32(1e-10f); + const float32x4_t kMu = vmovq_n_f32(mu); + const float32x4_t kThresh = vmovq_n_f32(error_threshold); + int i; + // vectorized code (four at once) + for (i = 0; i + 3 < PART_LEN1; i += 4) { + const float32x4_t xPow = vld1q_f32(&aec->xPow[i]); + const float32x4_t ef_re_base = vld1q_f32(&ef[0][i]); + const float32x4_t ef_im_base = vld1q_f32(&ef[1][i]); + const float32x4_t xPowPlus = vaddq_f32(xPow, k1e_10f); + float32x4_t ef_re = vdivq_f32(ef_re_base, xPowPlus); + float32x4_t ef_im = vdivq_f32(ef_im_base, xPowPlus); + const float32x4_t ef_re2 = vmulq_f32(ef_re, ef_re); + const float32x4_t ef_sum2 = vmlaq_f32(ef_re2, ef_im, ef_im); + const float32x4_t absEf = vsqrtq_f32(ef_sum2); + const uint32x4_t bigger = vcgtq_f32(absEf, kThresh); + const float32x4_t absEfPlus = vaddq_f32(absEf, k1e_10f); + const float32x4_t absEfInv = vdivq_f32(kThresh, absEfPlus); + uint32x4_t ef_re_if = vreinterpretq_u32_f32(vmulq_f32(ef_re, absEfInv)); + uint32x4_t ef_im_if = vreinterpretq_u32_f32(vmulq_f32(ef_im, absEfInv)); + uint32x4_t ef_re_u32 = vandq_u32(vmvnq_u32(bigger), + vreinterpretq_u32_f32(ef_re)); + uint32x4_t ef_im_u32 = vandq_u32(vmvnq_u32(bigger), + vreinterpretq_u32_f32(ef_im)); + ef_re_if = vandq_u32(bigger, ef_re_if); + ef_im_if = vandq_u32(bigger, ef_im_if); + ef_re_u32 = vorrq_u32(ef_re_u32, ef_re_if); + ef_im_u32 = vorrq_u32(ef_im_u32, ef_im_if); + ef_re = vmulq_f32(vreinterpretq_f32_u32(ef_re_u32), kMu); + ef_im = vmulq_f32(vreinterpretq_f32_u32(ef_im_u32), kMu); + vst1q_f32(&ef[0][i], ef_re); + vst1q_f32(&ef[1][i], ef_im); + } + // scalar code for the remaining items. + for (; i < PART_LEN1; i++) { + float abs_ef; + ef[0][i] /= (aec->xPow[i] + 1e-10f); + ef[1][i] /= (aec->xPow[i] + 1e-10f); + abs_ef = sqrtf(ef[0][i] * ef[0][i] + ef[1][i] * ef[1][i]); + + if (abs_ef > error_threshold) { + abs_ef = error_threshold / (abs_ef + 1e-10f); + ef[0][i] *= abs_ef; + ef[1][i] *= abs_ef; + } + + // Stepsize factor + ef[0][i] *= mu; + ef[1][i] *= mu; + } +} + +static void FilterAdaptationNEON(AecCore* aec, + float* fft, + float ef[2][PART_LEN1]) { + int i; + const int num_partitions = aec->num_partitions; + for (i = 0; i < num_partitions; i++) { + int xPos = (i + aec->xfBufBlockPos) * PART_LEN1; + int pos = i * PART_LEN1; + int j; + // Check for wrap + if (i + aec->xfBufBlockPos >= num_partitions) { + xPos -= num_partitions * PART_LEN1; + } + + // Process the whole array... + for (j = 0; j < PART_LEN; j += 4) { + // Load xfBuf and ef. + const float32x4_t xfBuf_re = vld1q_f32(&aec->xfBuf[0][xPos + j]); + const float32x4_t xfBuf_im = vld1q_f32(&aec->xfBuf[1][xPos + j]); + const float32x4_t ef_re = vld1q_f32(&ef[0][j]); + const float32x4_t ef_im = vld1q_f32(&ef[1][j]); + // Calculate the product of conjugate(xfBuf) by ef. + // re(conjugate(a) * b) = aRe * bRe + aIm * bIm + // im(conjugate(a) * b)= aRe * bIm - aIm * bRe + const float32x4_t a = vmulq_f32(xfBuf_re, ef_re); + const float32x4_t e = vmlaq_f32(a, xfBuf_im, ef_im); + const float32x4_t c = vmulq_f32(xfBuf_re, ef_im); + const float32x4_t f = vmlsq_f32(c, xfBuf_im, ef_re); + // Interleave real and imaginary parts. + const float32x4x2_t g_n_h = vzipq_f32(e, f); + // Store + vst1q_f32(&fft[2 * j + 0], g_n_h.val[0]); + vst1q_f32(&fft[2 * j + 4], g_n_h.val[1]); + } + // ... and fixup the first imaginary entry. + fft[1] = MulRe(aec->xfBuf[0][xPos + PART_LEN], + -aec->xfBuf[1][xPos + PART_LEN], + ef[0][PART_LEN], + ef[1][PART_LEN]); + + aec_rdft_inverse_128(fft); + memset(fft + PART_LEN, 0, sizeof(float) * PART_LEN); + + // fft scaling + { + const float scale = 2.0f / PART_LEN2; + const float32x4_t scale_ps = vmovq_n_f32(scale); + for (j = 0; j < PART_LEN; j += 4) { + const float32x4_t fft_ps = vld1q_f32(&fft[j]); + const float32x4_t fft_scale = vmulq_f32(fft_ps, scale_ps); + vst1q_f32(&fft[j], fft_scale); + } + } + aec_rdft_forward_128(fft); + + { + const float wt1 = aec->wfBuf[1][pos]; + aec->wfBuf[0][pos + PART_LEN] += fft[1]; + for (j = 0; j < PART_LEN; j += 4) { + float32x4_t wtBuf_re = vld1q_f32(&aec->wfBuf[0][pos + j]); + float32x4_t wtBuf_im = vld1q_f32(&aec->wfBuf[1][pos + j]); + const float32x4_t fft0 = vld1q_f32(&fft[2 * j + 0]); + const float32x4_t fft4 = vld1q_f32(&fft[2 * j + 4]); + const float32x4x2_t fft_re_im = vuzpq_f32(fft0, fft4); + wtBuf_re = vaddq_f32(wtBuf_re, fft_re_im.val[0]); + wtBuf_im = vaddq_f32(wtBuf_im, fft_re_im.val[1]); + + vst1q_f32(&aec->wfBuf[0][pos + j], wtBuf_re); + vst1q_f32(&aec->wfBuf[1][pos + j], wtBuf_im); + } + aec->wfBuf[1][pos] = wt1; + } + } +} + +extern const float WebRtcAec_weightCurve[65]; +extern const float WebRtcAec_overDriveCurve[65]; + +static float32x4_t vpowq_f32(float32x4_t a, float32x4_t b) { + // a^b = exp2(b * log2(a)) + // exp2(x) and log2(x) are calculated using polynomial approximations. + float32x4_t log2_a, b_log2_a, a_exp_b; + + // Calculate log2(x), x = a. + { + // To calculate log2(x), we decompose x like this: + // x = y * 2^n + // n is an integer + // y is in the [1.0, 2.0) range + // + // log2(x) = log2(y) + n + // n can be evaluated by playing with float representation. + // log2(y) in a small range can be approximated, this code uses an order + // five polynomial approximation. The coefficients have been + // estimated with the Remez algorithm and the resulting + // polynomial has a maximum relative error of 0.00086%. + + // Compute n. + // This is done by masking the exponent, shifting it into the top bit of + // the mantissa, putting eight into the biased exponent (to shift/ + // compensate the fact that the exponent has been shifted in the top/ + // fractional part and finally getting rid of the implicit leading one + // from the mantissa by substracting it out. + const uint32x4_t vec_float_exponent_mask = vdupq_n_u32(0x7F800000); + const uint32x4_t vec_eight_biased_exponent = vdupq_n_u32(0x43800000); + const uint32x4_t vec_implicit_leading_one = vdupq_n_u32(0x43BF8000); + const uint32x4_t two_n = vandq_u32(vreinterpretq_u32_f32(a), + vec_float_exponent_mask); + const uint32x4_t n_1 = vshrq_n_u32(two_n, kShiftExponentIntoTopMantissa); + const uint32x4_t n_0 = vorrq_u32(n_1, vec_eight_biased_exponent); + const float32x4_t n = + vsubq_f32(vreinterpretq_f32_u32(n_0), + vreinterpretq_f32_u32(vec_implicit_leading_one)); + // Compute y. + const uint32x4_t vec_mantissa_mask = vdupq_n_u32(0x007FFFFF); + const uint32x4_t vec_zero_biased_exponent_is_one = vdupq_n_u32(0x3F800000); + const uint32x4_t mantissa = vandq_u32(vreinterpretq_u32_f32(a), + vec_mantissa_mask); + const float32x4_t y = + vreinterpretq_f32_u32(vorrq_u32(mantissa, + vec_zero_biased_exponent_is_one)); + // Approximate log2(y) ~= (y - 1) * pol5(y). + // pol5(y) = C5 * y^5 + C4 * y^4 + C3 * y^3 + C2 * y^2 + C1 * y + C0 + const float32x4_t C5 = vdupq_n_f32(-3.4436006e-2f); + const float32x4_t C4 = vdupq_n_f32(3.1821337e-1f); + const float32x4_t C3 = vdupq_n_f32(-1.2315303f); + const float32x4_t C2 = vdupq_n_f32(2.5988452f); + const float32x4_t C1 = vdupq_n_f32(-3.3241990f); + const float32x4_t C0 = vdupq_n_f32(3.1157899f); + float32x4_t pol5_y = C5; + pol5_y = vmlaq_f32(C4, y, pol5_y); + pol5_y = vmlaq_f32(C3, y, pol5_y); + pol5_y = vmlaq_f32(C2, y, pol5_y); + pol5_y = vmlaq_f32(C1, y, pol5_y); + pol5_y = vmlaq_f32(C0, y, pol5_y); + const float32x4_t y_minus_one = + vsubq_f32(y, vreinterpretq_f32_u32(vec_zero_biased_exponent_is_one)); + const float32x4_t log2_y = vmulq_f32(y_minus_one, pol5_y); + + // Combine parts. + log2_a = vaddq_f32(n, log2_y); + } + + // b * log2(a) + b_log2_a = vmulq_f32(b, log2_a); + + // Calculate exp2(x), x = b * log2(a). + { + // To calculate 2^x, we decompose x like this: + // x = n + y + // n is an integer, the value of x - 0.5 rounded down, therefore + // y is in the [0.5, 1.5) range + // + // 2^x = 2^n * 2^y + // 2^n can be evaluated by playing with float representation. + // 2^y in a small range can be approximated, this code uses an order two + // polynomial approximation. The coefficients have been estimated + // with the Remez algorithm and the resulting polynomial has a + // maximum relative error of 0.17%. + // To avoid over/underflow, we reduce the range of input to ]-127, 129]. + const float32x4_t max_input = vdupq_n_f32(129.f); + const float32x4_t min_input = vdupq_n_f32(-126.99999f); + const float32x4_t x_min = vminq_f32(b_log2_a, max_input); + const float32x4_t x_max = vmaxq_f32(x_min, min_input); + // Compute n. + const float32x4_t half = vdupq_n_f32(0.5f); + const float32x4_t x_minus_half = vsubq_f32(x_max, half); + const int32x4_t x_minus_half_floor = vcvtq_s32_f32(x_minus_half); + + // Compute 2^n. + const int32x4_t float_exponent_bias = vdupq_n_s32(127); + const int32x4_t two_n_exponent = + vaddq_s32(x_minus_half_floor, float_exponent_bias); + const float32x4_t two_n = + vreinterpretq_f32_s32(vshlq_n_s32(two_n_exponent, kFloatExponentShift)); + // Compute y. + const float32x4_t y = vsubq_f32(x_max, vcvtq_f32_s32(x_minus_half_floor)); + + // Approximate 2^y ~= C2 * y^2 + C1 * y + C0. + const float32x4_t C2 = vdupq_n_f32(3.3718944e-1f); + const float32x4_t C1 = vdupq_n_f32(6.5763628e-1f); + const float32x4_t C0 = vdupq_n_f32(1.0017247f); + float32x4_t exp2_y = C2; + exp2_y = vmlaq_f32(C1, y, exp2_y); + exp2_y = vmlaq_f32(C0, y, exp2_y); + + // Combine parts. + a_exp_b = vmulq_f32(exp2_y, two_n); + } + + return a_exp_b; +} + +static void OverdriveAndSuppressNEON(AecCore* aec, + float hNl[PART_LEN1], + const float hNlFb, + float efw[2][PART_LEN1]) { + int i; + const float32x4_t vec_hNlFb = vmovq_n_f32(hNlFb); + const float32x4_t vec_one = vdupq_n_f32(1.0f); + const float32x4_t vec_minus_one = vdupq_n_f32(-1.0f); + const float32x4_t vec_overDriveSm = vmovq_n_f32(aec->overDriveSm); + + // vectorized code (four at once) + for (i = 0; i + 3 < PART_LEN1; i += 4) { + // Weight subbands + float32x4_t vec_hNl = vld1q_f32(&hNl[i]); + const float32x4_t vec_weightCurve = vld1q_f32(&WebRtcAec_weightCurve[i]); + const uint32x4_t bigger = vcgtq_f32(vec_hNl, vec_hNlFb); + const float32x4_t vec_weightCurve_hNlFb = vmulq_f32(vec_weightCurve, + vec_hNlFb); + const float32x4_t vec_one_weightCurve = vsubq_f32(vec_one, vec_weightCurve); + const float32x4_t vec_one_weightCurve_hNl = vmulq_f32(vec_one_weightCurve, + vec_hNl); + const uint32x4_t vec_if0 = vandq_u32(vmvnq_u32(bigger), + vreinterpretq_u32_f32(vec_hNl)); + const float32x4_t vec_one_weightCurve_add = + vaddq_f32(vec_weightCurve_hNlFb, vec_one_weightCurve_hNl); + const uint32x4_t vec_if1 = + vandq_u32(bigger, vreinterpretq_u32_f32(vec_one_weightCurve_add)); + + vec_hNl = vreinterpretq_f32_u32(vorrq_u32(vec_if0, vec_if1)); + + { + const float32x4_t vec_overDriveCurve = + vld1q_f32(&WebRtcAec_overDriveCurve[i]); + const float32x4_t vec_overDriveSm_overDriveCurve = + vmulq_f32(vec_overDriveSm, vec_overDriveCurve); + vec_hNl = vpowq_f32(vec_hNl, vec_overDriveSm_overDriveCurve); + vst1q_f32(&hNl[i], vec_hNl); + } + + // Suppress error signal + { + float32x4_t vec_efw_re = vld1q_f32(&efw[0][i]); + float32x4_t vec_efw_im = vld1q_f32(&efw[1][i]); + vec_efw_re = vmulq_f32(vec_efw_re, vec_hNl); + vec_efw_im = vmulq_f32(vec_efw_im, vec_hNl); + + // Ooura fft returns incorrect sign on imaginary component. It matters + // here because we are making an additive change with comfort noise. + vec_efw_im = vmulq_f32(vec_efw_im, vec_minus_one); + vst1q_f32(&efw[0][i], vec_efw_re); + vst1q_f32(&efw[1][i], vec_efw_im); + } + } + + // scalar code for the remaining items. + for (; i < PART_LEN1; i++) { + // Weight subbands + if (hNl[i] > hNlFb) { + hNl[i] = WebRtcAec_weightCurve[i] * hNlFb + + (1 - WebRtcAec_weightCurve[i]) * hNl[i]; + } + + hNl[i] = powf(hNl[i], aec->overDriveSm * WebRtcAec_overDriveCurve[i]); + + // Suppress error signal + efw[0][i] *= hNl[i]; + efw[1][i] *= hNl[i]; + + // Ooura fft returns incorrect sign on imaginary component. It matters + // here because we are making an additive change with comfort noise. + efw[1][i] *= -1; + } +} + +void WebRtcAec_InitAec_neon(void) { + WebRtcAec_FilterFar = FilterFarNEON; + WebRtcAec_ScaleErrorSignal = ScaleErrorSignalNEON; + WebRtcAec_FilterAdaptation = FilterAdaptationNEON; + WebRtcAec_OverdriveAndSuppress = OverdriveAndSuppressNEON; +} + diff --git a/webrtc/modules/audio_processing/aec/aec_core_sse2.c b/webrtc/modules/audio_processing/aec/aec_core_sse2.c index 8d4afdbaa..1489d26e5 100644 --- a/webrtc/modules/audio_processing/aec/aec_core_sse2.c +++ b/webrtc/modules/audio_processing/aec/aec_core_sse2.c @@ -354,8 +354,8 @@ static __m128 mm_pow_ps(__m128 a, __m128 b) { return a_exp_b; } -extern const float WebRtcAec_weightCurve[65]; -extern const float WebRtcAec_overDriveCurve[65]; +extern ALIGN16_BEG const float ALIGN16_END WebRtcAec_weightCurve[65]; +extern ALIGN16_BEG const float ALIGN16_END WebRtcAec_overDriveCurve[65]; static void OverdriveAndSuppressSSE2(AecCore* aec, float hNl[PART_LEN1], diff --git a/webrtc/modules/audio_processing/aec/echo_cancellation.c b/webrtc/modules/audio_processing/aec/echo_cancellation.c index bbdd5f628..ba3b9243e 100644 --- a/webrtc/modules/audio_processing/aec/echo_cancellation.c +++ b/webrtc/modules/audio_processing/aec/echo_cancellation.c @@ -104,18 +104,18 @@ int webrtc_aec_instance_count = 0; static void EstBufDelayNormal(aecpc_t* aecInst); static void EstBufDelayExtended(aecpc_t* aecInst); static int ProcessNormal(aecpc_t* self, - const int16_t* near, - const int16_t* near_high, - int16_t* out, - int16_t* out_high, + const float* near, + const float* near_high, + float* out, + float* out_high, int16_t num_samples, int16_t reported_delay_ms, int32_t skew); static void ProcessExtended(aecpc_t* self, - const int16_t* near, - const int16_t* near_high, - int16_t* out, - int16_t* out_high, + const float* near, + const float* near_high, + float* out, + float* out_high, int16_t num_samples, int16_t reported_delay_ms, int32_t skew); @@ -254,7 +254,7 @@ int32_t WebRtcAec_Init(void* aecInst, int32_t sampFreq, int32_t scSampFreq) { aecpc->checkBuffSize = 1; aecpc->firstVal = 0; - aecpc->startup_phase = 1; + aecpc->startup_phase = WebRtcAec_reported_delay_enabled(aecpc->aec); aecpc->bufSizeStart = 0; aecpc->checkBufSizeCtr = 0; aecpc->msInSndCardBuf = 0; @@ -372,10 +372,10 @@ int32_t WebRtcAec_BufferFarend(void* aecInst, } int32_t WebRtcAec_Process(void* aecInst, - const int16_t* nearend, - const int16_t* nearendH, - int16_t* out, - int16_t* outH, + const float* nearend, + const float* nearendH, + float* out, + float* outH, int16_t nrOfSamples, int16_t msInSndCardBuf, int32_t skew) { @@ -632,10 +632,10 @@ AecCore* WebRtcAec_aec_core(void* handle) { } static int ProcessNormal(aecpc_t* aecpc, - const int16_t* nearend, - const int16_t* nearendH, - int16_t* out, - int16_t* outH, + const float* nearend, + const float* nearendH, + float* out, + float* outH, int16_t nrOfSamples, int16_t msInSndCardBuf, int32_t skew) { @@ -689,10 +689,10 @@ static int ProcessNormal(aecpc_t* aecpc, if (aecpc->startup_phase) { // Only needed if they don't already point to the same place. if (nearend != out) { - memcpy(out, nearend, sizeof(short) * nrOfSamples); + memcpy(out, nearend, sizeof(*out) * nrOfSamples); } if (nearendH != outH) { - memcpy(outH, nearendH, sizeof(short) * nrOfSamples); + memcpy(outH, nearendH, sizeof(*outH) * nrOfSamples); } // The AEC is in the start up mode @@ -766,7 +766,9 @@ static int ProcessNormal(aecpc_t* aecpc, } } else { // AEC is enabled. - EstBufDelayNormal(aecpc); + if (WebRtcAec_reported_delay_enabled(aecpc->aec)) { + EstBufDelayNormal(aecpc); + } // Note that 1 frame is supported for NB and 2 frames for WB. for (i = 0; i < nFrames; i++) { @@ -787,10 +789,10 @@ static int ProcessNormal(aecpc_t* aecpc, } static void ProcessExtended(aecpc_t* self, - const int16_t* near, - const int16_t* near_high, - int16_t* out, - int16_t* out_high, + const float* near, + const float* near_high, + float* out, + float* out_high, int16_t num_samples, int16_t reported_delay_ms, int32_t skew) { @@ -821,10 +823,10 @@ static void ProcessExtended(aecpc_t* self, if (!self->farend_started) { // Only needed if they don't already point to the same place. if (near != out) { - memcpy(out, near, sizeof(short) * num_samples); + memcpy(out, near, sizeof(*out) * num_samples); } if (near_high != out_high) { - memcpy(out_high, near_high, sizeof(short) * num_samples); + memcpy(out_high, near_high, sizeof(*out_high) * num_samples); } return; } @@ -842,7 +844,9 @@ static void ProcessExtended(aecpc_t* self, self->startup_phase = 0; } - EstBufDelayExtended(self); + if (WebRtcAec_reported_delay_enabled(self->aec)) { + EstBufDelayExtended(self); + } { // |delay_diff_offset| gives us the option to manually rewind the delay on diff --git a/webrtc/modules/audio_processing/aec/include/echo_cancellation.h b/webrtc/modules/audio_processing/aec/include/echo_cancellation.h index 4c852cf64..dc64a345c 100644 --- a/webrtc/modules/audio_processing/aec/include/echo_cancellation.h +++ b/webrtc/modules/audio_processing/aec/include/echo_cancellation.h @@ -68,7 +68,7 @@ extern "C" { * * Inputs Description * ------------------------------------------------------------------- - * void **aecInst Pointer to the AEC instance to be created + * void** aecInst Pointer to the AEC instance to be created * and initialized * * Outputs Description @@ -83,7 +83,7 @@ int32_t WebRtcAec_Create(void** aecInst); * * Inputs Description * ------------------------------------------------------------------- - * void *aecInst Pointer to the AEC instance + * void* aecInst Pointer to the AEC instance * * Outputs Description * ------------------------------------------------------------------- @@ -97,7 +97,7 @@ int32_t WebRtcAec_Free(void* aecInst); * * Inputs Description * ------------------------------------------------------------------- - * void *aecInst Pointer to the AEC instance + * void* aecInst Pointer to the AEC instance * int32_t sampFreq Sampling frequency of data * int32_t scSampFreq Soundcard sampling frequency * @@ -113,8 +113,8 @@ int32_t WebRtcAec_Init(void* aecInst, int32_t sampFreq, int32_t scSampFreq); * * Inputs Description * ------------------------------------------------------------------- - * void *aecInst Pointer to the AEC instance - * int16_t *farend In buffer containing one frame of + * void* aecInst Pointer to the AEC instance + * int16_t* farend In buffer containing one frame of * farend signal for L band * int16_t nrOfSamples Number of samples in farend buffer * @@ -132,10 +132,10 @@ int32_t WebRtcAec_BufferFarend(void* aecInst, * * Inputs Description * ------------------------------------------------------------------- - * void *aecInst Pointer to the AEC instance - * int16_t *nearend In buffer containing one frame of + * void* aecInst Pointer to the AEC instance + * float* nearend In buffer containing one frame of * nearend+echo signal for L band - * int16_t *nearendH In buffer containing one frame of + * float* nearendH In buffer containing one frame of * nearend+echo signal for H band * int16_t nrOfSamples Number of samples in nearend buffer * int16_t msInSndCardBuf Delay estimate for sound card and @@ -146,18 +146,18 @@ int32_t WebRtcAec_BufferFarend(void* aecInst, * * Outputs Description * ------------------------------------------------------------------- - * int16_t *out Out buffer, one frame of processed nearend + * float* out Out buffer, one frame of processed nearend * for L band - * int16_t *outH Out buffer, one frame of processed nearend + * float* outH Out buffer, one frame of processed nearend * for H band * int32_t return 0: OK * -1: error */ int32_t WebRtcAec_Process(void* aecInst, - const int16_t* nearend, - const int16_t* nearendH, - int16_t* out, - int16_t* outH, + const float* nearend, + const float* nearendH, + float* out, + float* outH, int16_t nrOfSamples, int16_t msInSndCardBuf, int32_t skew); @@ -167,7 +167,7 @@ int32_t WebRtcAec_Process(void* aecInst, * * Inputs Description * ------------------------------------------------------------------- - * void *handle Pointer to the AEC instance + * void* handle Pointer to the AEC instance * AecConfig config Config instance that contains all * properties to be set * @@ -183,11 +183,11 @@ int WebRtcAec_set_config(void* handle, AecConfig config); * * Inputs Description * ------------------------------------------------------------------- - * void *handle Pointer to the AEC instance + * void* handle Pointer to the AEC instance * * Outputs Description * ------------------------------------------------------------------- - * int *status 0: Almost certainly nearend single-talk + * int* status 0: Almost certainly nearend single-talk * 1: Might not be neared single-talk * int return 0: OK * -1: error @@ -199,11 +199,11 @@ int WebRtcAec_get_echo_status(void* handle, int* status); * * Inputs Description * ------------------------------------------------------------------- - * void *handle Pointer to the AEC instance + * void* handle Pointer to the AEC instance * * Outputs Description * ------------------------------------------------------------------- - * AecMetrics *metrics Struct which will be filled out with the + * AecMetrics* metrics Struct which will be filled out with the * current echo metrics. * int return 0: OK * -1: error @@ -232,7 +232,7 @@ int WebRtcAec_GetDelayMetrics(void* handle, int* median, int* std); * * Inputs Description * ------------------------------------------------------------------- - * void *aecInst Pointer to the AEC instance + * void* aecInst Pointer to the AEC instance * * Outputs Description * ------------------------------------------------------------------- diff --git a/webrtc/modules/audio_processing/aec/system_delay_unittest.cc b/webrtc/modules/audio_processing/aec/system_delay_unittest.cc index a19030ae3..5fbc56003 100644 --- a/webrtc/modules/audio_processing/aec/system_delay_unittest.cc +++ b/webrtc/modules/audio_processing/aec/system_delay_unittest.cc @@ -9,12 +9,12 @@ */ #include "testing/gtest/include/gtest/gtest.h" - extern "C" { #include "webrtc/modules/audio_processing/aec/aec_core.h" } #include "webrtc/modules/audio_processing/aec/echo_cancellation_internal.h" #include "webrtc/modules/audio_processing/aec/include/echo_cancellation.h" +#include "webrtc/test/testsupport/gtest_disable.h" #include "webrtc/typedefs.h" namespace { @@ -46,16 +46,18 @@ class SystemDelayTest : public ::testing::Test { aecpc_t* self_; int samples_per_frame_; // Dummy input/output speech data. - int16_t far_[160]; - int16_t near_[160]; - int16_t out_[160]; + static const int kSamplesPerChunk = 160; + int16_t far_[kSamplesPerChunk]; + float near_[kSamplesPerChunk]; + float out_[kSamplesPerChunk]; }; SystemDelayTest::SystemDelayTest() : handle_(NULL), self_(NULL), samples_per_frame_(0) { // Dummy input data are set with more or less arbitrary non-zero values. memset(far_, 1, sizeof(far_)); - memset(near_, 2, sizeof(near_)); + for (int i = 0; i < kSamplesPerChunk; i++) + near_[i] = 514.0; memset(out_, 0, sizeof(out_)); } @@ -246,11 +248,15 @@ TEST_F(SystemDelayTest, CorrectDelayAfterUnstableStartup) { } } -TEST_F(SystemDelayTest, CorrectDelayAfterStableBufferBuildUp) { +TEST_F(SystemDelayTest, + DISABLED_ON_ANDROID(CorrectDelayAfterStableBufferBuildUp)) { // In this test we start by establishing the device buffer size during stable // conditions, but with an empty internal far-end buffer. Once that is done we // verify that the system delay is increased correctly until we have reach an // internal buffer size of 75% of what's been reported. + + // This test assumes the reported delays are used. + WebRtcAec_enable_reported_delay(WebRtcAec_aec_core(handle_), 1); for (size_t i = 0; i < kNumSampleRates; i++) { Init(kSampleRateHz[i]); @@ -327,11 +333,14 @@ TEST_F(SystemDelayTest, CorrectDelayWhenBufferUnderrun) { } } -TEST_F(SystemDelayTest, CorrectDelayDuringDrift) { +TEST_F(SystemDelayTest, DISABLED_ON_ANDROID(CorrectDelayDuringDrift)) { // This drift test should verify that the system delay is never exceeding the // device buffer. The drift is simulated by decreasing the reported device // buffer size by 1 ms every 100 ms. If the device buffer size goes below 30 // ms we jump (add) 10 ms to give a repeated pattern. + + // This test assumes the reported delays are used. + WebRtcAec_enable_reported_delay(WebRtcAec_aec_core(handle_), 1); for (size_t i = 0; i < kNumSampleRates; i++) { Init(kSampleRateHz[i]); RunStableStartup(); @@ -358,13 +367,16 @@ TEST_F(SystemDelayTest, CorrectDelayDuringDrift) { } } -TEST_F(SystemDelayTest, ShouldRecoverAfterGlitch) { +TEST_F(SystemDelayTest, DISABLED_ON_ANDROID(ShouldRecoverAfterGlitch)) { // This glitch test should verify that the system delay recovers if there is // a glitch in data. The data glitch is constructed as 200 ms of buffering // after which the stable procedure continues. The glitch is never reported by // the device. // The system is said to be in a non-causal state if the difference between // the device buffer and system delay is less than a block (64 samples). + + // This test assumes the reported delays are used. + WebRtcAec_enable_reported_delay(WebRtcAec_aec_core(handle_), 1); for (size_t i = 0; i < kNumSampleRates; i++) { Init(kSampleRateHz[i]); RunStableStartup(); diff --git a/webrtc/modules/audio_processing/aecm/aecm_core_c.c b/webrtc/modules/audio_processing/aecm/aecm_core_c.c index 63d4ac902..f8491e973 100644 --- a/webrtc/modules/audio_processing/aecm/aecm_core_c.c +++ b/webrtc/modules/audio_processing/aecm/aecm_core_c.c @@ -260,7 +260,7 @@ static int TimeToFrequencyDomain(AecmCore_t* aecm, __asm __volatile( "smulbb %[tmp32no1], %[real], %[real]\n\t" "smlabb %[tmp32no2], %[imag], %[imag], %[tmp32no1]\n\t" - :[tmp32no1]"+r"(tmp32no1), + :[tmp32no1]"+&r"(tmp32no1), [tmp32no2]"=r"(tmp32no2) :[real]"r"(freq_signal[i].real), [imag]"r"(freq_signal[i].imag) diff --git a/webrtc/modules/audio_processing/aecm/echo_control_mobile.c b/webrtc/modules/audio_processing/aecm/echo_control_mobile.c index b896de0a2..088bbf03f 100644 --- a/webrtc/modules/audio_processing/aecm/echo_control_mobile.c +++ b/webrtc/modules/audio_processing/aecm/echo_control_mobile.c @@ -443,27 +443,14 @@ int32_t WebRtcAecm_Process(void *aecmInst, const int16_t *nearendNoisy, // Call the AECM /*WebRtcAecm_ProcessFrame(aecm->aecmCore, farend, &nearend[FRAME_LEN * i], &out[FRAME_LEN * i], aecm->knownDelay);*/ - if (nearendClean == NULL) - { - if (WebRtcAecm_ProcessFrame(aecm->aecmCore, - farend_ptr, - &nearendNoisy[FRAME_LEN * i], - NULL, - &out[FRAME_LEN * i]) == -1) - { - return -1; - } - } else - { - if (WebRtcAecm_ProcessFrame(aecm->aecmCore, - farend_ptr, - &nearendNoisy[FRAME_LEN * i], - &nearendClean[FRAME_LEN * i], - &out[FRAME_LEN * i]) == -1) - { - return -1; - } - } + if (WebRtcAecm_ProcessFrame(aecm->aecmCore, + farend_ptr, + &nearendNoisy[FRAME_LEN * i], + (nearendClean + ? &nearendClean[FRAME_LEN * i] + : NULL), + &out[FRAME_LEN * i]) == -1) + return -1; } } diff --git a/webrtc/modules/audio_processing/aecm/include/echo_control_mobile.h b/webrtc/modules/audio_processing/aecm/include/echo_control_mobile.h index 8ea2e87e2..ac43576dd 100644 --- a/webrtc/modules/audio_processing/aecm/include/echo_control_mobile.h +++ b/webrtc/modules/audio_processing/aecm/include/echo_control_mobile.h @@ -45,7 +45,7 @@ extern "C" { * * Inputs Description * ------------------------------------------------------------------- - * void **aecmInst Pointer to the AECM instance to be + * void** aecmInst Pointer to the AECM instance to be * created and initialized * * Outputs Description @@ -60,11 +60,11 @@ int32_t WebRtcAecm_Create(void **aecmInst); * * Inputs Description * ------------------------------------------------------------------- - * void *aecmInst Pointer to the AECM instance + * void* aecmInst Pointer to the AECM instance * * Outputs Description * ------------------------------------------------------------------- - * int32_t return 0: OK + * int32_t return 0: OK * -1: error */ int32_t WebRtcAecm_Free(void *aecmInst); @@ -74,7 +74,7 @@ int32_t WebRtcAecm_Free(void *aecmInst); * * Inputs Description * ------------------------------------------------------------------- - * void *aecmInst Pointer to the AECM instance + * void* aecmInst Pointer to the AECM instance * int32_t sampFreq Sampling frequency of data * * Outputs Description @@ -89,8 +89,8 @@ int32_t WebRtcAecm_Init(void* aecmInst, int32_t sampFreq); * * Inputs Description * ------------------------------------------------------------------- - * void *aecmInst Pointer to the AECM instance - * int16_t *farend In buffer containing one frame of + * void* aecmInst Pointer to the AECM instance + * int16_t* farend In buffer containing one frame of * farend signal * int16_t nrOfSamples Number of samples in farend buffer * @@ -106,14 +106,14 @@ int32_t WebRtcAecm_BufferFarend(void* aecmInst, /* * Runs the AECM on an 80 or 160 sample blocks of data. * - * Inputs Description + * Inputs Description * ------------------------------------------------------------------- - * void *aecmInst Pointer to the AECM instance - * int16_t *nearendNoisy In buffer containing one frame of + * void* aecmInst Pointer to the AECM instance + * int16_t* nearendNoisy In buffer containing one frame of * reference nearend+echo signal. If * noise reduction is active, provide * the noisy signal here. - * int16_t *nearendClean In buffer containing one frame of + * int16_t* nearendClean In buffer containing one frame of * nearend+echo signal. If noise * reduction is active, provide the * clean signal here. Otherwise pass a @@ -122,11 +122,11 @@ int32_t WebRtcAecm_BufferFarend(void* aecmInst, * int16_t msInSndCardBuf Delay estimate for sound card and * system buffers * - * Outputs Description + * Outputs Description * ------------------------------------------------------------------- - * int16_t *out Out buffer, one frame of processed nearend - * int32_t return 0: OK - * -1: error + * int16_t* out Out buffer, one frame of processed nearend + * int32_t return 0: OK + * -1: error */ int32_t WebRtcAecm_Process(void* aecmInst, const int16_t* nearendNoisy, @@ -140,8 +140,8 @@ int32_t WebRtcAecm_Process(void* aecmInst, * * Inputs Description * ------------------------------------------------------------------- - * void *aecmInst Pointer to the AECM instance - * AecmConfig config Config instance that contains all + * void* aecmInst Pointer to the AECM instance + * AecmConfig config Config instance that contains all * properties to be set * * Outputs Description @@ -156,11 +156,11 @@ int32_t WebRtcAecm_set_config(void* aecmInst, AecmConfig config); * * Inputs Description * ------------------------------------------------------------------- - * void *aecmInst Pointer to the AECM instance + * void* aecmInst Pointer to the AECM instance * * Outputs Description * ------------------------------------------------------------------- - * AecmConfig *config Pointer to the config instance that + * AecmConfig* config Pointer to the config instance that * all properties will be written to * int32_t return 0: OK * -1: error @@ -178,7 +178,7 @@ int32_t WebRtcAecm_get_config(void *aecmInst, AecmConfig *config); * * Outputs Description * ------------------------------------------------------------------- - * int32_t return 0: OK + * int32_t return 0: OK * -1: error */ int32_t WebRtcAecm_InitEchoPath(void* aecmInst, @@ -197,7 +197,7 @@ int32_t WebRtcAecm_InitEchoPath(void* aecmInst, * * Outputs Description * ------------------------------------------------------------------- - * int32_t return 0: OK + * int32_t return 0: OK * -1: error */ int32_t WebRtcAecm_GetEchoPath(void* aecmInst, @@ -209,7 +209,7 @@ int32_t WebRtcAecm_GetEchoPath(void* aecmInst, * * Outputs Description * ------------------------------------------------------------------- - * size_t return : size in bytes + * size_t return Size in bytes */ size_t WebRtcAecm_echo_path_size_bytes(); @@ -218,7 +218,7 @@ size_t WebRtcAecm_echo_path_size_bytes(); * * Inputs Description * ------------------------------------------------------------------- - * void *aecmInst Pointer to the AECM instance + * void* aecmInst Pointer to the AECM instance * * Outputs Description * ------------------------------------------------------------------- diff --git a/webrtc/modules/audio_processing/agc/digital_agc.c b/webrtc/modules/audio_processing/agc/digital_agc.c index faef91417..4b169c180 100644 --- a/webrtc/modules/audio_processing/agc/digital_agc.c +++ b/webrtc/modules/audio_processing/agc/digital_agc.c @@ -118,7 +118,7 @@ int32_t WebRtcAgc_CalculateGainTable(int32_t *gainTable, // Q16 limiterLvlX = analogTarget - limiterOffset; limiterIdx = 2 + WebRtcSpl_DivW32W16ResW16(WEBRTC_SPL_LSHIFT_W32((int32_t)limiterLvlX, 13), - WEBRTC_SPL_RSHIFT_U16(kLog10_2, 1)); + (kLog10_2 / 2)); tmp16no1 = WebRtcSpl_DivW32W16ResW16(limiterOffset + (kCompRatio >> 1), kCompRatio); limiterLvl = targetLevelDbfs + tmp16no1; @@ -773,7 +773,7 @@ int16_t WebRtcAgc_ProcessVad(AgcVad_t *state, // (i) VAD state tmp16 = WEBRTC_SPL_LSHIFT_W16(3, 12); tmp32 = WEBRTC_SPL_MUL_16_16(tmp16, (dB - state->meanLongTerm)); tmp32 = WebRtcSpl_DivW32W16(tmp32, state->stdLongTerm); - tmpU16 = WEBRTC_SPL_LSHIFT_U16((uint16_t)13, 12); + tmpU16 = (13 << 12); tmp32b = WEBRTC_SPL_MUL_16_U16(state->logRatio, tmpU16); tmp32 += WEBRTC_SPL_RSHIFT_W32(tmp32b, 10); diff --git a/webrtc/modules/audio_processing/audio_buffer.cc b/webrtc/modules/audio_processing/audio_buffer.cc index 90824770b..b0f1eb6c1 100644 --- a/webrtc/modules/audio_processing/audio_buffer.cc +++ b/webrtc/modules/audio_processing/audio_buffer.cc @@ -11,6 +11,7 @@ #include "webrtc/modules/audio_processing/audio_buffer.h" #include "webrtc/common_audio/include/audio_util.h" +#include "webrtc/common_audio/resampler/push_sinc_resampler.h" #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" namespace webrtc { @@ -22,154 +23,343 @@ enum { kSamplesPer32kHzChannel = 320 }; -void StereoToMono(const int16_t* left, const int16_t* right, - int16_t* out, int samples_per_channel) { - assert(left != NULL && right != NULL && out != NULL); - for (int i = 0; i < samples_per_channel; i++) { - int32_t data32 = (static_cast(left[i]) + - static_cast(right[i])) >> 1; +bool HasKeyboardChannel(AudioProcessing::ChannelLayout layout) { + switch (layout) { + case AudioProcessing::kMono: + case AudioProcessing::kStereo: + return false; + case AudioProcessing::kMonoAndKeyboard: + case AudioProcessing::kStereoAndKeyboard: + return true; + } + assert(false); + return false; +} + +int KeyboardChannelIndex(AudioProcessing::ChannelLayout layout) { + switch (layout) { + case AudioProcessing::kMono: + case AudioProcessing::kStereo: + assert(false); + return -1; + case AudioProcessing::kMonoAndKeyboard: + return 1; + case AudioProcessing::kStereoAndKeyboard: + return 2; + } + assert(false); + return -1; +} + - out[i] = WebRtcSpl_SatW32ToW16(data32); +void StereoToMono(const float* left, const float* right, float* out, + int samples_per_channel) { + for (int i = 0; i < samples_per_channel; ++i) { + out[i] = (left[i] + right[i]) / 2; } } + +void StereoToMono(const int16_t* left, const int16_t* right, int16_t* out, + int samples_per_channel) { + for (int i = 0; i < samples_per_channel; ++i) { + out[i] = (left[i] + right[i]) >> 1; + } +} + } // namespace -struct AudioChannel { - AudioChannel() { - memset(data, 0, sizeof(data)); +// One int16_t and one float ChannelBuffer that are kept in sync. The sync is +// broken when someone requests write access to either ChannelBuffer, and +// reestablished when someone requests the outdated ChannelBuffer. It is +// therefore safe to use the return value of ibuf() and fbuf() until the next +// call to the other method. +class IFChannelBuffer { + public: + IFChannelBuffer(int samples_per_channel, int num_channels) + : ivalid_(true), + ibuf_(samples_per_channel, num_channels), + fvalid_(true), + fbuf_(samples_per_channel, num_channels) {} + + ChannelBuffer* ibuf() { + RefreshI(); + fvalid_ = false; + return &ibuf_; + } + + ChannelBuffer* fbuf() { + RefreshF(); + ivalid_ = false; + return &fbuf_; } - int16_t data[kSamplesPer32kHzChannel]; + private: + void RefreshF() { + if (!fvalid_) { + assert(ivalid_); + const int16_t* const int_data = ibuf_.data(); + float* const float_data = fbuf_.data(); + const int length = fbuf_.length(); + for (int i = 0; i < length; ++i) + float_data[i] = int_data[i]; + fvalid_ = true; + } + } + + void RefreshI() { + if (!ivalid_) { + assert(fvalid_); + const float* const float_data = fbuf_.data(); + int16_t* const int_data = ibuf_.data(); + const int length = ibuf_.length(); + for (int i = 0; i < length; ++i) + int_data[i] = WEBRTC_SPL_SAT(std::numeric_limits::max(), + float_data[i], + std::numeric_limits::min()); + ivalid_ = true; + } + } + + bool ivalid_; + ChannelBuffer ibuf_; + bool fvalid_; + ChannelBuffer fbuf_; }; -struct SplitAudioChannel { - SplitAudioChannel() { - memset(low_pass_data, 0, sizeof(low_pass_data)); - memset(high_pass_data, 0, sizeof(high_pass_data)); - memset(analysis_filter_state1, 0, sizeof(analysis_filter_state1)); - memset(analysis_filter_state2, 0, sizeof(analysis_filter_state2)); - memset(synthesis_filter_state1, 0, sizeof(synthesis_filter_state1)); - memset(synthesis_filter_state2, 0, sizeof(synthesis_filter_state2)); +class SplitChannelBuffer { + public: + SplitChannelBuffer(int samples_per_split_channel, int num_channels) + : low_(samples_per_split_channel, num_channels), + high_(samples_per_split_channel, num_channels) { } + ~SplitChannelBuffer() {} - int16_t low_pass_data[kSamplesPer16kHzChannel]; - int16_t high_pass_data[kSamplesPer16kHzChannel]; + int16_t* low_channel(int i) { return low_.ibuf()->channel(i); } + int16_t* high_channel(int i) { return high_.ibuf()->channel(i); } + float* low_channel_f(int i) { return low_.fbuf()->channel(i); } + float* high_channel_f(int i) { return high_.fbuf()->channel(i); } - int32_t analysis_filter_state1[6]; - int32_t analysis_filter_state2[6]; - int32_t synthesis_filter_state1[6]; - int32_t synthesis_filter_state2[6]; + private: + IFChannelBuffer low_; + IFChannelBuffer high_; }; -// TODO(andrew): check range of input parameters? -AudioBuffer::AudioBuffer(int max_num_channels, - int samples_per_channel) - : max_num_channels_(max_num_channels), - num_channels_(0), +AudioBuffer::AudioBuffer(int input_samples_per_channel, + int num_input_channels, + int process_samples_per_channel, + int num_process_channels, + int output_samples_per_channel) + : input_samples_per_channel_(input_samples_per_channel), + num_input_channels_(num_input_channels), + proc_samples_per_channel_(process_samples_per_channel), + num_proc_channels_(num_process_channels), + output_samples_per_channel_(output_samples_per_channel), + samples_per_split_channel_(proc_samples_per_channel_), num_mixed_channels_(0), num_mixed_low_pass_channels_(0), - data_was_mixed_(false), - samples_per_channel_(samples_per_channel), - samples_per_split_channel_(samples_per_channel), reference_copied_(false), activity_(AudioFrame::kVadUnknown), - is_muted_(false), - data_(NULL), - channels_(NULL), - split_channels_(NULL), - mixed_channels_(NULL), - mixed_low_pass_channels_(NULL), - low_pass_reference_channels_(NULL) { - channels_.reset(new AudioChannel[max_num_channels_]); - mixed_channels_.reset(new AudioChannel[max_num_channels_]); - mixed_low_pass_channels_.reset(new AudioChannel[max_num_channels_]); - low_pass_reference_channels_.reset(new AudioChannel[max_num_channels_]); - - if (samples_per_channel_ == kSamplesPer32kHzChannel) { - split_channels_.reset(new SplitAudioChannel[max_num_channels_]); + keyboard_data_(NULL), + channels_(new IFChannelBuffer(proc_samples_per_channel_, + num_proc_channels_)) { + assert(input_samples_per_channel_ > 0); + assert(proc_samples_per_channel_ > 0); + assert(output_samples_per_channel_ > 0); + assert(num_input_channels_ > 0 && num_input_channels_ <= 2); + assert(num_proc_channels_ <= num_input_channels); + + if (num_input_channels_ == 2 && num_proc_channels_ == 1) { + input_buffer_.reset(new ChannelBuffer(input_samples_per_channel_, + num_proc_channels_)); + } + + if (input_samples_per_channel_ != proc_samples_per_channel_ || + output_samples_per_channel_ != proc_samples_per_channel_) { + // Create an intermediate buffer for resampling. + process_buffer_.reset(new ChannelBuffer(proc_samples_per_channel_, + num_proc_channels_)); + } + + if (input_samples_per_channel_ != proc_samples_per_channel_) { + input_resamplers_.reserve(num_proc_channels_); + for (int i = 0; i < num_proc_channels_; ++i) { + input_resamplers_.push_back( + new PushSincResampler(input_samples_per_channel_, + proc_samples_per_channel_)); + } + } + + if (output_samples_per_channel_ != proc_samples_per_channel_) { + output_resamplers_.reserve(num_proc_channels_); + for (int i = 0; i < num_proc_channels_; ++i) { + output_resamplers_.push_back( + new PushSincResampler(proc_samples_per_channel_, + output_samples_per_channel_)); + } + } + + if (proc_samples_per_channel_ == kSamplesPer32kHzChannel) { samples_per_split_channel_ = kSamplesPer16kHzChannel; + split_channels_.reset(new SplitChannelBuffer(samples_per_split_channel_, + num_proc_channels_)); + filter_states_.reset(new SplitFilterStates[num_proc_channels_]); } } AudioBuffer::~AudioBuffer() {} -void AudioBuffer::InitForNewData(int num_channels) { - num_channels_ = num_channels; - data_ = NULL; - data_was_mixed_ = false; +void AudioBuffer::CopyFrom(const float* const* data, + int samples_per_channel, + AudioProcessing::ChannelLayout layout) { + assert(samples_per_channel == input_samples_per_channel_); + assert(ChannelsFromLayout(layout) == num_input_channels_); + InitForNewData(); + + if (HasKeyboardChannel(layout)) { + keyboard_data_ = data[KeyboardChannelIndex(layout)]; + } + + // Downmix. + const float* const* data_ptr = data; + if (num_input_channels_ == 2 && num_proc_channels_ == 1) { + StereoToMono(data[0], + data[1], + input_buffer_->channel(0), + input_samples_per_channel_); + data_ptr = input_buffer_->channels(); + } + + // Resample. + if (input_samples_per_channel_ != proc_samples_per_channel_) { + for (int i = 0; i < num_proc_channels_; ++i) { + input_resamplers_[i]->Resample(data_ptr[i], + input_samples_per_channel_, + process_buffer_->channel(i), + proc_samples_per_channel_); + } + data_ptr = process_buffer_->channels(); + } + + // Convert to int16. + for (int i = 0; i < num_proc_channels_; ++i) { + ScaleAndRoundToInt16(data_ptr[i], proc_samples_per_channel_, + channels_->ibuf()->channel(i)); + } +} + +void AudioBuffer::CopyTo(int samples_per_channel, + AudioProcessing::ChannelLayout layout, + float* const* data) { + assert(samples_per_channel == output_samples_per_channel_); + assert(ChannelsFromLayout(layout) == num_proc_channels_); + + // Convert to float. + float* const* data_ptr = data; + if (output_samples_per_channel_ != proc_samples_per_channel_) { + // Convert to an intermediate buffer for subsequent resampling. + data_ptr = process_buffer_->channels(); + } + for (int i = 0; i < num_proc_channels_; ++i) { + ScaleToFloat(channels_->ibuf()->channel(i), + proc_samples_per_channel_, + data_ptr[i]); + } + + // Resample. + if (output_samples_per_channel_ != proc_samples_per_channel_) { + for (int i = 0; i < num_proc_channels_; ++i) { + output_resamplers_[i]->Resample(data_ptr[i], + proc_samples_per_channel_, + data[i], + output_samples_per_channel_); + } + } +} + +void AudioBuffer::InitForNewData() { + keyboard_data_ = NULL; num_mixed_channels_ = 0; num_mixed_low_pass_channels_ = 0; reference_copied_ = false; activity_ = AudioFrame::kVadUnknown; - is_muted_ = false; } -int16_t* AudioBuffer::data(int channel) const { - assert(channel >= 0 && channel < num_channels_); - if (data_ != NULL) { - return data_; - } +const int16_t* AudioBuffer::data(int channel) const { + assert(channel >= 0 && channel < num_proc_channels_); + return channels_->ibuf()->channel(channel); +} - return channels_[channel].data; +int16_t* AudioBuffer::data(int channel) { + const AudioBuffer* t = this; + return const_cast(t->data(channel)); } -int16_t* AudioBuffer::low_pass_split_data(int channel) const { - assert(channel >= 0 && channel < num_channels_); - if (split_channels_.get() == NULL) { - return data(channel); - } +float* AudioBuffer::data_f(int channel) { + assert(channel >= 0 && channel < num_proc_channels_); + return channels_->fbuf()->channel(channel); +} - return split_channels_[channel].low_pass_data; +const int16_t* AudioBuffer::low_pass_split_data(int channel) const { + assert(channel >= 0 && channel < num_proc_channels_); + return split_channels_.get() ? split_channels_->low_channel(channel) + : data(channel); } -int16_t* AudioBuffer::high_pass_split_data(int channel) const { - assert(channel >= 0 && channel < num_channels_); - if (split_channels_.get() == NULL) { - return NULL; - } +int16_t* AudioBuffer::low_pass_split_data(int channel) { + const AudioBuffer* t = this; + return const_cast(t->low_pass_split_data(channel)); +} + +float* AudioBuffer::low_pass_split_data_f(int channel) { + assert(channel >= 0 && channel < num_proc_channels_); + return split_channels_.get() ? split_channels_->low_channel_f(channel) + : data_f(channel); +} + +const int16_t* AudioBuffer::high_pass_split_data(int channel) const { + assert(channel >= 0 && channel < num_proc_channels_); + return split_channels_.get() ? split_channels_->high_channel(channel) : NULL; +} + +int16_t* AudioBuffer::high_pass_split_data(int channel) { + const AudioBuffer* t = this; + return const_cast(t->high_pass_split_data(channel)); +} - return split_channels_[channel].high_pass_data; +float* AudioBuffer::high_pass_split_data_f(int channel) { + assert(channel >= 0 && channel < num_proc_channels_); + return split_channels_.get() ? split_channels_->high_channel_f(channel) + : NULL; } -int16_t* AudioBuffer::mixed_data(int channel) const { +const int16_t* AudioBuffer::mixed_data(int channel) const { assert(channel >= 0 && channel < num_mixed_channels_); - return mixed_channels_[channel].data; + return mixed_channels_->channel(channel); } -int16_t* AudioBuffer::mixed_low_pass_data(int channel) const { +const int16_t* AudioBuffer::mixed_low_pass_data(int channel) const { assert(channel >= 0 && channel < num_mixed_low_pass_channels_); - return mixed_low_pass_channels_[channel].data; + return mixed_low_pass_channels_->channel(channel); } -int16_t* AudioBuffer::low_pass_reference(int channel) const { - assert(channel >= 0 && channel < num_channels_); +const int16_t* AudioBuffer::low_pass_reference(int channel) const { + assert(channel >= 0 && channel < num_proc_channels_); if (!reference_copied_) { return NULL; } - return low_pass_reference_channels_[channel].data; + return low_pass_reference_channels_->channel(channel); } -int32_t* AudioBuffer::analysis_filter_state1(int channel) const { - assert(channel >= 0 && channel < num_channels_); - return split_channels_[channel].analysis_filter_state1; +const float* AudioBuffer::keyboard_data() const { + return keyboard_data_; } -int32_t* AudioBuffer::analysis_filter_state2(int channel) const { - assert(channel >= 0 && channel < num_channels_); - return split_channels_[channel].analysis_filter_state2; -} - -int32_t* AudioBuffer::synthesis_filter_state1(int channel) const { - assert(channel >= 0 && channel < num_channels_); - return split_channels_[channel].synthesis_filter_state1; -} - -int32_t* AudioBuffer::synthesis_filter_state2(int channel) const { - assert(channel >= 0 && channel < num_channels_); - return split_channels_[channel].synthesis_filter_state2; +SplitFilterStates* AudioBuffer::filter_states(int channel) { + assert(channel >= 0 && channel < num_proc_channels_); + return &filter_states_[channel]; } void AudioBuffer::set_activity(AudioFrame::VADActivity activity) { @@ -180,141 +370,96 @@ AudioFrame::VADActivity AudioBuffer::activity() const { return activity_; } -bool AudioBuffer::is_muted() const { - return is_muted_; -} - int AudioBuffer::num_channels() const { - return num_channels_; + return num_proc_channels_; } int AudioBuffer::samples_per_channel() const { - return samples_per_channel_; + return proc_samples_per_channel_; } int AudioBuffer::samples_per_split_channel() const { return samples_per_split_channel_; } +int AudioBuffer::samples_per_keyboard_channel() const { + // We don't resample the keyboard channel. + return input_samples_per_channel_; +} + // TODO(andrew): Do deinterleaving and mixing in one step? void AudioBuffer::DeinterleaveFrom(AudioFrame* frame) { - assert(frame->num_channels_ <= max_num_channels_); - assert(frame->samples_per_channel_ == samples_per_channel_); - - InitForNewData(frame->num_channels_); + assert(proc_samples_per_channel_ == input_samples_per_channel_); + assert(num_proc_channels_ == num_input_channels_); + assert(frame->num_channels_ == num_proc_channels_); + assert(frame->samples_per_channel_ == proc_samples_per_channel_); + InitForNewData(); activity_ = frame->vad_activity_; - if (frame->energy_ == 0) { - is_muted_ = true; - } - - if (num_channels_ == 1) { - // We can get away with a pointer assignment in this case. - data_ = frame->data_; - return; - } int16_t* interleaved = frame->data_; - for (int i = 0; i < num_channels_; i++) { - int16_t* deinterleaved = channels_[i].data; + for (int i = 0; i < num_proc_channels_; i++) { + int16_t* deinterleaved = channels_->ibuf()->channel(i); int interleaved_idx = i; - for (int j = 0; j < samples_per_channel_; j++) { + for (int j = 0; j < proc_samples_per_channel_; j++) { deinterleaved[j] = interleaved[interleaved_idx]; - interleaved_idx += num_channels_; + interleaved_idx += num_proc_channels_; } } } void AudioBuffer::InterleaveTo(AudioFrame* frame, bool data_changed) const { - assert(frame->num_channels_ == num_channels_); - assert(frame->samples_per_channel_ == samples_per_channel_); + assert(proc_samples_per_channel_ == output_samples_per_channel_); + assert(num_proc_channels_ == num_input_channels_); + assert(frame->num_channels_ == num_proc_channels_); + assert(frame->samples_per_channel_ == proc_samples_per_channel_); frame->vad_activity_ = activity_; if (!data_changed) { return; } - if (num_channels_ == 1) { - if (data_was_mixed_) { - memcpy(frame->data_, - channels_[0].data, - sizeof(int16_t) * samples_per_channel_); - } else { - // These should point to the same buffer in this case. - assert(data_ == frame->data_); - } - - return; - } - int16_t* interleaved = frame->data_; - for (int i = 0; i < num_channels_; i++) { - int16_t* deinterleaved = channels_[i].data; + for (int i = 0; i < num_proc_channels_; i++) { + int16_t* deinterleaved = channels_->ibuf()->channel(i); int interleaved_idx = i; - for (int j = 0; j < samples_per_channel_; j++) { + for (int j = 0; j < proc_samples_per_channel_; j++) { interleaved[interleaved_idx] = deinterleaved[j]; - interleaved_idx += num_channels_; + interleaved_idx += num_proc_channels_; } } } -void AudioBuffer::CopyFrom(const float* const* data, int samples_per_channel, - int num_channels) { - assert(num_channels <= max_num_channels_); - assert(samples_per_channel == samples_per_channel_); - - InitForNewData(num_channels); - for (int i = 0; i < num_channels_; ++i) { - ScaleAndRoundToInt16(data[i], samples_per_channel, channels_[i].data); - } -} - -void AudioBuffer::CopyTo(int samples_per_channel, int num_channels, - float* const* data) const { - assert(num_channels == num_channels_); - assert(samples_per_channel == samples_per_channel_); - for (int i = 0; i < num_channels_; ++i) { - ScaleToFloat(channels_[i].data, samples_per_channel, data[i]); - } -} - -// TODO(andrew): would be good to support the no-mix case with pointer -// assignment. -// TODO(andrew): handle mixing to multiple channels? -void AudioBuffer::Mix(int num_mixed_channels) { - // We currently only support the stereo to mono case. - assert(num_channels_ == 2); - assert(num_mixed_channels == 1); - - StereoToMono(channels_[0].data, - channels_[1].data, - channels_[0].data, - samples_per_channel_); - - num_channels_ = num_mixed_channels; - data_was_mixed_ = true; -} - void AudioBuffer::CopyAndMix(int num_mixed_channels) { // We currently only support the stereo to mono case. - assert(num_channels_ == 2); + assert(num_proc_channels_ == 2); assert(num_mixed_channels == 1); + if (!mixed_channels_.get()) { + mixed_channels_.reset( + new ChannelBuffer(proc_samples_per_channel_, + num_mixed_channels)); + } - StereoToMono(channels_[0].data, - channels_[1].data, - mixed_channels_[0].data, - samples_per_channel_); + StereoToMono(channels_->ibuf()->channel(0), + channels_->ibuf()->channel(1), + mixed_channels_->channel(0), + proc_samples_per_channel_); num_mixed_channels_ = num_mixed_channels; } void AudioBuffer::CopyAndMixLowPass(int num_mixed_channels) { // We currently only support the stereo to mono case. - assert(num_channels_ == 2); + assert(num_proc_channels_ == 2); assert(num_mixed_channels == 1); + if (!mixed_low_pass_channels_.get()) { + mixed_low_pass_channels_.reset( + new ChannelBuffer(samples_per_split_channel_, + num_mixed_channels)); + } StereoToMono(low_pass_split_data(0), low_pass_split_data(1), - mixed_low_pass_channels_[0].data, + mixed_low_pass_channels_->channel(0), samples_per_split_channel_); num_mixed_low_pass_channels_ = num_mixed_channels; @@ -322,10 +467,14 @@ void AudioBuffer::CopyAndMixLowPass(int num_mixed_channels) { void AudioBuffer::CopyLowPassToReference() { reference_copied_ = true; - for (int i = 0; i < num_channels_; i++) { - memcpy(low_pass_reference_channels_[i].data, - low_pass_split_data(i), - sizeof(int16_t) * samples_per_split_channel_); + if (!low_pass_reference_channels_.get()) { + low_pass_reference_channels_.reset( + new ChannelBuffer(samples_per_split_channel_, + num_proc_channels_)); + } + for (int i = 0; i < num_proc_channels_; i++) { + low_pass_reference_channels_->CopyFrom(low_pass_split_data(i), i); } } + } // namespace webrtc diff --git a/webrtc/modules/audio_processing/audio_buffer.h b/webrtc/modules/audio_processing/audio_buffer.h index 1030fec35..67e4f4850 100644 --- a/webrtc/modules/audio_processing/audio_buffer.h +++ b/webrtc/modules/audio_processing/audio_buffer.h @@ -8,44 +8,77 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_MAIN_SOURCE_AUDIO_BUFFER_H_ -#define WEBRTC_MODULES_AUDIO_PROCESSING_MAIN_SOURCE_AUDIO_BUFFER_H_ +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AUDIO_BUFFER_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AUDIO_BUFFER_H_ +#include + +#include "webrtc/modules/audio_processing/common.h" +#include "webrtc/modules/audio_processing/include/audio_processing.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/scoped_vector.h" #include "webrtc/typedefs.h" namespace webrtc { -struct AudioChannel; -struct SplitAudioChannel; +class PushSincResampler; +class SplitChannelBuffer; +class IFChannelBuffer; + +struct SplitFilterStates { + SplitFilterStates() { + memset(analysis_filter_state1, 0, sizeof(analysis_filter_state1)); + memset(analysis_filter_state2, 0, sizeof(analysis_filter_state2)); + memset(synthesis_filter_state1, 0, sizeof(synthesis_filter_state1)); + memset(synthesis_filter_state2, 0, sizeof(synthesis_filter_state2)); + } + + static const int kStateSize = 6; + int analysis_filter_state1[kStateSize]; + int analysis_filter_state2[kStateSize]; + int synthesis_filter_state1[kStateSize]; + int synthesis_filter_state2[kStateSize]; +}; class AudioBuffer { public: - AudioBuffer(int max_num_channels, int samples_per_channel); + // TODO(ajm): Switch to take ChannelLayouts. + AudioBuffer(int input_samples_per_channel, + int num_input_channels, + int process_samples_per_channel, + int num_process_channels, + int output_samples_per_channel); virtual ~AudioBuffer(); int num_channels() const; int samples_per_channel() const; int samples_per_split_channel() const; + int samples_per_keyboard_channel() const; + + int16_t* data(int channel); + const int16_t* data(int channel) const; + int16_t* low_pass_split_data(int channel); + const int16_t* low_pass_split_data(int channel) const; + int16_t* high_pass_split_data(int channel); + const int16_t* high_pass_split_data(int channel) const; + const int16_t* mixed_data(int channel) const; + const int16_t* mixed_low_pass_data(int channel) const; + const int16_t* low_pass_reference(int channel) const; - int16_t* data(int channel) const; - int16_t* low_pass_split_data(int channel) const; - int16_t* high_pass_split_data(int channel) const; - int16_t* mixed_data(int channel) const; - int16_t* mixed_low_pass_data(int channel) const; - int16_t* low_pass_reference(int channel) const; + // Float versions of the accessors, with automatic conversion back and forth + // as necessary. The range of the numbers are the same as for int16_t. + float* data_f(int channel); + float* low_pass_split_data_f(int channel); + float* high_pass_split_data_f(int channel); - int32_t* analysis_filter_state1(int channel) const; - int32_t* analysis_filter_state2(int channel) const; - int32_t* synthesis_filter_state1(int channel) const; - int32_t* synthesis_filter_state2(int channel) const; + const float* keyboard_data() const; + + SplitFilterStates* filter_states(int channel); void set_activity(AudioFrame::VADActivity activity); AudioFrame::VADActivity activity() const; - bool is_muted() const; - // Use for int16 interleaved data. void DeinterleaveFrom(AudioFrame* audioFrame); void InterleaveTo(AudioFrame* audioFrame) const; @@ -54,40 +87,45 @@ class AudioBuffer { void InterleaveTo(AudioFrame* frame, bool data_changed) const; // Use for float deinterleaved data. - void CopyFrom(const float* const* data, int samples_per_channel, - int num_channels); - void CopyTo(int samples_per_channel, int num_channels, - float* const* data) const; + void CopyFrom(const float* const* data, + int samples_per_channel, + AudioProcessing::ChannelLayout layout); + void CopyTo(int samples_per_channel, + AudioProcessing::ChannelLayout layout, + float* const* data); - void Mix(int num_mixed_channels); void CopyAndMix(int num_mixed_channels); void CopyAndMixLowPass(int num_mixed_channels); void CopyLowPassToReference(); private: // Called from DeinterleaveFrom() and CopyFrom(). - void InitForNewData(int num_channels); + void InitForNewData(); - const int max_num_channels_; - int num_channels_; + const int input_samples_per_channel_; + const int num_input_channels_; + const int proc_samples_per_channel_; + const int num_proc_channels_; + const int output_samples_per_channel_; + int samples_per_split_channel_; int num_mixed_channels_; int num_mixed_low_pass_channels_; - // Whether the original data was replaced with mixed data. - bool data_was_mixed_; - const int samples_per_channel_; - int samples_per_split_channel_; bool reference_copied_; AudioFrame::VADActivity activity_; - bool is_muted_; - - int16_t* data_; - scoped_array channels_; - scoped_array split_channels_; - scoped_array mixed_channels_; - // TODO(andrew): improve this, we don't need the full 32 kHz space here. - scoped_array mixed_low_pass_channels_; - scoped_array low_pass_reference_channels_; + + const float* keyboard_data_; + scoped_ptr channels_; + scoped_ptr split_channels_; + scoped_ptr filter_states_; + scoped_ptr > mixed_channels_; + scoped_ptr > mixed_low_pass_channels_; + scoped_ptr > low_pass_reference_channels_; + scoped_ptr > input_buffer_; + scoped_ptr > process_buffer_; + ScopedVector input_resamplers_; + ScopedVector output_resamplers_; }; + } // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_PROCESSING_MAIN_SOURCE_AUDIO_BUFFER_H_ +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AUDIO_BUFFER_H_ diff --git a/webrtc/modules/audio_processing/audio_processing.gypi b/webrtc/modules/audio_processing/audio_processing.gypi index 920cbca91..b1d18c5b0 100644 --- a/webrtc/modules/audio_processing/audio_processing.gypi +++ b/webrtc/modules/audio_processing/audio_processing.gypi @@ -54,6 +54,7 @@ 'audio_buffer.h', 'audio_processing_impl.cc', 'audio_processing_impl.h', + 'common.h', 'echo_cancellation_impl.cc', 'echo_cancellation_impl.h', 'echo_control_mobile_impl.cc', @@ -69,6 +70,8 @@ 'noise_suppression_impl.h', 'processing_component.cc', 'processing_component.h', + 'rms_level.cc', + 'rms_level.h', 'typing_detection.cc', 'typing_detection.h', 'utility/delay_estimator.c', @@ -196,6 +199,7 @@ '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', ], 'sources': [ + 'aec/aec_core_neon.c', 'aecm/aecm_core_neon.c', 'ns/nsx_core_neon.c', ], diff --git a/webrtc/modules/audio_processing/audio_processing_impl.cc b/webrtc/modules/audio_processing/audio_processing_impl.cc index e19cfec97..de387edb2 100644 --- a/webrtc/modules/audio_processing/audio_processing_impl.cc +++ b/webrtc/modules/audio_processing/audio_processing_impl.cc @@ -15,6 +15,7 @@ #include "webrtc/common_audio/include/audio_util.h" #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" #include "webrtc/modules/audio_processing/audio_buffer.h" +#include "webrtc/modules/audio_processing/common.h" #include "webrtc/modules/audio_processing/echo_cancellation_impl.h" #include "webrtc/modules/audio_processing/echo_control_mobile_impl.h" #include "webrtc/modules/audio_processing/gain_control_impl.h" @@ -47,24 +48,6 @@ } while (0) namespace webrtc { -namespace { - -const int kChunkSizeMs = 10; - -int ChannelsFromLayout(AudioProcessing::ChannelLayout layout) { - switch (layout) { - case AudioProcessing::kMono: - case AudioProcessing::kMonoAndKeyboard: - return 1; - case AudioProcessing::kStereo: - case AudioProcessing::kStereoAndKeyboard: - return 2; - } - assert(false); - return -1; -} - -} // namespace // Throughout webrtc, it's assumed that success is represented by zero. COMPILE_ASSERT(AudioProcessing::kNoError == 0, no_error_must_be_zero); @@ -97,24 +80,19 @@ AudioProcessingImpl::AudioProcessingImpl(const Config& config) noise_suppression_(NULL), voice_detection_(NULL), crit_(CriticalSectionWrapper::CreateCriticalSection()), - render_audio_(NULL), - capture_audio_(NULL), #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP debug_file_(FileWrapper::Create()), event_msg_(new audioproc::Event()), #endif - sample_rate_hz_(kSampleRate16kHz), - reverse_sample_rate_hz_(kSampleRate16kHz), - split_sample_rate_hz_(kSampleRate16kHz), - samples_per_channel_(kChunkSizeMs * sample_rate_hz_ / 1000), - reverse_samples_per_channel_( - kChunkSizeMs * reverse_sample_rate_hz_ / 1000), + fwd_in_format_(kSampleRate16kHz, 1), + fwd_proc_format_(kSampleRate16kHz, 1), + fwd_out_format_(kSampleRate16kHz), + rev_in_format_(kSampleRate16kHz, 1), + rev_proc_format_(kSampleRate16kHz, 1), + split_rate_(kSampleRate16kHz), stream_delay_ms_(0), delay_offset_ms_(0), was_stream_delay_set_(false), - num_reverse_channels_(1), - num_input_channels_(1), - num_output_channels_(1), output_will_be_muted_(false), key_pressed_(false) { echo_cancellation_ = new EchoCancellationImpl(this, crit_); @@ -156,59 +134,52 @@ AudioProcessingImpl::~AudioProcessingImpl() { debug_file_->CloseFile(); } #endif - - if (render_audio_) { - delete render_audio_; - render_audio_ = NULL; - } - - if (capture_audio_) { - delete capture_audio_; - capture_audio_ = NULL; - } } - delete crit_; crit_ = NULL; } -int AudioProcessingImpl::split_sample_rate_hz() const { - return split_sample_rate_hz_; -} - int AudioProcessingImpl::Initialize() { CriticalSectionScoped crit_scoped(crit_); return InitializeLocked(); } -int AudioProcessingImpl::Initialize(int sample_rate_hz, +int AudioProcessingImpl::set_sample_rate_hz(int rate) { + CriticalSectionScoped crit_scoped(crit_); + return InitializeLocked(rate, + rate, + rev_in_format_.rate(), + fwd_in_format_.num_channels(), + fwd_proc_format_.num_channels(), + rev_in_format_.num_channels()); +} + +int AudioProcessingImpl::Initialize(int input_sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, - int num_input_channels, - int num_output_channels, - int num_reverse_channels) { + ChannelLayout input_layout, + ChannelLayout output_layout, + ChannelLayout reverse_layout) { CriticalSectionScoped crit_scoped(crit_); - return InitializeLocked(sample_rate_hz, + return InitializeLocked(input_sample_rate_hz, + output_sample_rate_hz, reverse_sample_rate_hz, - num_input_channels, - num_output_channels, - num_reverse_channels); + ChannelsFromLayout(input_layout), + ChannelsFromLayout(output_layout), + ChannelsFromLayout(reverse_layout)); } int AudioProcessingImpl::InitializeLocked() { - if (render_audio_ != NULL) { - delete render_audio_; - render_audio_ = NULL; - } - - if (capture_audio_ != NULL) { - delete capture_audio_; - capture_audio_ = NULL; - } - - render_audio_ = new AudioBuffer(num_reverse_channels_, - reverse_samples_per_channel_); - capture_audio_ = new AudioBuffer(num_input_channels_, - samples_per_channel_); + render_audio_.reset(new AudioBuffer(rev_in_format_.samples_per_channel(), + rev_in_format_.num_channels(), + rev_proc_format_.samples_per_channel(), + rev_proc_format_.num_channels(), + rev_proc_format_.samples_per_channel())); + capture_audio_.reset(new AudioBuffer(fwd_in_format_.samples_per_channel(), + fwd_in_format_.num_channels(), + fwd_proc_format_.samples_per_channel(), + fwd_proc_format_.num_channels(), + fwd_out_format_.samples_per_channel())); // Initialize all components. std::list::iterator it; @@ -231,24 +202,15 @@ int AudioProcessingImpl::InitializeLocked() { return kNoError; } -int AudioProcessingImpl::InitializeLocked(int sample_rate_hz, +int AudioProcessingImpl::InitializeLocked(int input_sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, int num_input_channels, int num_output_channels, int num_reverse_channels) { - if (sample_rate_hz != kSampleRate8kHz && - sample_rate_hz != kSampleRate16kHz && - sample_rate_hz != kSampleRate32kHz) { - return kBadSampleRateError; - } - if (reverse_sample_rate_hz != kSampleRate8kHz && - reverse_sample_rate_hz != kSampleRate16kHz && - reverse_sample_rate_hz != kSampleRate32kHz) { - return kBadSampleRateError; - } - // TODO(ajm): The reverse sample rate is constrained to be identical to the - // forward rate for now. - if (reverse_sample_rate_hz != sample_rate_hz) { + if (input_sample_rate_hz <= 0 || + output_sample_rate_hz <= 0 || + reverse_sample_rate_hz <= 0) { return kBadSampleRateError; } if (num_output_channels > num_input_channels) { @@ -260,23 +222,50 @@ int AudioProcessingImpl::InitializeLocked(int sample_rate_hz, num_reverse_channels > 2 || num_reverse_channels < 1) { return kBadNumberChannelsError; } - if (echo_control_mobile_->is_enabled() && sample_rate_hz > kSampleRate16kHz) { - LOG(LS_ERROR) << "AECM only supports 16 or 8 kHz sample rates"; - return kUnsupportedComponentError; + + fwd_in_format_.set(input_sample_rate_hz, num_input_channels); + fwd_out_format_.set(output_sample_rate_hz); + rev_in_format_.set(reverse_sample_rate_hz, num_reverse_channels); + + // We process at the closest native rate >= min(input rate, output rate)... + int min_proc_rate = std::min(fwd_in_format_.rate(), fwd_out_format_.rate()); + int fwd_proc_rate; + if (min_proc_rate > kSampleRate16kHz) { + fwd_proc_rate = kSampleRate32kHz; + } else if (min_proc_rate > kSampleRate8kHz) { + fwd_proc_rate = kSampleRate16kHz; + } else { + fwd_proc_rate = kSampleRate8kHz; + } + // ...with one exception. + if (echo_control_mobile_->is_enabled() && min_proc_rate > kSampleRate16kHz) { + fwd_proc_rate = kSampleRate16kHz; + } + + fwd_proc_format_.set(fwd_proc_rate, num_output_channels); + + // We normally process the reverse stream at 16 kHz. Unless... + int rev_proc_rate = kSampleRate16kHz; + if (fwd_proc_format_.rate() == kSampleRate8kHz) { + // ...the forward stream is at 8 kHz. + rev_proc_rate = kSampleRate8kHz; + } else { + if (rev_in_format_.rate() == kSampleRate32kHz) { + // ...or the input is at 32 kHz, in which case we use the splitting + // filter rather than the resampler. + rev_proc_rate = kSampleRate32kHz; + } } - sample_rate_hz_ = sample_rate_hz; - reverse_sample_rate_hz_ = reverse_sample_rate_hz; - reverse_samples_per_channel_ = kChunkSizeMs * reverse_sample_rate_hz / 1000; - samples_per_channel_ = kChunkSizeMs * sample_rate_hz / 1000; - num_input_channels_ = num_input_channels; - num_output_channels_ = num_output_channels; - num_reverse_channels_ = num_reverse_channels; + // TODO(ajm): Enable this. + // Always downmix the reverse stream to mono for analysis. + //rev_proc_format_.set(rev_proc_rate, 1); + rev_proc_format_.set(rev_proc_rate, rev_in_format_.num_channels()); - if (sample_rate_hz_ == kSampleRate32kHz) { - split_sample_rate_hz_ = kSampleRate16kHz; + if (fwd_proc_format_.rate() == kSampleRate32kHz) { + split_rate_ = kSampleRate16kHz; } else { - split_sample_rate_hz_ = sample_rate_hz_; + split_rate_ = fwd_proc_format_.rate(); } return InitializeLocked(); @@ -284,20 +273,23 @@ int AudioProcessingImpl::InitializeLocked(int sample_rate_hz, // Calls InitializeLocked() if any of the audio parameters have changed from // their current values. -int AudioProcessingImpl::MaybeInitializeLocked(int sample_rate_hz, +int AudioProcessingImpl::MaybeInitializeLocked(int input_sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, int num_input_channels, int num_output_channels, int num_reverse_channels) { - if (sample_rate_hz == sample_rate_hz_ && - reverse_sample_rate_hz == reverse_sample_rate_hz_ && - num_input_channels == num_input_channels_ && - num_output_channels == num_output_channels_ && - num_reverse_channels == num_reverse_channels_) { + if (input_sample_rate_hz == fwd_in_format_.rate() && + output_sample_rate_hz == fwd_out_format_.rate() && + reverse_sample_rate_hz == rev_in_format_.rate() && + num_input_channels == fwd_in_format_.num_channels() && + num_output_channels == fwd_proc_format_.num_channels() && + num_reverse_channels == rev_in_format_.num_channels()) { return kNoError; } - return InitializeLocked(sample_rate_hz, + return InitializeLocked(input_sample_rate_hz, + output_sample_rate_hz, reverse_sample_rate_hz, num_input_channels, num_output_channels, @@ -315,86 +307,34 @@ int AudioProcessingImpl::EnableExperimentalNs(bool enable) { return kNoError; } -int AudioProcessingImpl::set_sample_rate_hz(int rate) { +int AudioProcessingImpl::input_sample_rate_hz() const { CriticalSectionScoped crit_scoped(crit_); - if (rate == sample_rate_hz_) { - return kNoError; - } - if (rate != kSampleRate8kHz && - rate != kSampleRate16kHz && - rate != kSampleRate32kHz) { - return kBadParameterError; - } - if (echo_control_mobile_->is_enabled() && rate > kSampleRate16kHz) { - LOG(LS_ERROR) << "AECM only supports 16 kHz or lower sample rates"; - return kUnsupportedComponentError; - } - - sample_rate_hz_ = rate; - samples_per_channel_ = rate / 100; - - if (sample_rate_hz_ == kSampleRate32kHz) { - split_sample_rate_hz_ = kSampleRate16kHz; - } else { - split_sample_rate_hz_ = sample_rate_hz_; - } - - return InitializeLocked(); + return fwd_in_format_.rate(); } int AudioProcessingImpl::sample_rate_hz() const { CriticalSectionScoped crit_scoped(crit_); - return sample_rate_hz_; + return fwd_in_format_.rate(); } -int AudioProcessingImpl::set_num_reverse_channels(int channels) { - CriticalSectionScoped crit_scoped(crit_); - if (channels == num_reverse_channels_) { - return kNoError; - } - // Only stereo supported currently. - if (channels > 2 || channels < 1) { - return kBadParameterError; - } - - num_reverse_channels_ = channels; - - return InitializeLocked(); +int AudioProcessingImpl::proc_sample_rate_hz() const { + return fwd_proc_format_.rate(); } -int AudioProcessingImpl::num_reverse_channels() const { - return num_reverse_channels_; +int AudioProcessingImpl::proc_split_sample_rate_hz() const { + return split_rate_; } -int AudioProcessingImpl::set_num_channels( - int input_channels, - int output_channels) { - CriticalSectionScoped crit_scoped(crit_); - if (input_channels == num_input_channels_ && - output_channels == num_output_channels_) { - return kNoError; - } - if (output_channels > input_channels) { - return kBadParameterError; - } - // Only stereo supported currently. - if (input_channels > 2 || input_channels < 1 || - output_channels > 2 || output_channels < 1) { - return kBadParameterError; - } - - num_input_channels_ = input_channels; - num_output_channels_ = output_channels; - - return InitializeLocked(); +int AudioProcessingImpl::num_reverse_channels() const { + return rev_proc_format_.num_channels(); } int AudioProcessingImpl::num_input_channels() const { - return num_input_channels_; + return fwd_in_format_.num_channels(); } int AudioProcessingImpl::num_output_channels() const { - return num_output_channels_; + return fwd_proc_format_.num_channels(); } void AudioProcessingImpl::set_output_will_be_muted(bool muted) { @@ -405,24 +345,25 @@ bool AudioProcessingImpl::output_will_be_muted() const { return output_will_be_muted_; } -int AudioProcessingImpl::ProcessStream(float* const* data, +int AudioProcessingImpl::ProcessStream(const float* const* src, int samples_per_channel, - int sample_rate_hz, + int input_sample_rate_hz, ChannelLayout input_layout, - ChannelLayout output_layout) { + int output_sample_rate_hz, + ChannelLayout output_layout, + float* const* dest) { CriticalSectionScoped crit_scoped(crit_); - if (!data) { + if (!src || !dest) { return kNullPointerError; } - const int num_input_channels = ChannelsFromLayout(input_layout); - // TODO(ajm): We now always set the output channels equal to the input - // channels here. Restore the ability to downmix. - // TODO(ajm): The reverse sample rate is constrained to be identical to the - // forward rate for now. - RETURN_ON_ERR(MaybeInitializeLocked(sample_rate_hz, sample_rate_hz, - num_input_channels, num_input_channels, num_reverse_channels_)); - if (samples_per_channel != samples_per_channel_) { + RETURN_ON_ERR(MaybeInitializeLocked(input_sample_rate_hz, + output_sample_rate_hz, + rev_in_format_.rate(), + ChannelsFromLayout(input_layout), + ChannelsFromLayout(output_layout), + rev_in_format_.num_channels())); + if (samples_per_channel != fwd_in_format_.samples_per_channel()) { return kBadDataLengthError; } @@ -431,23 +372,25 @@ int AudioProcessingImpl::ProcessStream(float* const* data, event_msg_->set_type(audioproc::Event::STREAM); audioproc::Stream* msg = event_msg_->mutable_stream(); const size_t channel_size = sizeof(float) * samples_per_channel; - for (int i = 0; i < num_input_channels; ++i) - msg->add_input_channel(data[i], channel_size); + for (int i = 0; i < fwd_in_format_.num_channels(); ++i) + msg->add_input_channel(src[i], channel_size); } #endif - capture_audio_->CopyFrom(data, samples_per_channel, num_output_channels_); + capture_audio_->CopyFrom(src, samples_per_channel, input_layout); RETURN_ON_ERR(ProcessStreamLocked()); if (output_copy_needed(is_data_processed())) { - capture_audio_->CopyTo(samples_per_channel, num_output_channels_, data); + capture_audio_->CopyTo(fwd_out_format_.samples_per_channel(), + output_layout, + dest); } #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP if (debug_file_->Open()) { audioproc::Stream* msg = event_msg_->mutable_stream(); const size_t channel_size = sizeof(float) * samples_per_channel; - for (int i = 0; i < num_output_channels_; ++i) - msg->add_output_channel(data[i], channel_size); + for (int i = 0; i < fwd_proc_format_.num_channels(); ++i) + msg->add_output_channel(dest[i], channel_size); RETURN_ON_ERR(WriteMessageToDebugFile()); } #endif @@ -460,15 +403,27 @@ int AudioProcessingImpl::ProcessStream(AudioFrame* frame) { if (!frame) { return kNullPointerError; } + // Must be a native rate. + if (frame->sample_rate_hz_ != kSampleRate8kHz && + frame->sample_rate_hz_ != kSampleRate16kHz && + frame->sample_rate_hz_ != kSampleRate32kHz) { + return kBadSampleRateError; + } + if (echo_control_mobile_->is_enabled() && + frame->sample_rate_hz_ > kSampleRate16kHz) { + LOG(LS_ERROR) << "AECM only supports 16 or 8 kHz sample rates"; + return kUnsupportedComponentError; + } - // TODO(ajm): We now always set the output channels equal to the input - // channels here. Restore the ability to downmix. - // TODO(ajm): The reverse sample rate is constrained to be identical to the - // forward rate for now. + // TODO(ajm): The input and output rates and channels are currently + // constrained to be identical in the int16 interface. RETURN_ON_ERR(MaybeInitializeLocked(frame->sample_rate_hz_, - frame->sample_rate_hz_, frame->num_channels_, frame->num_channels_, - num_reverse_channels_)); - if (frame->samples_per_channel_ != samples_per_channel_) { + frame->sample_rate_hz_, + rev_in_format_.rate(), + frame->num_channels_, + frame->num_channels_, + rev_in_format_.num_channels())); + if (frame->samples_per_channel_ != fwd_in_format_.samples_per_channel()) { return kBadDataLengthError; } @@ -484,10 +439,6 @@ int AudioProcessingImpl::ProcessStream(AudioFrame* frame) { #endif capture_audio_->DeinterleaveFrom(frame); - if (num_output_channels_ < num_input_channels_) { - capture_audio_->Mix(num_output_channels_); - frame->num_channels_ = num_output_channels_; - } RETURN_ON_ERR(ProcessStreamLocked()); capture_audio_->InterleaveTo(frame, output_copy_needed(is_data_processed())); @@ -517,46 +468,46 @@ int AudioProcessingImpl::ProcessStreamLocked() { } #endif + AudioBuffer* ca = capture_audio_.get(); // For brevity. bool data_processed = is_data_processed(); if (analysis_needed(data_processed)) { - for (int i = 0; i < num_output_channels_; i++) { + for (int i = 0; i < fwd_proc_format_.num_channels(); i++) { // Split into a low and high band. - WebRtcSpl_AnalysisQMF(capture_audio_->data(i), - capture_audio_->samples_per_channel(), - capture_audio_->low_pass_split_data(i), - capture_audio_->high_pass_split_data(i), - capture_audio_->analysis_filter_state1(i), - capture_audio_->analysis_filter_state2(i)); + WebRtcSpl_AnalysisQMF(ca->data(i), + ca->samples_per_channel(), + ca->low_pass_split_data(i), + ca->high_pass_split_data(i), + ca->filter_states(i)->analysis_filter_state1, + ca->filter_states(i)->analysis_filter_state2); } } - RETURN_ON_ERR(high_pass_filter_->ProcessCaptureAudio(capture_audio_)); - RETURN_ON_ERR(gain_control_->AnalyzeCaptureAudio(capture_audio_)); - RETURN_ON_ERR(echo_cancellation_->ProcessCaptureAudio(capture_audio_)); + RETURN_ON_ERR(high_pass_filter_->ProcessCaptureAudio(ca)); + RETURN_ON_ERR(gain_control_->AnalyzeCaptureAudio(ca)); + RETURN_ON_ERR(echo_cancellation_->ProcessCaptureAudio(ca)); - if (echo_control_mobile_->is_enabled() && - noise_suppression_->is_enabled()) { - capture_audio_->CopyLowPassToReference(); + if (echo_control_mobile_->is_enabled() && noise_suppression_->is_enabled()) { + ca->CopyLowPassToReference(); } - RETURN_ON_ERR(noise_suppression_->ProcessCaptureAudio(capture_audio_)); - RETURN_ON_ERR(echo_control_mobile_->ProcessCaptureAudio(capture_audio_)); - RETURN_ON_ERR(voice_detection_->ProcessCaptureAudio(capture_audio_)); - RETURN_ON_ERR(gain_control_->ProcessCaptureAudio(capture_audio_)); + RETURN_ON_ERR(noise_suppression_->ProcessCaptureAudio(ca)); + RETURN_ON_ERR(echo_control_mobile_->ProcessCaptureAudio(ca)); + RETURN_ON_ERR(voice_detection_->ProcessCaptureAudio(ca)); + RETURN_ON_ERR(gain_control_->ProcessCaptureAudio(ca)); if (synthesis_needed(data_processed)) { - for (int i = 0; i < num_output_channels_; i++) { + for (int i = 0; i < fwd_proc_format_.num_channels(); i++) { // Recombine low and high bands. - WebRtcSpl_SynthesisQMF(capture_audio_->low_pass_split_data(i), - capture_audio_->high_pass_split_data(i), - capture_audio_->samples_per_split_channel(), - capture_audio_->data(i), - capture_audio_->synthesis_filter_state1(i), - capture_audio_->synthesis_filter_state2(i)); + WebRtcSpl_SynthesisQMF(ca->low_pass_split_data(i), + ca->high_pass_split_data(i), + ca->samples_per_split_channel(), + ca->data(i), + ca->filter_states(i)->synthesis_filter_state1, + ca->filter_states(i)->synthesis_filter_state2); } } // The level estimator operates on the recombined data. - RETURN_ON_ERR(level_estimator_->ProcessStream(capture_audio_)); + RETURN_ON_ERR(level_estimator_->ProcessStream(ca)); was_stream_delay_set_ = false; return kNoError; @@ -570,16 +521,15 @@ int AudioProcessingImpl::AnalyzeReverseStream(const float* const* data, if (data == NULL) { return kNullPointerError; } - if (sample_rate_hz != sample_rate_hz_) { - return kBadSampleRateError; - } const int num_channels = ChannelsFromLayout(layout); - // TODO(ajm): The reverse sample rate is constrained to be identical to the - // forward rate for now. - RETURN_ON_ERR(MaybeInitializeLocked(sample_rate_hz_, sample_rate_hz_, - num_input_channels_, num_output_channels_, num_channels)); - if (samples_per_channel != reverse_samples_per_channel_) { + RETURN_ON_ERR(MaybeInitializeLocked(fwd_in_format_.rate(), + fwd_out_format_.rate(), + sample_rate_hz, + fwd_in_format_.num_channels(), + fwd_proc_format_.num_channels(), + num_channels)); + if (samples_per_channel != rev_in_format_.samples_per_channel()) { return kBadDataLengthError; } @@ -594,7 +544,7 @@ int AudioProcessingImpl::AnalyzeReverseStream(const float* const* data, } #endif - render_audio_->CopyFrom(data, samples_per_channel, num_channels); + render_audio_->CopyFrom(data, samples_per_channel, layout); return AnalyzeReverseStreamLocked(); } @@ -603,15 +553,24 @@ int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) { if (frame == NULL) { return kNullPointerError; } - if (frame->sample_rate_hz_ != sample_rate_hz_) { + // Must be a native rate. + if (frame->sample_rate_hz_ != kSampleRate8kHz && + frame->sample_rate_hz_ != kSampleRate16kHz && + frame->sample_rate_hz_ != kSampleRate32kHz) { + return kBadSampleRateError; + } + // This interface does not tolerate different forward and reverse rates. + if (frame->sample_rate_hz_ != fwd_in_format_.rate()) { return kBadSampleRateError; } - // TODO(ajm): The reverse sample rate is constrained to be identical to the - // forward rate for now. - RETURN_ON_ERR(MaybeInitializeLocked(sample_rate_hz_, sample_rate_hz_, - num_input_channels_, num_output_channels_, frame->num_channels_)); - if (frame->samples_per_channel_ != reverse_samples_per_channel_) { + RETURN_ON_ERR(MaybeInitializeLocked(fwd_in_format_.rate(), + fwd_out_format_.rate(), + frame->sample_rate_hz_, + fwd_in_format_.num_channels(), + fwd_in_format_.num_channels(), + frame->num_channels_)); + if (frame->samples_per_channel_ != rev_in_format_.samples_per_channel()) { return kBadDataLengthError; } @@ -631,26 +590,23 @@ int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) { return AnalyzeReverseStreamLocked(); } -// TODO(ajm): Have AnalyzeReverseStream accept sample rates not matching the -// primary stream and convert ourselves rather than having the user manage it. -// We can be smarter and use the splitting filter when appropriate. Similarly, -// perform downmixing here. int AudioProcessingImpl::AnalyzeReverseStreamLocked() { - if (sample_rate_hz_ == kSampleRate32kHz) { - for (int i = 0; i < num_reverse_channels_; i++) { + AudioBuffer* ra = render_audio_.get(); // For brevity. + if (rev_proc_format_.rate() == kSampleRate32kHz) { + for (int i = 0; i < rev_proc_format_.num_channels(); i++) { // Split into low and high band. - WebRtcSpl_AnalysisQMF(render_audio_->data(i), - render_audio_->samples_per_channel(), - render_audio_->low_pass_split_data(i), - render_audio_->high_pass_split_data(i), - render_audio_->analysis_filter_state1(i), - render_audio_->analysis_filter_state2(i)); + WebRtcSpl_AnalysisQMF(ra->data(i), + ra->samples_per_channel(), + ra->low_pass_split_data(i), + ra->high_pass_split_data(i), + ra->filter_states(i)->analysis_filter_state1, + ra->filter_states(i)->analysis_filter_state2); } } - RETURN_ON_ERR(echo_cancellation_->ProcessRenderAudio(render_audio_)); - RETURN_ON_ERR(echo_control_mobile_->ProcessRenderAudio(render_audio_)); - RETURN_ON_ERR(gain_control_->ProcessRenderAudio(render_audio_)); + RETURN_ON_ERR(echo_cancellation_->ProcessRenderAudio(ra)); + RETURN_ON_ERR(echo_control_mobile_->ProcessRenderAudio(ra)); + RETURN_ON_ERR(gain_control_->ProcessRenderAudio(ra)); return kNoError; } @@ -832,18 +788,19 @@ bool AudioProcessingImpl::is_data_processed() const { bool AudioProcessingImpl::output_copy_needed(bool is_data_processed) const { // Check if we've upmixed or downmixed the audio. - return (num_output_channels_ != num_input_channels_ || is_data_processed); + return ((fwd_proc_format_.num_channels() != fwd_in_format_.num_channels()) || + is_data_processed); } bool AudioProcessingImpl::synthesis_needed(bool is_data_processed) const { - return (is_data_processed && sample_rate_hz_ == kSampleRate32kHz); + return (is_data_processed && fwd_proc_format_.rate() == kSampleRate32kHz); } bool AudioProcessingImpl::analysis_needed(bool is_data_processed) const { if (!is_data_processed && !voice_detection_->is_enabled()) { // Only level_estimator_ is enabled. return false; - } else if (sample_rate_hz_ == kSampleRate32kHz) { + } else if (fwd_proc_format_.rate() == kSampleRate32kHz) { // Something besides level_estimator_ is enabled, and we have super-wb. return true; } @@ -881,12 +838,12 @@ int AudioProcessingImpl::WriteMessageToDebugFile() { int AudioProcessingImpl::WriteInitMessage() { event_msg_->set_type(audioproc::Event::INIT); audioproc::Init* msg = event_msg_->mutable_init(); - msg->set_sample_rate(sample_rate_hz_); - msg->set_device_sample_rate(echo_cancellation_->device_sample_rate_hz()); - msg->set_num_input_channels(num_input_channels_); - msg->set_num_output_channels(num_output_channels_); - msg->set_num_reverse_channels(num_reverse_channels_); - msg->set_reverse_sample_rate(reverse_sample_rate_hz_); + msg->set_sample_rate(fwd_in_format_.rate()); + msg->set_num_input_channels(fwd_in_format_.num_channels()); + msg->set_num_output_channels(fwd_proc_format_.num_channels()); + msg->set_num_reverse_channels(rev_in_format_.num_channels()); + msg->set_reverse_sample_rate(rev_in_format_.rate()); + msg->set_output_sample_rate(fwd_out_format_.rate()); int err = WriteMessageToDebugFile(); if (err != kNoError) { @@ -896,4 +853,5 @@ int AudioProcessingImpl::WriteInitMessage() { return kNoError; } #endif // WEBRTC_AUDIOPROC_DEBUG_DUMP + } // namespace webrtc diff --git a/webrtc/modules/audio_processing/audio_processing_impl.h b/webrtc/modules/audio_processing/audio_processing_impl.h index 95af8f58f..d34f305a9 100644 --- a/webrtc/modules/audio_processing/audio_processing_impl.h +++ b/webrtc/modules/audio_processing/audio_processing_impl.h @@ -19,6 +19,7 @@ #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { + class AudioBuffer; class CriticalSectionWrapper; class EchoCancellationImpl; @@ -39,6 +40,44 @@ class Event; } // namespace audioproc #endif +class AudioRate { + public: + explicit AudioRate(int sample_rate_hz) + : rate_(sample_rate_hz), + samples_per_channel_(AudioProcessing::kChunkSizeMs * rate_ / 1000) {} + virtual ~AudioRate() {} + + void set(int rate) { + rate_ = rate; + samples_per_channel_ = AudioProcessing::kChunkSizeMs * rate_ / 1000; + } + + int rate() const { return rate_; } + int samples_per_channel() const { return samples_per_channel_; } + + private: + int rate_; + int samples_per_channel_; +}; + +class AudioFormat : public AudioRate { + public: + AudioFormat(int sample_rate_hz, int num_channels) + : AudioRate(sample_rate_hz), + num_channels_(num_channels) {} + virtual ~AudioFormat() {} + + void set(int rate, int num_channels) { + AudioRate::set(rate); + num_channels_ = num_channels; + } + + int num_channels() const { return num_channels_; } + + private: + int num_channels_; +}; + class AudioProcessingImpl : public AudioProcessing { public: explicit AudioProcessingImpl(const Config& config); @@ -46,33 +85,35 @@ class AudioProcessingImpl : public AudioProcessing { // AudioProcessing methods. virtual int Initialize() OVERRIDE; - virtual int Initialize(int sample_rate_hz, + virtual int Initialize(int input_sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, - int num_input_channels, - int num_output_channels, - int num_reverse_channels) OVERRIDE; + ChannelLayout input_layout, + ChannelLayout output_layout, + ChannelLayout reverse_layout) OVERRIDE; virtual void SetExtraOptions(const Config& config) OVERRIDE; virtual int EnableExperimentalNs(bool enable) OVERRIDE; virtual bool experimental_ns_enabled() const OVERRIDE { return false; } virtual int set_sample_rate_hz(int rate) OVERRIDE; + virtual int input_sample_rate_hz() const OVERRIDE; virtual int sample_rate_hz() const OVERRIDE; - virtual int split_sample_rate_hz() const OVERRIDE; - virtual int set_num_channels(int input_channels, - int output_channels) OVERRIDE; + virtual int proc_sample_rate_hz() const OVERRIDE; + virtual int proc_split_sample_rate_hz() const OVERRIDE; virtual int num_input_channels() const OVERRIDE; virtual int num_output_channels() const OVERRIDE; - virtual int set_num_reverse_channels(int channels) OVERRIDE; virtual int num_reverse_channels() const OVERRIDE; virtual void set_output_will_be_muted(bool muted) OVERRIDE; virtual bool output_will_be_muted() const OVERRIDE; virtual int ProcessStream(AudioFrame* frame) OVERRIDE; - virtual int ProcessStream(float* const* data, + virtual int ProcessStream(const float* const* src, int samples_per_channel, - int sample_rate_hz, + int input_sample_rate_hz, ChannelLayout input_layout, - ChannelLayout output_layout) OVERRIDE; + int output_sample_rate_hz, + ChannelLayout output_layout, + float* const* dest) OVERRIDE; virtual int AnalyzeReverseStream(AudioFrame* frame) OVERRIDE; virtual int AnalyzeReverseStream(const float* const* data, int samples_per_channel, @@ -102,12 +143,14 @@ class AudioProcessingImpl : public AudioProcessing { virtual int InitializeLocked(); private: - int InitializeLocked(int sample_rate_hz, + int InitializeLocked(int input_sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, int num_input_channels, int num_output_channels, int num_reverse_channels); - int MaybeInitializeLocked(int sample_rate_hz, + int MaybeInitializeLocked(int input_sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, int num_input_channels, int num_output_channels, @@ -130,8 +173,8 @@ class AudioProcessingImpl : public AudioProcessing { std::list component_list_; CriticalSectionWrapper* crit_; - AudioBuffer* render_audio_; - AudioBuffer* capture_audio_; + scoped_ptr render_audio_; + scoped_ptr capture_audio_; #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP // TODO(andrew): make this more graceful. Ideally we would split this stuff // out into a separate class with an "enabled" and "disabled" implementation. @@ -142,22 +185,22 @@ class AudioProcessingImpl : public AudioProcessing { std::string event_str_; // Memory for protobuf serialization. #endif - int sample_rate_hz_; - int reverse_sample_rate_hz_; - int split_sample_rate_hz_; - int samples_per_channel_; - int reverse_samples_per_channel_; + AudioFormat fwd_in_format_; + AudioFormat fwd_proc_format_; + AudioRate fwd_out_format_; + AudioFormat rev_in_format_; + AudioFormat rev_proc_format_; + int split_rate_; + int stream_delay_ms_; int delay_offset_ms_; bool was_stream_delay_set_; - int num_reverse_channels_; - int num_input_channels_; - int num_output_channels_; bool output_will_be_muted_; bool key_pressed_; }; + } // namespace webrtc #endif // WEBRTC_MODULES_AUDIO_PROCESSING_MAIN_SOURCE_AUDIO_PROCESSING_IMPL_H_ diff --git a/webrtc/modules/audio_processing/common.h b/webrtc/modules/audio_processing/common.h new file mode 100644 index 000000000..42454df29 --- /dev/null +++ b/webrtc/modules/audio_processing/common.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_PROCESSING_COMMON_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_COMMON_H_ + +#include +#include + +#include "webrtc/modules/audio_processing/include/audio_processing.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +static inline int ChannelsFromLayout(AudioProcessing::ChannelLayout layout) { + switch (layout) { + case AudioProcessing::kMono: + case AudioProcessing::kMonoAndKeyboard: + return 1; + case AudioProcessing::kStereo: + case AudioProcessing::kStereoAndKeyboard: + return 2; + } + assert(false); + return -1; +} + +// Helper to encapsulate a contiguous data buffer with access to a pointer +// array of the deinterleaved channels. +template +class ChannelBuffer { + public: + ChannelBuffer(int samples_per_channel, int num_channels) + : data_(new T[samples_per_channel * num_channels]), + channels_(new T*[num_channels]), + samples_per_channel_(samples_per_channel), + num_channels_(num_channels) { + memset(data_.get(), 0, sizeof(T) * samples_per_channel * num_channels); + for (int i = 0; i < num_channels; ++i) + channels_[i] = &data_[i * samples_per_channel]; + } + ~ChannelBuffer() {} + + void CopyFrom(const void* channel_ptr, int i) { + assert(i < num_channels_); + memcpy(channels_[i], channel_ptr, samples_per_channel_ * sizeof(T)); + } + + T* data() { return data_.get(); } + T* channel(int i) { + assert(i < num_channels_); + return channels_[i]; + } + T** channels() { return channels_.get(); } + + int samples_per_channel() { return samples_per_channel_; } + int num_channels() { return num_channels_; } + int length() { return samples_per_channel_ * num_channels_; } + + private: + scoped_ptr data_; + scoped_ptr channels_; + int samples_per_channel_; + int num_channels_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_COMMON_H_ diff --git a/webrtc/modules/audio_processing/debug.proto b/webrtc/modules/audio_processing/debug.proto index 7d4e9d179..dce2f7920 100644 --- a/webrtc/modules/audio_processing/debug.proto +++ b/webrtc/modules/audio_processing/debug.proto @@ -4,11 +4,12 @@ package webrtc.audioproc; message Init { optional int32 sample_rate = 1; - optional int32 device_sample_rate = 2; + optional int32 device_sample_rate = 2 [deprecated=true]; optional int32 num_input_channels = 3; optional int32 num_output_channels = 4; optional int32 num_reverse_channels = 5; optional int32 reverse_sample_rate = 6; + optional int32 output_sample_rate = 7; } // May contain interleaved or deinterleaved data, but don't store both formats. diff --git a/webrtc/modules/audio_processing/echo_cancellation_impl.cc b/webrtc/modules/audio_processing/echo_cancellation_impl.cc index 0d6d159b1..e770f9fe3 100644 --- a/webrtc/modules/audio_processing/echo_cancellation_impl.cc +++ b/webrtc/modules/audio_processing/echo_cancellation_impl.cc @@ -63,12 +63,12 @@ EchoCancellationImpl::EchoCancellationImpl(const AudioProcessing* apm, drift_compensation_enabled_(false), metrics_enabled_(false), suppression_level_(kModerateSuppression), - device_sample_rate_hz_(48000), stream_drift_samples_(0), was_stream_drift_set_(false), stream_has_echo_(false), delay_logging_enabled_(false), - delay_correction_enabled_(false) {} + delay_correction_enabled_(false), + reported_delay_enabled_(true) {} EchoCancellationImpl::~EchoCancellationImpl() {} @@ -129,10 +129,10 @@ int EchoCancellationImpl::ProcessCaptureAudio(AudioBuffer* audio) { Handle* my_handle = handle(handle_index); err = WebRtcAec_Process( my_handle, - audio->low_pass_split_data(i), - audio->high_pass_split_data(i), - audio->low_pass_split_data(i), - audio->high_pass_split_data(i), + audio->low_pass_split_data_f(i), + audio->high_pass_split_data_f(i), + audio->low_pass_split_data_f(i), + audio->high_pass_split_data_f(i), static_cast(audio->samples_per_split_channel()), apm_->stream_delay_ms(), stream_drift_samples_); @@ -202,20 +202,6 @@ bool EchoCancellationImpl::is_drift_compensation_enabled() const { return drift_compensation_enabled_; } -int EchoCancellationImpl::set_device_sample_rate_hz(int rate) { - CriticalSectionScoped crit_scoped(crit_); - if (rate < 8000 || rate > 96000) { - return apm_->kBadParameterError; - } - - device_sample_rate_hz_ = rate; - return Initialize(); -} - -int EchoCancellationImpl::device_sample_rate_hz() const { - return device_sample_rate_hz_; -} - void EchoCancellationImpl::set_stream_drift_samples(int drift) { was_stream_drift_set_ = true; stream_drift_samples_ = drift; @@ -337,6 +323,7 @@ int EchoCancellationImpl::Initialize() { void EchoCancellationImpl::SetExtraOptions(const Config& config) { delay_correction_enabled_ = config.Get().enabled; + reported_delay_enabled_ = config.Get().enabled; Configure(); } @@ -351,16 +338,19 @@ void* EchoCancellationImpl::CreateHandle() const { return handle; } -int EchoCancellationImpl::DestroyHandle(void* handle) const { +void EchoCancellationImpl::DestroyHandle(void* handle) const { assert(handle != NULL); - return WebRtcAec_Free(static_cast(handle)); + WebRtcAec_Free(static_cast(handle)); } int EchoCancellationImpl::InitializeHandle(void* handle) const { assert(handle != NULL); + // TODO(ajm): Drift compensation is disabled in practice. If restored, it + // should be managed internally and not depend on the hardware sample rate. + // For now, just hardcode a 48 kHz value. return WebRtcAec_Init(static_cast(handle), - apm_->sample_rate_hz(), - device_sample_rate_hz_); + apm_->proc_sample_rate_hz(), + 48000); } int EchoCancellationImpl::ConfigureHandle(void* handle) const { @@ -373,6 +363,8 @@ int EchoCancellationImpl::ConfigureHandle(void* handle) const { WebRtcAec_enable_delay_correction(WebRtcAec_aec_core( static_cast(handle)), delay_correction_enabled_ ? 1 : 0); + WebRtcAec_enable_reported_delay(WebRtcAec_aec_core( + static_cast(handle)), reported_delay_enabled_ ? 1 : 0); return WebRtcAec_set_config(static_cast(handle), config); } diff --git a/webrtc/modules/audio_processing/echo_cancellation_impl.h b/webrtc/modules/audio_processing/echo_cancellation_impl.h index f5572b940..b9c116a06 100644 --- a/webrtc/modules/audio_processing/echo_cancellation_impl.h +++ b/webrtc/modules/audio_processing/echo_cancellation_impl.h @@ -31,7 +31,6 @@ class EchoCancellationImpl : public EchoCancellation, // EchoCancellation implementation. virtual bool is_enabled() const OVERRIDE; - virtual int device_sample_rate_hz() const OVERRIDE; virtual int stream_drift_samples() const OVERRIDE; // ProcessingComponent implementation. @@ -43,7 +42,6 @@ class EchoCancellationImpl : public EchoCancellation, virtual int Enable(bool enable) OVERRIDE; virtual int enable_drift_compensation(bool enable) OVERRIDE; virtual bool is_drift_compensation_enabled() const OVERRIDE; - virtual int set_device_sample_rate_hz(int rate) OVERRIDE; virtual void set_stream_drift_samples(int drift) OVERRIDE; virtual int set_suppression_level(SuppressionLevel level) OVERRIDE; virtual SuppressionLevel suppression_level() const OVERRIDE; @@ -60,7 +58,7 @@ class EchoCancellationImpl : public EchoCancellation, virtual void* CreateHandle() const OVERRIDE; virtual int InitializeHandle(void* handle) const OVERRIDE; virtual int ConfigureHandle(void* handle) const OVERRIDE; - virtual int DestroyHandle(void* handle) const OVERRIDE; + virtual void DestroyHandle(void* handle) const OVERRIDE; virtual int num_handles_required() const OVERRIDE; virtual int GetHandleError(void* handle) const OVERRIDE; @@ -69,12 +67,12 @@ class EchoCancellationImpl : public EchoCancellation, bool drift_compensation_enabled_; bool metrics_enabled_; SuppressionLevel suppression_level_; - int device_sample_rate_hz_; int stream_drift_samples_; bool was_stream_drift_set_; bool stream_has_echo_; bool delay_logging_enabled_; bool delay_correction_enabled_; + bool reported_delay_enabled_; }; } // namespace webrtc diff --git a/webrtc/modules/audio_processing/echo_cancellation_impl_unittest.cc b/webrtc/modules/audio_processing/echo_cancellation_impl_unittest.cc index f9bc3213f..49bcf9459 100644 --- a/webrtc/modules/audio_processing/echo_cancellation_impl_unittest.cc +++ b/webrtc/modules/audio_processing/echo_cancellation_impl_unittest.cc @@ -14,6 +14,7 @@ extern "C" { } #include "webrtc/modules/audio_processing/include/audio_processing.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/test/testsupport/gtest_disable.h" namespace webrtc { @@ -47,4 +48,34 @@ TEST(EchoCancellationInternalTest, DelayCorrection) { EXPECT_EQ(0, WebRtcAec_delay_correction_enabled(aec_core)); } +TEST(EchoCancellationInternalTest, ReportedDelay) { + scoped_ptr ap(AudioProcessing::Create(0)); + EXPECT_TRUE(ap->echo_cancellation()->aec_core() == NULL); + + EXPECT_EQ(ap->kNoError, ap->echo_cancellation()->Enable(true)); + EXPECT_TRUE(ap->echo_cancellation()->is_enabled()); + + AecCore* aec_core = ap->echo_cancellation()->aec_core(); + ASSERT_TRUE(aec_core != NULL); + // Enabled by default. + EXPECT_EQ(1, WebRtcAec_reported_delay_enabled(aec_core)); + + Config config; + config.Set(new ReportedDelay(false)); + ap->SetExtraOptions(config); + EXPECT_EQ(0, WebRtcAec_reported_delay_enabled(aec_core)); + + // Retains setting after initialization. + EXPECT_EQ(ap->kNoError, ap->Initialize()); + EXPECT_EQ(0, WebRtcAec_reported_delay_enabled(aec_core)); + + config.Set(new ReportedDelay(true)); + ap->SetExtraOptions(config); + EXPECT_EQ(1, WebRtcAec_reported_delay_enabled(aec_core)); + + // Retains setting after initialization. + EXPECT_EQ(ap->kNoError, ap->Initialize()); + EXPECT_EQ(1, WebRtcAec_reported_delay_enabled(aec_core)); +} + } // namespace webrtc diff --git a/webrtc/modules/audio_processing/echo_control_mobile_impl.cc b/webrtc/modules/audio_processing/echo_control_mobile_impl.cc index 8434b6178..a03adc530 100644 --- a/webrtc/modules/audio_processing/echo_control_mobile_impl.cc +++ b/webrtc/modules/audio_processing/echo_control_mobile_impl.cc @@ -128,7 +128,7 @@ int EchoControlMobileImpl::ProcessCaptureAudio(AudioBuffer* audio) { for (int i = 0; i < audio->num_channels(); i++) { // TODO(ajm): improve how this works, possibly inside AECM. // This is kind of hacked up. - int16_t* noisy = audio->low_pass_reference(i); + const int16_t* noisy = audio->low_pass_reference(i); int16_t* clean = audio->low_pass_split_data(i); if (noisy == NULL) { noisy = clean; @@ -241,7 +241,7 @@ int EchoControlMobileImpl::Initialize() { return apm_->kNoError; } - if (apm_->sample_rate_hz() == apm_->kSampleRate32kHz) { + if (apm_->proc_sample_rate_hz() > apm_->kSampleRate16kHz) { LOG(LS_ERROR) << "AECM only supports 16 kHz or lower sample rates"; return apm_->kBadSampleRateError; } @@ -260,14 +260,14 @@ void* EchoControlMobileImpl::CreateHandle() const { return handle; } -int EchoControlMobileImpl::DestroyHandle(void* handle) const { - return WebRtcAecm_Free(static_cast(handle)); +void EchoControlMobileImpl::DestroyHandle(void* handle) const { + WebRtcAecm_Free(static_cast(handle)); } int EchoControlMobileImpl::InitializeHandle(void* handle) const { assert(handle != NULL); Handle* my_handle = static_cast(handle); - if (WebRtcAecm_Init(my_handle, apm_->sample_rate_hz()) != 0) { + if (WebRtcAecm_Init(my_handle, apm_->proc_sample_rate_hz()) != 0) { return GetHandleError(my_handle); } if (external_echo_path_ != NULL) { diff --git a/webrtc/modules/audio_processing/echo_control_mobile_impl.h b/webrtc/modules/audio_processing/echo_control_mobile_impl.h index f00d59bcf..4f5b5931a 100644 --- a/webrtc/modules/audio_processing/echo_control_mobile_impl.h +++ b/webrtc/modules/audio_processing/echo_control_mobile_impl.h @@ -49,7 +49,7 @@ class EchoControlMobileImpl : public EchoControlMobile, virtual void* CreateHandle() const OVERRIDE; virtual int InitializeHandle(void* handle) const OVERRIDE; virtual int ConfigureHandle(void* handle) const OVERRIDE; - virtual int DestroyHandle(void* handle) const OVERRIDE; + virtual void DestroyHandle(void* handle) const OVERRIDE; virtual int num_handles_required() const OVERRIDE; virtual int GetHandleError(void* handle) const OVERRIDE; diff --git a/webrtc/modules/audio_processing/gain_control_impl.cc b/webrtc/modules/audio_processing/gain_control_impl.cc index 59532210b..a67b67ecb 100644 --- a/webrtc/modules/audio_processing/gain_control_impl.cc +++ b/webrtc/modules/audio_processing/gain_control_impl.cc @@ -59,7 +59,7 @@ int GainControlImpl::ProcessRenderAudio(AudioBuffer* audio) { assert(audio->samples_per_split_channel() <= 160); - int16_t* mixed_data = audio->low_pass_split_data(0); + const int16_t* mixed_data = audio->low_pass_split_data(0); if (audio->num_channels() > 1) { audio->CopyAndMixLowPass(1); mixed_data = audio->mixed_low_pass_data(0); @@ -317,8 +317,8 @@ void* GainControlImpl::CreateHandle() const { return handle; } -int GainControlImpl::DestroyHandle(void* handle) const { - return WebRtcAgc_Free(static_cast(handle)); +void GainControlImpl::DestroyHandle(void* handle) const { + WebRtcAgc_Free(static_cast(handle)); } int GainControlImpl::InitializeHandle(void* handle) const { @@ -326,7 +326,7 @@ int GainControlImpl::InitializeHandle(void* handle) const { minimum_capture_level_, maximum_capture_level_, MapSetting(mode_), - apm_->sample_rate_hz()); + apm_->proc_sample_rate_hz()); } int GainControlImpl::ConfigureHandle(void* handle) const { diff --git a/webrtc/modules/audio_processing/gain_control_impl.h b/webrtc/modules/audio_processing/gain_control_impl.h index e1669ccc7..811598700 100644 --- a/webrtc/modules/audio_processing/gain_control_impl.h +++ b/webrtc/modules/audio_processing/gain_control_impl.h @@ -60,7 +60,7 @@ class GainControlImpl : public GainControl, virtual void* CreateHandle() const OVERRIDE; virtual int InitializeHandle(void* handle) const OVERRIDE; virtual int ConfigureHandle(void* handle) const OVERRIDE; - virtual int DestroyHandle(void* handle) const OVERRIDE; + virtual void DestroyHandle(void* handle) const OVERRIDE; virtual int num_handles_required() const OVERRIDE; virtual int GetHandleError(void* handle) const OVERRIDE; diff --git a/webrtc/modules/audio_processing/high_pass_filter_impl.cc b/webrtc/modules/audio_processing/high_pass_filter_impl.cc index d4836ef55..0a23ff235 100644 --- a/webrtc/modules/audio_processing/high_pass_filter_impl.cc +++ b/webrtc/modules/audio_processing/high_pass_filter_impl.cc @@ -148,14 +148,13 @@ void* HighPassFilterImpl::CreateHandle() const { return new FilterState; } -int HighPassFilterImpl::DestroyHandle(void* handle) const { +void HighPassFilterImpl::DestroyHandle(void* handle) const { delete static_cast(handle); - return apm_->kNoError; } int HighPassFilterImpl::InitializeHandle(void* handle) const { return InitializeFilter(static_cast(handle), - apm_->sample_rate_hz()); + apm_->proc_sample_rate_hz()); } int HighPassFilterImpl::ConfigureHandle(void* /*handle*/) const { diff --git a/webrtc/modules/audio_processing/high_pass_filter_impl.h b/webrtc/modules/audio_processing/high_pass_filter_impl.h index 1796e77ff..6f91f3bc0 100644 --- a/webrtc/modules/audio_processing/high_pass_filter_impl.h +++ b/webrtc/modules/audio_processing/high_pass_filter_impl.h @@ -38,7 +38,7 @@ class HighPassFilterImpl : public HighPassFilter, virtual void* CreateHandle() const OVERRIDE; virtual int InitializeHandle(void* handle) const OVERRIDE; virtual int ConfigureHandle(void* handle) const OVERRIDE; - virtual int DestroyHandle(void* handle) const OVERRIDE; + virtual void DestroyHandle(void* handle) const OVERRIDE; virtual int num_handles_required() const OVERRIDE; virtual int GetHandleError(void* handle) const OVERRIDE; diff --git a/webrtc/modules/audio_processing/include/audio_processing.h b/webrtc/modules/audio_processing/include/audio_processing.h index 096193cec..77c3f3add 100644 --- a/webrtc/modules/audio_processing/include/audio_processing.h +++ b/webrtc/modules/audio_processing/include/audio_processing.h @@ -53,6 +53,18 @@ struct DelayCorrection { bool enabled; }; +// Use to disable the reported system delays. By disabling the reported system +// delays the echo cancellation algorithm assumes the process and reverse +// streams to be aligned. This configuration only applies to EchoCancellation +// and not EchoControlMobile and is set with AudioProcessing::SetExtraOptions(). +// Note that by disabling reported system delays the EchoCancellation may +// regress in performance. +struct ReportedDelay { + ReportedDelay() : enabled(true) {} + explicit ReportedDelay(bool enabled) : enabled(enabled) {} + bool enabled; +}; + // Must be provided through AudioProcessing::Create(Confg&). It will have no // impact if used with AudioProcessing::SetExtraOptions(). struct ExperimentalAgc { @@ -92,8 +104,9 @@ static const int kAudioProcMaxNativeSampleRateHz = 32000; // 2. Parameter getters are never called concurrently with the corresponding // setter. // -// APM accepts only 16-bit linear PCM audio data in frames of 10 ms. Multiple -// channels should be interleaved. +// APM accepts only linear PCM audio data in chunks of 10 ms. The int16 +// interfaces use interleaved data, while the float interfaces use deinterleaved +// data. // // Usage example, omitting error checking: // AudioProcessing* apm = AudioProcessing::Create(0); @@ -162,15 +175,27 @@ class AudioProcessing { // Initializes internal states, while retaining all user settings. This // should be called before beginning to process a new audio stream. However, // it is not necessary to call before processing the first stream after - // creation. It is also not necessary to call if the audio parameters (sample + // creation. + // + // It is also not necessary to call if the audio parameters (sample // rate and number of channels) have changed. Passing updated parameters // directly to |ProcessStream()| and |AnalyzeReverseStream()| is permissible. + // If the parameters are known at init-time though, they may be provided. virtual int Initialize() = 0; - virtual int Initialize(int sample_rate_hz, + + // The int16 interfaces require: + // - only |NativeRate|s be used + // - that the input, output and reverse rates must match + // - that |output_layout| matches |input_layout| + // + // The float interfaces accept arbitrary rates and support differing input + // and output layouts, but the output may only remove channels, not add. + virtual int Initialize(int input_sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, - int num_input_channels, - int num_output_channels, - int num_reverse_channels) = 0; + ChannelLayout input_layout, + ChannelLayout output_layout, + ChannelLayout reverse_layout) = 0; // Pass down additional options which don't have explicit setters. This // ensures the options are applied immediately. @@ -179,28 +204,21 @@ class AudioProcessing { virtual int EnableExperimentalNs(bool enable) = 0; virtual bool experimental_ns_enabled() const = 0; - // DEPRECATED: It is now possible to modify the sample rate directly in a call - // to |ProcessStream|. - // Sets the sample |rate| in Hz for both the primary and reverse audio - // streams. 8000, 16000 or 32000 Hz are permitted. + // DEPRECATED. + // TODO(ajm): Remove after Chromium has upgraded to using Initialize(). virtual int set_sample_rate_hz(int rate) = 0; + // TODO(ajm): Remove after voice engine no longer requires it to resample + // the reverse stream to the forward rate. + virtual int input_sample_rate_hz() const = 0; + // TODO(ajm): Remove after Chromium no longer depends on it. virtual int sample_rate_hz() const = 0; - virtual int split_sample_rate_hz() const = 0; - - // DEPRECATED: It is now possible to modify the number of channels directly in - // a call to |ProcessStream|. - // Sets the number of channels for the primary audio stream. Input frames must - // contain a number of channels given by |input_channels|, while output frames - // will be returned with number of channels given by |output_channels|. - virtual int set_num_channels(int input_channels, int output_channels) = 0; + + // TODO(ajm): Only intended for internal use. Make private and friend the + // necessary classes? + virtual int proc_sample_rate_hz() const = 0; + virtual int proc_split_sample_rate_hz() const = 0; virtual int num_input_channels() const = 0; virtual int num_output_channels() const = 0; - - // DEPRECATED: It is now possible to modify the number of channels directly in - // a call to |AnalyzeReverseStream|. - // Sets the number of channels for the reverse audio stream. Input frames must - // contain a number of channels given by |channels|. - virtual int set_num_reverse_channels(int channels) = 0; virtual int num_reverse_channels() const = 0; // Set to true when the output of AudioProcessing will be muted or in some @@ -223,15 +241,19 @@ class AudioProcessing { virtual int ProcessStream(AudioFrame* frame) = 0; // Accepts deinterleaved float audio with the range [-1, 1]. Each element - // of |data| points to a channel buffer, arranged according to + // of |src| points to a channel buffer, arranged according to // |input_layout|. At output, the channels will be arranged according to - // |output_layout|. - // TODO(ajm): Output layout conversion does not yet work. - virtual int ProcessStream(float* const* data, + // |output_layout| at |output_sample_rate_hz| in |dest|. + // + // The output layout may only remove channels, not add. |src| and |dest| + // may use the same memory, if desired. + virtual int ProcessStream(const float* const* src, int samples_per_channel, - int sample_rate_hz, + int input_sample_rate_hz, ChannelLayout input_layout, - ChannelLayout output_layout) = 0; + int output_sample_rate_hz, + ChannelLayout output_layout, + float* const* dest) = 0; // Analyzes a 10 ms |frame| of the reverse direction audio stream. The frame // will not be modified. On the client-side, this is the far-end (or to be @@ -245,7 +267,7 @@ class AudioProcessing { // // The |sample_rate_hz_|, |num_channels_|, and |samples_per_channel_| // members of |frame| must be valid. |sample_rate_hz_| must correspond to - // |sample_rate_hz()| + // |input_sample_rate_hz()| // // TODO(ajm): add const to input; requires an implementation fix. virtual int AnalyzeReverseStream(AudioFrame* frame) = 0; @@ -342,11 +364,13 @@ class AudioProcessing { kBadStreamParameterWarning = -13 }; - enum { + enum NativeRate { kSampleRate8kHz = 8000, kSampleRate16kHz = 16000, kSampleRate32kHz = 32000 }; + + static const int kChunkSizeMs = 10; }; // The acoustic echo cancellation (AEC) component provides better performance @@ -367,16 +391,10 @@ class EchoCancellation { // render and capture devices are used, particularly with webcams. // // This enables a compensation mechanism, and requires that - // |set_device_sample_rate_hz()| and |set_stream_drift_samples()| be called. + // set_stream_drift_samples() be called. virtual int enable_drift_compensation(bool enable) = 0; virtual bool is_drift_compensation_enabled() const = 0; - // Provides the sampling rate of the audio devices. It is assumed the render - // and capture devices use the same nominal sample rate. Required if and only - // if drift compensation is enabled. - virtual int set_device_sample_rate_hz(int rate) = 0; - virtual int device_sample_rate_hz() const = 0; - // Sets the difference between the number of samples rendered and captured by // the audio devices since the last call to |ProcessStream()|. Must be called // if drift compensation is enabled, prior to |ProcessStream()|. @@ -616,8 +634,7 @@ class LevelEstimator { // frames since the last call to RMS(). The returned value is positive but // should be interpreted as negative. It is constrained to [0, 127]. // - // The computation follows: - // http://tools.ietf.org/html/draft-ietf-avtext-client-to-mixer-audio-level-05 + // The computation follows: https://tools.ietf.org/html/rfc6465 // with the intent that it can provide the RTP audio level indication. // // Frames passed to ProcessStream() with an |_energy| of zero are considered diff --git a/webrtc/modules/audio_processing/include/mock_audio_processing.h b/webrtc/modules/audio_processing/include/mock_audio_processing.h index ba1d85826..c1ac23adf 100644 --- a/webrtc/modules/audio_processing/include/mock_audio_processing.h +++ b/webrtc/modules/audio_processing/include/mock_audio_processing.h @@ -26,10 +26,6 @@ class MockEchoCancellation : public EchoCancellation { int(bool enable)); MOCK_CONST_METHOD0(is_drift_compensation_enabled, bool()); - MOCK_METHOD1(set_device_sample_rate_hz, - int(int rate)); - MOCK_CONST_METHOD0(device_sample_rate_hz, - int()); MOCK_METHOD1(set_stream_drift_samples, void(int drift)); MOCK_CONST_METHOD0(stream_drift_samples, @@ -181,12 +177,13 @@ class MockAudioProcessing : public AudioProcessing { MOCK_METHOD0(Initialize, int()); - MOCK_METHOD5(Initialize, + MOCK_METHOD6(Initialize, int(int sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, - int num_input_channels, - int num_output_channels, - int num_reverse_channels)); + ChannelLayout input_layout, + ChannelLayout output_layout, + ChannelLayout reverse_layout)); MOCK_METHOD1(SetExtraOptions, void(const Config& config)); MOCK_METHOD1(EnableExperimentalNs, @@ -195,18 +192,18 @@ class MockAudioProcessing : public AudioProcessing { bool()); MOCK_METHOD1(set_sample_rate_hz, int(int rate)); + MOCK_CONST_METHOD0(input_sample_rate_hz, + int()); MOCK_CONST_METHOD0(sample_rate_hz, int()); - MOCK_CONST_METHOD0(split_sample_rate_hz, + MOCK_CONST_METHOD0(proc_sample_rate_hz, + int()); + MOCK_CONST_METHOD0(proc_split_sample_rate_hz, int()); - MOCK_METHOD2(set_num_channels, - int(int input_channels, int output_channels)); MOCK_CONST_METHOD0(num_input_channels, int()); MOCK_CONST_METHOD0(num_output_channels, int()); - MOCK_METHOD1(set_num_reverse_channels, - int(int channels)); MOCK_CONST_METHOD0(num_reverse_channels, int()); MOCK_METHOD1(set_output_will_be_muted, @@ -215,10 +212,14 @@ class MockAudioProcessing : public AudioProcessing { bool()); MOCK_METHOD1(ProcessStream, int(AudioFrame* frame)); - MOCK_METHOD5(ProcessStream, - int(float* const* data, int frames, int sample_rate_hz, + MOCK_METHOD7(ProcessStream, + int(const float* const* src, + int samples_per_channel, + int input_sample_rate_hz, ChannelLayout input_layout, - ChannelLayout output_layout)); + int output_sample_rate_hz, + ChannelLayout output_layout, + float* const* dest)); MOCK_METHOD1(AnalyzeReverseStream, int(AudioFrame* frame)); MOCK_METHOD4(AnalyzeReverseStream, diff --git a/webrtc/modules/audio_processing/level_estimator_impl.cc b/webrtc/modules/audio_processing/level_estimator_impl.cc index a512ef157..cfe295a6a 100644 --- a/webrtc/modules/audio_processing/level_estimator_impl.cc +++ b/webrtc/modules/audio_processing/level_estimator_impl.cc @@ -10,107 +10,31 @@ #include "webrtc/modules/audio_processing/level_estimator_impl.h" -#include -#include -#include - #include "webrtc/modules/audio_processing/audio_buffer.h" +#include "webrtc/modules/audio_processing/include/audio_processing.h" +#include "webrtc/modules/audio_processing/rms_level.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" namespace webrtc { -namespace { - -const double kMaxSquaredLevel = 32768.0 * 32768.0; - -class Level { - public: - static const int kMinLevel = 127; - - Level() - : sum_square_(0.0), - sample_count_(0) {} - ~Level() {} - - void Init() { - sum_square_ = 0.0; - sample_count_ = 0; - } - - void Process(int16_t* data, int length) { - assert(data != NULL); - assert(length > 0); - sum_square_ += SumSquare(data, length); - sample_count_ += length; - } - - void ProcessMuted(int length) { - assert(length > 0); - sample_count_ += length; - } - - int RMS() { - if (sample_count_ == 0 || sum_square_ == 0.0) { - Init(); - return kMinLevel; - } - - // Normalize by the max level. - double rms = sum_square_ / (sample_count_ * kMaxSquaredLevel); - // 20log_10(x^0.5) = 10log_10(x) - rms = 10 * log10(rms); - if (rms > 0) - rms = 0; - else if (rms < -kMinLevel) - rms = -kMinLevel; - - rms = -rms; - Init(); - return static_cast(rms + 0.5); - } - - private: - static double SumSquare(int16_t* data, int length) { - double sum_square = 0.0; - for (int i = 0; i < length; ++i) { - double data_d = static_cast(data[i]); - sum_square += data_d * data_d; - } - return sum_square; - } - - double sum_square_; - int sample_count_; -}; -} // namespace LevelEstimatorImpl::LevelEstimatorImpl(const AudioProcessing* apm, CriticalSectionWrapper* crit) : ProcessingComponent(), - apm_(apm), crit_(crit) {} LevelEstimatorImpl::~LevelEstimatorImpl() {} int LevelEstimatorImpl::ProcessStream(AudioBuffer* audio) { if (!is_component_enabled()) { - return apm_->kNoError; + return AudioProcessing::kNoError; } - Level* level = static_cast(handle(0)); - if (audio->is_muted()) { - level->ProcessMuted(audio->samples_per_channel()); - return apm_->kNoError; + RMSLevel* rms_level = static_cast(handle(0)); + for (int i = 0; i < audio->num_channels(); ++i) { + rms_level->Process(audio->data(i), audio->samples_per_channel()); } - int16_t* mixed_data = audio->data(0); - if (audio->num_channels() > 1) { - audio->CopyAndMix(1); - mixed_data = audio->mixed_data(0); - } - - level->Process(mixed_data, audio->samples_per_channel()); - - return apm_->kNoError; + return AudioProcessing::kNoError; } int LevelEstimatorImpl::Enable(bool enable) { @@ -124,43 +48,38 @@ bool LevelEstimatorImpl::is_enabled() const { int LevelEstimatorImpl::RMS() { if (!is_component_enabled()) { - return apm_->kNotEnabledError; + return AudioProcessing::kNotEnabledError; } - Level* level = static_cast(handle(0)); - return level->RMS(); + RMSLevel* rms_level = static_cast(handle(0)); + return rms_level->RMS(); } +// The ProcessingComponent implementation is pretty weird in this class since +// we have only a single instance of the trivial underlying component. void* LevelEstimatorImpl::CreateHandle() const { - return new Level; + return new RMSLevel; } -int LevelEstimatorImpl::DestroyHandle(void* handle) const { - assert(handle != NULL); - Level* level = static_cast(handle); - delete level; - return apm_->kNoError; +void LevelEstimatorImpl::DestroyHandle(void* handle) const { + delete static_cast(handle); } int LevelEstimatorImpl::InitializeHandle(void* handle) const { - assert(handle != NULL); - Level* level = static_cast(handle); - level->Init(); - - return apm_->kNoError; + static_cast(handle)->Reset(); + return AudioProcessing::kNoError; } int LevelEstimatorImpl::ConfigureHandle(void* /*handle*/) const { - return apm_->kNoError; + return AudioProcessing::kNoError; } int LevelEstimatorImpl::num_handles_required() const { return 1; } -int LevelEstimatorImpl::GetHandleError(void* handle) const { - // The component has no detailed errors. - assert(handle != NULL); - return apm_->kUnspecifiedError; +int LevelEstimatorImpl::GetHandleError(void* /*handle*/) const { + return AudioProcessing::kUnspecifiedError; } + } // namespace webrtc diff --git a/webrtc/modules/audio_processing/level_estimator_impl.h b/webrtc/modules/audio_processing/level_estimator_impl.h index 2490d7930..b38337d4d 100644 --- a/webrtc/modules/audio_processing/level_estimator_impl.h +++ b/webrtc/modules/audio_processing/level_estimator_impl.h @@ -13,6 +13,7 @@ #include "webrtc/modules/audio_processing/include/audio_processing.h" #include "webrtc/modules/audio_processing/processing_component.h" +#include "webrtc/modules/audio_processing/rms_level.h" namespace webrtc { @@ -40,11 +41,10 @@ class LevelEstimatorImpl : public LevelEstimator, virtual void* CreateHandle() const OVERRIDE; virtual int InitializeHandle(void* handle) const OVERRIDE; virtual int ConfigureHandle(void* handle) const OVERRIDE; - virtual int DestroyHandle(void* handle) const OVERRIDE; + virtual void DestroyHandle(void* handle) const OVERRIDE; virtual int num_handles_required() const OVERRIDE; virtual int GetHandleError(void* handle) const OVERRIDE; - const AudioProcessing* apm_; CriticalSectionWrapper* crit_; }; diff --git a/webrtc/modules/audio_processing/lib_core_neon_offsets.gypi b/webrtc/modules/audio_processing/lib_core_neon_offsets.gypi index a4d6b7488..f32ddd47f 100644 --- a/webrtc/modules/audio_processing/lib_core_neon_offsets.gypi +++ b/webrtc/modules/audio_processing/lib_core_neon_offsets.gypi @@ -14,7 +14,7 @@ 'lib_intermediate_name': '', 'conditions' : [ ['android_webview_build==1', { - 'lib_intermediate_name' : '<(android_src)/$(call intermediates-dir-for, STATIC_LIBRARIES, lib_core_neon_offsets,,, $(GYP_VAR_PREFIX))/lib_core_neon_offsets.a', + 'lib_intermediate_name' : '$(abspath $(call intermediates-dir-for,STATIC_LIBRARIES,lib_core_neon_offsets,,,$(gyp_var_prefix)))/lib_core_neon_offsets.a', }], ], }, diff --git a/webrtc/modules/audio_processing/noise_suppression_impl.cc b/webrtc/modules/audio_processing/noise_suppression_impl.cc index 9ecbf8d37..eea0a04a2 100644 --- a/webrtc/modules/audio_processing/noise_suppression_impl.cc +++ b/webrtc/modules/audio_processing/noise_suppression_impl.cc @@ -68,10 +68,10 @@ int NoiseSuppressionImpl::ProcessCaptureAudio(AudioBuffer* audio) { Handle* my_handle = static_cast(handle(i)); #if defined(WEBRTC_NS_FLOAT) err = WebRtcNs_Process(static_cast(handle(i)), - audio->low_pass_split_data(i), - audio->high_pass_split_data(i), - audio->low_pass_split_data(i), - audio->high_pass_split_data(i)); + audio->low_pass_split_data_f(i), + audio->high_pass_split_data_f(i), + audio->low_pass_split_data_f(i), + audio->high_pass_split_data_f(i)); #elif defined(WEBRTC_NS_FIXED) err = WebRtcNsx_Process(static_cast(handle(i)), audio->low_pass_split_data(i), @@ -141,19 +141,21 @@ void* NoiseSuppressionImpl::CreateHandle() const { return handle; } -int NoiseSuppressionImpl::DestroyHandle(void* handle) const { +void NoiseSuppressionImpl::DestroyHandle(void* handle) const { #if defined(WEBRTC_NS_FLOAT) - return WebRtcNs_Free(static_cast(handle)); + WebRtcNs_Free(static_cast(handle)); #elif defined(WEBRTC_NS_FIXED) - return WebRtcNsx_Free(static_cast(handle)); + WebRtcNsx_Free(static_cast(handle)); #endif } int NoiseSuppressionImpl::InitializeHandle(void* handle) const { #if defined(WEBRTC_NS_FLOAT) - return WebRtcNs_Init(static_cast(handle), apm_->sample_rate_hz()); + return WebRtcNs_Init(static_cast(handle), + apm_->proc_sample_rate_hz()); #elif defined(WEBRTC_NS_FIXED) - return WebRtcNsx_Init(static_cast(handle), apm_->sample_rate_hz()); + return WebRtcNsx_Init(static_cast(handle), + apm_->proc_sample_rate_hz()); #endif } diff --git a/webrtc/modules/audio_processing/noise_suppression_impl.h b/webrtc/modules/audio_processing/noise_suppression_impl.h index 46b7be2fe..cadbbd9cd 100644 --- a/webrtc/modules/audio_processing/noise_suppression_impl.h +++ b/webrtc/modules/audio_processing/noise_suppression_impl.h @@ -42,7 +42,7 @@ class NoiseSuppressionImpl : public NoiseSuppression, virtual void* CreateHandle() const OVERRIDE; virtual int InitializeHandle(void* handle) const OVERRIDE; virtual int ConfigureHandle(void* handle) const OVERRIDE; - virtual int DestroyHandle(void* handle) const OVERRIDE; + virtual void DestroyHandle(void* handle) const OVERRIDE; virtual int num_handles_required() const OVERRIDE; virtual int GetHandleError(void* handle) const OVERRIDE; diff --git a/webrtc/modules/audio_processing/ns/include/noise_suppression.h b/webrtc/modules/audio_processing/ns/include/noise_suppression.h index 32b180380..3cf889e2d 100644 --- a/webrtc/modules/audio_processing/ns/include/noise_suppression.h +++ b/webrtc/modules/audio_processing/ns/include/noise_suppression.h @@ -99,10 +99,10 @@ int WebRtcNs_set_policy(NsHandle* NS_inst, int mode); * -1 - Error */ int WebRtcNs_Process(NsHandle* NS_inst, - short* spframe, - short* spframe_H, - short* outframe, - short* outframe_H); + float* spframe, + float* spframe_H, + float* outframe, + float* outframe_H); /* Returns the internally used prior speech probability of the current frame. * There is a frequency bin based one as well, with which this should not be diff --git a/webrtc/modules/audio_processing/ns/noise_suppression.c b/webrtc/modules/audio_processing/ns/noise_suppression.c index 848467f08..075ab88c1 100644 --- a/webrtc/modules/audio_processing/ns/noise_suppression.c +++ b/webrtc/modules/audio_processing/ns/noise_suppression.c @@ -43,8 +43,8 @@ int WebRtcNs_set_policy(NsHandle* NS_inst, int mode) { } -int WebRtcNs_Process(NsHandle* NS_inst, short* spframe, short* spframe_H, - short* outframe, short* outframe_H) { +int WebRtcNs_Process(NsHandle* NS_inst, float* spframe, float* spframe_H, + float* outframe, float* outframe_H) { return WebRtcNs_ProcessCore( (NSinst_t*) NS_inst, spframe, spframe_H, outframe, outframe_H); } diff --git a/webrtc/modules/audio_processing/ns/ns_core.c b/webrtc/modules/audio_processing/ns/ns_core.c index 124a66d8d..ec267ae0f 100644 --- a/webrtc/modules/audio_processing/ns/ns_core.c +++ b/webrtc/modules/audio_processing/ns/ns_core.c @@ -715,10 +715,10 @@ void WebRtcNs_SpeechNoiseProb(NSinst_t* inst, float* probSpeechFinal, float* snr } int WebRtcNs_ProcessCore(NSinst_t* inst, - short* speechFrame, - short* speechFrameHB, - short* outFrame, - short* outFrameHB) { + float* speechFrame, + float* speechFrameHB, + float* outFrame, + float* outFrameHB) { // main routine for noise reduction int flagHB = 0; @@ -731,8 +731,8 @@ int WebRtcNs_ProcessCore(NSinst_t* inst, float snrPrior, currentEstimateStsa; float tmpFloat1, tmpFloat2, tmpFloat3, probSpeech, probNonSpeech; float gammaNoiseTmp, gammaNoiseOld; - float noiseUpdateTmp, fTmp, dTmp; - float fin[BLOCKL_MAX], fout[BLOCKL_MAX]; + float noiseUpdateTmp, fTmp; + float fout[BLOCKL_MAX]; float winData[ANAL_BLOCKL_MAX]; float magn[HALF_ANAL_BLOCKL], noise[HALF_ANAL_BLOCKL]; float theFilter[HALF_ANAL_BLOCKL], theFilterTmp[HALF_ANAL_BLOCKL]; @@ -775,26 +775,17 @@ int WebRtcNs_ProcessCore(NSinst_t* inst, updateParsFlag = inst->modelUpdatePars[0]; // - //for LB do all processing - // convert to float - for (i = 0; i < inst->blockLen10ms; i++) { - fin[i] = (float)speechFrame[i]; - } // update analysis buffer for L band memcpy(inst->dataBuf, inst->dataBuf + inst->blockLen10ms, sizeof(float) * (inst->anaLen - inst->blockLen10ms)); - memcpy(inst->dataBuf + inst->anaLen - inst->blockLen10ms, fin, + memcpy(inst->dataBuf + inst->anaLen - inst->blockLen10ms, speechFrame, sizeof(float) * inst->blockLen10ms); if (flagHB == 1) { - // convert to float - for (i = 0; i < inst->blockLen10ms; i++) { - fin[i] = (float)speechFrameHB[i]; - } // update analysis buffer for H band memcpy(inst->dataBufHB, inst->dataBufHB + inst->blockLen10ms, sizeof(float) * (inst->anaLen - inst->blockLen10ms)); - memcpy(inst->dataBufHB + inst->anaLen - inst->blockLen10ms, fin, + memcpy(inst->dataBufHB + inst->anaLen - inst->blockLen10ms, speechFrameHB, sizeof(float) * inst->blockLen10ms); } @@ -833,30 +824,16 @@ int WebRtcNs_ProcessCore(NSinst_t* inst, inst->outBuf[i] = fout[i + inst->blockLen10ms]; } } - // convert to short - for (i = 0; i < inst->blockLen10ms; i++) { - dTmp = fout[i]; - if (dTmp < WEBRTC_SPL_WORD16_MIN) { - dTmp = WEBRTC_SPL_WORD16_MIN; - } else if (dTmp > WEBRTC_SPL_WORD16_MAX) { - dTmp = WEBRTC_SPL_WORD16_MAX; - } - outFrame[i] = (short)dTmp; - } + for (i = 0; i < inst->blockLen10ms; ++i) + outFrame[i] = WEBRTC_SPL_SAT( + WEBRTC_SPL_WORD16_MAX, fout[i], WEBRTC_SPL_WORD16_MIN); // for time-domain gain of HB - if (flagHB == 1) { - for (i = 0; i < inst->blockLen10ms; i++) { - dTmp = inst->dataBufHB[i]; - if (dTmp < WEBRTC_SPL_WORD16_MIN) { - dTmp = WEBRTC_SPL_WORD16_MIN; - } else if (dTmp > WEBRTC_SPL_WORD16_MAX) { - dTmp = WEBRTC_SPL_WORD16_MAX; - } - outFrameHB[i] = (short)dTmp; - } - } // end of H band gain computation - // + if (flagHB == 1) + for (i = 0; i < inst->blockLen10ms; ++i) + outFrameHB[i] = WEBRTC_SPL_SAT( + WEBRTC_SPL_WORD16_MAX, inst->dataBufHB[i], WEBRTC_SPL_WORD16_MIN); + return 0; } @@ -1239,16 +1216,9 @@ int WebRtcNs_ProcessCore(NSinst_t* inst, inst->outLen -= inst->blockLen10ms; } - // convert to short - for (i = 0; i < inst->blockLen10ms; i++) { - dTmp = fout[i]; - if (dTmp < WEBRTC_SPL_WORD16_MIN) { - dTmp = WEBRTC_SPL_WORD16_MIN; - } else if (dTmp > WEBRTC_SPL_WORD16_MAX) { - dTmp = WEBRTC_SPL_WORD16_MAX; - } - outFrame[i] = (short)dTmp; - } + for (i = 0; i < inst->blockLen10ms; ++i) + outFrame[i] = WEBRTC_SPL_SAT( + WEBRTC_SPL_WORD16_MAX, fout[i], WEBRTC_SPL_WORD16_MIN); // for time-domain gain of HB if (flagHB == 1) { @@ -1289,13 +1259,9 @@ int WebRtcNs_ProcessCore(NSinst_t* inst, } //apply gain for (i = 0; i < inst->blockLen10ms; i++) { - dTmp = gainTimeDomainHB * inst->dataBufHB[i]; - if (dTmp < WEBRTC_SPL_WORD16_MIN) { - dTmp = WEBRTC_SPL_WORD16_MIN; - } else if (dTmp > WEBRTC_SPL_WORD16_MAX) { - dTmp = WEBRTC_SPL_WORD16_MAX; - } - outFrameHB[i] = (short)dTmp; + float o = gainTimeDomainHB * inst->dataBufHB[i]; + outFrameHB[i] = WEBRTC_SPL_SAT( + WEBRTC_SPL_WORD16_MAX, o, WEBRTC_SPL_WORD16_MIN); } } // end of H band gain computation // diff --git a/webrtc/modules/audio_processing/ns/ns_core.h b/webrtc/modules/audio_processing/ns/ns_core.h index 50daa137c..785239ebd 100644 --- a/webrtc/modules/audio_processing/ns/ns_core.h +++ b/webrtc/modules/audio_processing/ns/ns_core.h @@ -167,10 +167,10 @@ int WebRtcNs_set_policy_core(NSinst_t* inst, int mode); int WebRtcNs_ProcessCore(NSinst_t* inst, - short* inFrameLow, - short* inFrameHigh, - short* outFrameLow, - short* outFrameHigh); + float* inFrameLow, + float* inFrameHigh, + float* outFrameLow, + float* outFrameHigh); #ifdef __cplusplus diff --git a/webrtc/modules/audio_processing/ns/nsx_core.c b/webrtc/modules/audio_processing/ns/nsx_core.c index e627a2eff..2c8270f56 100644 --- a/webrtc/modules/audio_processing/ns/nsx_core.c +++ b/webrtc/modules/audio_processing/ns/nsx_core.c @@ -1407,9 +1407,9 @@ void WebRtcNsx_DataAnalysis(NsxInst_t* inst, short* speechFrame, uint16_t* magnU tmpU32no1 = WEBRTC_SPL_RSHIFT_U32((uint32_t)sum_log_i_log_magn, 12); // Q5 // Shift the largest value of sum_log_i and tmp32no3 before multiplication - tmp_u16 = WEBRTC_SPL_LSHIFT_U16((uint16_t)sum_log_i, 1); // Q6 + tmp_u16 = ((uint16_t)sum_log_i << 1); // Q6 if ((uint32_t)sum_log_i > tmpU32no1) { - tmp_u16 = WEBRTC_SPL_RSHIFT_U16(tmp_u16, zeros); + tmp_u16 >>= zeros; } else { tmpU32no1 = WEBRTC_SPL_RSHIFT_U32(tmpU32no1, zeros); } @@ -2071,8 +2071,8 @@ int WebRtcNsx_ProcessCore(NsxInst_t* inst, short* speechFrame, short* speechFram tmpU16no1 += nonSpeechProbFinal[i]; // Q8 tmpU32no1 += (uint32_t)(inst->noiseSupFilter[i]); // Q14 } - avgProbSpeechHB = (int16_t)(4096 - - WEBRTC_SPL_RSHIFT_U16(tmpU16no1, inst->stages - 7)); // Q12 + assert(inst->stages >= 7); + avgProbSpeechHB = (4096 - (tmpU16no1 >> (inst->stages - 7))); // Q12 avgFilterGainHB = (int16_t)WEBRTC_SPL_RSHIFT_U32( tmpU32no1, inst->stages - 3); // Q14 diff --git a/webrtc/modules/audio_processing/processing_component.h b/webrtc/modules/audio_processing/processing_component.h index 27400998e..8ee3ac6c7 100644 --- a/webrtc/modules/audio_processing/processing_component.h +++ b/webrtc/modules/audio_processing/processing_component.h @@ -38,7 +38,7 @@ class ProcessingComponent { virtual void* CreateHandle() const = 0; virtual int InitializeHandle(void* handle) const = 0; virtual int ConfigureHandle(void* handle) const = 0; - virtual int DestroyHandle(void* handle) const = 0; + virtual void DestroyHandle(void* handle) const = 0; virtual int num_handles_required() const = 0; virtual int GetHandleError(void* handle) const = 0; diff --git a/webrtc/modules/audio_processing/rms_level.cc b/webrtc/modules/audio_processing/rms_level.cc new file mode 100644 index 000000000..14136bf30 --- /dev/null +++ b/webrtc/modules/audio_processing/rms_level.cc @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/audio_processing/rms_level.h" + +#include +#include + +namespace webrtc { + +static const float kMaxSquaredLevel = 32768 * 32768; + +RMSLevel::RMSLevel() + : sum_square_(0), + sample_count_(0) {} + +RMSLevel::~RMSLevel() {} + +void RMSLevel::Reset() { + sum_square_ = 0; + sample_count_ = 0; +} + +void RMSLevel::Process(const int16_t* data, int length) { + for (int i = 0; i < length; ++i) { + sum_square_ += data[i] * data[i]; + } + sample_count_ += length; +} + +void RMSLevel::ProcessMuted(int length) { + sample_count_ += length; +} + +int RMSLevel::RMS() { + if (sample_count_ == 0 || sum_square_ == 0) { + Reset(); + return kMinLevel; + } + + // Normalize by the max level. + float rms = sum_square_ / (sample_count_ * kMaxSquaredLevel); + // 20log_10(x^0.5) = 10log_10(x) + rms = 10 * log10(rms); + assert(rms <= 0); + if (rms < -kMinLevel) + rms = -kMinLevel; + + rms = -rms; + Reset(); + return static_cast(rms + 0.5); +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/rms_level.h b/webrtc/modules/audio_processing/rms_level.h new file mode 100644 index 000000000..055d271bb --- /dev/null +++ b/webrtc/modules/audio_processing/rms_level.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014 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_MODULES_AUDIO_PROCESSING_RMS_LEVEL_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_RMS_LEVEL_H_ + +#include "webrtc/typedefs.h" + +namespace webrtc { + +// Computes the root mean square (RMS) level in dBFs (decibels from digital +// full-scale) of audio data. The computation follows RFC 6465: +// https://tools.ietf.org/html/rfc6465 +// with the intent that it can provide the RTP audio level indication. +// +// The expected approach is to provide constant-sized chunks of audio to +// Process(). When enough chunks have been accumulated to form a packet, call +// RMS() to get the audio level indicator for the RTP header. +class RMSLevel { + public: + static const int kMinLevel = 127; + + RMSLevel(); + ~RMSLevel(); + + // Can be called to reset internal states, but is not required during normal + // operation. + void Reset(); + + // Pass each chunk of audio to Process() to accumulate the level. + void Process(const int16_t* data, int length); + + // If all samples with the given |length| have a magnitude of zero, this is + // a shortcut to avoid some computation. + void ProcessMuted(int length); + + // Computes the RMS level over all data passed to Process() since the last + // call to RMS(). The returned value is positive but should be interpreted as + // negative as per the RFC. It is constrained to [0, 127]. + int RMS(); + + private: + float sum_square_; + int sample_count_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_RMS_LEVEL_H_ + diff --git a/webrtc/modules/audio_processing/test/audio_processing_unittest.cc b/webrtc/modules/audio_processing/test/audio_processing_unittest.cc index 2aa37ca81..65c2d8d4d 100644 --- a/webrtc/modules/audio_processing/test/audio_processing_unittest.cc +++ b/webrtc/modules/audio_processing/test/audio_processing_unittest.cc @@ -8,11 +8,15 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include #include #include +#include #include #include "webrtc/common_audio/include/audio_util.h" +#include "webrtc/common_audio/resampler/include/push_resampler.h" +#include "webrtc/common_audio/resampler/push_sinc_resampler.h" #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" #include "webrtc/modules/audio_processing/include/audio_processing.h" #include "webrtc/modules/audio_processing/test/test_utils.h" @@ -61,33 +65,54 @@ const int kProcessSampleRates[] = {8000, 16000, 32000}; const size_t kProcessSampleRatesSize = sizeof(kProcessSampleRates) / sizeof(*kProcessSampleRates); -void ConvertToFloat(const AudioFrame& frame, ChannelBuffer* cb) { - ChannelBuffer cb_int(frame.samples_per_channel_, - frame.num_channels_); - Deinterleave(frame.data_, - frame.samples_per_channel_, - frame.num_channels_, +void ConvertToFloat(const int16_t* int_data, ChannelBuffer* cb) { + ChannelBuffer cb_int(cb->samples_per_channel(), + cb->num_channels()); + Deinterleave(int_data, + cb->samples_per_channel(), + cb->num_channels(), cb_int.channels()); ScaleToFloat(cb_int.data(), - frame.samples_per_channel_ * frame.num_channels_, + cb->samples_per_channel() * cb->num_channels(), cb->data()); } +void ConvertToFloat(const AudioFrame& frame, ChannelBuffer* cb) { + ConvertToFloat(frame.data_, cb); +} + +// Number of channels including the keyboard channel. +int TotalChannelsFromLayout(AudioProcessing::ChannelLayout layout) { + switch (layout) { + case AudioProcessing::kMono: + return 1; + case AudioProcessing::kMonoAndKeyboard: + case AudioProcessing::kStereo: + return 2; + case AudioProcessing::kStereoAndKeyboard: + return 3; + } + assert(false); + return -1; +} + int TruncateToMultipleOf10(int value) { return (value / 10) * 10; } -// TODO(andrew): Use the MonoToStereo routine from AudioFrameOperations. -void MixStereoToMono(const int16_t* stereo, - int16_t* mono, +void MixStereoToMono(const float* stereo, float* mono, int samples_per_channel) { - for (int i = 0; i < samples_per_channel; i++) { - int32_t mono_s32 = (static_cast(stereo[i * 2]) + - static_cast(stereo[i * 2 + 1])) >> 1; - mono[i] = static_cast(mono_s32); + for (int i = 0; i < samples_per_channel; ++i) { + mono[i] = (stereo[i * 2] + stereo[i * 2 + 1]) / 2; } } +void MixStereoToMono(const int16_t* stereo, int16_t* mono, + int samples_per_channel) { + for (int i = 0; i < samples_per_channel; i++) + mono[i] = (stereo[i * 2] + stereo[i * 2 + 1]) >> 1; +} + void CopyLeftToRightChannel(int16_t* stereo, int samples_per_channel) { for (int i = 0; i < samples_per_channel; i++) { stereo[i * 2 + 1] = stereo[i * 2]; @@ -211,6 +236,35 @@ void OpenFileAndWriteMessage(const std::string filename, } #endif // WEBRTC_AUDIOPROC_BIT_EXACT +std::string ResourceFilePath(std::string name, int sample_rate_hz) { + std::ostringstream ss; + // Resource files are all stereo. + ss << name << sample_rate_hz / 1000 << "_stereo"; + return test::ResourcePath(ss.str(), "pcm"); +} + +std::string OutputFilePath(std::string name, + int input_rate, + int output_rate, + int reverse_rate, + int num_input_channels, + int num_output_channels, + int num_reverse_channels) { + std::ostringstream ss; + ss << name << "_i" << num_input_channels << "_" << input_rate / 1000 + << "_r" << num_reverse_channels << "_" << reverse_rate / 1000 << "_"; + if (num_output_channels == 1) { + ss << "mono"; + } else if (num_output_channels == 2) { + ss << "stereo"; + } else { + assert(false); + } + ss << output_rate / 1000 << ".pcm"; + + return test::OutputPath() + ss.str(); +} + void OpenFileAndReadMessage(const std::string filename, ::google::protobuf::MessageLite* msg) { FILE* file = fopen(filename.c_str(), "rb"); @@ -242,18 +296,13 @@ class ApmTest : public ::testing::Test { }; void Init(int sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, int num_reverse_channels, int num_input_channels, int num_output_channels, bool open_output_file); void Init(AudioProcessing* ap); - std::string ResourceFilePath(std::string name, int sample_rate_hz); - std::string OutputFilePath(std::string name, - int sample_rate_hz, - int num_reverse_channels, - int num_input_channels, - int num_output_channels); void EnableAllComponents(); bool ReadFrame(FILE* file, AudioFrame* frame); bool ReadFrame(FILE* file, AudioFrame* frame, ChannelBuffer* cb); @@ -268,7 +317,6 @@ class ApmTest : public ::testing::Test { void RunQuantizedVolumeDoesNotGetStuckTest(int sample_rate); void RunManualVolumeChangeIsPossibleTest(int sample_rate); void StreamParametersTest(Format format); - void SampleRatesTest(Format format); int ProcessStreamChooser(Format format); int AnalyzeReverseStreamChooser(Format format); void ProcessDebugDump(const std::string& in_filename, @@ -284,6 +332,7 @@ class ApmTest : public ::testing::Test { AudioFrame* revframe_; scoped_ptr > float_cb_; scoped_ptr > revfloat_cb_; + int output_sample_rate_hz_; int num_output_channels_; FILE* far_file_; FILE* near_file_; @@ -300,6 +349,7 @@ ApmTest::ApmTest() #endif frame_(NULL), revframe_(NULL), + output_sample_rate_hz_(0), num_output_channels_(0), far_file_(NULL), near_file_(NULL), @@ -316,9 +366,9 @@ void ApmTest::SetUp() { revframe_ = new AudioFrame(); #if defined(WEBRTC_AUDIOPROC_FIXED_PROFILE) - Init(16000, 16000, 2, 2, 2, false); + Init(16000, 16000, 16000, 2, 2, 2, false); #else - Init(32000, 32000, 2, 2, 2, false); + Init(32000, 32000, 32000, 2, 2, 2, false); #endif } @@ -349,49 +399,25 @@ void ApmTest::TearDown() { out_file_ = NULL; } -std::string ApmTest::ResourceFilePath(std::string name, int sample_rate_hz) { - std::ostringstream ss; - // Resource files are all stereo. - ss << name << sample_rate_hz / 1000 << "_stereo"; - return test::ResourcePath(ss.str(), "pcm"); -} - -std::string ApmTest::OutputFilePath(std::string name, - int sample_rate_hz, - int num_reverse_channels, - int num_input_channels, - int num_output_channels) { - std::ostringstream ss; - ss << name << sample_rate_hz / 1000 << "_" << num_reverse_channels << "r" << - num_input_channels << "i" << "_"; - if (num_output_channels == 1) { - ss << "mono"; - } else if (num_output_channels == 2) { - ss << "stereo"; - } else { - assert(false); - return ""; - } - ss << ".pcm"; - - return output_path_ + ss.str(); -} - void ApmTest::Init(AudioProcessing* ap) { - ASSERT_EQ(ap->kNoError, ap->Initialize(frame_->sample_rate_hz_, - revframe_->sample_rate_hz_, - frame_->num_channels_, - num_output_channels_, - revframe_->num_channels_)); + ASSERT_EQ(kNoErr, + ap->Initialize(frame_->sample_rate_hz_, + output_sample_rate_hz_, + revframe_->sample_rate_hz_, + LayoutFromChannels(frame_->num_channels_), + LayoutFromChannels(num_output_channels_), + LayoutFromChannels(revframe_->num_channels_))); } void ApmTest::Init(int sample_rate_hz, + int output_sample_rate_hz, int reverse_sample_rate_hz, int num_input_channels, int num_output_channels, int num_reverse_channels, bool open_output_file) { SetContainerFormat(sample_rate_hz, num_input_channels, frame_, &float_cb_); + output_sample_rate_hz_ = output_sample_rate_hz; num_output_channels_ = num_output_channels; SetContainerFormat(reverse_sample_rate_hz, num_reverse_channels, revframe_, @@ -418,8 +444,13 @@ void ApmTest::Init(int sample_rate_hz, if (out_file_) { ASSERT_EQ(0, fclose(out_file_)); } - filename = OutputFilePath("out", sample_rate_hz, num_reverse_channels, - num_input_channels, num_output_channels); + filename = OutputFilePath("out", + sample_rate_hz, + output_sample_rate_hz, + reverse_sample_rate_hz, + num_input_channels, + num_output_channels, + num_reverse_channels); out_file_ = fopen(filename.c_str(), "wb"); ASSERT_TRUE(out_file_ != NULL) << "Could not open file " << filename << "\n"; @@ -485,12 +516,13 @@ int ApmTest::ProcessStreamChooser(Format format) { if (format == kIntFormat) { return apm_->ProcessStream(frame_); } - // TODO(ajm): Update to match the number of output channels when supported. return apm_->ProcessStream(float_cb_->channels(), frame_->samples_per_channel_, frame_->sample_rate_hz_, LayoutFromChannels(frame_->num_channels_), - LayoutFromChannels(frame_->num_channels_)); + output_sample_rate_hz_, + LayoutFromChannels(num_output_channels_), + float_cb_->channels()); } int ApmTest::AnalyzeReverseStreamChooser(Format format) { @@ -726,27 +758,19 @@ TEST_F(ApmTest, Channels) { } } -void ApmTest::SampleRatesTest(Format format) { +TEST_F(ApmTest, SampleRatesInt) { // Testing invalid sample rates SetContainerFormat(10000, 2, frame_, &float_cb_); - EXPECT_EQ(apm_->kBadSampleRateError, ProcessStreamChooser(format)); + EXPECT_EQ(apm_->kBadSampleRateError, ProcessStreamChooser(kIntFormat)); // Testing valid sample rates int fs[] = {8000, 16000, 32000}; for (size_t i = 0; i < sizeof(fs) / sizeof(*fs); i++) { SetContainerFormat(fs[i], 2, frame_, &float_cb_); - EXPECT_NOERR(ProcessStreamChooser(format)); - EXPECT_EQ(fs[i], apm_->sample_rate_hz()); + EXPECT_NOERR(ProcessStreamChooser(kIntFormat)); + EXPECT_EQ(fs[i], apm_->input_sample_rate_hz()); } } -TEST_F(ApmTest, SampleRatesInt) { - SampleRatesTest(kIntFormat); -} - -TEST_F(ApmTest, SampleRatesFloat) { - SampleRatesTest(kFloatFormat); -} - TEST_F(ApmTest, EchoCancellation) { EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->enable_drift_compensation(true)); @@ -755,19 +779,6 @@ TEST_F(ApmTest, EchoCancellation) { apm_->echo_cancellation()->enable_drift_compensation(false)); EXPECT_FALSE(apm_->echo_cancellation()->is_drift_compensation_enabled()); - EXPECT_EQ(apm_->kBadParameterError, - apm_->echo_cancellation()->set_device_sample_rate_hz(4000)); - EXPECT_EQ(apm_->kBadParameterError, - apm_->echo_cancellation()->set_device_sample_rate_hz(100000)); - - int rate[] = {16000, 44100, 48000}; - for (size_t i = 0; i < sizeof(rate)/sizeof(*rate); i++) { - EXPECT_EQ(apm_->kNoError, - apm_->echo_cancellation()->set_device_sample_rate_hz(rate[i])); - EXPECT_EQ(rate[i], - apm_->echo_cancellation()->device_sample_rate_hz()); - } - EchoCancellation::SuppressionLevel level[] = { EchoCancellation::kLowSuppression, EchoCancellation::kModerateSuppression, @@ -816,7 +827,7 @@ TEST_F(ApmTest, EchoCancellation) { EXPECT_FALSE(apm_->echo_cancellation()->aec_core() != NULL); } -TEST_F(ApmTest, EchoCancellationReportsCorrectDelays) { +TEST_F(ApmTest, DISABLED_EchoCancellationReportsCorrectDelays) { // Enable AEC only. EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->enable_drift_compensation(false)); @@ -825,6 +836,9 @@ TEST_F(ApmTest, EchoCancellationReportsCorrectDelays) { EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->enable_delay_logging(true)); EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(true)); + Config config; + config.Set(new ReportedDelay(true)); + apm_->SetExtraOptions(config); // Internally in the AEC the amount of lookahead the delay estimation can // handle is 15 blocks and the maximum delay is set to 60 blocks. @@ -845,7 +859,13 @@ TEST_F(ApmTest, EchoCancellationReportsCorrectDelays) { // within a valid region (set to +-1.5 blocks). Note that these cases are // sampling frequency dependent. for (size_t i = 0; i < kProcessSampleRatesSize; i++) { - Init(kProcessSampleRates[i], kProcessSampleRates[i], 2, 2, 2, false); + Init(kProcessSampleRates[i], + kProcessSampleRates[i], + kProcessSampleRates[i], + 2, + 2, + 2, + false); // Sampling frequency dependent variables. const int num_ms_per_block = std::max(4, 640 / frame_->samples_per_channel_); @@ -898,7 +918,7 @@ TEST_F(ApmTest, EchoControlMobile) { EXPECT_EQ(apm_->kUnsupportedComponentError, apm_->ProcessStream(frame_)); // Turn AECM on (and AEC off) - Init(16000, 16000, 2, 2, 2, false); + Init(16000, 16000, 16000, 2, 2, 2, false); EXPECT_EQ(apm_->kNoError, apm_->echo_control_mobile()->Enable(true)); EXPECT_TRUE(apm_->echo_control_mobile()->is_enabled()); @@ -926,8 +946,8 @@ TEST_F(ApmTest, EchoControlMobile) { // Set and get echo path const size_t echo_path_size = apm_->echo_control_mobile()->echo_path_size_bytes(); - scoped_array echo_path_in(new char[echo_path_size]); - scoped_array echo_path_out(new char[echo_path_size]); + scoped_ptr echo_path_in(new char[echo_path_size]); + scoped_ptr echo_path_out(new char[echo_path_size]); EXPECT_EQ(apm_->kNullPointerError, apm_->echo_control_mobile()->SetEchoPath(NULL, echo_path_size)); EXPECT_EQ(apm_->kNullPointerError, @@ -1061,7 +1081,7 @@ TEST_F(ApmTest, GainControl) { } void ApmTest::RunQuantizedVolumeDoesNotGetStuckTest(int sample_rate) { - Init(sample_rate, sample_rate, 2, 2, 2, false); + Init(sample_rate, sample_rate, sample_rate, 2, 2, 2, false); EXPECT_EQ(apm_->kNoError, apm_->gain_control()->set_mode(GainControl::kAdaptiveAnalog)); EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true)); @@ -1092,7 +1112,7 @@ TEST_F(ApmTest, QuantizedVolumeDoesNotGetStuck) { } void ApmTest::RunManualVolumeChangeIsPossibleTest(int sample_rate) { - Init(sample_rate, sample_rate, 2, 2, 2, false); + Init(sample_rate, sample_rate, sample_rate, 2, 2, 2, false); EXPECT_EQ(apm_->kNoError, apm_->gain_control()->set_mode(GainControl::kAdaptiveAnalog)); EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true)); @@ -1212,15 +1232,6 @@ TEST_F(ApmTest, LevelEstimator) { EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_)); EXPECT_EQ(70, apm_->level_estimator()->RMS()); - // Min value if energy_ == 0. - SetFrameTo(frame_, 10000); - uint32_t energy = frame_->energy_; // Save default to restore below. - frame_->energy_ = 0; - EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_)); - EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_)); - EXPECT_EQ(127, apm_->level_estimator()->RMS()); - frame_->energy_ = energy; - // Verify reset after enable/disable. SetFrameTo(frame_, 32767); EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_)); @@ -1314,7 +1325,7 @@ TEST_F(ApmTest, AllProcessingDisabledByDefault) { TEST_F(ApmTest, NoProcessingWhenAllComponentsDisabled) { for (size_t i = 0; i < kSampleRatesSize; i++) { - Init(kSampleRates[i], kSampleRates[i], 2, 2, 2, false); + Init(kSampleRates[i], kSampleRates[i], kSampleRates[i], 2, 2, 2, false); SetFrameTo(frame_, 1000, 2000); AudioFrame frame_copy; frame_copy.CopyFrom(*frame_); @@ -1329,23 +1340,29 @@ TEST_F(ApmTest, IdenticalInputChannelsResultInIdenticalOutputChannels) { EnableAllComponents(); for (size_t i = 0; i < kProcessSampleRatesSize; i++) { - Init(kProcessSampleRates[i], kProcessSampleRates[i], 2, 2, 2, false); + Init(kProcessSampleRates[i], + kProcessSampleRates[i], + kProcessSampleRates[i], + 2, + 2, + 2, + false); int analog_level = 127; - EXPECT_EQ(0, feof(far_file_)); - EXPECT_EQ(0, feof(near_file_)); + ASSERT_EQ(0, feof(far_file_)); + ASSERT_EQ(0, feof(near_file_)); while (ReadFrame(far_file_, revframe_) && ReadFrame(near_file_, frame_)) { CopyLeftToRightChannel(revframe_->data_, revframe_->samples_per_channel_); - EXPECT_EQ(apm_->kNoError, apm_->AnalyzeReverseStream(revframe_)); + ASSERT_EQ(kNoErr, apm_->AnalyzeReverseStream(revframe_)); CopyLeftToRightChannel(frame_->data_, frame_->samples_per_channel_); frame_->vad_activity_ = AudioFrame::kVadUnknown; - EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(0)); + ASSERT_EQ(kNoErr, apm_->set_stream_delay_ms(0)); apm_->echo_cancellation()->set_stream_drift_samples(0); - EXPECT_EQ(apm_->kNoError, + ASSERT_EQ(kNoErr, apm_->gain_control()->set_stream_analog_level(analog_level)); - EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_)); + ASSERT_EQ(kNoErr, apm_->ProcessStream(frame_)); analog_level = apm_->gain_control()->stream_analog_level(); VerifyChannelsAreEqual(frame_->data_, frame_->samples_per_channel_); @@ -1403,6 +1420,11 @@ TEST_F(ApmTest, SplittingFilter) { // TODO(andrew): This test, and the one below, rely rather tenuously on the // behavior of the AEC. Think of something more robust. EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(true)); + // Make sure we have extended filter enabled. This makes sure nothing is + // touched until we have a farend frame. + Config config; + config.Set(new DelayCorrection(true)); + apm_->SetExtraOptions(config); SetFrameTo(frame_, 1000); frame_copy.CopyFrom(*frame_); EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(0)); @@ -1442,7 +1464,13 @@ void ApmTest::ProcessDebugDump(const std::string& in_filename, if (msg.has_reverse_sample_rate()) { reverse_sample_rate = msg.reverse_sample_rate(); } + int output_sample_rate = msg.sample_rate(); + if (msg.has_output_sample_rate()) { + output_sample_rate = msg.output_sample_rate(); + } + Init(msg.sample_rate(), + output_sample_rate, reverse_sample_rate, msg.num_input_channels(), msg.num_output_channels(), @@ -1644,11 +1672,12 @@ TEST_F(ApmTest, FloatAndIntInterfacesGiveIdenticalResults) { const int num_render_channels = test->num_reverse_channels(); const int num_input_channels = test->num_input_channels(); const int num_output_channels = test->num_output_channels(); - const int samples_per_channel = test->sample_rate() * kChunkSizeMs / 1000; + const int samples_per_channel = test->sample_rate() * + AudioProcessing::kChunkSizeMs / 1000; const int output_length = samples_per_channel * num_output_channels; - Init(test->sample_rate(), test->sample_rate(), num_input_channels, - num_output_channels, num_render_channels, true); + Init(test->sample_rate(), test->sample_rate(), test->sample_rate(), + num_input_channels, num_output_channels, num_render_channels, true); Init(fapm.get()); ChannelBuffer output_cb(samples_per_channel, num_input_channels); @@ -1674,12 +1703,15 @@ TEST_F(ApmTest, FloatAndIntInterfacesGiveIdenticalResults) { EXPECT_NOERR(fapm->gain_control()->set_stream_analog_level(analog_level)); EXPECT_NOERR(apm_->ProcessStream(frame_)); + // TODO(ajm): Update to support different output rates. EXPECT_NOERR(fapm->ProcessStream( float_cb_->channels(), samples_per_channel, test->sample_rate(), LayoutFromChannels(num_input_channels), - LayoutFromChannels(num_output_channels))); + test->sample_rate(), + LayoutFromChannels(num_output_channels), + float_cb_->channels())); // Convert to interleaved int16. ScaleAndRoundToInt16(float_cb_->data(), output_length, output_cb.data()); @@ -1746,8 +1778,13 @@ TEST_F(ApmTest, DISABLED_ON_ANDROID(Process)) { if (test->num_input_channels() != test->num_output_channels()) continue; - Init(test->sample_rate(), test->sample_rate(), test->num_input_channels(), - test->num_output_channels(), test->num_reverse_channels(), true); + Init(test->sample_rate(), + test->sample_rate(), + test->sample_rate(), + test->num_input_channels(), + test->num_output_channels(), + test->num_reverse_channels(), + true); int frame_count = 0; int has_echo_count = 0; @@ -1890,8 +1927,496 @@ TEST_F(ApmTest, DISABLED_ON_ANDROID(Process)) { OpenFileAndWriteMessage(ref_filename_, ref_data); } } + #endif // WEBRTC_AUDIOPROC_BIT_EXACT +TEST_F(ApmTest, NoErrorsWithKeyboardChannel) { + struct ChannelFormat { + AudioProcessing::ChannelLayout in_layout; + AudioProcessing::ChannelLayout out_layout; + }; + ChannelFormat cf[] = { + {AudioProcessing::kMonoAndKeyboard, AudioProcessing::kMono}, + {AudioProcessing::kStereoAndKeyboard, AudioProcessing::kMono}, + {AudioProcessing::kStereoAndKeyboard, AudioProcessing::kStereo}, + }; + size_t channel_format_size = sizeof(cf) / sizeof(*cf); + + scoped_ptr ap(AudioProcessing::Create()); + // Enable one component just to ensure some processing takes place. + ap->noise_suppression()->Enable(true); + for (size_t i = 0; i < channel_format_size; ++i) { + const int in_rate = 44100; + const int out_rate = 48000; + ChannelBuffer in_cb(SamplesFromRate(in_rate), + TotalChannelsFromLayout(cf[i].in_layout)); + ChannelBuffer out_cb(SamplesFromRate(out_rate), + ChannelsFromLayout(cf[i].out_layout)); + + // Run over a few chunks. + for (int j = 0; j < 10; ++j) { + EXPECT_NOERR(ap->ProcessStream( + in_cb.channels(), + in_cb.samples_per_channel(), + in_rate, + cf[i].in_layout, + out_rate, + cf[i].out_layout, + out_cb.channels())); + } + } +} + +// Reads a 10 ms chunk of int16 interleaved audio from the given (assumed +// stereo) file, converts to deinterleaved float (optionally downmixing) and +// returns the result in |cb|. Returns false if the file ended (or on error) and +// true otherwise. +// +// |int_data| and |float_data| are just temporary space that must be +// sufficiently large to hold the 10 ms chunk. +bool ReadChunk(FILE* file, int16_t* int_data, float* float_data, + ChannelBuffer* cb) { + // The files always contain stereo audio. + size_t frame_size = cb->samples_per_channel() * 2; + size_t read_count = fread(int_data, sizeof(int16_t), frame_size, file); + if (read_count != frame_size) { + // Check that the file really ended. + assert(feof(file)); + return false; // This is expected. + } + + ScaleToFloat(int_data, frame_size, float_data); + if (cb->num_channels() == 1) { + MixStereoToMono(float_data, cb->data(), cb->samples_per_channel()); + } else { + Deinterleave(float_data, cb->samples_per_channel(), 2, + cb->channels()); + } + + return true; +} + +// Compares the reference and test arrays over a region around the expected +// delay. Finds the highest SNR in that region and adds the variance and squared +// error results to the supplied accumulators. +void UpdateBestSNR(const float* ref, + const float* test, + int length, + int expected_delay, + double* variance_acc, + double* sq_error_acc) { + double best_snr = std::numeric_limits::min(); + double best_variance = 0; + double best_sq_error = 0; + // Search over a region of eight samples around the expected delay. + for (int delay = std::max(expected_delay - 4, 0); delay <= expected_delay + 4; + ++delay) { + double sq_error = 0; + double variance = 0; + for (int i = 0; i < length - delay; ++i) { + double error = test[i + delay] - ref[i]; + sq_error += error * error; + variance += ref[i] * ref[i]; + } + + if (sq_error == 0) { + *variance_acc += variance; + return; + } + double snr = variance / sq_error; + if (snr > best_snr) { + best_snr = snr; + best_variance = variance; + best_sq_error = sq_error; + } + } + + *variance_acc += best_variance; + *sq_error_acc += best_sq_error; +} + +// Used to test a multitude of sample rate and channel combinations. It works +// by first producing a set of reference files (in SetUpTestCase) that are +// assumed to be correct, as the used parameters are verified by other tests +// in this collection. Primarily the reference files are all produced at +// "native" rates which do not involve any resampling. + +// Each test pass produces an output file with a particular format. The output +// is matched against the reference file closest to its internal processing +// format. If necessary the output is resampled back to its process format. +// Due to the resampling distortion, we don't expect identical results, but +// enforce SNR thresholds which vary depending on the format. 0 is a special +// case SNR which corresponds to inf, or zero error. +typedef std::tr1::tuple AudioProcessingTestData; +class AudioProcessingTest + : public testing::TestWithParam { + public: + AudioProcessingTest() + : input_rate_(std::tr1::get<0>(GetParam())), + output_rate_(std::tr1::get<1>(GetParam())), + reverse_rate_(std::tr1::get<2>(GetParam())), + expected_snr_(std::tr1::get<3>(GetParam())) {} + + virtual ~AudioProcessingTest() {} + + static void SetUpTestCase() { + // Create all needed output reference files. + const int kNativeRates[] = {8000, 16000, 32000}; + const size_t kNativeRatesSize = + sizeof(kNativeRates) / sizeof(*kNativeRates); + const int kNumChannels[] = {1, 2}; + const size_t kNumChannelsSize = + sizeof(kNumChannels) / sizeof(*kNumChannels); + for (size_t i = 0; i < kNativeRatesSize; ++i) { + for (size_t j = 0; j < kNumChannelsSize; ++j) { + for (size_t k = 0; k < kNumChannelsSize; ++k) { + // The reference files always have matching input and output channels. + ProcessFormat(kNativeRates[i], + kNativeRates[i], + kNativeRates[i], + kNumChannels[j], + kNumChannels[j], + kNumChannels[k], + "ref"); + } + } + } + } + + // Runs a process pass on files with the given parameters and dumps the output + // to a file specified with |output_file_prefix|. + static void ProcessFormat(int input_rate, + int output_rate, + int reverse_rate, + int num_input_channels, + int num_output_channels, + int num_reverse_channels, + std::string output_file_prefix) { + scoped_ptr ap(AudioProcessing::Create()); + EnableAllAPComponents(ap.get()); + ap->Initialize(input_rate, + output_rate, + reverse_rate, + LayoutFromChannels(num_input_channels), + LayoutFromChannels(num_output_channels), + LayoutFromChannels(num_reverse_channels)); + + FILE* far_file = fopen(ResourceFilePath("far", reverse_rate).c_str(), "rb"); + FILE* near_file = fopen(ResourceFilePath("near", input_rate).c_str(), "rb"); + FILE* out_file = fopen(OutputFilePath(output_file_prefix, + input_rate, + output_rate, + reverse_rate, + num_input_channels, + num_output_channels, + num_reverse_channels).c_str(), "wb"); + ASSERT_TRUE(far_file != NULL); + ASSERT_TRUE(near_file != NULL); + ASSERT_TRUE(out_file != NULL); + + ChannelBuffer fwd_cb(SamplesFromRate(input_rate), + num_input_channels); + ChannelBuffer rev_cb(SamplesFromRate(reverse_rate), + num_reverse_channels); + ChannelBuffer out_cb(SamplesFromRate(output_rate), + num_output_channels); + + // Temporary buffers. + const int max_length = + 2 * std::max(out_cb.samples_per_channel(), + std::max(fwd_cb.samples_per_channel(), + rev_cb.samples_per_channel())); + scoped_ptr float_data(new float[max_length]); + scoped_ptr int_data(new int16_t[max_length]); + + int analog_level = 127; + while (ReadChunk(far_file, int_data.get(), float_data.get(), &rev_cb) && + ReadChunk(near_file, int_data.get(), float_data.get(), &fwd_cb)) { + EXPECT_NOERR(ap->AnalyzeReverseStream( + rev_cb.channels(), + rev_cb.samples_per_channel(), + reverse_rate, + LayoutFromChannels(num_reverse_channels))); + + EXPECT_NOERR(ap->set_stream_delay_ms(0)); + ap->echo_cancellation()->set_stream_drift_samples(0); + EXPECT_NOERR(ap->gain_control()->set_stream_analog_level(analog_level)); + + EXPECT_NOERR(ap->ProcessStream( + fwd_cb.channels(), + fwd_cb.samples_per_channel(), + input_rate, + LayoutFromChannels(num_input_channels), + output_rate, + LayoutFromChannels(num_output_channels), + out_cb.channels())); + + Interleave(out_cb.channels(), + out_cb.samples_per_channel(), + out_cb.num_channels(), + float_data.get()); + // Dump output to file. + ASSERT_EQ(static_cast(out_cb.length()), + fwrite(float_data.get(), sizeof(float_data[0]), + out_cb.length(), out_file)); + + analog_level = ap->gain_control()->stream_analog_level(); + } + fclose(far_file); + fclose(near_file); + fclose(out_file); + } + + protected: + int input_rate_; + int output_rate_; + int reverse_rate_; + double expected_snr_; +}; + +TEST_P(AudioProcessingTest, Formats) { + struct ChannelFormat { + int num_input; + int num_output; + int num_reverse; + }; + ChannelFormat cf[] = { + {1, 1, 1}, + {1, 1, 2}, + {2, 1, 1}, + {2, 1, 2}, + {2, 2, 1}, + {2, 2, 2}, + }; + size_t channel_format_size = sizeof(cf) / sizeof(*cf); + + for (size_t i = 0; i < channel_format_size; ++i) { + ProcessFormat(input_rate_, + output_rate_, + reverse_rate_, + cf[i].num_input, + cf[i].num_output, + cf[i].num_reverse, + "out"); + int min_ref_rate = std::min(input_rate_, output_rate_); + int ref_rate; + if (min_ref_rate > 16000) { + ref_rate = 32000; + } else if (min_ref_rate > 8000) { + ref_rate = 16000; + } else { + ref_rate = 8000; + } +#ifdef WEBRTC_AUDIOPROC_FIXED_PROFILE + ref_rate = std::min(ref_rate, 16000); +#endif + + FILE* out_file = fopen(OutputFilePath("out", + input_rate_, + output_rate_, + reverse_rate_, + cf[i].num_input, + cf[i].num_output, + cf[i].num_reverse).c_str(), "rb"); + // The reference files always have matching input and output channels. + FILE* ref_file = fopen(OutputFilePath("ref", + ref_rate, + ref_rate, + ref_rate, + cf[i].num_output, + cf[i].num_output, + cf[i].num_reverse).c_str(), "rb"); + ASSERT_TRUE(out_file != NULL); + ASSERT_TRUE(ref_file != NULL); + + const int ref_length = SamplesFromRate(ref_rate) * cf[i].num_output; + const int out_length = SamplesFromRate(output_rate_) * cf[i].num_output; + // Data from the reference file. + scoped_ptr ref_data(new float[ref_length]); + // Data from the output file. + scoped_ptr out_data(new float[out_length]); + // Data from the resampled output, in case the reference and output rates + // don't match. + scoped_ptr cmp_data(new float[ref_length]); + + PushResampler resampler; + resampler.InitializeIfNeeded(output_rate_, ref_rate, cf[i].num_output); + + // Compute the resampling delay of the output relative to the reference, + // to find the region over which we should search for the best SNR. + float expected_delay_sec = 0; + if (input_rate_ != ref_rate) { + // Input resampling delay. + expected_delay_sec += + PushSincResampler::AlgorithmicDelaySeconds(input_rate_); + } + if (output_rate_ != ref_rate) { + // Output resampling delay. + expected_delay_sec += + PushSincResampler::AlgorithmicDelaySeconds(ref_rate); + // Delay of converting the output back to its processing rate for testing. + expected_delay_sec += + PushSincResampler::AlgorithmicDelaySeconds(output_rate_); + } + int expected_delay = floor(expected_delay_sec * ref_rate + 0.5f) * + cf[i].num_output; + + double variance = 0; + double sq_error = 0; + while (fread(out_data.get(), sizeof(out_data[0]), out_length, out_file) && + fread(ref_data.get(), sizeof(ref_data[0]), ref_length, ref_file)) { + float* out_ptr = out_data.get(); + if (output_rate_ != ref_rate) { + // Resample the output back to its internal processing rate if necssary. + ASSERT_EQ(ref_length, resampler.Resample(out_ptr, + out_length, + cmp_data.get(), + ref_length)); + out_ptr = cmp_data.get(); + } + + // Update the |sq_error| and |variance| accumulators with the highest SNR + // of reference vs output. + UpdateBestSNR(ref_data.get(), + out_ptr, + ref_length, + expected_delay, + &variance, + &sq_error); + } + + std::cout << "(" << input_rate_ << ", " + << output_rate_ << ", " + << reverse_rate_ << ", " + << cf[i].num_input << ", " + << cf[i].num_output << ", " + << cf[i].num_reverse << "): "; + if (sq_error > 0) { + double snr = 10 * log10(variance / sq_error); + EXPECT_GE(snr, expected_snr_); + EXPECT_NE(0, expected_snr_); + std::cout << "SNR=" << snr << " dB" << std::endl; + } else { + EXPECT_EQ(expected_snr_, 0); + std::cout << "SNR=" << "inf dB" << std::endl; + } + + fclose(out_file); + fclose(ref_file); + } +} + +#if defined(WEBRTC_AUDIOPROC_FLOAT_PROFILE) +INSTANTIATE_TEST_CASE_P( + CommonFormats, AudioProcessingTest, testing::Values( + std::tr1::make_tuple(48000, 48000, 48000, 25), + std::tr1::make_tuple(48000, 48000, 32000, 25), + std::tr1::make_tuple(48000, 48000, 16000, 25), + std::tr1::make_tuple(48000, 44100, 48000, 20), + std::tr1::make_tuple(48000, 44100, 32000, 20), + std::tr1::make_tuple(48000, 44100, 16000, 20), + std::tr1::make_tuple(48000, 32000, 48000, 25), + std::tr1::make_tuple(48000, 32000, 32000, 25), + std::tr1::make_tuple(48000, 32000, 16000, 25), + std::tr1::make_tuple(48000, 16000, 48000, 25), + std::tr1::make_tuple(48000, 16000, 32000, 25), + std::tr1::make_tuple(48000, 16000, 16000, 25), + + std::tr1::make_tuple(44100, 48000, 48000, 20), + std::tr1::make_tuple(44100, 48000, 32000, 20), + std::tr1::make_tuple(44100, 48000, 16000, 20), + std::tr1::make_tuple(44100, 44100, 48000, 20), + std::tr1::make_tuple(44100, 44100, 32000, 20), + std::tr1::make_tuple(44100, 44100, 16000, 20), + std::tr1::make_tuple(44100, 32000, 48000, 20), + std::tr1::make_tuple(44100, 32000, 32000, 20), + std::tr1::make_tuple(44100, 32000, 16000, 20), + std::tr1::make_tuple(44100, 16000, 48000, 20), + std::tr1::make_tuple(44100, 16000, 32000, 20), + std::tr1::make_tuple(44100, 16000, 16000, 20), + + std::tr1::make_tuple(32000, 48000, 48000, 25), + std::tr1::make_tuple(32000, 48000, 32000, 25), + std::tr1::make_tuple(32000, 48000, 16000, 25), + std::tr1::make_tuple(32000, 44100, 48000, 20), + std::tr1::make_tuple(32000, 44100, 32000, 20), + std::tr1::make_tuple(32000, 44100, 16000, 20), + std::tr1::make_tuple(32000, 32000, 48000, 30), + std::tr1::make_tuple(32000, 32000, 32000, 0), + std::tr1::make_tuple(32000, 32000, 16000, 30), + std::tr1::make_tuple(32000, 16000, 48000, 25), + std::tr1::make_tuple(32000, 16000, 32000, 25), + std::tr1::make_tuple(32000, 16000, 16000, 25), + + std::tr1::make_tuple(16000, 48000, 48000, 25), + std::tr1::make_tuple(16000, 48000, 32000, 25), + std::tr1::make_tuple(16000, 48000, 16000, 25), + std::tr1::make_tuple(16000, 44100, 48000, 15), + std::tr1::make_tuple(16000, 44100, 32000, 15), + std::tr1::make_tuple(16000, 44100, 16000, 15), + std::tr1::make_tuple(16000, 32000, 48000, 25), + std::tr1::make_tuple(16000, 32000, 32000, 25), + std::tr1::make_tuple(16000, 32000, 16000, 25), + std::tr1::make_tuple(16000, 16000, 48000, 30), + std::tr1::make_tuple(16000, 16000, 32000, 30), + std::tr1::make_tuple(16000, 16000, 16000, 0))); + +#elif defined(WEBRTC_AUDIOPROC_FIXED_PROFILE) +INSTANTIATE_TEST_CASE_P( + CommonFormats, AudioProcessingTest, testing::Values( + std::tr1::make_tuple(48000, 48000, 48000, 20), + std::tr1::make_tuple(48000, 48000, 32000, 20), + std::tr1::make_tuple(48000, 48000, 16000, 20), + std::tr1::make_tuple(48000, 44100, 48000, 15), + std::tr1::make_tuple(48000, 44100, 32000, 15), + std::tr1::make_tuple(48000, 44100, 16000, 15), + std::tr1::make_tuple(48000, 32000, 48000, 20), + std::tr1::make_tuple(48000, 32000, 32000, 20), + std::tr1::make_tuple(48000, 32000, 16000, 20), + std::tr1::make_tuple(48000, 16000, 48000, 20), + std::tr1::make_tuple(48000, 16000, 32000, 20), + std::tr1::make_tuple(48000, 16000, 16000, 20), + + std::tr1::make_tuple(44100, 48000, 48000, 19), + std::tr1::make_tuple(44100, 48000, 32000, 19), + std::tr1::make_tuple(44100, 48000, 16000, 19), + std::tr1::make_tuple(44100, 44100, 48000, 15), + std::tr1::make_tuple(44100, 44100, 32000, 15), + std::tr1::make_tuple(44100, 44100, 16000, 15), + std::tr1::make_tuple(44100, 32000, 48000, 19), + std::tr1::make_tuple(44100, 32000, 32000, 19), + std::tr1::make_tuple(44100, 32000, 16000, 19), + std::tr1::make_tuple(44100, 16000, 48000, 19), + std::tr1::make_tuple(44100, 16000, 32000, 19), + std::tr1::make_tuple(44100, 16000, 16000, 19), + + std::tr1::make_tuple(32000, 48000, 48000, 19), + std::tr1::make_tuple(32000, 48000, 32000, 19), + std::tr1::make_tuple(32000, 48000, 16000, 19), + std::tr1::make_tuple(32000, 44100, 48000, 15), + std::tr1::make_tuple(32000, 44100, 32000, 15), + std::tr1::make_tuple(32000, 44100, 16000, 15), + std::tr1::make_tuple(32000, 32000, 48000, 19), + std::tr1::make_tuple(32000, 32000, 32000, 19), + std::tr1::make_tuple(32000, 32000, 16000, 19), + std::tr1::make_tuple(32000, 16000, 48000, 19), + std::tr1::make_tuple(32000, 16000, 32000, 19), + std::tr1::make_tuple(32000, 16000, 16000, 19), + + std::tr1::make_tuple(16000, 48000, 48000, 25), + std::tr1::make_tuple(16000, 48000, 32000, 25), + std::tr1::make_tuple(16000, 48000, 16000, 25), + std::tr1::make_tuple(16000, 44100, 48000, 15), + std::tr1::make_tuple(16000, 44100, 32000, 15), + std::tr1::make_tuple(16000, 44100, 16000, 15), + std::tr1::make_tuple(16000, 32000, 48000, 25), + std::tr1::make_tuple(16000, 32000, 32000, 25), + std::tr1::make_tuple(16000, 32000, 16000, 25), + std::tr1::make_tuple(16000, 16000, 48000, 30), + std::tr1::make_tuple(16000, 16000, 32000, 30), + std::tr1::make_tuple(16000, 16000, 16000, 0))); +#endif + // TODO(henrike): re-implement functionality lost when removing the old main // function. See // https://code.google.com/p/webrtc/issues/detail?id=1981 diff --git a/webrtc/modules/audio_processing/test/process_test.cc b/webrtc/modules/audio_processing/test/process_test.cc index 2d7c065f9..a36a072c9 100644 --- a/webrtc/modules/audio_processing/test/process_test.cc +++ b/webrtc/modules/audio_processing/test/process_test.cc @@ -78,6 +78,7 @@ void usage() { printf(" --no_delay_logging\n"); printf(" --aec_suppression_level LEVEL [0 - 2]\n"); printf(" --extended_filter\n"); + printf(" --no_reported_delay\n"); printf("\n -aecm Echo control mobile\n"); printf(" --aecm_echo_path_in_file FILE\n"); printf(" --aecm_echo_path_out_file FILE\n"); @@ -155,7 +156,6 @@ void void_main(int argc, char* argv[]) { const char* aecm_echo_path_out_filename = NULL; int32_t sample_rate_hz = 16000; - int32_t device_sample_rate_hz = 16000; int num_capture_input_channels = 1; int num_capture_output_channels = 1; @@ -258,6 +258,11 @@ void void_main(int argc, char* argv[]) { config.Set(new DelayCorrection(true)); apm->SetExtraOptions(config); + } else if (strcmp(argv[i], "--no_reported_delay") == 0) { + Config config; + config.Set(new ReportedDelay(false)); + apm->SetExtraOptions(config); + } else if (strcmp(argv[i], "-aecm") == 0) { ASSERT_EQ(apm->kNoError, apm->echo_control_mobile()->Enable(true)); @@ -519,7 +524,7 @@ void void_main(int argc, char* argv[]) { const size_t path_size = apm->echo_control_mobile()->echo_path_size_bytes(); - scoped_array echo_path(new char[path_size]); + scoped_ptr echo_path(new char[path_size]); ASSERT_EQ(path_size, fread(echo_path.get(), sizeof(char), path_size, @@ -563,6 +568,8 @@ void void_main(int argc, char* argv[]) { Event event_msg; scoped_ptr > reverse_cb; scoped_ptr > primary_cb; + int output_sample_rate = 32000; + AudioProcessing::ChannelLayout output_layout = AudioProcessing::kMono; while (ReadMessageFromFile(pb_file, &event_msg)) { std::ostringstream trace_stream; trace_stream << "Processed frames: " << reverse_count << " (reverse), " @@ -578,18 +585,21 @@ void void_main(int argc, char* argv[]) { ASSERT_TRUE(msg.has_num_output_channels()); ASSERT_TRUE(msg.has_num_reverse_channels()); int reverse_sample_rate = msg.sample_rate(); - if (msg.has_reverse_sample_rate()) + if (msg.has_reverse_sample_rate()) { reverse_sample_rate = msg.reverse_sample_rate(); - ASSERT_EQ(apm->kNoError, apm->Initialize(msg.sample_rate(), - reverse_sample_rate, - msg.num_input_channels(), - msg.num_output_channels(), - msg.num_reverse_channels())); - ASSERT_TRUE(msg.has_device_sample_rate()); - ASSERT_EQ(apm->kNoError, - apm->echo_cancellation()->set_device_sample_rate_hz( - msg.device_sample_rate())); - + } + output_sample_rate = msg.sample_rate(); + if (msg.has_output_sample_rate()) { + output_sample_rate = msg.output_sample_rate(); + } + output_layout = LayoutFromChannels(msg.num_output_channels()); + ASSERT_EQ(kNoErr, apm->Initialize( + msg.sample_rate(), + output_sample_rate, + reverse_sample_rate, + LayoutFromChannels(msg.num_input_channels()), + output_layout, + LayoutFromChannels(msg.num_reverse_channels()))); samples_per_channel = msg.sample_rate() / 100; far_frame.sample_rate_hz_ = msg.sample_rate(); @@ -606,11 +616,13 @@ void void_main(int argc, char* argv[]) { if (verbose) { printf("Init at frame: %d (primary), %d (reverse)\n", primary_count, reverse_count); - printf(" Sample rate: %d Hz\n", msg.sample_rate()); + printf(" Primary rates: %d Hz (in), %d Hz (out)\n", + msg.sample_rate(), output_sample_rate); printf(" Primary channels: %d (in), %d (out)\n", msg.num_input_channels(), msg.num_output_channels()); - printf(" Reverse channels: %d \n", msg.num_reverse_channels()); + printf(" Reverse rate: %d\n", reverse_sample_rate); + printf(" Reverse channels: %d\n", msg.num_reverse_channels()); } } else if (event_msg.type() == Event::REVERSE_STREAM) { @@ -715,7 +727,9 @@ void void_main(int argc, char* argv[]) { near_frame.samples_per_channel_, near_frame.sample_rate_hz_, LayoutFromChannels(near_frame.num_channels_), - LayoutFromChannels(apm->num_output_channels())); + output_sample_rate, + output_layout, + primary_cb->channels()); } if (err == apm->kBadStreamParameterWarning) { @@ -814,19 +828,20 @@ void void_main(int argc, char* argv[]) { fread(&sample_rate_hz, sizeof(sample_rate_hz), 1, event_file)); samples_per_channel = sample_rate_hz / 100; + int32_t unused_device_sample_rate_hz; ASSERT_EQ(1u, - fread(&device_sample_rate_hz, - sizeof(device_sample_rate_hz), + fread(&unused_device_sample_rate_hz, + sizeof(unused_device_sample_rate_hz), 1, event_file)); - // TODO(bjornv): Replace set_sample_rate_hz() when we have a smarter - // AnalyzeReverseStream(). - ASSERT_EQ(apm->kNoError, apm->set_sample_rate_hz(sample_rate_hz)); - - ASSERT_EQ(apm->kNoError, - apm->echo_cancellation()->set_device_sample_rate_hz( - device_sample_rate_hz)); + ASSERT_EQ(kNoErr, apm->Initialize( + sample_rate_hz, + sample_rate_hz, + sample_rate_hz, + LayoutFromChannels(num_capture_input_channels), + LayoutFromChannels(num_capture_output_channels), + LayoutFromChannels(num_render_channels))); far_frame.sample_rate_hz_ = sample_rate_hz; far_frame.samples_per_channel_ = samples_per_channel; @@ -995,7 +1010,7 @@ void void_main(int argc, char* argv[]) { if (aecm_echo_path_out_file != NULL) { const size_t path_size = apm->echo_control_mobile()->echo_path_size_bytes(); - scoped_array echo_path(new char[path_size]); + scoped_ptr echo_path(new char[path_size]); apm->echo_control_mobile()->GetEchoPath(echo_path.get(), path_size); ASSERT_EQ(path_size, fwrite(echo_path.get(), sizeof(char), diff --git a/webrtc/modules/audio_processing/test/test_utils.h b/webrtc/modules/audio_processing/test/test_utils.h index c55b53ef9..e5204da5a 100644 --- a/webrtc/modules/audio_processing/test/test_utils.h +++ b/webrtc/modules/audio_processing/test/test_utils.h @@ -9,6 +9,7 @@ */ #include "webrtc/audio_processing/debug.pb.h" +#include "webrtc/modules/audio_processing/common.h" #include "webrtc/modules/audio_processing/include/audio_processing.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" @@ -18,37 +19,6 @@ namespace webrtc { static const AudioProcessing::Error kNoErr = AudioProcessing::kNoError; #define EXPECT_NOERR(expr) EXPECT_EQ(kNoErr, (expr)) -static const int kChunkSizeMs = 10; - -// Helper to encapsulate a contiguous data buffer with access to a pointer -// array of the deinterleaved channels. -template -class ChannelBuffer { - public: - ChannelBuffer(int samples_per_channel, int num_channels) - : data_(new T[samples_per_channel * num_channels]), - channels_(new T*[num_channels]), - samples_per_channel_(samples_per_channel) { - memset(data_.get(), 0, sizeof(T) * samples_per_channel * num_channels); - for (int i = 0; i < num_channels; ++i) - channels_[i] = &data_[i * samples_per_channel]; - } - ~ChannelBuffer() {} - - void CopyFrom(const void* channel_ptr, int index) { - memcpy(channels_[index], channel_ptr, samples_per_channel_ * sizeof(T)); - } - - T* data() { return data_.get(); } - T* channel(int index) { return channels_[index]; } - T** channels() { return channels_.get(); } - - private: - scoped_ptr data_; - scoped_ptr channels_; - int samples_per_channel_; -}; - // Exits on failure; do not use in unit tests. static inline FILE* OpenFile(const std::string& filename, const char* mode) { FILE* file = fopen(filename.c_str(), mode); @@ -59,10 +29,15 @@ static inline FILE* OpenFile(const std::string& filename, const char* mode) { return file; } +static inline int SamplesFromRate(int rate) { + return AudioProcessing::kChunkSizeMs * rate / 1000; +} + static inline void SetFrameSampleRate(AudioFrame* frame, int sample_rate_hz) { frame->sample_rate_hz_ = sample_rate_hz; - frame->samples_per_channel_ = kChunkSizeMs * sample_rate_hz / 1000; + frame->samples_per_channel_ = AudioProcessing::kChunkSizeMs * + sample_rate_hz / 1000; } template diff --git a/webrtc/modules/audio_processing/test/unpack.cc b/webrtc/modules/audio_processing/test/unpack.cc index 478e29678..c90ba8274 100644 --- a/webrtc/modules/audio_processing/test/unpack.cc +++ b/webrtc/modules/audio_processing/test/unpack.cc @@ -165,8 +165,6 @@ while (ReadMessageFromFile(debug_file, &event_msg)) { // These should print out zeros if they're missing. fprintf(settings_file, "Init at frame: %d\n", frame_count); fprintf(settings_file, " Sample rate: %d\n", msg.sample_rate()); - fprintf(settings_file, " Device sample rate: %d\n", - msg.device_sample_rate()); fprintf(settings_file, " Input channels: %d\n", msg.num_input_channels()); fprintf(settings_file, " Output channels: %d\n", diff --git a/webrtc/modules/audio_processing/utility/ring_buffer_unittest.cc b/webrtc/modules/audio_processing/utility/ring_buffer_unittest.cc index bd68f9489..5dacf0b80 100644 --- a/webrtc/modules/audio_processing/utility/ring_buffer_unittest.cc +++ b/webrtc/modules/audio_processing/utility/ring_buffer_unittest.cc @@ -61,8 +61,8 @@ static void RandomStressTest(int** data_ptr) { srand(seed); for (int i = 0; i < kNumTests; i++) { const int buffer_size = std::max(rand() % kMaxBufferSize, 1); - scoped_array write_data(new int[buffer_size]); - scoped_array read_data(new int[buffer_size]); + scoped_ptr write_data(new int[buffer_size]); + scoped_ptr read_data(new int[buffer_size]); scoped_ring_buffer buffer(WebRtc_CreateBuffer(buffer_size, sizeof(int))); ASSERT_TRUE(buffer.get() != NULL); ASSERT_EQ(0, WebRtc_InitBuffer(buffer.get())); diff --git a/webrtc/modules/audio_processing/voice_detection_impl.cc b/webrtc/modules/audio_processing/voice_detection_impl.cc index 1b1dd8b80..c6e497ffa 100644 --- a/webrtc/modules/audio_processing/voice_detection_impl.cc +++ b/webrtc/modules/audio_processing/voice_detection_impl.cc @@ -61,7 +61,7 @@ int VoiceDetectionImpl::ProcessCaptureAudio(AudioBuffer* audio) { } assert(audio->samples_per_split_channel() <= 160); - int16_t* mixed_data = audio->low_pass_split_data(0); + const int16_t* mixed_data = audio->low_pass_split_data(0); if (audio->num_channels() > 1) { audio->CopyAndMixLowPass(1); mixed_data = audio->mixed_low_pass_data(0); @@ -70,7 +70,7 @@ int VoiceDetectionImpl::ProcessCaptureAudio(AudioBuffer* audio) { // TODO(ajm): concatenate data in frame buffer here. int vad_ret = WebRtcVad_Process(static_cast(handle(0)), - apm_->split_sample_rate_hz(), + apm_->proc_split_sample_rate_hz(), mixed_data, frame_size_samples_); if (vad_ret == 0) { @@ -146,7 +146,8 @@ int VoiceDetectionImpl::Initialize() { } using_external_vad_ = false; - frame_size_samples_ = frame_size_ms_ * (apm_->split_sample_rate_hz() / 1000); + frame_size_samples_ = frame_size_ms_ * + apm_->proc_split_sample_rate_hz() / 1000; // TODO(ajm): intialize frame buffer here. return apm_->kNoError; @@ -163,8 +164,8 @@ void* VoiceDetectionImpl::CreateHandle() const { return handle; } -int VoiceDetectionImpl::DestroyHandle(void* handle) const { - return WebRtcVad_Free(static_cast(handle)); +void VoiceDetectionImpl::DestroyHandle(void* handle) const { + WebRtcVad_Free(static_cast(handle)); } int VoiceDetectionImpl::InitializeHandle(void* handle) const { diff --git a/webrtc/modules/audio_processing/voice_detection_impl.h b/webrtc/modules/audio_processing/voice_detection_impl.h index 5d06517a4..1dfdf20ae 100644 --- a/webrtc/modules/audio_processing/voice_detection_impl.h +++ b/webrtc/modules/audio_processing/voice_detection_impl.h @@ -47,7 +47,7 @@ class VoiceDetectionImpl : public VoiceDetection, virtual void* CreateHandle() const OVERRIDE; virtual int InitializeHandle(void* handle) const OVERRIDE; virtual int ConfigureHandle(void* handle) const OVERRIDE; - virtual int DestroyHandle(void* handle) const OVERRIDE; + virtual void DestroyHandle(void* handle) const OVERRIDE; virtual int num_handles_required() const OVERRIDE; virtual int GetHandleError(void* handle) const OVERRIDE; diff --git a/webrtc/modules/bitrate_controller/BUILD.gn b/webrtc/modules/bitrate_controller/BUILD.gn new file mode 100644 index 000000000..c3ac28ce4 --- /dev/null +++ b/webrtc/modules/bitrate_controller/BUILD.gn @@ -0,0 +1,28 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("bitrate_controller") { + sources = [ + "bitrate_controller_impl.cc", + "bitrate_controller_impl.h", + "include/bitrate_controller.h", + "send_side_bandwidth_estimation.cc", + "send_side_bandwidth_estimation.h", + ] + + if (is_win) { + cflags = [ + # TODO(jschuh): Bug 1348: fix this warning. + "/wd4267" # size_t to int truncations + ] + } + + deps = [ "../../system_wrappers" ] +} diff --git a/webrtc/modules/bitrate_controller/OWNERS b/webrtc/modules/bitrate_controller/OWNERS index 6c7028550..9420ba2e9 100644 --- a/webrtc/modules/bitrate_controller/OWNERS +++ b/webrtc/modules/bitrate_controller/OWNERS @@ -3,3 +3,10 @@ stefan@webrtc.org henrik.lundin@webrtc.org mflodman@webrtc.org asapersson@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/bitrate_controller/bitrate_controller_impl.cc b/webrtc/modules/bitrate_controller/bitrate_controller_impl.cc index 48f59b850..cff5dd185 100644 --- a/webrtc/modules/bitrate_controller/bitrate_controller_impl.cc +++ b/webrtc/modules/bitrate_controller/bitrate_controller_impl.cc @@ -139,6 +139,21 @@ void BitrateControllerImpl::SetBitrateObserver( it->second->start_bitrate_ = start_bitrate; it->second->min_bitrate_ = min_bitrate; it->second->max_bitrate_ = max_bitrate; + // Set the send-side bandwidth to the max of the sum of start bitrates and + // the current estimate, so that if the user wants to immediately use more + // bandwidth, that can be enforced. + uint32_t sum_start_bitrate = 0; + BitrateObserverConfList::iterator it; + for (it = bitrate_observers_.begin(); it != bitrate_observers_.end(); + ++it) { + sum_start_bitrate += it->second->start_bitrate_; + } + uint32_t current_estimate; + uint8_t loss; + uint32_t rtt; + bandwidth_estimation_.CurrentEstimate(¤t_estimate, &loss, &rtt); + bandwidth_estimation_.SetSendBitrate(std::max(sum_start_bitrate, + current_estimate)); } else { // Add new settings. bitrate_observers_.push_back(BitrateObserverConfiguration(observer, @@ -159,12 +174,10 @@ void BitrateControllerImpl::SetBitrateObserver( } void BitrateControllerImpl::UpdateMinMaxBitrate() { - uint32_t sum_start_bitrate = 0; uint32_t sum_min_bitrate = 0; uint32_t sum_max_bitrate = 0; BitrateObserverConfList::iterator it; for (it = bitrate_observers_.begin(); it != bitrate_observers_.end(); ++it) { - sum_start_bitrate += it->second->start_bitrate_; sum_min_bitrate += it->second->min_bitrate_; sum_max_bitrate += it->second->max_bitrate_; } diff --git a/webrtc/modules/bitrate_controller/bitrate_controller_unittest.cc b/webrtc/modules/bitrate_controller/bitrate_controller_unittest.cc index b15eb29d8..8523d505b 100644 --- a/webrtc/modules/bitrate_controller/bitrate_controller_unittest.cc +++ b/webrtc/modules/bitrate_controller/bitrate_controller_unittest.cc @@ -83,6 +83,24 @@ TEST_F(BitrateControllerTest, Basic) { controller_->RemoveBitrateObserver(&bitrate_observer); } +TEST_F(BitrateControllerTest, UpdatingBitrateObserver) { + TestBitrateObserver bitrate_observer; + controller_->SetBitrateObserver(&bitrate_observer, 200000, 100000, 1500000); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + EXPECT_EQ(200000u, bitrate_observer.last_bitrate_); + + controller_->SetBitrateObserver(&bitrate_observer, 1500000, 100000, 1500000); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + EXPECT_EQ(1500000u, bitrate_observer.last_bitrate_); + + controller_->SetBitrateObserver(&bitrate_observer, 500000, 100000, 1500000); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + EXPECT_EQ(1500000u, bitrate_observer.last_bitrate_); +} + TEST_F(BitrateControllerTest, OneBitrateObserverOneRtcpObserver) { TestBitrateObserver bitrate_observer; controller_->SetBitrateObserver(&bitrate_observer, 200000, 100000, 300000); diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn new file mode 100644 index 000000000..0ccb18a13 --- /dev/null +++ b/webrtc/modules/desktop_capture/BUILD.gn @@ -0,0 +1,19 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("desktop_capture") { + # TODO(jiayl): Implement. +} + +if (!is_ios && (cpu_arch == "x86" || cpu_arch == "x64")) { + source_set("desktop_capture_differ_sse2") { + # TODO(jiayl): Implement. + } +} diff --git a/webrtc/modules/desktop_capture/OWNERS b/webrtc/modules/desktop_capture/OWNERS index 3276530e9..4c0340d6d 100644 --- a/webrtc/modules/desktop_capture/OWNERS +++ b/webrtc/modules/desktop_capture/OWNERS @@ -1,3 +1,10 @@ alexeypa@chromium.org sergeyu@chromium.org wez@chromium.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index 5f967453f..6f4a08301 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -69,6 +69,12 @@ "win/scoped_gdi_object.h", "win/scoped_thread_desktop.cc", "win/scoped_thread_desktop.h", + "win/screen_capturer_win_gdi.cc", + "win/screen_capturer_win_gdi.h", + "win/screen_capturer_win_magnifier.cc", + "win/screen_capturer_win_magnifier.h", + "win/screen_capture_utils.cc", + "win/screen_capture_utils.h", "win/window_capture_utils.cc", "win/window_capture_utils.h", "window_capturer.cc", diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.cc b/webrtc/modules/desktop_capture/desktop_capture_options.cc index 26044e127..105853bf9 100644 --- a/webrtc/modules/desktop_capture/desktop_capture_options.cc +++ b/webrtc/modules/desktop_capture/desktop_capture_options.cc @@ -19,6 +19,10 @@ DesktopCaptureOptions::DesktopCaptureOptions() // XDamage is often broken, so don't use it by default. use_update_notifications_ = false; #endif + +#if defined(WEBRTC_WIN) + allow_use_magnification_api_ = false; +#endif } DesktopCaptureOptions::~DesktopCaptureOptions() {} diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.h b/webrtc/modules/desktop_capture/desktop_capture_options.h index 2a188a03a..c6aabd452 100644 --- a/webrtc/modules/desktop_capture/desktop_capture_options.h +++ b/webrtc/modules/desktop_capture/desktop_capture_options.h @@ -10,7 +10,7 @@ #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ #define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/scoped_refptr.h" #if defined(USE_X11) @@ -66,6 +66,15 @@ class DesktopCaptureOptions { disable_effects_ = disable_effects; } +#if defined(WEBRTC_WIN) + bool allow_use_magnification_api() const { + return allow_use_magnification_api_; + } + void set_allow_use_magnification_api(bool allow) { + allow_use_magnification_api_ = allow; + } +#endif + private: #if defined(USE_X11) scoped_refptr x_display_; @@ -74,6 +83,10 @@ class DesktopCaptureOptions { #if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) scoped_refptr configuration_monitor_; #endif + +#if defined(WEBRTC_WIN) + bool allow_use_magnification_api_; +#endif bool use_update_notifications_; bool disable_effects_; }; diff --git a/webrtc/modules/desktop_capture/desktop_geometry.h b/webrtc/modules/desktop_capture/desktop_geometry.h index e51273d8d..047eeec3d 100644 --- a/webrtc/modules/desktop_capture/desktop_geometry.h +++ b/webrtc/modules/desktop_capture/desktop_geometry.h @@ -11,8 +11,8 @@ #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ #define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/typedefs.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" namespace webrtc { @@ -58,7 +58,7 @@ class DesktopSize { int32_t width() const { return width_; } int32_t height() const { return height_; } - bool is_empty() const { return width_ <= 0 && height_ <= 0; } + bool is_empty() const { return width_ <= 0 || height_ <= 0; } bool equals(const DesktopSize& other) const { return width_ == other.width_ && height_ == other.height_; diff --git a/webrtc/modules/desktop_capture/desktop_region.h b/webrtc/modules/desktop_capture/desktop_region.h index fc7c6ed9e..c4528ae34 100644 --- a/webrtc/modules/desktop_capture/desktop_region.h +++ b/webrtc/modules/desktop_capture/desktop_region.h @@ -14,8 +14,8 @@ #include #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/desktop_capture/desktop_geometry.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/differ.h b/webrtc/modules/desktop_capture/differ.h index 8edce80b4..0b419d2dd 100644 --- a/webrtc/modules/desktop_capture/differ.h +++ b/webrtc/modules/desktop_capture/differ.h @@ -76,7 +76,7 @@ class Differ { int bytes_per_row_; // Diff information for each block in the image. - scoped_array diff_info_; + scoped_ptr diff_info_; // Dimensions and total size of diff info array. int diff_info_width_; diff --git a/webrtc/modules/desktop_capture/differ_unittest.cc b/webrtc/modules/desktop_capture/differ_unittest.cc index 40fde4dbc..da1a21461 100644 --- a/webrtc/modules/desktop_capture/differ_unittest.cc +++ b/webrtc/modules/desktop_capture/differ_unittest.cc @@ -200,8 +200,8 @@ class DifferTest : public testing::Test { int buffer_size_; // Previous and current screen buffers. - scoped_array prev_; - scoped_array curr_; + scoped_ptr prev_; + scoped_ptr curr_; private: DISALLOW_COPY_AND_ASSIGN(DifferTest); diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.h b/webrtc/modules/desktop_capture/mac/desktop_configuration.h index 031d92de2..bb2339bb0 100644 --- a/webrtc/modules/desktop_capture/mac/desktop_configuration.h +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.h @@ -59,10 +59,11 @@ struct MacDesktopConfiguration { const MacDisplayConfiguration* FindDisplayConfigurationById( CGDirectDisplayID id); - // Bounds of the desktop in Density-Independent Pixels (DIPs). + // Bounds of the desktop excluding monitors with DPI settings different from + // the main monitor. In Density-Independent Pixels (DIPs). DesktopRect bounds; - // Bounds of the desktop in physical pixels. + // Same as bounds, but expressed in physical pixels. DesktopRect pixel_bounds; // Scale factor from DIPs to physical pixels. diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.mm b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm index 838973e42..35fa65be2 100644 --- a/webrtc/modules/desktop_capture/mac/desktop_configuration.mm +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm @@ -134,11 +134,15 @@ MacDisplayConfiguration GetConfigurationForScreen(NSScreen* screen) { // Add the display to the configuration. desktop_config.displays.push_back(display_config); - // Update the desktop bounds to account for this display. - desktop_config.bounds = - JoinRects(desktop_config.bounds, display_config.bounds); - desktop_config.pixel_bounds = - JoinRects(desktop_config.pixel_bounds, display_config.pixel_bounds); + // Update the desktop bounds to account for this display, unless the current + // display uses different DPI settings. + if (display_config.dip_to_pixel_scale == + desktop_config.dip_to_pixel_scale) { + desktop_config.bounds = + JoinRects(desktop_config.bounds, display_config.bounds); + desktop_config.pixel_bounds = + JoinRects(desktop_config.pixel_bounds, display_config.pixel_bounds); + } } return desktop_config; diff --git a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h index 73d425aea..4d1dd1ffd 100644 --- a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h +++ b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h @@ -14,7 +14,7 @@ #include #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/mouse_cursor.cc b/webrtc/modules/desktop_capture/mouse_cursor.cc index 07c89f043..22a9c0ee8 100644 --- a/webrtc/modules/desktop_capture/mouse_cursor.cc +++ b/webrtc/modules/desktop_capture/mouse_cursor.cc @@ -10,6 +10,8 @@ #include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include + #include "webrtc/modules/desktop_capture/desktop_frame.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/mouse_cursor.h b/webrtc/modules/desktop_capture/mouse_cursor.h index 3acfa45a3..22887f9ae 100644 --- a/webrtc/modules/desktop_capture/mouse_cursor.h +++ b/webrtc/modules/desktop_capture/mouse_cursor.h @@ -11,8 +11,8 @@ #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ #define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/desktop_capture/desktop_geometry.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc index 7849005f9..c6af2b700 100644 --- a/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc @@ -50,7 +50,12 @@ class MouseCursorMonitorTest : public testing::Test, // tests. Figure out how to do that without breaking other tests in // modules_unittests and enable these tests on Mac. // https://code.google.com/p/webrtc/issues/detail?id=2532 -#if !defined(WEBRTC_MAC) +// +// Disabled on Windows due to flake, see: +// https://code.google.com/p/webrtc/issues/detail?id=3408 +// Disabled on Linux due to flake, see: +// https://code.google.com/p/webrtc/issues/detail?id=3245 +#if !defined(WEBRTC_MAC) && !defined(WEBRTC_WIN) && !defined(WEBRTC_LINUX) #define MAYBE(x) x #else #define MAYBE(x) DISABLED_##x diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc index d68fe4c5a..fd0b222a3 100644 --- a/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc @@ -10,6 +10,8 @@ #include "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" +#include + #include "webrtc/modules/desktop_capture/desktop_frame.h" #include "webrtc/modules/desktop_capture/mouse_cursor.h" #include "webrtc/modules/desktop_capture/win/cursor.h" @@ -28,6 +30,9 @@ class MouseCursorMonitorWin : public MouseCursorMonitor { virtual void Capture() OVERRIDE; private: + // Get the rect of the currently selected screen, relative to the primary + // display's top-left. If the screen is disabled or disconnected, or any error + // happens, an empty rect is returned. DesktopRect GetScreenRect(); HWND window_; @@ -148,11 +153,10 @@ DesktopRect MouseCursorMonitorWin::GetScreenRect() { if (!result) return DesktopRect(); - return DesktopRect::MakeXYWH( - GetSystemMetrics(SM_XVIRTUALSCREEN) + device_mode.dmPosition.x, - GetSystemMetrics(SM_YVIRTUALSCREEN) + device_mode.dmPosition.y, - device_mode.dmPelsWidth, - device_mode.dmPelsHeight); + return DesktopRect::MakeXYWH(device_mode.dmPosition.x, + device_mode.dmPosition.y, + device_mode.dmPelsWidth, + device_mode.dmPelsHeight); } MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc index 9e196779d..f09593db9 100644 --- a/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc @@ -17,6 +17,7 @@ #include "webrtc/modules/desktop_capture/desktop_capture_options.h" #include "webrtc/modules/desktop_capture/desktop_frame.h" #include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/x11/x_error_trap.h" #include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" @@ -145,10 +146,12 @@ void MouseCursorMonitorX11::Capture() { Window root_window; Window child_window; unsigned int mask; + + XErrorTrap error_trap(display()); Bool result = XQueryPointer(display(), window_, &root_window, &child_window, &root_x, &root_y, &win_x, &win_y, &mask); CursorState state; - if (!result) { + if (!result || error_trap.GetLastErrorAndDisable() != 0) { state = OUTSIDE; } else { // In screen mode (window_ == root_window) the mouse is always inside. diff --git a/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc index b045f0526..45a3507b9 100644 --- a/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc +++ b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc @@ -10,6 +10,7 @@ #include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include #include #include "webrtc/modules/desktop_capture/desktop_frame.h" diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper.cc b/webrtc/modules/desktop_capture/screen_capturer_helper.cc index 75af043c8..86761c170 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_helper.cc +++ b/webrtc/modules/desktop_capture/screen_capturer_helper.cc @@ -10,6 +10,7 @@ #include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include #include #include "webrtc/system_wrappers/interface/logging.h" diff --git a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc index 94c1f707e..50ff7a285 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc +++ b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc @@ -106,7 +106,7 @@ TEST_F(ScreenCapturerTest, Capture) { delete frame; } -#if defined(OS_WIN) +#if defined(WEBRTC_WIN) TEST_F(ScreenCapturerTest, UseSharedBuffers) { DesktopFrame* frame = NULL; @@ -129,6 +129,20 @@ TEST_F(ScreenCapturerTest, UseSharedBuffers) { delete frame; } -#endif // defined(OS_WIN) +TEST_F(ScreenCapturerTest, UseMagnifier) { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_use_magnification_api(true); + capturer_.reset(ScreenCapturer::Create(options)); + + DesktopFrame* frame = NULL; + EXPECT_CALL(callback_, OnCaptureCompleted(_)).WillOnce(SaveArg<0>(&frame)); + + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + ASSERT_TRUE(frame); + delete frame; +} + +#endif // defined(WEBRTC_WIN) } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_win.cc b/webrtc/modules/desktop_capture/screen_capturer_win.cc index db0a57c03..5950795d4 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_win.cc +++ b/webrtc/modules/desktop_capture/screen_capturer_win.cc @@ -10,450 +10,20 @@ #include "webrtc/modules/desktop_capture/screen_capturer.h" -#include - #include "webrtc/modules/desktop_capture/desktop_capture_options.h" -#include "webrtc/modules/desktop_capture/desktop_frame.h" -#include "webrtc/modules/desktop_capture/desktop_frame_win.h" -#include "webrtc/modules/desktop_capture/desktop_region.h" -#include "webrtc/modules/desktop_capture/differ.h" -#include "webrtc/modules/desktop_capture/mouse_cursor.h" -#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" -#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" -#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" -#include "webrtc/modules/desktop_capture/win/cursor.h" -#include "webrtc/modules/desktop_capture/win/desktop.h" -#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" -#include "webrtc/system_wrappers/interface/logging.h" -#include "webrtc/system_wrappers/interface/scoped_ptr.h" -#include "webrtc/system_wrappers/interface/tick_util.h" +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h" +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h" namespace webrtc { -namespace { - -// Constants from dwmapi.h. -const UINT DWM_EC_DISABLECOMPOSITION = 0; -const UINT DWM_EC_ENABLECOMPOSITION = 1; - -typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT); - -const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll"; - -// ScreenCapturerWin captures 32bit RGB using GDI. -// -// ScreenCapturerWin is double-buffered as required by ScreenCapturer. -class ScreenCapturerWin : public ScreenCapturer { - public: - ScreenCapturerWin(const DesktopCaptureOptions& options); - virtual ~ScreenCapturerWin(); - - // Overridden from ScreenCapturer: - virtual void Start(Callback* callback) OVERRIDE; - virtual void Capture(const DesktopRegion& region) OVERRIDE; - virtual void SetMouseShapeObserver( - MouseShapeObserver* mouse_shape_observer) OVERRIDE; - virtual bool GetScreenList(ScreenList* screens) OVERRIDE; - virtual bool SelectScreen(ScreenId id) OVERRIDE; - - private: - // Make sure that the device contexts match the screen configuration. - void PrepareCaptureResources(); - - // Captures the current screen contents into the current buffer. Returns true - // if succeeded. - bool CaptureImage(); - - // Capture the current cursor shape. - void CaptureCursor(); - - // Get the rect of the currently selected screen. If the screen is disabled - // or disconnected, or any error happens, an empty rect is returned. - DesktopRect GetScreenRect(); - - Callback* callback_; - MouseShapeObserver* mouse_shape_observer_; - ScreenId current_screen_id_; - std::wstring current_device_key_; - - // A thread-safe list of invalid rectangles, and the size of the most - // recently captured screen. - ScreenCapturerHelper helper_; - - // Snapshot of the last cursor bitmap we sent to the client. This is used - // to diff against the current cursor so we only send a cursor-change - // message when the shape has changed. - MouseCursorShape last_cursor_; - - ScopedThreadDesktop desktop_; - - // GDI resources used for screen capture. - HDC desktop_dc_; - HDC memory_dc_; - - // Queue of the frames buffers. - ScreenCaptureFrameQueue queue_; - - // Rectangle describing the bounds of the desktop device context. - DesktopRect desktop_dc_rect_; - - // Class to calculate the difference between two screen bitmaps. - scoped_ptr differ_; - - HMODULE dwmapi_library_; - DwmEnableCompositionFunc composition_func_; - - // Used to suppress duplicate logging of SetThreadExecutionState errors. - bool set_thread_execution_state_failed_; - - DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWin); -}; - -ScreenCapturerWin::ScreenCapturerWin(const DesktopCaptureOptions& options) - : callback_(NULL), - mouse_shape_observer_(NULL), - current_screen_id_(kFullDesktopScreenId), - desktop_dc_(NULL), - memory_dc_(NULL), - dwmapi_library_(NULL), - composition_func_(NULL), - set_thread_execution_state_failed_(false) { - if (options.disable_effects()) { - // Load dwmapi.dll dynamically since it is not available on XP. - if (!dwmapi_library_) - dwmapi_library_ = LoadLibrary(kDwmapiLibraryName); - - if (dwmapi_library_) { - composition_func_ = reinterpret_cast( - GetProcAddress(dwmapi_library_, "DwmEnableComposition")); - } - } -} - -ScreenCapturerWin::~ScreenCapturerWin() { - if (desktop_dc_) - ReleaseDC(NULL, desktop_dc_); - if (memory_dc_) - DeleteDC(memory_dc_); - - // Restore Aero. - if (composition_func_) - (*composition_func_)(DWM_EC_ENABLECOMPOSITION); - - if (dwmapi_library_) - FreeLibrary(dwmapi_library_); -} - -void ScreenCapturerWin::Capture(const DesktopRegion& region) { - TickTime capture_start_time = TickTime::Now(); - - queue_.MoveToNextFrame(); - - // Request that the system not power-down the system, or the display hardware. - if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { - if (!set_thread_execution_state_failed_) { - set_thread_execution_state_failed_ = true; - LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " - << GetLastError(); - } - } - - // Make sure the GDI capture resources are up-to-date. - PrepareCaptureResources(); - - // Copy screen bits to the current buffer. - if (!CaptureImage()) { - callback_->OnCaptureCompleted(NULL); - return; - } - - const DesktopFrame* current_frame = queue_.current_frame(); - const DesktopFrame* last_frame = queue_.previous_frame(); - if (last_frame && last_frame->size().equals(current_frame->size())) { - // Make sure the differencer is set up correctly for these previous and - // current screens. - if (!differ_.get() || - (differ_->width() != current_frame->size().width()) || - (differ_->height() != current_frame->size().height()) || - (differ_->bytes_per_row() != current_frame->stride())) { - differ_.reset(new Differ(current_frame->size().width(), - current_frame->size().height(), - DesktopFrame::kBytesPerPixel, - current_frame->stride())); - } - - // Calculate difference between the two last captured frames. - DesktopRegion region; - differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(), - ®ion); - helper_.InvalidateRegion(region); - } else { - // No previous frame is available, or the screen is resized. Invalidate the - // whole screen. - helper_.InvalidateScreen(current_frame->size()); - } - - helper_.set_size_most_recent(current_frame->size()); - - // Emit the current frame. - DesktopFrame* frame = queue_.current_frame()->Share(); - frame->set_dpi(DesktopVector( - GetDeviceCaps(desktop_dc_, LOGPIXELSX), - GetDeviceCaps(desktop_dc_, LOGPIXELSY))); - frame->mutable_updated_region()->Clear(); - helper_.TakeInvalidRegion(frame->mutable_updated_region()); - frame->set_capture_time_ms( - (TickTime::Now() - capture_start_time).Milliseconds()); - callback_->OnCaptureCompleted(frame); - - // Check for cursor shape update. - CaptureCursor(); -} - -void ScreenCapturerWin::SetMouseShapeObserver( - MouseShapeObserver* mouse_shape_observer) { - assert(!mouse_shape_observer_); - assert(mouse_shape_observer); - - mouse_shape_observer_ = mouse_shape_observer; -} - -bool ScreenCapturerWin::GetScreenList(ScreenList* screens) { - assert(screens->size() == 0); - BOOL enum_result = TRUE; - for (int device_index = 0; ; ++device_index) { - DISPLAY_DEVICE device; - device.cb = sizeof(device); - enum_result = EnumDisplayDevices(NULL, device_index, &device, 0); - // |enum_result| is 0 if we have enumerated all devices. - if (!enum_result) - break; - - // We only care about active displays. - if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) - continue; - Screen screen; - screen.id = device_index; - screens->push_back(screen); - } - return true; -} - -bool ScreenCapturerWin::SelectScreen(ScreenId id) { - if (id == kFullDesktopScreenId) { - current_screen_id_ = id; - return true; - } - DISPLAY_DEVICE device; - device.cb = sizeof(device); - BOOL enum_result = EnumDisplayDevices(NULL, id, &device, 0); - if (!enum_result) - return false; - - current_device_key_ = device.DeviceKey; - current_screen_id_ = id; - return true; -} - -void ScreenCapturerWin::Start(Callback* callback) { - assert(!callback_); - assert(callback); - - callback_ = callback; - - // Vote to disable Aero composited desktop effects while capturing. Windows - // will restore Aero automatically if the process exits. This has no effect - // under Windows 8 or higher. See crbug.com/124018. - if (composition_func_) - (*composition_func_)(DWM_EC_DISABLECOMPOSITION); -} - -void ScreenCapturerWin::PrepareCaptureResources() { - // Switch to the desktop receiving user input if different from the current - // one. - scoped_ptr input_desktop(Desktop::GetInputDesktop()); - if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { - // Release GDI resources otherwise SetThreadDesktop will fail. - if (desktop_dc_) { - ReleaseDC(NULL, desktop_dc_); - desktop_dc_ = NULL; - } - - if (memory_dc_) { - DeleteDC(memory_dc_); - memory_dc_ = NULL; - } - - // If SetThreadDesktop() fails, the thread is still assigned a desktop. - // So we can continue capture screen bits, just from the wrong desktop. - desktop_.SetThreadDesktop(input_desktop.release()); - - // Re-assert our vote to disable Aero. - // See crbug.com/124018 and crbug.com/129906. - if (composition_func_ != NULL) { - (*composition_func_)(DWM_EC_DISABLECOMPOSITION); - } - } - - // If the display bounds have changed then recreate GDI resources. - // TODO(wez): Also check for pixel format changes. - DesktopRect screen_rect(DesktopRect::MakeXYWH( - GetSystemMetrics(SM_XVIRTUALSCREEN), - GetSystemMetrics(SM_YVIRTUALSCREEN), - GetSystemMetrics(SM_CXVIRTUALSCREEN), - GetSystemMetrics(SM_CYVIRTUALSCREEN))); - if (!screen_rect.equals(desktop_dc_rect_)) { - if (desktop_dc_) { - ReleaseDC(NULL, desktop_dc_); - desktop_dc_ = NULL; - } - if (memory_dc_) { - DeleteDC(memory_dc_); - memory_dc_ = NULL; - } - desktop_dc_rect_ = DesktopRect(); - } - - if (desktop_dc_ == NULL) { - assert(memory_dc_ == NULL); - - // Create GDI device contexts to capture from the desktop into memory. - desktop_dc_ = GetDC(NULL); - if (!desktop_dc_) - abort(); - memory_dc_ = CreateCompatibleDC(desktop_dc_); - if (!memory_dc_) - abort(); - desktop_dc_rect_ = screen_rect; - - // Make sure the frame buffers will be reallocated. - queue_.Reset(); - - helper_.ClearInvalidRegion(); - } -} - -bool ScreenCapturerWin::CaptureImage() { - DesktopRect screen_rect = GetScreenRect(); - if (screen_rect.is_empty()) - return false; - DesktopSize size = screen_rect.size(); - // If the current buffer is from an older generation then allocate a new one. - // Note that we can't reallocate other buffers at this point, since the caller - // may still be reading from them. - if (!queue_.current_frame() || - !queue_.current_frame()->size().equals(size)) { - assert(desktop_dc_ != NULL); - assert(memory_dc_ != NULL); - - size_t buffer_size = size.width() * size.height() * - DesktopFrame::kBytesPerPixel; - SharedMemory* shared_memory = - callback_->CreateSharedMemory(buffer_size); - scoped_ptr buffer( - DesktopFrameWin::Create(size, shared_memory, desktop_dc_)); - queue_.ReplaceCurrentFrame(buffer.release()); - } - - // Select the target bitmap into the memory dc and copy the rect from desktop - // to memory. - DesktopFrameWin* current = static_cast( - queue_.current_frame()->GetUnderlyingFrame()); - HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap()); - if (previous_object != NULL) { - BitBlt(memory_dc_, - 0, 0, screen_rect.width(), screen_rect.height(), - desktop_dc_, - screen_rect.left(), screen_rect.top(), - SRCCOPY | CAPTUREBLT); - - // Select back the previously selected object to that the device contect - // could be destroyed independently of the bitmap if needed. - SelectObject(memory_dc_, previous_object); - } - return true; -} - -void ScreenCapturerWin::CaptureCursor() { - CURSORINFO cursor_info; - cursor_info.cbSize = sizeof(CURSORINFO); - if (!GetCursorInfo(&cursor_info)) { - LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError(); - return; - } - - // Note that |cursor_info.hCursor| does not need to be freed. - scoped_ptr cursor_image( - CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); - if (!cursor_image.get()) - return; - - scoped_ptr cursor(new MouseCursorShape); - cursor->hotspot = cursor_image->hotspot(); - cursor->size = cursor_image->image()->size(); - uint8_t* current_row = cursor_image->image()->data(); - for (int y = 0; y < cursor_image->image()->size().height(); ++y) { - cursor->data.append(current_row, - current_row + cursor_image->image()->size().width() * - DesktopFrame::kBytesPerPixel); - current_row += cursor_image->image()->stride(); - } - - // Compare the current cursor with the last one we sent to the client. If - // they're the same, then don't bother sending the cursor again. - if (last_cursor_.size.equals(cursor->size) && - last_cursor_.hotspot.equals(cursor->hotspot) && - last_cursor_.data == cursor->data) { - return; - } - - LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x" - << cursor->size.height(); - - // Record the last cursor image that we sent to the client. - last_cursor_ = *cursor; - - if (mouse_shape_observer_) - mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); -} - -DesktopRect ScreenCapturerWin::GetScreenRect() { - DesktopRect rect = desktop_dc_rect_; - if (current_screen_id_ == kFullDesktopScreenId) - return rect; - - DISPLAY_DEVICE device; - device.cb = sizeof(device); - BOOL result = EnumDisplayDevices(NULL, current_screen_id_, &device, 0); - if (!result) - return DesktopRect(); - - // Verifies the device index still maps to the same display device. DeviceKey - // is documented as reserved, but it actually contains the registry key for - // the device and is unique for each monitor, while DeviceID is not. - if (current_device_key_ != device.DeviceKey) - return DesktopRect(); - - DEVMODE device_mode; - device_mode.dmSize = sizeof(device_mode); - device_mode.dmDriverExtra = 0; - result = EnumDisplaySettingsEx( - device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0); - if (!result) - return DesktopRect(); - - rect = DesktopRect::MakeXYWH( - rect.left() + device_mode.dmPosition.x, - rect.top() + device_mode.dmPosition.y, - device_mode.dmPelsWidth, - device_mode.dmPelsHeight); - return rect; -} -} // namespace - // static ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { - return new ScreenCapturerWin(options); + scoped_ptr gdi_capturer(new ScreenCapturerWinGdi(options)); + + if (options.allow_use_magnification_api()) + return new ScreenCapturerWinMagnifier(gdi_capturer.Pass()); + + return gdi_capturer.release(); } } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/shared_memory.h b/webrtc/modules/desktop_capture/shared_memory.h index bb43b28b1..7870d833f 100644 --- a/webrtc/modules/desktop_capture/shared_memory.h +++ b/webrtc/modules/desktop_capture/shared_memory.h @@ -17,8 +17,8 @@ #include #endif +#include "webrtc/base/constructormagic.h" #include "webrtc/typedefs.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/win/cursor.cc b/webrtc/modules/desktop_capture/win/cursor.cc index 11bb2dbb6..00055c44a 100644 --- a/webrtc/modules/desktop_capture/win/cursor.cc +++ b/webrtc/modules/desktop_capture/win/cursor.cc @@ -137,7 +137,7 @@ MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) { int width = bitmap_info.bmWidth; int height = bitmap_info.bmHeight; - scoped_array mask_data(new uint32_t[width * height]); + scoped_ptr mask_data(new uint32_t[width * height]); // Get pixel data from |scoped_mask| converting it to 32bpp along the way. // GetDIBits() sets the alpha component of every pixel to 0. diff --git a/webrtc/modules/desktop_capture/win/cursor_unittest.cc b/webrtc/modules/desktop_capture/win/cursor_unittest.cc index 9d2387483..b046ace31 100644 --- a/webrtc/modules/desktop_capture/win/cursor_unittest.cc +++ b/webrtc/modules/desktop_capture/win/cursor_unittest.cc @@ -62,7 +62,7 @@ bool ConvertToMouseShapeAndCompare(unsigned left, unsigned right) { // Get the pixels from |scoped_color|. int size = width * height; - scoped_array data(new uint32_t[size]); + scoped_ptr data(new uint32_t[size]); EXPECT_TRUE(GetBitmapBits(scoped_color, size * sizeof(uint32_t), data.get())); // Compare the 32bpp image in |mouse_shape| with the one loaded from |right|. diff --git a/webrtc/modules/desktop_capture/win/desktop.h b/webrtc/modules/desktop_capture/win/desktop.h index bdc490c72..fda56ca8d 100644 --- a/webrtc/modules/desktop_capture/win/desktop.h +++ b/webrtc/modules/desktop_capture/win/desktop.h @@ -11,10 +11,10 @@ #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ #define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ -#include #include +#include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/win/scoped_gdi_object.h b/webrtc/modules/desktop_capture/win/scoped_gdi_object.h index 0ca35c526..366df6d4f 100644 --- a/webrtc/modules/desktop_capture/win/scoped_gdi_object.h +++ b/webrtc/modules/desktop_capture/win/scoped_gdi_object.h @@ -13,7 +13,7 @@ #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h index 39514237e..f12731d97 100644 --- a/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h +++ b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h @@ -13,7 +13,7 @@ #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.cc b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc new file mode 100644 index 000000000..1b3354527 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/desktop_capture/win/screen_capture_utils.h" + +#include +#include + +namespace webrtc { + +bool GetScreenList(ScreenCapturer::ScreenList* screens) { + assert(screens->size() == 0); + + BOOL enum_result = TRUE; + for (int device_index = 0;; ++device_index) { + DISPLAY_DEVICE device; + device.cb = sizeof(device); + enum_result = EnumDisplayDevices(NULL, device_index, &device, 0); + + // |enum_result| is 0 if we have enumerated all devices. + if (!enum_result) + break; + + // We only care about active displays. + if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) + continue; + + ScreenCapturer::Screen screen; + screen.id = device_index; + screens->push_back(screen); + } + return true; +} + +bool IsScreenValid(ScreenId screen, std::wstring* device_key) { + if (screen == kFullDesktopScreenId) { + *device_key = L""; + return true; + } + + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL enum_result = EnumDisplayDevices(NULL, screen, &device, 0); + if (enum_result) + *device_key = device.DeviceKey; + + return !!enum_result; +} + +DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key) { + if (screen == kFullDesktopScreenId) { + return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN)); + } + + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL result = EnumDisplayDevices(NULL, screen, &device, 0); + if (!result) + return DesktopRect(); + + // Verifies the device index still maps to the same display device, to make + // sure we are capturing the same device when devices are added or removed. + // DeviceKey is documented as reserved, but it actually contains the registry + // key for the device and is unique for each monitor, while DeviceID is not. + if (device_key != device.DeviceKey) + return DesktopRect(); + + DEVMODE device_mode; + device_mode.dmSize = sizeof(device_mode); + device_mode.dmDriverExtra = 0; + result = EnumDisplaySettingsEx( + device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0); + if (!result) + return DesktopRect(); + + return DesktopRect::MakeXYWH(device_mode.dmPosition.x, + device_mode.dmPosition.y, + device_mode.dmPelsWidth, + device_mode.dmPelsHeight); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.h b/webrtc/modules/desktop_capture/win/screen_capture_utils.h new file mode 100644 index 000000000..42473e047 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 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_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +namespace webrtc { + +// Output the list of active screens into |screens|. Returns true if succeeded, +// or false if it fails to enumerate the display devices. +bool GetScreenList(ScreenCapturer::ScreenList* screens); + +// Returns true if |screen| is a valid screen. The screen device key is +// returned through |device_key| if the screen is valid. The device key can be +// used in GetScreenRect to verify the screen matches the previously obtained +// id. +bool IsScreenValid(ScreenId screen, std::wstring* device_key); + +// Get the rect of the screen identified by |screen|, relative to the primary +// display's top-left. If the screen device key does not match |device_key|, or +// the screen does not exist, or any error happens, an empty rect is returned. +DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc new file mode 100644 index 000000000..dc10eceb7 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h" + +#include + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/desktop.h" +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +namespace { + +// Constants from dwmapi.h. +const UINT DWM_EC_DISABLECOMPOSITION = 0; +const UINT DWM_EC_ENABLECOMPOSITION = 1; + +const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll"; + +} // namespace + +ScreenCapturerWinGdi::ScreenCapturerWinGdi(const DesktopCaptureOptions& options) + : callback_(NULL), + mouse_shape_observer_(NULL), + current_screen_id_(kFullDesktopScreenId), + desktop_dc_(NULL), + memory_dc_(NULL), + dwmapi_library_(NULL), + composition_func_(NULL), + set_thread_execution_state_failed_(false) { + if (options.disable_effects()) { + // Load dwmapi.dll dynamically since it is not available on XP. + if (!dwmapi_library_) + dwmapi_library_ = LoadLibrary(kDwmapiLibraryName); + + if (dwmapi_library_) { + composition_func_ = reinterpret_cast( + GetProcAddress(dwmapi_library_, "DwmEnableComposition")); + } + } +} + +ScreenCapturerWinGdi::~ScreenCapturerWinGdi() { + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); + if (memory_dc_) + DeleteDC(memory_dc_); + + // Restore Aero. + if (composition_func_) + (*composition_func_)(DWM_EC_ENABLECOMPOSITION); + + if (dwmapi_library_) + FreeLibrary(dwmapi_library_); +} + +void ScreenCapturerWinGdi::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Request that the system not power-down the system, or the display hardware. + if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { + if (!set_thread_execution_state_failed_) { + set_thread_execution_state_failed_ = true; + LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " + << GetLastError(); + } + } + + // Make sure the GDI capture resources are up-to-date. + PrepareCaptureResources(); + + if (!CaptureImage()) { + callback_->OnCaptureCompleted(NULL); + return; + } + + const DesktopFrame* current_frame = queue_.current_frame(); + const DesktopFrame* last_frame = queue_.previous_frame(); + if (last_frame && last_frame->size().equals(current_frame->size())) { + // Make sure the differencer is set up correctly for these previous and + // current screens. + if (!differ_.get() || + (differ_->width() != current_frame->size().width()) || + (differ_->height() != current_frame->size().height()) || + (differ_->bytes_per_row() != current_frame->stride())) { + differ_.reset(new Differ(current_frame->size().width(), + current_frame->size().height(), + DesktopFrame::kBytesPerPixel, + current_frame->stride())); + } + + // Calculate difference between the two last captured frames. + DesktopRegion region; + differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(), + ®ion); + helper_.InvalidateRegion(region); + } else { + // No previous frame is available, or the screen is resized. Invalidate the + // whole screen. + helper_.InvalidateScreen(current_frame->size()); + } + + helper_.set_size_most_recent(current_frame->size()); + + // Emit the current frame. + DesktopFrame* frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector( + GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->Clear(); + helper_.TakeInvalidRegion(frame->mutable_updated_region()); + frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(frame); + + // Check for cursor shape update. + if (mouse_shape_observer_) + CaptureCursor(); +} + +void ScreenCapturerWinGdi::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + assert(!mouse_shape_observer_); + assert(mouse_shape_observer); + + mouse_shape_observer_ = mouse_shape_observer; +} + +bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) { + return webrtc::GetScreenList(screens); +} + +bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) { + bool valid = IsScreenValid(id, ¤t_device_key_); + if (valid) + current_screen_id_ = id; + return valid; +} + +void ScreenCapturerWinGdi::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; + + // Vote to disable Aero composited desktop effects while capturing. Windows + // will restore Aero automatically if the process exits. This has no effect + // under Windows 8 or higher. See crbug.com/124018. + if (composition_func_) + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); +} + +void ScreenCapturerWinGdi::PrepareCaptureResources() { + // Switch to the desktop receiving user input if different from the current + // one. + scoped_ptr input_desktop(Desktop::GetInputDesktop()); + if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = NULL; + } + + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + + // Re-assert our vote to disable Aero. + // See crbug.com/124018 and crbug.com/129906. + if (composition_func_ != NULL) { + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); + } + } + + // If the display bounds have changed then recreate GDI resources. + // TODO(wez): Also check for pixel format changes. + DesktopRect screen_rect(DesktopRect::MakeXYWH( + GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN))); + if (!screen_rect.equals(desktop_dc_rect_)) { + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = NULL; + } + desktop_dc_rect_ = DesktopRect(); + } + + if (desktop_dc_ == NULL) { + assert(memory_dc_ == NULL); + + // Create GDI device contexts to capture from the desktop into memory. + desktop_dc_ = GetDC(NULL); + if (!desktop_dc_) + abort(); + memory_dc_ = CreateCompatibleDC(desktop_dc_); + if (!memory_dc_) + abort(); + + desktop_dc_rect_ = screen_rect; + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + } +} + +bool ScreenCapturerWinGdi::CaptureImage() { + DesktopRect screen_rect = + GetScreenRect(current_screen_id_, current_device_key_); + if (screen_rect.is_empty()) + return false; + + DesktopSize size = screen_rect.size(); + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame() || + !queue_.current_frame()->size().equals(screen_rect.size())) { + assert(desktop_dc_ != NULL); + assert(memory_dc_ != NULL); + + size_t buffer_size = size.width() * size.height() * + DesktopFrame::kBytesPerPixel; + SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size); + + scoped_ptr buffer; + buffer.reset( + DesktopFrameWin::Create(size, shared_memory, desktop_dc_)); + queue_.ReplaceCurrentFrame(buffer.release()); + } + + // Select the target bitmap into the memory dc and copy the rect from desktop + // to memory. + DesktopFrameWin* current = static_cast( + queue_.current_frame()->GetUnderlyingFrame()); + HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap()); + if (previous_object != NULL) { + BitBlt(memory_dc_, + 0, 0, screen_rect.width(), screen_rect.height(), + desktop_dc_, + screen_rect.left(), screen_rect.top(), + SRCCOPY | CAPTUREBLT); + + // Select back the previously selected object to that the device contect + // could be destroyed independently of the bitmap if needed. + SelectObject(memory_dc_, previous_object); + } + return true; +} + +void ScreenCapturerWinGdi::CaptureCursor() { + assert(mouse_shape_observer_); + + CURSORINFO cursor_info; + cursor_info.cbSize = sizeof(CURSORINFO); + if (!GetCursorInfo(&cursor_info)) { + LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError(); + return; + } + + // Note that |cursor_info.hCursor| does not need to be freed. + scoped_ptr cursor_image( + CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); + if (!cursor_image.get()) + return; + + scoped_ptr cursor(new MouseCursorShape); + cursor->hotspot = cursor_image->hotspot(); + cursor->size = cursor_image->image()->size(); + uint8_t* current_row = cursor_image->image()->data(); + for (int y = 0; y < cursor_image->image()->size().height(); ++y) { + cursor->data.append(current_row, + current_row + cursor_image->image()->size().width() * + DesktopFrame::kBytesPerPixel); + current_row += cursor_image->image()->stride(); + } + + // Compare the current cursor with the last one we sent to the client. If + // they're the same, then don't bother sending the cursor again. + if (last_cursor_.size.equals(cursor->size) && + last_cursor_.hotspot.equals(cursor->hotspot) && + last_cursor_.data == cursor->data) { + return; + } + + LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x" + << cursor->size.height(); + + // Record the last cursor image that we sent to the client. + last_cursor_ = *cursor; + + mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h new file mode 100644 index 000000000..2db87d097 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2014 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_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include + +#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class Differ; +class MouseShapeObserver; + +// ScreenCapturerWinGdi captures 32bit RGB using GDI. +// +// ScreenCapturerWinGdi is double-buffered as required by ScreenCapturer. +class ScreenCapturerWinGdi : public ScreenCapturer { + public: + explicit ScreenCapturerWinGdi(const DesktopCaptureOptions& options); + virtual ~ScreenCapturerWinGdi(); + + // Overridden from ScreenCapturer: + virtual void Start(Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + virtual bool GetScreenList(ScreenList* screens) OVERRIDE; + virtual bool SelectScreen(ScreenId id) OVERRIDE; + + private: + typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT); + + // Make sure that the device contexts match the screen configuration. + void PrepareCaptureResources(); + + // Captures the current screen contents into the current buffer. Returns true + // if succeeded. + bool CaptureImage(); + + // Capture the current cursor shape. + void CaptureCursor(); + + Callback* callback_; + MouseShapeObserver* mouse_shape_observer_; + ScreenId current_screen_id_; + std::wstring current_device_key_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Snapshot of the last cursor bitmap we sent to the client. This is used + // to diff against the current cursor so we only send a cursor-change + // message when the shape has changed. + MouseCursorShape last_cursor_; + + ScopedThreadDesktop desktop_; + + // GDI resources used for screen capture. + HDC desktop_dc_; + HDC memory_dc_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Rectangle describing the bounds of the desktop device context, relative to + // the primary display's top-left. + DesktopRect desktop_dc_rect_; + + // Class to calculate the difference between two screen bitmaps. + scoped_ptr differ_; + + HMODULE dwmapi_library_; + DwmEnableCompositionFunc composition_func_; + + // Used to suppress duplicate logging of SetThreadExecutionState errors. + bool set_thread_execution_state_failed_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinGdi); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc new file mode 100644 index 000000000..042cb937a --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h" + +#include + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/desktop.h" +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +// kMagnifierWindowClass has to be "Magnifier" according to the Magnification +// API. The other strings can be anything. +static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost"; +static LPCTSTR kHostWindowName = L"MagnifierHost"; +static LPCTSTR kMagnifierWindowClass = L"Magnifier"; +static LPCTSTR kMagnifierWindowName = L"MagnifierWindow"; + +Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES); + +ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier( + scoped_ptr fallback_capturer) + : fallback_capturer_(fallback_capturer.Pass()), + fallback_capturer_started_(false), + callback_(NULL), + current_screen_id_(kFullDesktopScreenId), + excluded_window_(NULL), + set_thread_execution_state_failed_(false), + desktop_dc_(NULL), + mag_lib_handle_(NULL), + mag_initialize_func_(NULL), + mag_uninitialize_func_(NULL), + set_window_source_func_(NULL), + set_window_filter_list_func_(NULL), + set_image_scaling_callback_func_(NULL), + host_window_(NULL), + magnifier_window_(NULL), + magnifier_initialized_(false), + magnifier_capture_succeeded_(true) { +} + +ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() { + // DestroyWindow must be called before MagUninitialize. magnifier_window_ is + // destroyed automatically when host_window_ is destroyed. + if (host_window_) + DestroyWindow(host_window_); + + if (magnifier_initialized_) + mag_uninitialize_func_(); + + if (mag_lib_handle_) + FreeLibrary(mag_lib_handle_); + + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); +} + +void ScreenCapturerWinMagnifier::Start(Callback* callback) { + assert(!callback_); + assert(callback); + callback_ = callback; + + InitializeMagnifier(); +} + +void ScreenCapturerWinMagnifier::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Request that the system not power-down the system, or the display hardware. + if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { + if (!set_thread_execution_state_failed_) { + set_thread_execution_state_failed_ = true; + LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " + << GetLastError(); + } + } + // Switch to the desktop receiving user input if different from the current + // one. + scoped_ptr input_desktop(Desktop::GetInputDesktop()); + if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + } + + bool succeeded = false; + + // Do not try to use the magnfiier if it's capturing non-primary screen, or it + // failed before. + if (magnifier_initialized_ && IsCapturingPrimaryScreenOnly() && + magnifier_capture_succeeded_) { + DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_); + CreateCurrentFrameIfNecessary(rect.size()); + + // CaptureImage may fail in some situations, e.g. windows8 metro mode. + succeeded = CaptureImage(rect); + } + + // Defer to the fallback capturer if magnifier capturer did not work. + if (!succeeded) { + LOG_F(LS_WARNING) << "Switching to the fallback screen capturer."; + StartFallbackCapturer(); + fallback_capturer_->Capture(region); + return; + } + + const DesktopFrame* current_frame = queue_.current_frame(); + const DesktopFrame* last_frame = queue_.previous_frame(); + if (last_frame && last_frame->size().equals(current_frame->size())) { + // Make sure the differencer is set up correctly for these previous and + // current screens. + if (!differ_.get() || (differ_->width() != current_frame->size().width()) || + (differ_->height() != current_frame->size().height()) || + (differ_->bytes_per_row() != current_frame->stride())) { + differ_.reset(new Differ(current_frame->size().width(), + current_frame->size().height(), + DesktopFrame::kBytesPerPixel, + current_frame->stride())); + } + + // Calculate difference between the two last captured frames. + DesktopRegion region; + differ_->CalcDirtyRegion( + last_frame->data(), current_frame->data(), ®ion); + helper_.InvalidateRegion(region); + } else { + // No previous frame is available, or the screen is resized. Invalidate the + // whole screen. + helper_.InvalidateScreen(current_frame->size()); + } + + helper_.set_size_most_recent(current_frame->size()); + + // Emit the current frame. + DesktopFrame* frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->Clear(); + helper_.TakeInvalidRegion(frame->mutable_updated_region()); + frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(frame); +} + +void ScreenCapturerWinMagnifier::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + assert(false); // NOTREACHED(); +} + +bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) { + return webrtc::GetScreenList(screens); +} + +bool ScreenCapturerWinMagnifier::SelectScreen(ScreenId id) { + bool valid = IsScreenValid(id, ¤t_device_key_); + + // Set current_screen_id_ even if the fallback capturer is being used, so we + // can switch back to the magnifier when possible. + if (valid) + current_screen_id_ = id; + + if (fallback_capturer_started_) + fallback_capturer_->SelectScreen(id); + + return valid; +} + +void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) { + excluded_window_ = (HWND)excluded_window; + if (excluded_window_ && magnifier_initialized_) { + set_window_filter_list_func_( + magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_); + } +} + +bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) { + assert(magnifier_initialized_); + + // Set the magnifier control to cover the captured rect. The content of the + // magnifier control will be the captured image. + BOOL result = SetWindowPos(magnifier_window_, + NULL, + rect.left(), rect.top(), + rect.width(), rect.height(), + 0); + if (!result) { + LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError() + << ". Rect = {" << rect.left() << ", " << rect.top() + << ", " << rect.right() << ", " << rect.bottom() << "}"; + return false; + } + + magnifier_capture_succeeded_ = false; + + RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()}; + + // OnCaptured will be called via OnMagImageScalingCallback and fill in the + // frame before set_window_source_func_ returns. + result = set_window_source_func_(magnifier_window_, native_rect); + + if (!result) { + LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: " << GetLastError() + << ". Rect = {" << rect.left() << ", " << rect.top() + << ", " << rect.right() << ", " << rect.bottom() << "}"; + return false; + } + + return magnifier_capture_succeeded_; +} + +BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback( + HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty) { + assert(tls_index_.Value() != TLS_OUT_OF_INDEXES); + + ScreenCapturerWinMagnifier* owner = + reinterpret_cast( + TlsGetValue(tls_index_.Value())); + + owner->OnCaptured(srcdata, srcheader); + + return TRUE; +} + +bool ScreenCapturerWinMagnifier::InitializeMagnifier() { + assert(!magnifier_initialized_); + + desktop_dc_ = GetDC(NULL); + + mag_lib_handle_ = LoadLibrary(L"Magnification.dll"); + if (!mag_lib_handle_) + return false; + + // Initialize Magnification API function pointers. + mag_initialize_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagInitialize")); + mag_uninitialize_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagUninitialize")); + set_window_source_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagSetWindowSource")); + set_window_filter_list_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList")); + set_image_scaling_callback_func_ = + reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback")); + + if (!mag_initialize_func_ || !mag_uninitialize_func_ || + !set_window_source_func_ || !set_window_filter_list_func_ || + !set_image_scaling_callback_func_) { + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "library functions missing."; + return false; + } + + BOOL result = mag_initialize_func_(); + if (!result) { + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagInitialize " << GetLastError(); + return false; + } + + HMODULE hInstance = NULL; + result = GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&DefWindowProc), + &hInstance); + if (!result) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from GetModulehandleExA " << GetLastError(); + return false; + } + + // Register the host window class. See the MSDN documentation of the + // Magnification API for more infomation. + WNDCLASSEX wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = &DefWindowProc; + wcex.hInstance = hInstance; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.lpszClassName = kMagnifierHostClass; + + // Ignore the error which may happen when the class is already registered. + RegisterClassEx(&wcex); + + // Create the host window. + host_window_ = CreateWindowEx(WS_EX_LAYERED, + kMagnifierHostClass, + kHostWindowName, + 0, + 0, 0, 0, 0, + NULL, + NULL, + hInstance, + NULL); + if (!host_window_) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from creating host window " << GetLastError(); + return false; + } + + // Create the magnifier control. + magnifier_window_ = CreateWindow(kMagnifierWindowClass, + kMagnifierWindowName, + WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, + host_window_, + NULL, + hInstance, + NULL); + if (!magnifier_window_) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from creating magnifier window " + << GetLastError(); + return false; + } + + // Hide the host window. + ShowWindow(host_window_, SW_HIDE); + + // Set the scaling callback to receive captured image. + result = set_image_scaling_callback_func_( + magnifier_window_, + &ScreenCapturerWinMagnifier::OnMagImageScalingCallback); + if (!result) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagSetImageScalingCallback " + << GetLastError(); + return false; + } + + if (excluded_window_) { + result = set_window_filter_list_func_( + magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_); + if (!result) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagSetWindowFilterList " + << GetLastError(); + return false; + } + } + + if (tls_index_.Value() == TLS_OUT_OF_INDEXES) { + // More than one threads may get here at the same time, but only one will + // write to tls_index_ using CompareExchange. + DWORD new_tls_index = TlsAlloc(); + if (!tls_index_.CompareExchange(new_tls_index, TLS_OUT_OF_INDEXES)) + TlsFree(new_tls_index); + } + + assert(tls_index_.Value() != TLS_OUT_OF_INDEXES); + TlsSetValue(tls_index_.Value(), this); + + magnifier_initialized_ = true; + return true; +} + +void ScreenCapturerWinMagnifier::OnCaptured(void* data, + const MAGIMAGEHEADER& header) { + DesktopFrame* current_frame = queue_.current_frame(); + + // Verify the format. + // TODO(jiayl): support capturing sources with pixel formats other than RGBA. + int captured_bytes_per_pixel = header.cbSize / header.width / header.height; + if (header.format != GUID_WICPixelFormat32bppRGBA || + header.width != static_cast(current_frame->size().width()) || + header.height != static_cast(current_frame->size().height()) || + header.stride != static_cast(current_frame->stride()) || + captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) { + LOG_F(LS_WARNING) << "Output format does not match the captured format: " + << "width = " << header.width << ", " + << "height = " << header.height << ", " + << "stride = " << header.stride << ", " + << "bpp = " << captured_bytes_per_pixel << ", " + << "pixel format RGBA ? " + << (header.format == GUID_WICPixelFormat32bppRGBA) << "."; + return; + } + + // Copy the data into the frame. + current_frame->CopyPixelsFrom( + reinterpret_cast(data), + header.stride, + DesktopRect::MakeXYWH(0, 0, header.width, header.height)); + + magnifier_capture_succeeded_ = true; +} + +void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary( + const DesktopSize& size) { + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) { + size_t buffer_size = + size.width() * size.height() * DesktopFrame::kBytesPerPixel; + SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size); + + scoped_ptr buffer; + if (shared_memory) { + buffer.reset(new SharedMemoryDesktopFrame( + size, size.width() * DesktopFrame::kBytesPerPixel, shared_memory)); + } else { + buffer.reset(new BasicDesktopFrame(size)); + } + queue_.ReplaceCurrentFrame(buffer.release()); + } +} + +bool ScreenCapturerWinMagnifier::IsCapturingPrimaryScreenOnly() const { + if (current_screen_id_ != kFullDesktopScreenId) + return current_screen_id_ == 0; // the primary screen is always '0'. + + return GetSystemMetrics(SM_CMONITORS) == 1; +} + +void ScreenCapturerWinMagnifier::StartFallbackCapturer() { + assert(fallback_capturer_); + if (!fallback_capturer_started_) { + fallback_capturer_started_ = true; + + fallback_capturer_->Start(callback_); + fallback_capturer_->SelectScreen(current_screen_id_); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h new file mode 100644 index 000000000..b6d559083 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2014 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_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ + +#include +#include +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" +#include "webrtc/system_wrappers/interface/atomic32.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class DesktopFrame; +class DesktopRect; +class Differ; +class MouseShapeObserver; + +// Captures the screen using the Magnification API to support window exclusion. +// Each capturer must run on a dedicated thread because it uses thread local +// storage for redirecting the library callback. Also the thread must have a UI +// message loop to handle the window messages for the magnifier window. +class ScreenCapturerWinMagnifier : public ScreenCapturer { + public: + // |fallback_capturer| will be used to capture the screen if a non-primary + // screen is being captured, or the OS does not support Magnification API, or + // the magnifier capturer fails (e.g. in Windows8 Metro mode). + explicit ScreenCapturerWinMagnifier( + scoped_ptr fallback_capturer); + virtual ~ScreenCapturerWinMagnifier(); + + // Overridden from ScreenCapturer: + virtual void Start(Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + virtual bool GetScreenList(ScreenList* screens) OVERRIDE; + virtual bool SelectScreen(ScreenId id) OVERRIDE; + virtual void SetExcludedWindow(WindowId window) OVERRIDE; + + private: + typedef BOOL(WINAPI* MagImageScalingCallback)(HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty); + typedef BOOL(WINAPI* MagInitializeFunc)(void); + typedef BOOL(WINAPI* MagUninitializeFunc)(void); + typedef BOOL(WINAPI* MagSetWindowSourceFunc)(HWND hwnd, RECT rect); + typedef BOOL(WINAPI* MagSetWindowFilterListFunc)(HWND hwnd, + DWORD dwFilterMode, + int count, + HWND* pHWND); + typedef BOOL(WINAPI* MagSetImageScalingCallbackFunc)( + HWND hwnd, + MagImageScalingCallback callback); + + static BOOL WINAPI OnMagImageScalingCallback(HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty); + + // Captures the screen within |rect| in the desktop coordinates. Returns true + // if succeeded. + // It can only capture the primary screen for now. The magnification library + // crashes under some screen configurations (e.g. secondary screen on top of + // primary screen) if it tries to capture a non-primary screen. The caller + // must make sure not calling it on non-primary screens. + bool CaptureImage(const DesktopRect& rect); + + // Helper method for setting up the magnifier control. Returns true if + // succeeded. + bool InitializeMagnifier(); + + // Called by OnMagImageScalingCallback to output captured data. + void OnCaptured(void* data, const MAGIMAGEHEADER& header); + + // Makes sure the current frame exists and matches |size|. + void CreateCurrentFrameIfNecessary(const DesktopSize& size); + + // Returns true if we are capturing the primary screen only. + bool IsCapturingPrimaryScreenOnly() const; + + // Start the fallback capturer and select the screen. + void StartFallbackCapturer(); + + static Atomic32 tls_index_; + + scoped_ptr fallback_capturer_; + bool fallback_capturer_started_; + Callback* callback_; + ScreenId current_screen_id_; + std::wstring current_device_key_; + HWND excluded_window_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Class to calculate the difference between two screen bitmaps. + scoped_ptr differ_; + + // Used to suppress duplicate logging of SetThreadExecutionState errors. + bool set_thread_execution_state_failed_; + + ScopedThreadDesktop desktop_; + + // Used for getting the screen dpi. + HDC desktop_dc_; + + HMODULE mag_lib_handle_; + MagInitializeFunc mag_initialize_func_; + MagUninitializeFunc mag_uninitialize_func_; + MagSetWindowSourceFunc set_window_source_func_; + MagSetWindowFilterListFunc set_window_filter_list_func_; + MagSetImageScalingCallbackFunc set_image_scaling_callback_func_; + + // The hidden window hosting the magnifier control. + HWND host_window_; + // The magnifier control that captures the screen. + HWND magnifier_window_; + + // True if the magnifier control has been successfully initialized. + bool magnifier_initialized_; + + // True if the last OnMagImageScalingCallback was called and handled + // successfully. Reset at the beginning of each CaptureImage call. + bool magnifier_capture_succeeded_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinMagnifier); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ diff --git a/webrtc/modules/desktop_capture/win/window_capture_utils.cc b/webrtc/modules/desktop_capture/win/window_capture_utils.cc index c878a63cd..03e021954 100644 --- a/webrtc/modules/desktop_capture/win/window_capture_utils.cc +++ b/webrtc/modules/desktop_capture/win/window_capture_utils.cc @@ -21,6 +21,7 @@ GetCroppedWindowRect(HWND window, return false; } WINDOWPLACEMENT window_placement; + window_placement.length = sizeof(window_placement); if (!GetWindowPlacement(window, &window_placement)) { return false; } diff --git a/webrtc/modules/desktop_capture/window_capturer.h b/webrtc/modules/desktop_capture/window_capturer.h index 86b3d19bf..ad75c88d5 100644 --- a/webrtc/modules/desktop_capture/window_capturer.h +++ b/webrtc/modules/desktop_capture/window_capturer.h @@ -11,12 +11,12 @@ #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_ #define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_ -#include #include +#include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/desktop_capture/desktop_capture_types.h" #include "webrtc/modules/desktop_capture/desktop_capturer.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/window_capturer_mac.mm b/webrtc/modules/desktop_capture/window_capturer_mac.mm index 3ceae31ec..d177fc40c 100644 --- a/webrtc/modules/desktop_capture/window_capturer_mac.mm +++ b/webrtc/modules/desktop_capture/window_capturer_mac.mm @@ -42,6 +42,18 @@ bool CFStringRefToUtf8(const CFStringRef string, std::string* str_utf8) { return true; } +bool IsWindowValid(CGWindowID id) { + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast(&id), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + bool valid = window_array && CFArrayGetCount(window_array); + CFRelease(window_id_array); + CFRelease(window_array); + + return valid; +} + class WindowCapturerMac : public WindowCapturer { public: WindowCapturerMac(); @@ -115,22 +127,8 @@ bool CFStringRefToUtf8(const CFStringRef string, std::string* str_utf8) { } bool WindowCapturerMac::SelectWindow(WindowId id) { - // Request description for the specified window to make sure |id| is valid. - CGWindowID ids[1]; - ids[0] = id; - CFArrayRef window_id_array = - CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); - CFArrayRef window_array = - CGWindowListCreateDescriptionFromArray(window_id_array); - int results_count = window_array ? CFArrayGetCount(window_array) : 0; - CFRelease(window_id_array); - CFRelease(window_array); - - if (results_count == 0) { - // Could not find the window. It might have been closed. + if (!IsWindowValid(id)) return false; - } - window_id_ = id; return true; } @@ -180,6 +178,11 @@ bool CFStringRefToUtf8(const CFStringRef string, std::string* str_utf8) { } void WindowCapturerMac::Capture(const DesktopRegion& region) { + if (!IsWindowValid(window_id_)) { + callback_->OnCaptureCompleted(NULL); + return; + } + CGImageRef window_image = CGWindowListCreateImage( CGRectNull, kCGWindowListOptionIncludingWindow, window_id_, kCGWindowImageBoundsIgnoreFraming); diff --git a/webrtc/modules/desktop_capture/window_capturer_win.cc b/webrtc/modules/desktop_capture/window_capturer_win.cc index b9764b3a4..a00218562 100644 --- a/webrtc/modules/desktop_capture/window_capturer_win.cc +++ b/webrtc/modules/desktop_capture/window_capturer_win.cc @@ -182,8 +182,8 @@ void WindowCapturerWin::Capture(const DesktopRegion& region) { return; } - // Stop capturing if the window has been minimized or hidden. - if (IsIconic(window_) || !IsWindowVisible(window_)) { + // Stop capturing if the window has been closed or hidden. + if (!IsWindow(window_) || !IsWindowVisible(window_)) { callback_->OnCaptureCompleted(NULL); return; } diff --git a/webrtc/modules/desktop_capture/window_capturer_x11.cc b/webrtc/modules/desktop_capture/window_capturer_x11.cc index baeb894b3..b641c9321 100755 --- a/webrtc/modules/desktop_capture/window_capturer_x11.cc +++ b/webrtc/modules/desktop_capture/window_capturer_x11.cc @@ -278,6 +278,12 @@ void WindowCapturerLinux::Start(Callback* callback) { } void WindowCapturerLinux::Capture(const DesktopRegion& region) { + if (!x_server_pixel_buffer_.IsWindowValid()) { + LOG(LS_INFO) << "The window is no longer valid."; + callback_->OnCaptureCompleted(NULL); + return; + } + x_display_->ProcessPendingXEvents(); if (!has_composite_extension_) { diff --git a/webrtc/modules/desktop_capture/x11/x_error_trap.h b/webrtc/modules/desktop_capture/x11/x_error_trap.h index fd8346928..aa771145d 100644 --- a/webrtc/modules/desktop_capture/x11/x_error_trap.h +++ b/webrtc/modules/desktop_capture/x11/x_error_trap.h @@ -13,7 +13,7 @@ #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" namespace webrtc { diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc index 6983a6dcc..be00fa769 100644 --- a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc @@ -213,6 +213,18 @@ bool XServerPixelBuffer::InitPixmaps(int depth) { return true; } +bool XServerPixelBuffer::IsWindowValid() const { + XWindowAttributes attributes; + { + XErrorTrap error_trap(display_); + if (!XGetWindowAttributes(display_, window_, &attributes) || + error_trap.GetLastErrorAndDisable() != 0) { + return false; + } + } + return true; +} + void XServerPixelBuffer::Synchronize() { if (shm_segment_info_ && !shm_pixmap_) { // XShmGetImage can fail if the display is being reconfigured. diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h index b81096c81..98f263f3a 100644 --- a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h @@ -40,6 +40,9 @@ class XServerPixelBuffer { // Returns the size of the window the buffer was initialized for. const DesktopSize& window_size() { return window_size_; } + // Returns true if the window can be found. + bool IsWindowValid() const; + // If shared memory is being used without pixmaps, synchronize this pixel // buffer with the root window contents (otherwise, this is a no-op). // This is to avoid doing a full-screen capture for each individual diff --git a/webrtc/modules/interface/module_common_types.h b/webrtc/modules/interface/module_common_types.h index 6c7ac10c1..2c9470710 100644 --- a/webrtc/modules/interface/module_common_types.h +++ b/webrtc/modules/interface/module_common_types.h @@ -16,8 +16,8 @@ #include +#include "webrtc/base/constructormagic.h" #include "webrtc/common_types.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" #ifdef _WIN32 @@ -103,6 +103,8 @@ struct WebRtcRTPHeader { RTPHeader header; FrameType frameType; RTPTypeHeader type; + // NTP time of the capture time in local timebase in milliseconds. + int64_t ntp_time_ms; }; class RTPFragmentationHeader { @@ -665,6 +667,10 @@ class AudioFrame { AudioFrame(); virtual ~AudioFrame() {} + // Resets all members to their default state (except does not modify the + // contents of |data_|). + void Reset(); + // |interleaved_| is not changed by this method. void UpdateFrame(int id, uint32_t timestamp, const int16_t* data, int samples_per_channel, int sample_rate_hz, @@ -682,13 +688,24 @@ class AudioFrame { AudioFrame& operator-=(const AudioFrame& rhs); int id_; + // RTP timestamp of the first sample in the AudioFrame. uint32_t timestamp_; + // Time since the first frame in milliseconds. + // -1 represents an uninitialized value. + int64_t elapsed_time_ms_; + // NTP time of the estimated capture time in local timebase in milliseconds. + // -1 represents an uninitialized value. + int64_t ntp_time_ms_; int16_t data_[kMaxDataSizeSamples]; int samples_per_channel_; int sample_rate_hz_; int num_channels_; SpeechType speech_type_; VADActivity vad_activity_; + // Note that there is no guarantee that |energy_| is correct. Any user of this + // member must verify that the value is correct. + // TODO(henrike) Remove |energy_|. + // See https://code.google.com/p/webrtc/issues/detail?id=3315. uint32_t energy_; bool interleaved_; @@ -697,16 +714,25 @@ class AudioFrame { }; inline AudioFrame::AudioFrame() - : id_(-1), - timestamp_(0), - data_(), - samples_per_channel_(0), - sample_rate_hz_(0), - num_channels_(1), - speech_type_(kUndefined), - vad_activity_(kVadUnknown), - energy_(0xffffffff), - interleaved_(true) {} + : data_() { + Reset(); +} + +inline void AudioFrame::Reset() { + id_ = -1; + // TODO(wu): Zero is a valid value for |timestamp_|. We should initialize + // to an invalid value, or add a new member to indicate invalidity. + timestamp_ = 0; + elapsed_time_ms_ = -1; + ntp_time_ms_ = -1; + samples_per_channel_ = 0; + sample_rate_hz_ = 0; + num_channels_ = 0; + speech_type_ = kUndefined; + vad_activity_ = kVadUnknown; + energy_ = 0xffffffff; + interleaved_ = true; +} inline void AudioFrame::UpdateFrame(int id, uint32_t timestamp, const int16_t* data, @@ -737,6 +763,8 @@ inline void AudioFrame::CopyFrom(const AudioFrame& src) { id_ = src.id_; timestamp_ = src.timestamp_; + elapsed_time_ms_ = src.elapsed_time_ms_; + ntp_time_ms_ = src.ntp_time_ms_; samples_per_channel_ = src.samples_per_channel_; sample_rate_hz_ = src.sample_rate_hz_; speech_type_ = src.speech_type_; diff --git a/webrtc/modules/media_file/BUILD.gn b/webrtc/modules/media_file/BUILD.gn new file mode 100644 index 000000000..971a6fa29 --- /dev/null +++ b/webrtc/modules/media_file/BUILD.gn @@ -0,0 +1,41 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +config("internal_config") { + visibility = ":*" # Only targets in this file can depend on this. + include_dirs = [ + "interface", + "../interface", + ] +} + +source_set("media_file") { + sources = [ + "interface/media_file.h", + "interface/media_file_defines.h", + "source/avi_file.cc", + "source/avi_file.h", + "source/media_file_impl.cc", + "source/media_file_impl.h", + "source/media_file_utility.cc", + "source/media_file_utility.h", + ] + + if (is_win) { + cflags = [ + # TODO(jschuh): Bug 1348: fix this warning. + "/wd4267", # size_t to int truncations + ] + } + + direct_dependent_configs = [ ":internal_config" ] + + deps = [ "../../system_wrappers" ] +} diff --git a/webrtc/modules/media_file/OWNERS b/webrtc/modules/media_file/OWNERS index 2cc47e481..3387ee9e6 100644 --- a/webrtc/modules/media_file/OWNERS +++ b/webrtc/modules/media_file/OWNERS @@ -1,4 +1,6 @@ pwestin@webrtc.org mflodman@webrtc.org perkj@webrtc.org -niklas.enbom@webrtc.org \ No newline at end of file +niklas.enbom@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/media_file/source/OWNERS b/webrtc/modules/media_file/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/media_file/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/media_file/source/media_file_utility.cc b/webrtc/modules/media_file/source/media_file_utility.cc index 85df0b3a4..e8883c9a4 100644 --- a/webrtc/modules/media_file/source/media_file_utility.cc +++ b/webrtc/modules/media_file/source/media_file_utility.cc @@ -2521,6 +2521,7 @@ int32_t ModuleFileUtility::FileDurationMs(const char* fileName, break; } #endif + break; } case kFileFormatPreencodedFile: { diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp index 096c04e1e..8dec125b0 100644 --- a/webrtc/modules/modules.gyp +++ b/webrtc/modules/modules.gyp @@ -16,9 +16,8 @@ 'audio_coding/codecs/isac/main/source/isac.gypi', 'audio_coding/codecs/isac/fix/source/isacfix.gypi', 'audio_coding/codecs/pcm16b/pcm16b.gypi', - 'audio_coding/main/source/audio_coding_module.gypi', + 'audio_coding/main/acm2/audio_coding_module.gypi', 'audio_coding/neteq/neteq.gypi', - 'audio_coding/neteq4/neteq.gypi', 'audio_conference_mixer/source/audio_conference_mixer.gypi', 'audio_device/audio_device.gypi', 'audio_processing/audio_processing.gypi', @@ -76,9 +75,8 @@ 'desktop_capture', 'iSACFix', 'media_file', - 'NetEq', - 'NetEq4', - 'NetEq4TestTools', + 'neteq', + 'neteq_test_tools', 'neteq_unittest_tools', 'paced_sender', 'PCM16B', # Needed by NetEq tests. @@ -103,10 +101,10 @@ ], 'sources': [ 'audio_coding/main/acm2/acm_receiver_unittest.cc', + 'audio_coding/main/acm2/audio_coding_module_unittest.cc', 'audio_coding/main/acm2/call_statistics_unittest.cc', 'audio_coding/main/acm2/initial_delay_manager_unittest.cc', 'audio_coding/main/acm2/nack_unittest.cc', - 'audio_coding/main/source/acm_neteq_unittest.cc', 'audio_coding/codecs/cng/cng_unittest.cc', 'audio_coding/codecs/isac/fix/source/filters_unittest.cc', 'audio_coding/codecs/isac/fix/source/filterbanks_unittest.cc', @@ -114,44 +112,45 @@ 'audio_coding/codecs/isac/fix/source/transform_unittest.cc', 'audio_coding/codecs/isac/main/source/isac_unittest.cc', 'audio_coding/codecs/opus/opus_unittest.cc', - 'audio_coding/neteq4/audio_classifier_unittest.cc', - 'audio_coding/neteq4/audio_multi_vector_unittest.cc', - 'audio_coding/neteq4/audio_vector_unittest.cc', - 'audio_coding/neteq4/background_noise_unittest.cc', - 'audio_coding/neteq4/buffer_level_filter_unittest.cc', - 'audio_coding/neteq4/comfort_noise_unittest.cc', - 'audio_coding/neteq4/decision_logic_unittest.cc', - 'audio_coding/neteq4/decoder_database_unittest.cc', - 'audio_coding/neteq4/delay_manager_unittest.cc', - 'audio_coding/neteq4/delay_peak_detector_unittest.cc', - 'audio_coding/neteq4/dsp_helper_unittest.cc', - 'audio_coding/neteq4/dtmf_buffer_unittest.cc', - 'audio_coding/neteq4/dtmf_tone_generator_unittest.cc', - 'audio_coding/neteq4/expand_unittest.cc', - 'audio_coding/neteq4/merge_unittest.cc', - 'audio_coding/neteq4/neteq_external_decoder_unittest.cc', - 'audio_coding/neteq4/neteq_impl_unittest.cc', - 'audio_coding/neteq4/neteq_stereo_unittest.cc', - 'audio_coding/neteq4/neteq_unittest.cc', - 'audio_coding/neteq4/normal_unittest.cc', - 'audio_coding/neteq4/packet_buffer_unittest.cc', - 'audio_coding/neteq4/payload_splitter_unittest.cc', - 'audio_coding/neteq4/post_decode_vad_unittest.cc', - 'audio_coding/neteq4/random_vector_unittest.cc', - 'audio_coding/neteq4/sync_buffer_unittest.cc', - 'audio_coding/neteq4/timestamp_scaler_unittest.cc', - 'audio_coding/neteq4/time_stretch_unittest.cc', - 'audio_coding/neteq4/mock/mock_audio_decoder.h', - 'audio_coding/neteq4/mock/mock_audio_vector.h', - 'audio_coding/neteq4/mock/mock_buffer_level_filter.h', - 'audio_coding/neteq4/mock/mock_decoder_database.h', - 'audio_coding/neteq4/mock/mock_delay_manager.h', - 'audio_coding/neteq4/mock/mock_delay_peak_detector.h', - 'audio_coding/neteq4/mock/mock_dtmf_buffer.h', - 'audio_coding/neteq4/mock/mock_dtmf_tone_generator.h', - 'audio_coding/neteq4/mock/mock_external_decoder_pcm16b.h', - 'audio_coding/neteq4/mock/mock_packet_buffer.h', - 'audio_coding/neteq4/mock/mock_payload_splitter.h', + 'audio_coding/neteq/audio_classifier_unittest.cc', + 'audio_coding/neteq/audio_multi_vector_unittest.cc', + 'audio_coding/neteq/audio_vector_unittest.cc', + 'audio_coding/neteq/background_noise_unittest.cc', + 'audio_coding/neteq/buffer_level_filter_unittest.cc', + 'audio_coding/neteq/comfort_noise_unittest.cc', + 'audio_coding/neteq/decision_logic_unittest.cc', + 'audio_coding/neteq/decoder_database_unittest.cc', + 'audio_coding/neteq/delay_manager_unittest.cc', + 'audio_coding/neteq/delay_peak_detector_unittest.cc', + 'audio_coding/neteq/dsp_helper_unittest.cc', + 'audio_coding/neteq/dtmf_buffer_unittest.cc', + 'audio_coding/neteq/dtmf_tone_generator_unittest.cc', + 'audio_coding/neteq/expand_unittest.cc', + 'audio_coding/neteq/merge_unittest.cc', + 'audio_coding/neteq/neteq_external_decoder_unittest.cc', + 'audio_coding/neteq/neteq_impl_unittest.cc', + 'audio_coding/neteq/neteq_stereo_unittest.cc', + 'audio_coding/neteq/neteq_unittest.cc', + 'audio_coding/neteq/normal_unittest.cc', + 'audio_coding/neteq/packet_buffer_unittest.cc', + 'audio_coding/neteq/payload_splitter_unittest.cc', + 'audio_coding/neteq/post_decode_vad_unittest.cc', + 'audio_coding/neteq/random_vector_unittest.cc', + 'audio_coding/neteq/sync_buffer_unittest.cc', + 'audio_coding/neteq/timestamp_scaler_unittest.cc', + 'audio_coding/neteq/time_stretch_unittest.cc', + 'audio_coding/neteq/mock/mock_audio_decoder.h', + 'audio_coding/neteq/mock/mock_audio_vector.h', + 'audio_coding/neteq/mock/mock_buffer_level_filter.h', + 'audio_coding/neteq/mock/mock_decoder_database.h', + 'audio_coding/neteq/mock/mock_delay_manager.h', + 'audio_coding/neteq/mock/mock_delay_peak_detector.h', + 'audio_coding/neteq/mock/mock_dtmf_buffer.h', + 'audio_coding/neteq/mock/mock_dtmf_tone_generator.h', + 'audio_coding/neteq/mock/mock_external_decoder_pcm16b.h', + 'audio_coding/neteq/mock/mock_packet_buffer.h', + 'audio_coding/neteq/mock/mock_payload_splitter.h', + 'audio_coding/neteq/tools/packet_unittest.cc', 'audio_processing/aec/system_delay_unittest.cc', 'audio_processing/aec/echo_cancellation_unittest.cc', 'audio_processing/echo_cancellation_impl_unittest.cc', @@ -181,7 +180,6 @@ 'remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.cc', 'remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h', 'remote_bitrate_estimator/remote_bitrate_estimators_test.cc', - 'remote_bitrate_estimator/rtp_to_ntp_unittest.cc', 'remote_bitrate_estimator/test/bwe_test_baselinefile.cc', 'remote_bitrate_estimator/test/bwe_test_baselinefile.h', 'remote_bitrate_estimator/test/bwe_test_fileutils.cc', @@ -201,6 +199,7 @@ 'rtp_rtcp/source/nack_rtx_unittest.cc', 'rtp_rtcp/source/producer_fec_unittest.cc', 'rtp_rtcp/source/receive_statistics_unittest.cc', + 'rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc', 'rtp_rtcp/source/rtcp_format_remb_unittest.cc', 'rtp_rtcp/source/rtcp_packet_unittest.cc', 'rtp_rtcp/source/rtcp_receiver_unittest.cc', @@ -222,6 +221,7 @@ 'rtp_rtcp/test/testAPI/test_api_rtcp.cc', 'rtp_rtcp/test/testAPI/test_api_video.cc', 'utility/source/audio_frame_operations_unittest.cc', + 'utility/source/file_player_unittests.cc', 'video_coding/codecs/test/packet_manipulator_unittest.cc', 'video_coding/codecs/test/stats_unittest.cc', 'video_coding/codecs/test/videoprocessor_unittest.cc', @@ -297,7 +297,7 @@ }], # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], @@ -337,13 +337,14 @@ 'audio_coding/main/test/EncodeDecodeTest.cc', 'audio_coding/main/test/iSACTest.cc', 'audio_coding/main/test/opus_test.cc', + 'audio_coding/main/test/PacketLossTest.cc', 'audio_coding/main/test/PCMFile.cc', 'audio_coding/main/test/RTPFile.cc', 'audio_coding/main/test/SpatialAudio.cc', 'audio_coding/main/test/TestAllCodecs.cc', 'audio_coding/main/test/target_delay_unittest.cc', 'audio_coding/main/test/Tester.cc', - 'audio_coding/main/test/TestFEC.cc', + 'audio_coding/main/test/TestRedFec.cc', 'audio_coding/main/test/TestStereo.cc', 'audio_coding/main/test/TestVADDTX.cc', 'audio_coding/main/test/TimedTrace.cc', @@ -357,7 +358,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], @@ -368,7 +369,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'targets': [ { 'target_name': 'modules_unittests_apk_target', diff --git a/webrtc/modules/modules_tests.isolate b/webrtc/modules/modules_tests.isolate index 7a051f66e..e5055f0d9 100644 --- a/webrtc/modules/modules_tests.isolate +++ b/webrtc/modules/modules_tests.isolate @@ -8,32 +8,30 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../data/', - '../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/modules_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../DEPS', - '../../resources/audio_coding/testfile32kHz.pcm', - '../../resources/audio_coding/teststereo32kHz.pcm', - '../../resources/foreman_cif.yuv', - '../../resources/paris_qcif.yuv', - '../../testing/test_env.py', + '<(DEPTH)/DEPS', + '<(DEPTH)/resources/audio_coding/testfile32kHz.pcm', + '<(DEPTH)/resources/audio_coding/teststereo32kHz.pcm', + '<(DEPTH)/resources/foreman_cif.yuv', + '<(DEPTH)/resources/paris_qcif.yuv', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/modules_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/modules/modules_unittests.isolate b/webrtc/modules/modules_unittests.isolate index e4139ba64..09ace1c18 100644 --- a/webrtc/modules/modules_unittests.isolate +++ b/webrtc/modules/modules_unittests.isolate @@ -8,99 +8,105 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../data/', - '../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], 'isolate_dependency_tracked': [ - '../../../resources/short_mixed_mono_48.dat', - '../../../resources/short_mixed_mono_48.pcm', - '../../../resources/short_mixed_stereo_48.dat', - '../../../resources/short_mixed_stereo_48.pcm', + '<(DEPTH)/resources/short_mixed_mono_48.dat', + '<(DEPTH)/resources/short_mixed_mono_48.pcm', + '<(DEPTH)/resources/short_mixed_stereo_48.dat', + '<(DEPTH)/resources/short_mixed_stereo_48.pcm', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/modules_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../DEPS', - '../../data/audio_processing/output_data_float.pb', - '../../data/voice_engine/audio_tiny48.wav', - '../../resources/att-downlink.rx', - '../../resources/att-uplink.rx', - '../../resources/audio_coding/neteq4_network_stats.dat', - '../../resources/audio_coding/neteq4_rtcp_stats.dat', - '../../resources/audio_coding/neteq4_universal_ref.pcm', - '../../resources/audio_coding/neteq_network_stats.dat', - '../../resources/audio_coding/neteq_rtcp_stats.dat', - '../../resources/audio_coding/neteq_universal_new.rtp', - '../../resources/audio_coding/neteq_universal_ref.pcm', - '../../resources/audio_coding/testfile32kHz.pcm', - '../../resources/deflicker_before_cif_short.yuv', - '../../resources/far16_stereo.pcm', - '../../resources/far32_stereo.pcm', - '../../resources/far8_stereo.pcm', - '../../resources/foremanColorEnhanced_cif_short.yuv', - '../../resources/foreman_cif.yuv', - '../../resources/foreman_cif_short.yuv', - '../../resources/near16_stereo.pcm', - '../../resources/near32_stereo.pcm', - '../../resources/near8_stereo.pcm', - '../../resources/ref03.aecdump', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_0_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_1_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_1_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_0_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_1_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_1_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingDelay1_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingDelay1_0_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingLoss1_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingLoss1_0_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_Multi1_1_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_Multi1_1_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_0_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_1_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_1_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyDelay_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyDelay_0_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyLoss_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyLoss_0_TOF.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_UnlimitedSpeed_0_AST.bin', - '../../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_UnlimitedSpeed_0_TOF.bin', - '../../resources/short_mixed_mono_48.dat', - '../../resources/short_mixed_mono_48.pcm', - '../../resources/short_mixed_stereo_48.dat', - '../../resources/short_mixed_stereo_48.pcm', - '../../resources/sprint-downlink.rx', - '../../resources/sprint-uplink.rx', - '../../resources/synthetic-trace.rx', - '../../resources/tmobile-downlink.rx', - '../../resources/tmobile-uplink.rx', - '../../resources/verizon3g-downlink.rx', - '../../resources/verizon3g-uplink.rx', - '../../resources/verizon4g-downlink.rx', - '../../resources/verizon4g-uplink.rx', - '../../resources/video_coding/frame-ethernet-ii.pcap', - '../../resources/video_coding/frame-loopback.pcap', - '../../resources/video_coding/pltype103.rtp', - '../../resources/video_coding/ssrcs-2.pcap', - '../../resources/video_coding/ssrcs-3.pcap', - '../../testing/test_env.py', + '<(DEPTH)/DEPS', + '<(DEPTH)/data/audio_processing/output_data_float.pb', + '<(DEPTH)/data/voice_engine/audio_tiny48.wav', + '<(DEPTH)/resources/att-downlink.rx', + '<(DEPTH)/resources/att-uplink.rx', + '<(DEPTH)/resources/audio_coding/neteq4_network_stats.dat', + '<(DEPTH)/resources/audio_coding/neteq4_rtcp_stats.dat', + '<(DEPTH)/resources/audio_coding/neteq4_universal_ref.pcm', + '<(DEPTH)/resources/audio_coding/neteq4_universal_ref_win_32.pcm', + '<(DEPTH)/resources/audio_coding/neteq4_universal_ref_win_64.pcm', + '<(DEPTH)/resources/audio_coding/neteq_network_stats.dat', + '<(DEPTH)/resources/audio_coding/neteq_rtcp_stats.dat', + '<(DEPTH)/resources/audio_coding/neteq_universal_new.rtp', + '<(DEPTH)/resources/audio_coding/neteq_universal_ref.pcm', + '<(DEPTH)/resources/audio_coding/testfile32kHz.pcm', + '<(DEPTH)/resources/deflicker_before_cif_short.yuv', + '<(DEPTH)/resources/far16_stereo.pcm', + '<(DEPTH)/resources/far32_stereo.pcm', + '<(DEPTH)/resources/far44_stereo.pcm', + '<(DEPTH)/resources/far48_stereo.pcm', + '<(DEPTH)/resources/far8_stereo.pcm', + '<(DEPTH)/resources/foremanColorEnhanced_cif_short.yuv', + '<(DEPTH)/resources/foreman_cif.yuv', + '<(DEPTH)/resources/foreman_cif_short.yuv', + '<(DEPTH)/resources/near16_stereo.pcm', + '<(DEPTH)/resources/near32_stereo.pcm', + '<(DEPTH)/resources/near44_stereo.pcm', + '<(DEPTH)/resources/near48_stereo.pcm', + '<(DEPTH)/resources/near8_stereo.pcm', + '<(DEPTH)/resources/ref03.aecdump', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_0_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_1_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_1_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_0_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_1_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke2_1_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingDelay1_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingDelay1_0_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingLoss1_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingLoss1_0_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_Multi1_1_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_Multi1_1_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_0_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_1_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyChoke_1_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyDelay_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyDelay_0_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyLoss_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_SteadyLoss_0_TOF.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_UnlimitedSpeed_0_AST.bin', + '<(DEPTH)/resources/remote_bitrate_estimator/VideoSendersTest_BweTest_UnlimitedSpeed_0_TOF.bin', + '<(DEPTH)/resources/short_mixed_mono_48.dat', + '<(DEPTH)/resources/short_mixed_mono_48.pcm', + '<(DEPTH)/resources/short_mixed_stereo_48.dat', + '<(DEPTH)/resources/short_mixed_stereo_48.pcm', + '<(DEPTH)/resources/sprint-downlink.rx', + '<(DEPTH)/resources/sprint-uplink.rx', + '<(DEPTH)/resources/synthetic-trace.rx', + '<(DEPTH)/resources/tmobile-downlink.rx', + '<(DEPTH)/resources/tmobile-uplink.rx', + '<(DEPTH)/resources/utility/encapsulated_pcm16b_8khz.wav', + '<(DEPTH)/resources/utility/encapsulated_pcmu_8khz.wav', + '<(DEPTH)/resources/verizon3g-downlink.rx', + '<(DEPTH)/resources/verizon3g-uplink.rx', + '<(DEPTH)/resources/verizon4g-downlink.rx', + '<(DEPTH)/resources/verizon4g-uplink.rx', + '<(DEPTH)/resources/video_coding/frame-ethernet-ii.pcap', + '<(DEPTH)/resources/video_coding/frame-loopback.pcap', + '<(DEPTH)/resources/video_coding/pltype103.rtp', + '<(DEPTH)/resources/video_coding/ssrcs-2.pcap', + '<(DEPTH)/resources/video_coding/ssrcs-3.pcap', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/modules_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/modules/pacing/BUILD.gn b/webrtc/modules/pacing/BUILD.gn new file mode 100644 index 000000000..d3eceb35b --- /dev/null +++ b/webrtc/modules/pacing/BUILD.gn @@ -0,0 +1,16 @@ +# Copyright (c) 2014 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. + +source_set("pacing") { + sources = [ + "include/paced_sender.h", + "paced_sender.cc", + ] + + deps = [ "../../system_wrappers" ] +} diff --git a/webrtc/modules/pacing/OWNERS b/webrtc/modules/pacing/OWNERS index 933a04500..4486a81e5 100644 --- a/webrtc/modules/pacing/OWNERS +++ b/webrtc/modules/pacing/OWNERS @@ -2,3 +2,10 @@ pwestin@webrtc.org stefan@webrtc.org mflodman@webrtc.org asapersson@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/pacing/paced_sender.cc b/webrtc/modules/pacing/paced_sender.cc index 125d3c0cd..e9f9bddce 100644 --- a/webrtc/modules/pacing/paced_sender.cc +++ b/webrtc/modules/pacing/paced_sender.cc @@ -124,7 +124,7 @@ PacedSender::PacedSender(Callback* callback, int max_bitrate_kbps, int min_bitrate_kbps) : callback_(callback), - enabled_(false), + enabled_(true), paused_(false), max_queue_length_ms_(kDefaultMaxQueueLengthMs), critsect_(CriticalSectionWrapper::CreateCriticalSection()), diff --git a/webrtc/modules/pacing/paced_sender_unittest.cc b/webrtc/modules/pacing/paced_sender_unittest.cc index 435c93c54..9763c8007 100644 --- a/webrtc/modules/pacing/paced_sender_unittest.cc +++ b/webrtc/modules/pacing/paced_sender_unittest.cc @@ -61,7 +61,6 @@ class PacedSenderTest : public ::testing::Test { // Need to initialize PacedSender after we initialize clock. send_bucket_.reset( new PacedSender(&callback_, kPaceMultiplier * kTargetBitrate, 0)); - send_bucket_->SetStatus(true); } void SendAndExpectPacket(PacedSender::Priority priority, @@ -276,7 +275,6 @@ TEST_F(PacedSenderTest, VerifyAverageBitrateVaryingMediaPayload) { PacedSenderPadding callback; send_bucket_.reset( new PacedSender(&callback, kPaceMultiplier * kTargetBitrate, 0)); - send_bucket_->SetStatus(true); send_bucket_->UpdateBitrate(kPaceMultiplier * kTargetBitrate, kTargetBitrate); int64_t start_time = TickTime::MillisecondTimestamp(); int media_bytes = 0; diff --git a/webrtc/modules/remote_bitrate_estimator/BUILD.gn b/webrtc/modules/remote_bitrate_estimator/BUILD.gn new file mode 100644 index 000000000..7ee4c8df9 --- /dev/null +++ b/webrtc/modules/remote_bitrate_estimator/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright (c) 2014 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. + +source_set("remote_bitrate_estimator") { + sources = [ + "overuse_detector.cc", + "overuse_detector.h", + "remote_bitrate_estimator_single_stream.cc", + "remote_rate_control.cc", + "remote_rate_control.h", + ] +} diff --git a/webrtc/modules/remote_bitrate_estimator/OWNERS b/webrtc/modules/remote_bitrate_estimator/OWNERS index b705ede2a..49d19429e 100644 --- a/webrtc/modules/remote_bitrate_estimator/OWNERS +++ b/webrtc/modules/remote_bitrate_estimator/OWNERS @@ -2,4 +2,11 @@ pwestin@webrtc.org stefan@webrtc.org henrik.lundin@webrtc.org mflodman@webrtc.org -asapersson@webrtc.org \ No newline at end of file +asapersson@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc index e4c67b595..6b208e499 100644 --- a/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc +++ b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc @@ -8,65 +8,63 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "gtest/gtest.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "webrtc/modules/remote_bitrate_estimator/test/bwe_test.h" #include "webrtc/test/testsupport/fileutils.h" +using std::string; + namespace webrtc { namespace testing { namespace bwe { #if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE -std::vector SingleEstimatorConfig() { +BweTestConfig::EstimatorConfig CreateEstimatorConfig( + int flow_id, bool plot_delay, bool plot_estimate) { static const AbsoluteSendTimeRemoteBitrateEstimatorFactory factory = AbsoluteSendTimeRemoteBitrateEstimatorFactory(); - std::vector result; - result.push_back(BweTestConfig::EstimatorConfig("AST", &factory, kAimdControl, - false)); - return result; + return BweTestConfig::EstimatorConfig("AST", flow_id, &factory, kAimdControl, + plot_delay, plot_estimate); } -std::vector AdaptiveVideoSenderFactories( - uint32_t count) { - static const AdaptiveVideoPacketSenderFactory factories[] = { - AdaptiveVideoPacketSenderFactory(30.00f, 150, 0x1234, 0.13f), - AdaptiveVideoPacketSenderFactory(30.00f, 300, 0x3456, 0.26f), - AdaptiveVideoPacketSenderFactory(15.00f, 600, 0x4567, 0.39f), - }; - - assert(count <= sizeof(factories) / sizeof(factories[0])); - - std::vector result; - for (uint32_t i = 0; i < count; ++i) { - result.push_back(&factories[i]); - } +BweTestConfig MakeAdaptiveBweTestConfig() { + BweTestConfig result; + result.estimator_configs.push_back(CreateEstimatorConfig(0, true, true)); return result; } -BweTestConfig MakeAdaptiveBweTestConfig(uint32_t sender_count) { - BweTestConfig result = { - AdaptiveVideoSenderFactories(sender_count), SingleEstimatorConfig() - }; +BweTestConfig MakeMultiFlowBweTestConfig(int flow_count) { + BweTestConfig result; + for (int i = 0; i < flow_count; ++i) { + result.estimator_configs.push_back(CreateEstimatorConfig(i, false, true)); + } return result; } // This test fixture is used to instantiate tests running with adaptive video // senders. -class BweSimulation : public BweTest { +class BweSimulation : public BweTest, + public ::testing::TestWithParam { public: BweSimulation() : BweTest() {} virtual ~BweSimulation() {} + virtual void SetUp() { + const BweTestConfig& config = GetParam(); + SetupTestFromConfig(config); + } + private: DISALLOW_COPY_AND_ASSIGN(BweSimulation); }; INSTANTIATE_TEST_CASE_P(VideoSendersTest, BweSimulation, - ::testing::Values(MakeAdaptiveBweTestConfig(1), - MakeAdaptiveBweTestConfig(3))); + ::testing::Values(MakeAdaptiveBweTestConfig())); TEST_P(BweSimulation, SprintUplinkTest) { VerboseLogging(true); + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); RateCounterFilter counter1(this, "sender_output"); TraceBasedDeliveryFilter filter(this, "link_capacity"); RateCounterFilter counter2(this, "receiver_input"); @@ -76,6 +74,7 @@ TEST_P(BweSimulation, SprintUplinkTest) { TEST_P(BweSimulation, Verizon4gDownlinkTest) { VerboseLogging(true); + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); RateCounterFilter counter1(this, "sender_output"); TraceBasedDeliveryFilter filter(this, "link_capacity"); RateCounterFilter counter2(this, "receiver_input"); @@ -85,6 +84,7 @@ TEST_P(BweSimulation, Verizon4gDownlinkTest) { TEST_P(BweSimulation, Choke1000kbps500kbps1000kbps) { VerboseLogging(true); + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); ChokeFilter filter(this); RateCounterFilter counter(this, "receiver_input"); filter.SetCapacity(1000); @@ -98,6 +98,7 @@ TEST_P(BweSimulation, Choke1000kbps500kbps1000kbps) { TEST_P(BweSimulation, Choke200kbps30kbps200kbps) { VerboseLogging(true); + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); ChokeFilter filter(this); RateCounterFilter counter(this, "receiver_input"); filter.SetCapacity(200); @@ -111,6 +112,7 @@ TEST_P(BweSimulation, Choke200kbps30kbps200kbps) { TEST_P(BweSimulation, GoogleWifiTrace3Mbps) { VerboseLogging(true); + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); RateCounterFilter counter1(this, "sender_output"); TraceBasedDeliveryFilter filter(this, "link_capacity"); filter.SetMaxDelay(500); @@ -118,6 +120,42 @@ TEST_P(BweSimulation, GoogleWifiTrace3Mbps) { ASSERT_TRUE(filter.Init(test::ResourcePath("google-wifi-3mbps", "rx"))); RunFor(300 * 1000); } + +class MultiFlowBweSimulation : public BweSimulation { + public: + MultiFlowBweSimulation() : BweSimulation() {} + virtual ~MultiFlowBweSimulation() {} + + private: + DISALLOW_COPY_AND_ASSIGN(MultiFlowBweSimulation); +}; + +INSTANTIATE_TEST_CASE_P(VideoSendersTest, MultiFlowBweSimulation, + ::testing::Values(MakeMultiFlowBweTestConfig(3))); + +TEST_P(MultiFlowBweSimulation, SelfFairnessTest) { + VerboseLogging(true); + const int kAllFlowIds[] = {0, 1, 2}; + const size_t kNumFlows = sizeof(kAllFlowIds) / sizeof(kAllFlowIds[0]); + scoped_ptr senders[kNumFlows]; + for (size_t i = 0; i < kNumFlows; ++i) { + senders[i].reset(new AdaptiveVideoSender(kAllFlowIds[i], this, 30, 300, 0, + 0)); + } + // Second and third flow. + ChokeFilter choke(this, CreateFlowIds(&kAllFlowIds[1], 2)); + choke.SetCapacity(1500); + // First flow. + ChokeFilter choke2(this, CreateFlowIds(&kAllFlowIds[0], 1)); + choke2.SetCapacity(1000); + + scoped_ptr rate_counters[kNumFlows]; + for (size_t i = 0; i < kNumFlows; ++i) { + rate_counters[i].reset(new RateCounterFilter( + this, CreateFlowIds(&kAllFlowIds[i], 1), "receiver_input")); + } + RunFor(30 * 60 * 1000); +} #endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE } // namespace bwe } // namespace testing diff --git a/webrtc/modules/remote_bitrate_estimator/rate_statistics.cc b/webrtc/modules/remote_bitrate_estimator/rate_statistics.cc index 4a9b44881..48485ffb5 100644 --- a/webrtc/modules/remote_bitrate_estimator/rate_statistics.cc +++ b/webrtc/modules/remote_bitrate_estimator/rate_statistics.cc @@ -10,6 +10,8 @@ #include "webrtc/modules/remote_bitrate_estimator/rate_statistics.h" +#include + namespace webrtc { RateStatistics::RateStatistics(uint32_t window_size_ms, float scale) diff --git a/webrtc/modules/remote_bitrate_estimator/rate_statistics.h b/webrtc/modules/remote_bitrate_estimator/rate_statistics.h index 429669059..f97371bd6 100644 --- a/webrtc/modules/remote_bitrate_estimator/rate_statistics.h +++ b/webrtc/modules/remote_bitrate_estimator/rate_statistics.h @@ -34,7 +34,7 @@ class RateStatistics { // Counters are kept in buckets (circular buffer), with one bucket // per millisecond. const int num_buckets_; - scoped_array buckets_; + scoped_ptr buckets_; // Total count recorded in buckets. uint32_t accumulated_count_; diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi index 810da4622..c2f1b3da4 100644 --- a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi +++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi @@ -21,10 +21,8 @@ 'sources': [ 'include/bwe_defines.h', 'include/remote_bitrate_estimator.h', - 'include/rtp_to_ntp.h', 'rate_statistics.cc', 'rate_statistics.h', - 'rtp_to_ntp.cc', ], # source }, { @@ -47,6 +45,7 @@ ], 'dependencies': [ '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', 'bwe_tools_util', 'rtp_rtcp', ], @@ -69,6 +68,7 @@ ], 'dependencies': [ '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', 'bwe_tools_util', 'rtp_rtcp', ], diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc index 581d4681d..577912eb1 100644 --- a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc +++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc @@ -236,7 +236,7 @@ RemoteBitrateEstimator* RemoteBitrateEstimatorFactory::Create( Clock* clock, RateControlType control_type, uint32_t min_bitrate_bps) const { - LOG(LS_INFO) << "RemoteBitrateEstimatorSingleStream: Instantiating."; + LOG(LS_INFO) << "RemoteBitrateEstimatorFactory: Instantiating."; return new RemoteBitrateEstimatorSingleStream(observer, clock, min_bitrate_bps); } diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc index 795089de2..f67c7f34f 100644 --- a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc +++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc @@ -10,8 +10,8 @@ #include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" namespace webrtc { diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h index 5071deb70..14cfc31ce 100644 --- a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h +++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h @@ -16,9 +16,9 @@ #include #include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc index e4fb4fa90..67b608481 100644 --- a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc +++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc @@ -8,72 +8,95 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include + #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "webrtc/modules/remote_bitrate_estimator/test/bwe_test.h" #include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/testsupport/perf_test.h" + +using std::string; namespace webrtc { namespace testing { namespace bwe { -std::vector VideoSenderFactories(uint32_t count) { - static const VideoPacketSenderFactory factories[] = { - VideoPacketSenderFactory(30.00f, 150, 0x1234, 0.13f), - VideoPacketSenderFactory(15.00f, 500, 0x2345, 0.16f), - VideoPacketSenderFactory(30.00f, 1200, 0x3456, 0.26f), - VideoPacketSenderFactory(7.49f, 150, 0x4567, 0.05f), - VideoPacketSenderFactory(7.50f, 150, 0x5678, 0.15f), - VideoPacketSenderFactory(7.51f, 150, 0x6789, 0.25f), - VideoPacketSenderFactory(15.02f, 150, 0x7890, 0.27f), - VideoPacketSenderFactory(15.03f, 150, 0x8901, 0.38f), - VideoPacketSenderFactory(30.02f, 150, 0x9012, 0.39f), - VideoPacketSenderFactory(30.03f, 150, 0x0123, 0.52f) - }; - - assert(count <= sizeof(factories) / sizeof(factories[0])); - - std::vector result; - for (uint32_t i = 0; i < count; ++i) { - result.push_back(&factories[i]); - } +enum Estimator { kAbsSendTime, kTransmissionOffset }; - return result; -} - -std::vector EstimatorConfigs() { +BweTestConfig::EstimatorConfig EstimatorConfigs(Estimator estimator, + int flow_id) { static const RemoteBitrateEstimatorFactory factories[] = { RemoteBitrateEstimatorFactory(), AbsoluteSendTimeRemoteBitrateEstimatorFactory() }; - - std::vector result; - result.push_back(BweTestConfig::EstimatorConfig("TOF", &factories[0])); - result.push_back(BweTestConfig::EstimatorConfig("AST", &factories[1])); - return result; + switch (estimator) { + case kTransmissionOffset: + return BweTestConfig::EstimatorConfig("TOF", flow_id, &factories[0], + kMimdControl, false, false); + case kAbsSendTime: + return BweTestConfig::EstimatorConfig("AST", flow_id, &factories[1], + kMimdControl, false, false); + } + assert(false); + return BweTestConfig::EstimatorConfig(); } -BweTestConfig MakeBweTestConfig(uint32_t sender_count) { - BweTestConfig result = { - VideoSenderFactories(sender_count), EstimatorConfigs() - }; +struct DefaultBweTestConfig { + BweTestConfig bwe_test_config; + size_t number_of_senders; +}; + +DefaultBweTestConfig MakeBweTestConfig(uint32_t sender_count, + Estimator estimator) { + DefaultBweTestConfig result; + result.bwe_test_config.estimator_configs.push_back( + EstimatorConfigs(estimator, 0)); + result.number_of_senders = sender_count; return result; } -INSTANTIATE_TEST_CASE_P(VideoSendersTest, BweTest, - ::testing::Values(MakeBweTestConfig(1), - MakeBweTestConfig(3))); +class DefaultBweTest : public BweTest, + public ::testing::TestWithParam { + public: + DefaultBweTest() : packet_senders_() {} + virtual ~DefaultBweTest() {} + + virtual void SetUp() { + const DefaultBweTestConfig& config = GetParam(); + SetupTestFromConfig(config.bwe_test_config); + for (size_t i = 0; i < config.number_of_senders; ++i) { + packet_senders_.push_back(new VideoSender(0, this, 30, 300, 0, 0)); + } + } + + virtual void TearDown() { + while (!packet_senders_.empty()) { + delete packet_senders_.front(); + packet_senders_.pop_front(); + } + } + + protected: + std::list packet_senders_; +}; -TEST_P(BweTest, UnlimitedSpeed) { +INSTANTIATE_TEST_CASE_P(VideoSendersTest, DefaultBweTest, + ::testing::Values(MakeBweTestConfig(1, kAbsSendTime), + MakeBweTestConfig(3, kAbsSendTime), + MakeBweTestConfig(1, kTransmissionOffset), + MakeBweTestConfig(3, kTransmissionOffset))); + +TEST_P(DefaultBweTest, UnlimitedSpeed) { VerboseLogging(false); RunFor(10 * 60 * 1000); } -TEST_P(BweTest, DISABLED_SteadyLoss) { +TEST_P(DefaultBweTest, DISABLED_SteadyLoss) { LossFilter loss(this); loss.SetLoss(20.0); RunFor(10 * 60 * 1000); } -TEST_P(BweTest, IncreasingLoss1) { +TEST_P(DefaultBweTest, IncreasingLoss1) { LossFilter loss(this); for (int i = 0; i < 76; ++i) { loss.SetLoss(i); @@ -81,13 +104,13 @@ TEST_P(BweTest, IncreasingLoss1) { } } -TEST_P(BweTest, SteadyDelay) { +TEST_P(DefaultBweTest, SteadyDelay) { DelayFilter delay(this); delay.SetDelay(1000); RunFor(10 * 60 * 1000); } -TEST_P(BweTest, DISABLED_IncreasingDelay1) { +TEST_P(DefaultBweTest, DISABLED_IncreasingDelay1) { DelayFilter delay(this); RunFor(10 * 60 * 1000); for (int i = 0; i < 30 * 2; ++i) { @@ -97,7 +120,7 @@ TEST_P(BweTest, DISABLED_IncreasingDelay1) { RunFor(10 * 60 * 1000); } -TEST_P(BweTest, IncreasingDelay2) { +TEST_P(DefaultBweTest, IncreasingDelay2) { DelayFilter delay(this); RateCounterFilter counter(this); RunFor(1 * 60 * 1000); @@ -109,7 +132,7 @@ TEST_P(BweTest, IncreasingDelay2) { RunFor(10 * 60 * 1000); } -TEST_P(BweTest, JumpyDelay1) { +TEST_P(DefaultBweTest, JumpyDelay1) { DelayFilter delay(this); RunFor(10 * 60 * 1000); for (int i = 1; i < 200; ++i) { @@ -122,14 +145,14 @@ TEST_P(BweTest, JumpyDelay1) { RunFor(10 * 60 * 1000); } -TEST_P(BweTest, SteadyJitter) { +TEST_P(DefaultBweTest, SteadyJitter) { JitterFilter jitter(this); RateCounterFilter counter(this); jitter.SetJitter(20); RunFor(2 * 60 * 1000); } -TEST_P(BweTest, IncreasingJitter1) { +TEST_P(DefaultBweTest, IncreasingJitter1) { JitterFilter jitter(this); for (int i = 0; i < 2 * 60 * 2; ++i) { jitter.SetJitter(i); @@ -138,7 +161,7 @@ TEST_P(BweTest, IncreasingJitter1) { RunFor(10 * 60 * 1000); } -TEST_P(BweTest, IncreasingJitter2) { +TEST_P(DefaultBweTest, IncreasingJitter2) { JitterFilter jitter(this); RunFor(30 * 1000); for (int i = 1; i < 51; ++i) { @@ -149,13 +172,13 @@ TEST_P(BweTest, IncreasingJitter2) { RunFor(10 * 60 * 1000); } -TEST_P(BweTest, SteadyReorder) { +TEST_P(DefaultBweTest, SteadyReorder) { ReorderFilter reorder(this); reorder.SetReorder(20.0); RunFor(10 * 60 * 1000); } -TEST_P(BweTest, IncreasingReorder1) { +TEST_P(DefaultBweTest, IncreasingReorder1) { ReorderFilter reorder(this); for (int i = 0; i < 76; ++i) { reorder.SetReorder(i); @@ -163,13 +186,13 @@ TEST_P(BweTest, IncreasingReorder1) { } } -TEST_P(BweTest, DISABLED_SteadyChoke) { +TEST_P(DefaultBweTest, DISABLED_SteadyChoke) { ChokeFilter choke(this); choke.SetCapacity(140); RunFor(10 * 60 * 1000); } -TEST_P(BweTest, DISABLED_IncreasingChoke1) { +TEST_P(DefaultBweTest, DISABLED_IncreasingChoke1) { ChokeFilter choke(this); for (int i = 1200; i >= 100; i -= 100) { choke.SetCapacity(i); @@ -177,7 +200,7 @@ TEST_P(BweTest, DISABLED_IncreasingChoke1) { } } -TEST_P(BweTest, DISABLED_IncreasingChoke2) { +TEST_P(DefaultBweTest, DISABLED_IncreasingChoke2) { ChokeFilter choke(this); RunFor(60 * 1000); for (int i = 1200; i >= 100; i -= 20) { @@ -186,7 +209,7 @@ TEST_P(BweTest, DISABLED_IncreasingChoke2) { } } -TEST_P(BweTest, DISABLED_Multi1) { +TEST_P(DefaultBweTest, DISABLED_Multi1) { DelayFilter delay(this); ChokeFilter choke(this); RateCounterFilter counter(this); @@ -201,7 +224,7 @@ TEST_P(BweTest, DISABLED_Multi1) { RunFor(5 * 60 * 1000); } -TEST_P(BweTest, Multi2) { +TEST_P(DefaultBweTest, Multi2) { ChokeFilter choke(this); JitterFilter jitter(this); RateCounterFilter counter(this); @@ -209,6 +232,108 @@ TEST_P(BweTest, Multi2) { jitter.SetJitter(120); RunFor(5 * 60 * 1000); } + +// This test fixture is used to instantiate tests running with adaptive video +// senders. +class BweFeedbackTest : public BweTest, + public ::testing::TestWithParam { + public: + BweFeedbackTest() : BweTest() {} + virtual ~BweFeedbackTest() {} + + virtual void SetUp() { + BweTestConfig config; + config.estimator_configs.push_back(EstimatorConfigs(kAbsSendTime, 0)); + SetupTestFromConfig(config); + } + + void PrintResults(double max_throughput_kbps, Stats throughput_kbps, + Stats delay_ms) { + double utilization = throughput_kbps.GetMean() / max_throughput_kbps; + webrtc::test::PrintResult("BwePerformance", + GetTestName(), + "Utilization", + utilization * 100.0, + "%", + false); + std::stringstream ss; + ss << throughput_kbps.GetStdDev() / throughput_kbps.GetMean(); + webrtc::test::PrintResult("BwePerformance", + GetTestName(), + "Utilization var coeff", + ss.str(), + "", + false); + webrtc::test::PrintResult("BwePerformance", + GetTestName(), + "Average delay", + delay_ms.AsString(), + "ms", + false); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BweFeedbackTest); +}; + +TEST_F(BweFeedbackTest, Choke1000kbps500kbps1000kbps) { + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); + ChokeFilter filter(this); + RateCounterFilter counter(this, "receiver_input"); + const int kHighCapacityKbps = 1000; + const int kLowCapacityKbps = 500; + filter.SetCapacity(kHighCapacityKbps); + filter.SetMaxDelay(500); + RunFor(60 * 1000); + filter.SetCapacity(kLowCapacityKbps); + RunFor(60 * 1000); + filter.SetCapacity(kHighCapacityKbps); + RunFor(60 * 1000); + PrintResults((2 * kHighCapacityKbps + kLowCapacityKbps) / 3.0, + counter.GetBitrateStats(), filter.GetDelayStats()); +} + +TEST_F(BweFeedbackTest, Choke200kbps30kbps200kbps) { + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); + ChokeFilter filter(this); + RateCounterFilter counter(this, "receiver_input"); + const int kHighCapacityKbps = 200; + const int kLowCapacityKbps = 30; + filter.SetCapacity(kHighCapacityKbps); + filter.SetMaxDelay(500); + RunFor(60 * 1000); + filter.SetCapacity(kLowCapacityKbps); + RunFor(60 * 1000); + filter.SetCapacity(kHighCapacityKbps); + RunFor(60 * 1000); + + PrintResults((2 * kHighCapacityKbps + kLowCapacityKbps) / 3.0, + counter.GetBitrateStats(), filter.GetDelayStats()); +} + +TEST_F(BweFeedbackTest, Verizon4gDownlinkTest) { + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); + RateCounterFilter counter1(this, "sender_output"); + TraceBasedDeliveryFilter filter(this, "link_capacity"); + RateCounterFilter counter2(this, "receiver_input"); + ASSERT_TRUE(filter.Init(test::ResourcePath("verizon4g-downlink", "rx"))); + RunFor(22 * 60 * 1000); + PrintResults(filter.GetBitrateStats().GetMean(), counter2.GetBitrateStats(), + filter.GetDelayStats()); +} + +// webrtc:3277 +TEST_F(BweFeedbackTest, DISABLED_GoogleWifiTrace3Mbps) { + AdaptiveVideoSender sender(0, this, 30, 300, 0, 0); + RateCounterFilter counter1(this, "sender_output"); + TraceBasedDeliveryFilter filter(this, "link_capacity"); + filter.SetMaxDelay(500); + RateCounterFilter counter2(this, "receiver_input"); + ASSERT_TRUE(filter.Init(test::ResourcePath("google-wifi-3mbps", "rx"))); + RunFor(300 * 1000); + PrintResults(filter.GetBitrateStats().GetMean(), counter2.GetBitrateStats(), + filter.GetDelayStats()); +} } // namespace bwe } // namespace testing } // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc index c50029106..90ce6a3e8 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc @@ -23,44 +23,40 @@ namespace webrtc { namespace testing { namespace bwe { -namespace stl_helpers { -template void DeleteElements(T* container) { - if (!container) return; - for (typename T::iterator it = container->begin(); it != container->end(); - ++it) { - delete *it; - } - container->clear(); -} -} // namespace stl_helpers - -class BweTest::TestedEstimator : public RemoteBitrateObserver { +class TestedEstimator : public RemoteBitrateObserver { public: static const uint32_t kRemoteBitrateEstimatorMinBitrateBps = 30000; + static const int kDelayPlotIntervalMs = 100; TestedEstimator(const string& test_name, const BweTestConfig::EstimatorConfig& config) : debug_name_(config.debug_name), + delay_log_prefix_(), + estimate_log_prefix_(), + last_delay_plot_ms_(0), + plot_delay_(config.plot_delay), + plot_estimate_(config.plot_estimate), clock_(0), stats_(), - relative_estimator_stats_(), latest_estimate_bps_(-1), estimator_(config.estimator_factory->Create( this, &clock_, config.control_type, kRemoteBitrateEstimatorMinBitrateBps)), - relative_estimator_(NULL), baseline_(BaseLineFileInterface::Create(test_name + "_" + debug_name_, config.update_baseline)) { assert(estimator_.get()); assert(baseline_.get()); + // Setup the prefix strings used when logging. + std::stringstream ss; + ss << "Delay_" << config.flow_id << "#2"; + delay_log_prefix_ = ss.str(); + ss.str(""); + ss << "Estimate_" << config.flow_id << "#1"; + estimate_log_prefix_ = ss.str(); // Default RTT in RemoteRateControl is 200 ms ; 50 ms is more realistic. estimator_->OnRttUpdate(50); } - void SetRelativeEstimator(TestedEstimator* relative_estimator) { - relative_estimator_ = relative_estimator; - } - void EatPacket(const Packet& packet) { BWE_TEST_LOGGING_CONTEXT(debug_name_); @@ -70,9 +66,15 @@ class BweTest::TestedEstimator : public RemoteBitrateObserver { // time once packet reaches the estimator. int64_t packet_time_ms = (packet.send_time_us() + 500) / 1000; BWE_TEST_LOGGING_TIME(packet_time_ms); - BWE_TEST_LOGGING_PLOT("Delay_#2", clock_.TimeInMilliseconds(), - packet_time_ms - - (packet.creation_time_us() + 500) / 1000); + if (plot_delay_) { + if (clock_.TimeInMilliseconds() - last_delay_plot_ms_ > + kDelayPlotIntervalMs) { + BWE_TEST_LOGGING_PLOT(delay_log_prefix_, clock_.TimeInMilliseconds(), + packet_time_ms - + (packet.creation_time_us() + 500) / 1000); + last_delay_plot_ms_ = clock_.TimeInMilliseconds(); + } + } int64_t step_ms = estimator_->TimeUntilNextProcess(); while ((clock_.TimeInMilliseconds() + step_ms) < packet_time_ms) { @@ -97,14 +99,9 @@ class BweTest::TestedEstimator : public RemoteBitrateObserver { double estimated_kbps = static_cast(estimated_bps) / 1000.0; stats_.Push(estimated_kbps); - BWE_TEST_LOGGING_PLOT("Estimate_#1", clock_.TimeInMilliseconds(), - estimated_kbps); - uint32_t relative_estimate_bps = 0; - if (relative_estimator_ && - relative_estimator_->LatestEstimate(&relative_estimate_bps)) { - double relative_estimate_kbps = - static_cast(relative_estimate_bps) / 1000.0; - relative_estimator_stats_.Push(estimated_kbps - relative_estimate_kbps); + if (plot_estimate_) { + BWE_TEST_LOGGING_PLOT(estimate_log_prefix_, clock_.TimeInMilliseconds(), + estimated_kbps); } return true; } @@ -115,10 +112,6 @@ class BweTest::TestedEstimator : public RemoteBitrateObserver { BWE_TEST_LOGGING_CONTEXT(debug_name_); BWE_TEST_LOGGING_CONTEXT("Mean"); stats_.Log("kbps"); - if (relative_estimator_) { - BWE_TEST_LOGGING_CONTEXT("Diff"); - relative_estimator_stats_.Log("kbps"); - } } void VerifyOrWriteBaseline() { @@ -144,145 +137,217 @@ class BweTest::TestedEstimator : public RemoteBitrateObserver { } string debug_name_; + string delay_log_prefix_; + string estimate_log_prefix_; + int64_t last_delay_plot_ms_; + bool plot_delay_; + bool plot_estimate_; SimulatedClock clock_; Stats stats_; - Stats relative_estimator_stats_; int64_t latest_estimate_bps_; scoped_ptr estimator_; - TestedEstimator* relative_estimator_; scoped_ptr baseline_; DISALLOW_IMPLICIT_CONSTRUCTORS(TestedEstimator); }; +class PacketProcessorRunner { + public: + explicit PacketProcessorRunner(PacketProcessor* processor) + : processor_(processor) {} + + bool HasProcessor(const PacketProcessor* processor) const { + return processor == processor_; + } + + void RunFor(int64_t time_ms, int64_t time_now_ms, Packets* in_out) { + Packets to_process; + FindPacketsToProcess(processor_->flow_ids(), in_out, &to_process); + processor_->RunFor(time_ms, &to_process); + QueuePackets(&to_process, time_now_ms * 1000); + if (!to_process.empty()) { + processor_->Plot((to_process.back().send_time_us() + 500) / 1000); + } + in_out->merge(to_process); + } + + private: + void FindPacketsToProcess(const FlowIds& flow_ids, Packets* in, + Packets* out) { + assert(out->empty()); + for (Packets::iterator it = in->begin(); it != in->end();) { + // TODO(holmer): Further optimize this by looking for consecutive flow ids + // in the packet list and only doing the binary search + splice once for a + // sequence. + if (std::binary_search(flow_ids.begin(), flow_ids.end(), it->flow_id())) { + Packets::iterator next = it; + ++next; + out->splice(out->end(), *in, it); + it = next; + } else { + ++it; + } + } + } + + void QueuePackets(Packets* batch, int64_t end_of_batch_time_us) { + queue_.merge(*batch); + if (queue_.empty()) { + return; + } + Packets to_transfer; + Packets::iterator it = queue_.begin(); + for (; it != queue_.end(); ++it) { + if (it->send_time_us() > end_of_batch_time_us) { + break; + } + } + to_transfer.splice(to_transfer.begin(), queue_, queue_.begin(), it); + batch->merge(to_transfer); + } + + PacketProcessor* processor_; + Packets queue_; +}; + BweTest::BweTest() : run_time_ms_(0), + time_now_ms_(-1), simulation_interval_ms_(-1), - previous_packets_(), - packet_senders_(), estimators_(), processors_() { } BweTest::~BweTest() { - stl_helpers::DeleteElements(&estimators_); - stl_helpers::DeleteElements(&packet_senders_); + BWE_TEST_LOGGING_GLOBAL_ENABLE(true); + for (EstimatorMap::iterator it = estimators_.begin(); it != estimators_.end(); + ++it) { + it->second->VerifyOrWriteBaseline(); + it->second->LogStats(); + } + BWE_TEST_LOGGING_GLOBAL_CONTEXT(""); + + for (EstimatorMap::iterator it = estimators_.begin(); + it != estimators_.end(); ++it) { + delete it->second; + } } -void BweTest::SetUp() { +void BweTest::SetupTestFromConfig(const BweTestConfig& config) { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); string test_name = string(test_info->test_case_name()) + "_" + string(test_info->name()); BWE_TEST_LOGGING_GLOBAL_CONTEXT(test_name); - - const BweTestConfig& config = GetParam(); - - uint32_t total_capacity = 0; - for (vector::const_iterator it = - config.sender_factories.begin(); it != config.sender_factories.end(); - ++it) { - PacketSender* sender = (*it)->Create(); - assert(sender); - total_capacity += sender->GetCapacityKbps(); - packet_senders_.push_back(sender); - processors_.push_back(sender); + for (vector::const_iterator it = + config.estimator_configs.begin(); it != config.estimator_configs.end(); + ++it) { + estimators_.insert(std::make_pair(it->flow_id, new TestedEstimator( + test_name, *it))); } - BWE_TEST_LOGGING_LOG1("RequiredLinkCapacity", "%d kbps", total_capacity) - - // Set simulation interval from first packet sender. - if (packet_senders_.size() > 0) { - simulation_interval_ms_ = packet_senders_[0]->GetFeedbackIntervalMs(); - } - - for (vector:: const_iterator it = - config.estimator_configs.begin(); it != config.estimator_configs.end(); - ++it) { - estimators_.push_back(new TestedEstimator(test_name, *it)); - } - if (estimators_.size() > 1) { - // Set all estimators as relative to the first one. - for (uint32_t i = 1; i < estimators_.size(); ++i) { - estimators_[i]->SetRelativeEstimator(estimators_[0]); - } - } - BWE_TEST_LOGGING_GLOBAL_ENABLE(false); } -void BweTest::TearDown() { - BWE_TEST_LOGGING_GLOBAL_ENABLE(true); - - for (vector::iterator eit = estimators_.begin(); - eit != estimators_.end(); ++eit) { - (*eit)->VerifyOrWriteBaseline(); - (*eit)->LogStats(); - } - - BWE_TEST_LOGGING_GLOBAL_CONTEXT(""); -} - -void BweTest::AddPacketProcessor( - PacketProcessor* processor) { +void BweTest::AddPacketProcessor(PacketProcessor* processor, bool is_sender) { assert(processor); - processors_.push_back(processor); + processors_.push_back(PacketProcessorRunner(processor)); + if (is_sender) { + senders_.push_back(static_cast(processor)); + } + const FlowIds& flow_ids = processor->flow_ids(); + for (size_t i = 0; i < flow_ids.size(); ++i) { + assert(estimators_.count(flow_ids[i]) == 1); + } } void BweTest::RemovePacketProcessor( PacketProcessor* processor) { - vector::iterator it = - std::find(processors_.begin(), processors_.end(), processor); - assert(it != processors_.end()); - processors_.erase(it); + for (vector::iterator it = processors_.begin(); + it != processors_.end(); ++it) { + if (it->HasProcessor(processor)) { + processors_.erase(it); + return; + } + } + assert(false); } void BweTest::VerboseLogging(bool enable) { BWE_TEST_LOGGING_GLOBAL_ENABLE(enable); } +void BweTest::GiveFeedbackToAffectedSenders(int flow_id, + TestedEstimator* estimator) { + std::list affected_senders; + for (std::vector::iterator psit = + senders_.begin(); psit != senders_.end(); ++psit) { + const FlowIds& flow_ids = (*psit)->flow_ids(); + if (std::binary_search(flow_ids.begin(), flow_ids.end(), flow_id)) { + affected_senders.push_back(*psit); + } + } + PacketSender::Feedback feedback = {0}; + if (estimator->CheckEstimate(&feedback) && !affected_senders.empty()) { + // Allocate the bitrate evenly between the senders. + feedback.estimated_bps /= affected_senders.size(); + for (std::list::iterator psit = affected_senders.begin(); + psit != affected_senders.end(); ++psit) { + (*psit)->GiveFeedback(feedback); + } + } +} + void BweTest::RunFor(int64_t time_ms) { - for (run_time_ms_ += time_ms; run_time_ms_ >= simulation_interval_ms_; - run_time_ms_ -= simulation_interval_ms_) { + // Set simulation interval from first packet sender. + // TODO(holmer): Support different feedback intervals for different flows. + if (!senders_.empty()) { + simulation_interval_ms_ = senders_[0]->GetFeedbackIntervalMs(); + } + assert(simulation_interval_ms_ > 0); + if (time_now_ms_ == -1) { + time_now_ms_ = simulation_interval_ms_; + } + for (run_time_ms_ += time_ms; + time_now_ms_ <= run_time_ms_ - simulation_interval_ms_; + time_now_ms_ += simulation_interval_ms_) { Packets packets; - for (vector::const_iterator it = + for (vector::iterator it = processors_.begin(); it != processors_.end(); ++it) { - (*it)->RunFor(simulation_interval_ms_, &packets); - if (!packets.empty()) { - (*it)->Plot((packets.back().send_time_us() + 500) / 1000); - } + it->RunFor(simulation_interval_ms_, time_now_ms_, &packets); } // Verify packets are in order between batches. - if (!packets.empty() && !previous_packets_.empty()) { - packets.splice(packets.begin(), previous_packets_, - --previous_packets_.end()); - ASSERT_TRUE(IsTimeSorted(packets)); - packets.erase(packets.begin()); + if (!packets.empty()) { + if (!previous_packets_.empty()) { + packets.splice(packets.begin(), previous_packets_, + --previous_packets_.end()); + ASSERT_TRUE(IsTimeSorted(packets)); + packets.erase(packets.begin()); + } + ASSERT_LE(packets.front().send_time_us(), time_now_ms_ * 1000); + ASSERT_LE(packets.back().send_time_us(), time_now_ms_ * 1000); } else { ASSERT_TRUE(IsTimeSorted(packets)); } - for (PacketsConstIt pit = packets.begin(); pit != packets.end(); ++pit) { - for (vector::iterator eit = estimators_.begin(); - eit != estimators_.end(); ++eit) { - (*eit)->EatPacket(*pit); - } + for (PacketsConstIt it = packets.begin(); it != packets.end(); ++it) { + EstimatorMap::iterator est_it = estimators_.find(it->flow_id()); + ASSERT_TRUE(est_it != estimators_.end()); + est_it->second->EatPacket(*it); } - previous_packets_.swap(packets); - - for (vector::iterator eit = estimators_.begin(); - eit != estimators_.end(); ++eit) { - PacketSender::Feedback feedback = {0}; - if ((*eit)->CheckEstimate(&feedback)) { - for (vector::iterator psit = packet_senders_.begin(); - psit != packet_senders_.end(); ++psit) { - (*psit)->GiveFeedback(feedback); - } - } + for (EstimatorMap::iterator est_it = estimators_.begin(); + est_it != estimators_.end(); ++est_it) { + GiveFeedbackToAffectedSenders(est_it->first, est_it->second); } } } + +string BweTest::GetTestName() const { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + return string(test_info->name()); +} } // namespace bwe } // namespace testing } // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h index cb708f421..495906537 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h @@ -8,12 +8,13 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include #include #include #include "gtest/gtest.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" namespace webrtc { @@ -26,60 +27,96 @@ struct BweTestConfig { struct EstimatorConfig { EstimatorConfig() : debug_name(), + flow_id(0), estimator_factory(NULL), control_type(kMimdControl), - update_baseline(false) { + update_baseline(false), + plot_delay(true), + plot_estimate(true) { } EstimatorConfig(std::string debug_name, - const RemoteBitrateEstimatorFactory* estimator_factory) + int flow_id, + const RemoteBitrateEstimatorFactory* estimator_factory, + bool plot_delay, + bool plot_estimate) : debug_name(debug_name), + flow_id(flow_id), estimator_factory(estimator_factory), control_type(kMimdControl), - update_baseline(false) { + update_baseline(false), + plot_delay(plot_delay), + plot_estimate(plot_estimate) { + } + EstimatorConfig(std::string debug_name, + int flow_id, + const RemoteBitrateEstimatorFactory* estimator_factory, + RateControlType control_type, + bool plot_delay, + bool plot_estimate) + : debug_name(debug_name), + flow_id(flow_id), + estimator_factory(estimator_factory), + control_type(control_type), + update_baseline(false), + plot_delay(plot_delay), + plot_estimate(plot_estimate) { } EstimatorConfig(std::string debug_name, + int flow_id, const RemoteBitrateEstimatorFactory* estimator_factory, RateControlType control_type, bool update_baseline) : debug_name(debug_name), + flow_id(flow_id), estimator_factory(estimator_factory), control_type(control_type), - update_baseline(update_baseline) { + update_baseline(update_baseline), + plot_delay(false), + plot_estimate(false) { } std::string debug_name; + int flow_id; const RemoteBitrateEstimatorFactory* estimator_factory; RateControlType control_type; bool update_baseline; + bool plot_delay; + bool plot_estimate; }; - std::vector sender_factories; std::vector estimator_configs; }; -class BweTest : public ::testing::TestWithParam, - public PacketProcessorListener { +class TestedEstimator; +class PacketProcessorRunner; + +class BweTest : public PacketProcessorListener { public: BweTest(); virtual ~BweTest(); - virtual void SetUp(); - virtual void TearDown(); - virtual void AddPacketProcessor(PacketProcessor* processor); + virtual void AddPacketProcessor(PacketProcessor* processor, bool is_sender); virtual void RemovePacketProcessor(PacketProcessor* processor); protected: + void SetupTestFromConfig(const BweTestConfig& config); void VerboseLogging(bool enable); void RunFor(int64_t time_ms); + std::string GetTestName() const; private: - class TestedEstimator; + typedef std::map EstimatorMap; + + void FindPacketsToProcess(const FlowIds& flow_ids, Packets* in, + Packets* out); + void GiveFeedbackToAffectedSenders(int flow_id, TestedEstimator* estimator); int64_t run_time_ms_; + int64_t time_now_ms_; int64_t simulation_interval_ms_; + EstimatorMap estimators_; Packets previous_packets_; - std::vector packet_senders_; - std::vector estimators_; - std::vector processors_; + std::vector senders_; + std::vector processors_; DISALLOW_COPY_AND_ASSIGN(BweTest); }; diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc index 48b66a440..48384feea 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc @@ -15,9 +15,9 @@ #include #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h" #include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/test/testsupport/fileutils.h" @@ -118,7 +118,7 @@ class BaseLineFileUpdate : public BaseLineFileInterface { virtual bool VerifyOrWrite() { if (!verifier_->VerifyOrWrite()) { std::string dir_path = webrtc::test::OutputPath() + kResourceSubDir; - if (!webrtc::test::CreateDirectory(dir_path)) { + if (!webrtc::test::CreateDir(dir_path)) { printf("WARNING: Cannot create output dir: %s\n", dir_path.c_str()); return false; } diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h index dddbdeeb2..e73a545e5 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h @@ -15,8 +15,8 @@ #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/interface/module_common_types.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" namespace webrtc { namespace testing { diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc index 893b6fee8..b3cd7db97 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc @@ -45,6 +45,11 @@ class DelayCapHelper { DISALLOW_COPY_AND_ASSIGN(DelayCapHelper); }; +const FlowIds CreateFlowIds(const int *flow_ids_array, size_t num_flow_ids) { + FlowIds flow_ids(&flow_ids_array[0], flow_ids_array + num_flow_ids); + return flow_ids; +} + class RateCounter { public: RateCounter() @@ -115,26 +120,29 @@ int Random::Gaussian(int mean, int standard_deviation) { } Packet::Packet() - : creation_time_us_(-1), + : flow_id_(0), + creation_time_us_(-1), send_time_us_(-1), payload_size_(0) { - memset(&header_, 0, sizeof(header_)); + memset(&header_, 0, sizeof(header_)); } -Packet::Packet(int64_t send_time_us, uint32_t payload_size, +Packet::Packet(int flow_id, int64_t send_time_us, uint32_t payload_size, const RTPHeader& header) - : creation_time_us_(send_time_us), - send_time_us_(send_time_us), - payload_size_(payload_size), - header_(header) { + : flow_id_(flow_id), + creation_time_us_(send_time_us), + send_time_us_(send_time_us), + payload_size_(payload_size), + header_(header) { } Packet::Packet(int64_t send_time_us, uint32_t sequence_number) - : creation_time_us_(send_time_us), + : flow_id_(0), + creation_time_us_(send_time_us), send_time_us_(send_time_us), payload_size_(0) { - memset(&header_, 0, sizeof(header_)); - header_.sequenceNumber = sequence_number; + memset(&header_, 0, sizeof(header_)); + header_.sequenceNumber = sequence_number; } bool Packet::operator<(const Packet& rhs) const { @@ -157,10 +165,20 @@ bool IsTimeSorted(const Packets& packets) { return true; } -PacketProcessor::PacketProcessor(PacketProcessorListener* listener) - : listener_(listener) { +PacketProcessor::PacketProcessor(PacketProcessorListener* listener, + bool is_sender) + : listener_(listener), flow_ids_(1, 0) { + if (listener_) { + listener_->AddPacketProcessor(this, is_sender); + } +} + +PacketProcessor::PacketProcessor(PacketProcessorListener* listener, + const FlowIds& flow_ids, + bool is_sender) + : listener_(listener), flow_ids_(flow_ids) { if (listener_) { - listener_->AddPacketProcessor(this); + listener_->AddPacketProcessor(this, is_sender); } } @@ -171,20 +189,36 @@ PacketProcessor::~PacketProcessor() { } RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener) - : PacketProcessor(listener), + : PacketProcessor(listener, false), rate_counter_(new RateCounter()), - pps_stats_(), + packets_per_second_stats_(), kbps_stats_(), name_("") {} RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener, const std::string& name) - : PacketProcessor(listener), + : PacketProcessor(listener, false), rate_counter_(new RateCounter()), - pps_stats_(), + packets_per_second_stats_(), kbps_stats_(), name_(name) {} +RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener, + const FlowIds& flow_ids, + const std::string& name) + : PacketProcessor(listener, flow_ids, false), + rate_counter_(new RateCounter()), + packets_per_second_stats_(), + kbps_stats_(), + name_(name) { + std::stringstream ss; + ss << name_ << "_"; + for (size_t i = 0; i < flow_ids.size(); ++i) { + ss << flow_ids[i] << ","; + } + name_ = ss.str(); +} + RateCounterFilter::~RateCounterFilter() { LogStats(); } @@ -199,7 +233,7 @@ uint32_t RateCounterFilter::bits_per_second() const { void RateCounterFilter::LogStats() { BWE_TEST_LOGGING_CONTEXT("RateCounterFilter"); - pps_stats_.Log("pps"); + packets_per_second_stats_.Log("pps"); kbps_stats_.Log("kbps"); } @@ -218,12 +252,12 @@ void RateCounterFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) { for (PacketsConstIt it = in_out->begin(); it != in_out->end(); ++it) { rate_counter_->UpdateRates(it->send_time_us(), it->payload_size()); } - pps_stats_.Push(rate_counter_->packets_per_second()); + packets_per_second_stats_.Push(rate_counter_->packets_per_second()); kbps_stats_.Push(rate_counter_->bits_per_second() / 1000.0); } LossFilter::LossFilter(PacketProcessorListener* listener) - : PacketProcessor(listener), + : PacketProcessor(listener, false), random_(0x12345678), loss_fraction_(0.0f) { } @@ -248,7 +282,7 @@ void LossFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) { } DelayFilter::DelayFilter(PacketProcessorListener* listener) - : PacketProcessor(listener), + : PacketProcessor(listener, false), delay_us_(0), last_send_time_us_(0) { } @@ -270,7 +304,7 @@ void DelayFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) { } JitterFilter::JitterFilter(PacketProcessorListener* listener) - : PacketProcessor(listener), + : PacketProcessor(listener, false), random_(0x89674523), stddev_jitter_us_(0), last_send_time_us_(0) { @@ -295,7 +329,7 @@ void JitterFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) { } ReorderFilter::ReorderFilter(PacketProcessorListener* listener) - : PacketProcessor(listener), + : PacketProcessor(listener, false), random_(0x27452389), reorder_fraction_(0.0f) { } @@ -327,7 +361,15 @@ void ReorderFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) { } ChokeFilter::ChokeFilter(PacketProcessorListener* listener) - : PacketProcessor(listener), + : PacketProcessor(listener, false), + kbps_(1200), + last_send_time_us_(0), + delay_cap_helper_(new DelayCapHelper()) { +} + +ChokeFilter::ChokeFilter(PacketProcessorListener* listener, + const FlowIds& flow_ids) + : PacketProcessor(listener, flow_ids, false), kbps_(1200), last_send_time_us_(0), delay_cap_helper_(new DelayCapHelper()) { @@ -369,26 +411,30 @@ Stats ChokeFilter::GetDelayStats() const { TraceBasedDeliveryFilter::TraceBasedDeliveryFilter( PacketProcessorListener* listener) - : PacketProcessor(listener), + : PacketProcessor(listener, false), current_offset_us_(0), delivery_times_us_(), next_delivery_it_(), local_time_us_(-1), rate_counter_(new RateCounter), name_(""), - delay_cap_helper_(new DelayCapHelper()) {} + delay_cap_helper_(new DelayCapHelper()), + packets_per_second_stats_(), + kbps_stats_() {} TraceBasedDeliveryFilter::TraceBasedDeliveryFilter( PacketProcessorListener* listener, const std::string& name) - : PacketProcessor(listener), + : PacketProcessor(listener, false), current_offset_us_(0), delivery_times_us_(), next_delivery_it_(), local_time_us_(-1), rate_counter_(new RateCounter), name_(name), - delay_cap_helper_(new DelayCapHelper()) {} + delay_cap_helper_(new DelayCapHelper()), + packets_per_second_stats_(), + kbps_stats_() {} TraceBasedDeliveryFilter::~TraceBasedDeliveryFilter() { } @@ -449,6 +495,8 @@ void TraceBasedDeliveryFilter::RunFor(int64_t time_ms, Packets* in_out) { } ++it; } + packets_per_second_stats_.Push(rate_counter_->packets_per_second()); + kbps_stats_.Push(rate_counter_->bits_per_second() / 1000.0); } void TraceBasedDeliveryFilter::SetMaxDelay(int max_delay_ms) { @@ -459,6 +507,10 @@ Stats TraceBasedDeliveryFilter::GetDelayStats() const { return delay_cap_helper_->delay_stats(); } +Stats TraceBasedDeliveryFilter::GetBitrateStats() const { + return kbps_stats_; +} + void TraceBasedDeliveryFilter::ProceedToNextSlot() { if (*next_delivery_it_ <= local_time_us_) { ++next_delivery_it_; @@ -478,12 +530,18 @@ void TraceBasedDeliveryFilter::ProceedToNextSlot() { } PacketSender::PacketSender(PacketProcessorListener* listener) - : PacketProcessor(listener) { + : PacketProcessor(listener, true) {} + +PacketSender::PacketSender(PacketProcessorListener* listener, + const FlowIds& flow_ids) + : PacketProcessor(listener, flow_ids, true) { + } -VideoSender::VideoSender(PacketProcessorListener* listener, float fps, - uint32_t kbps, uint32_t ssrc, float first_frame_offset) - : PacketSender(listener), +VideoSender::VideoSender(int flow_id, PacketProcessorListener* listener, + float fps, uint32_t kbps, uint32_t ssrc, + float first_frame_offset) + : PacketSender(listener, FlowIds(1, flow_id)), kMaxPayloadSizeBytes(1200), kTimestampBase(0xff80ff00ul), frame_period_ms_(1000.0 / fps), @@ -506,7 +564,7 @@ uint32_t VideoSender::GetCapacityKbps() const { void VideoSender::RunFor(int64_t time_ms, Packets* in_out) { assert(in_out); now_ms_ += time_ms; - Packets newPackets; + Packets new_packets; while (now_ms_ >= next_frame_ms_) { prototype_header_.sequenceNumber++; prototype_header_.timestamp = kTimestampBase + @@ -524,21 +582,23 @@ void VideoSender::RunFor(int64_t time_ms, Packets* in_out) { uint32_t payload_size = frame_size_bytes_; while (payload_size > 0) { uint32_t size = std::min(kMaxPayloadSizeBytes, payload_size); - newPackets.push_back(Packet(send_time_us, size, prototype_header_)); + new_packets.push_back(Packet(flow_ids()[0], send_time_us, size, + prototype_header_)); payload_size -= size; } next_frame_ms_ += frame_period_ms_; } - in_out->merge(newPackets); + in_out->merge(new_packets); } -AdaptiveVideoSender::AdaptiveVideoSender(PacketProcessorListener* listener, +AdaptiveVideoSender::AdaptiveVideoSender(int flow_id, + PacketProcessorListener* listener, float fps, uint32_t kbps, uint32_t ssrc, float first_frame_offset) - : VideoSender(listener, fps, kbps, ssrc, first_frame_offset) {} + : VideoSender(flow_id, listener, fps, kbps, ssrc, first_frame_offset) {} void AdaptiveVideoSender::GiveFeedback(const PacketSender::Feedback& feedback) { bytes_per_second_ = feedback.estimated_bps / 8; diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h index 5302ba3d0..8af07f918 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,10 @@ namespace bwe { class DelayCapHelper; class RateCounter; + +typedef std::vector FlowIds; +const FlowIds CreateFlowIds(const int *flow_ids_array, size_t num_flow_ids); + template class Stats { public: Stats() @@ -84,6 +89,13 @@ template class Stats { return max_; } + std::string AsString() { + std::stringstream ss; + ss << (GetMean() >= 0 ? GetMean() : -1) << ", " << + (GetStdDev() >= 0 ? GetStdDev() : -1); + return ss.str(); + } + void Log(const std::string& units) { BWE_TEST_LOGGING_LOG5("", "%f %s\t+/-%f\t[%f,%f]", GetMean(), units.c_str(), GetStdDev(), GetMin(), GetMax()); @@ -139,12 +151,13 @@ class Random { class Packet { public: Packet(); - Packet(int64_t send_time_us, uint32_t payload_size, + Packet(int flow_id, int64_t send_time_us, uint32_t payload_size, const RTPHeader& header); Packet(int64_t send_time_us, uint32_t sequence_number); bool operator<(const Packet& rhs) const; + int flow_id() const { return flow_id_; } int64_t creation_time_us() const { return creation_time_us_; } void set_send_time_us(int64_t send_time_us); int64_t send_time_us() const { return send_time_us_; } @@ -152,6 +165,7 @@ class Packet { const RTPHeader& header() const { return header_; } private: + int flow_id_; int64_t creation_time_us_; // Time when the packet was created. int64_t send_time_us_; // Time the packet left last processor touching it. uint32_t payload_size_; // Size of the (non-existent, simulated) payload. @@ -170,13 +184,16 @@ class PacketProcessorListener { public: virtual ~PacketProcessorListener() {} - virtual void AddPacketProcessor(PacketProcessor* processor) = 0; + virtual void AddPacketProcessor(PacketProcessor* processor, + bool is_sender) = 0; virtual void RemovePacketProcessor(PacketProcessor* processor) = 0; }; class PacketProcessor { public: - explicit PacketProcessor(PacketProcessorListener* listener); + PacketProcessor(PacketProcessorListener* listener, bool is_sender); + PacketProcessor(PacketProcessorListener* listener, const FlowIds& flow_ids, + bool is_sender); virtual ~PacketProcessor(); // Called after each simulation batch to allow the processor to plot any @@ -188,8 +205,11 @@ class PacketProcessor { // |send_time_us_|. The simulation time |time_ms| is optional to use. virtual void RunFor(int64_t time_ms, Packets* in_out) = 0; + const FlowIds& flow_ids() const { return flow_ids_; } + private: PacketProcessorListener* listener_; + FlowIds flow_ids_; DISALLOW_COPY_AND_ASSIGN(PacketProcessor); }; @@ -199,6 +219,9 @@ class RateCounterFilter : public PacketProcessor { explicit RateCounterFilter(PacketProcessorListener* listener); RateCounterFilter(PacketProcessorListener* listener, const std::string& name); + RateCounterFilter(PacketProcessorListener* listener, + const FlowIds& flow_ids, + const std::string& name); virtual ~RateCounterFilter(); uint32_t packets_per_second() const; @@ -211,7 +234,7 @@ class RateCounterFilter : public PacketProcessor { private: scoped_ptr rate_counter_; - Stats pps_stats_; + Stats packets_per_second_stats_; Stats kbps_stats_; std::string name_; @@ -283,6 +306,7 @@ class ReorderFilter : public PacketProcessor { class ChokeFilter : public PacketProcessor { public: explicit ChokeFilter(PacketProcessorListener* listener); + ChokeFilter(PacketProcessorListener* listener, const FlowIds& flow_ids); virtual ~ChokeFilter(); void SetCapacity(uint32_t kbps); @@ -315,6 +339,7 @@ class TraceBasedDeliveryFilter : public PacketProcessor { void SetMaxDelay(int max_delay_ms); Stats GetDelayStats() const; + Stats GetBitrateStats() const; private: void ProceedToNextSlot(); @@ -327,6 +352,8 @@ class TraceBasedDeliveryFilter : public PacketProcessor { scoped_ptr rate_counter_; std::string name_; scoped_ptr delay_cap_helper_; + Stats packets_per_second_stats_; + Stats kbps_stats_; DISALLOW_COPY_AND_ASSIGN(TraceBasedDeliveryFilter); }; @@ -338,6 +365,7 @@ class PacketSender : public PacketProcessor { }; explicit PacketSender(PacketProcessorListener* listener); + PacketSender(PacketProcessorListener* listener, const FlowIds& flow_ids); virtual ~PacketSender() {} virtual uint32_t GetCapacityKbps() const { return 0; } @@ -347,23 +375,17 @@ class PacketSender : public PacketProcessor { // Note that changing the feedback interval affects the timing of when the // output of the estimators is sampled and therefore the baseline files may // have to be regenerated. - virtual int64_t GetFeedbackIntervalMs() const { return 1000; } + virtual int GetFeedbackIntervalMs() const { return 1000; } virtual void GiveFeedback(const Feedback& feedback) {} private: DISALLOW_COPY_AND_ASSIGN(PacketSender); }; -struct PacketSenderFactory { - PacketSenderFactory() {} - virtual ~PacketSenderFactory() {} - virtual PacketSender* Create() const = 0; -}; - class VideoSender : public PacketSender { public: - VideoSender(PacketProcessorListener* listener, float fps, uint32_t kbps, - uint32_t ssrc, float first_frame_offset); + VideoSender(int flow_id, PacketProcessorListener* listener, float fps, + uint32_t kbps, uint32_t ssrc, float first_frame_offset); virtual ~VideoSender() {} uint32_t max_payload_size_bytes() const { return kMaxPayloadSizeBytes; } @@ -390,48 +412,17 @@ class VideoSender : public PacketSender { class AdaptiveVideoSender : public VideoSender { public: - AdaptiveVideoSender(PacketProcessorListener* listener, float fps, - uint32_t kbps, uint32_t ssrc, float first_frame_offset); + AdaptiveVideoSender(int flow_id, PacketProcessorListener* listener, + float fps, uint32_t kbps, uint32_t ssrc, + float first_frame_offset); virtual ~AdaptiveVideoSender() {} - virtual int64_t GetFeedbackIntervalMs() const { return 100; } + virtual int GetFeedbackIntervalMs() const { return 100; } virtual void GiveFeedback(const Feedback& feedback); private: DISALLOW_IMPLICIT_CONSTRUCTORS(AdaptiveVideoSender); }; - -class VideoPacketSenderFactory : public PacketSenderFactory { - public: - VideoPacketSenderFactory(float fps, uint32_t kbps, uint32_t ssrc, - float frame_offset) - : fps_(fps), - kbps_(kbps), - ssrc_(ssrc), - frame_offset_(frame_offset) { - } - virtual ~VideoPacketSenderFactory() {} - virtual PacketSender* Create() const { - return new VideoSender(NULL, fps_, kbps_, ssrc_, frame_offset_); - } - protected: - float fps_; - uint32_t kbps_; - uint32_t ssrc_; - float frame_offset_; -}; - -class AdaptiveVideoPacketSenderFactory : public VideoPacketSenderFactory { - public: - AdaptiveVideoPacketSenderFactory(float fps, uint32_t kbps, uint32_t ssrc, - float frame_offset) - : VideoPacketSenderFactory(fps, kbps, ssrc, frame_offset) {} - virtual ~AdaptiveVideoPacketSenderFactory() {} - virtual PacketSender* Create() const { - return new AdaptiveVideoSender(NULL, fps_, kbps_, ssrc_, frame_offset_); - } -}; - } // namespace bwe } // namespace testing } // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc index 56329b626..ec3292686 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc @@ -13,7 +13,7 @@ #include #include "gtest/gtest.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/test/testsupport/fileutils.h" using std::vector; @@ -184,7 +184,7 @@ class BweTestFramework_RateCounterFilterTest : public ::testing::Test { RTPHeader header; // "Send" a packet every 10 ms. for (int64_t i = 0; i < run_for_ms; i += 10, now_ms_ += 10) { - packets.push_back(Packet(now_ms_ * 1000, payload_bits / 8, header)); + packets.push_back(Packet(0, now_ms_ * 1000, payload_bits / 8, header)); } filter_.RunFor(run_for_ms, &packets); ASSERT_TRUE(IsTimeSorted(packets)); @@ -587,7 +587,7 @@ class BweTestFramework_ChokeFilterTest : public ::testing::Test { int64_t send_time_ms = now_ms_ + (i * run_for_ms) / packets_to_generate; header.sequenceNumber = sequence_number_++; // Payload is 1000 bits. - packets.push_back(Packet(send_time_ms * 1000, 125, header)); + packets.push_back(Packet(0, send_time_ms * 1000, 125, header)); send_times_us_.push_back(send_time_ms * 1000); } ASSERT_TRUE(IsTimeSorted(packets)); @@ -768,7 +768,7 @@ void TestVideoSender(VideoSender* sender, int64_t run_for_ms, TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s) { // 1 fps, 80 kbps - VideoSender sender(NULL, 1.0f, 80, 0x1234, 0); + VideoSender sender(0, NULL, 1.0f, 80, 0x1234, 0); EXPECT_EQ(10000u, sender.bytes_per_second()); // We're at 1 fps, so all packets should be generated on first call, giving 10 // packets of each 1000 bytes, total 10000 bytes. @@ -785,7 +785,7 @@ TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s) { TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s_Offset) { // 1 fps, 80 kbps, offset 0.5 of a frame period, ==0.5s in this case. - VideoSender sender(NULL, 1.0f, 80, 0x1234, 0.5f); + VideoSender sender(0, NULL, 1.0f, 80, 0x1234, 0.5f); EXPECT_EQ(10000u, sender.bytes_per_second()); // 499ms, no output. TestVideoSender(&sender, 499, 0, 0, 0); @@ -805,7 +805,7 @@ TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s_Offset) { TEST(BweTestFramework_VideoSenderTest, Fps50Kpbs80_11s) { // 50 fps, 80 kbps. - VideoSender sender(NULL, 50.0f, 80, 0x1234, 0); + VideoSender sender(0, NULL, 50.0f, 80, 0x1234, 0); EXPECT_EQ(10000u, sender.bytes_per_second()); // 9998ms, should see 500 frames, 200 byte payloads, total 100000 bytes. TestVideoSender(&sender, 9998, 500, 200, 100000); @@ -821,7 +821,7 @@ TEST(BweTestFramework_VideoSenderTest, Fps50Kpbs80_11s) { TEST(BweTestFramework_VideoSenderTest, Fps10Kpbs120_1s) { // 20 fps, 120 kbps. - VideoSender sender(NULL, 20.0f, 120, 0x1234, 0); + VideoSender sender(0, NULL, 20.0f, 120, 0x1234, 0); EXPECT_EQ(15000u, sender.bytes_per_second()); // 498ms, 10 frames with 750 byte payloads, total 7500 bytes. TestVideoSender(&sender, 498, 10, 750, 7500); @@ -837,7 +837,7 @@ TEST(BweTestFramework_VideoSenderTest, Fps10Kpbs120_1s) { TEST(BweTestFramework_VideoSenderTest, Fps30Kbps800_20s) { // 20 fps, 820 kbps. - VideoSender sender(NULL, 25.0f, 820, 0x1234, 0); + VideoSender sender(0, NULL, 25.0f, 820, 0x1234, 0); EXPECT_EQ(102500u, sender.bytes_per_second()); // 9998ms, 250 frames. 820 kbps = 102500 bytes/s, so total should be 1025000. // Each frame is 102500/25=4100 bytes, or 5 packets (4 @1000 bytes, 1 @100), @@ -858,7 +858,7 @@ TEST(BweTestFramework_VideoSenderTest, Fps30Kbps800_20s) { TEST(BweTestFramework_VideoSenderTest, TestAppendInOrder) { // 1 fps, 80 kbps, 250ms offset. - VideoSender sender1(NULL, 1.0f, 80, 0x1234, 0.25f); + VideoSender sender1(0, NULL, 1.0f, 80, 0x1234, 0.25f); EXPECT_EQ(10000u, sender1.bytes_per_second()); Packets packets; // Generate some packets, verify they are sorted. @@ -873,7 +873,7 @@ TEST(BweTestFramework_VideoSenderTest, TestAppendInOrder) { EXPECT_EQ(18u, packets.size()); // Another sender, 2 fps, 160 kpbs, 150ms offset - VideoSender sender2(NULL, 2.0f, 160, 0x2234, 0.30f); + VideoSender sender2(0, NULL, 2.0f, 160, 0x2234, 0.30f); EXPECT_EQ(20000u, sender2.bytes_per_second()); // Generate some packets, verify that they are merged with the packets already // on the list. @@ -887,7 +887,7 @@ TEST(BweTestFramework_VideoSenderTest, TestAppendInOrder) { } TEST(BweTestFramework_VideoSenderTest, FeedbackIneffective) { - VideoSender sender(NULL, 25.0f, 820, 0x1234, 0); + VideoSender sender(0, NULL, 25.0f, 820, 0x1234, 0); EXPECT_EQ(102500u, sender.bytes_per_second()); TestVideoSender(&sender, 9998, 1000, 500, 1025000); @@ -899,7 +899,7 @@ TEST(BweTestFramework_VideoSenderTest, FeedbackIneffective) { } TEST(BweTestFramework_AdaptiveVideoSenderTest, FeedbackChangesBitrate) { - AdaptiveVideoSender sender(NULL, 25.0f, 820, 0x1234, 0); + AdaptiveVideoSender sender(0, NULL, 25.0f, 820, 0x1234, 0); EXPECT_EQ(102500u, sender.bytes_per_second()); TestVideoSender(&sender, 9998, 1000, 500, 1025000); diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h index c94976355..340c992f0 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h @@ -98,8 +98,8 @@ #include #include +#include "webrtc/base/constructormagic.h" #include "webrtc/common_types.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name) \ diff --git a/webrtc/modules/rtp_rtcp/BUILD.gn b/webrtc/modules/rtp_rtcp/BUILD.gn new file mode 100644 index 000000000..e9a2e8d91 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/BUILD.gn @@ -0,0 +1,104 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("rtp_rtcp") { + sources = [ + # Common + "interface/fec_receiver.h", + "interface/receive_statistics.h", + "interface/remote_ntp_time_estimator.h", + "interface/rtp_header_parser.h", + "interface/rtp_payload_registry.h", + "interface/rtp_receiver.h", + "interface/rtp_rtcp.h", + "interface/rtp_rtcp_defines.h", + "source/bitrate.cc", + "source/bitrate.h", + "source/byte_io.h", + "source/fec_receiver_impl.cc", + "source/fec_receiver_impl.h", + "source/receive_statistics_impl.cc", + "source/receive_statistics_impl.h", + "source/remote_ntp_time_estimator.cc", + "source/rtp_header_parser.cc", + "source/rtp_rtcp_config.h", + "source/rtp_rtcp_impl.cc", + "source/rtp_rtcp_impl.h", + "source/rtcp_packet.cc", + "source/rtcp_packet.h", + "source/rtcp_receiver.cc", + "source/rtcp_receiver.h", + "source/rtcp_receiver_help.cc", + "source/rtcp_receiver_help.h", + "source/rtcp_sender.cc", + "source/rtcp_sender.h", + "source/rtcp_utility.cc", + "source/rtcp_utility.h", + "source/rtp_header_extension.cc", + "source/rtp_header_extension.h", + "source/rtp_receiver_impl.cc", + "source/rtp_receiver_impl.h", + "source/rtp_sender.cc", + "source/rtp_sender.h", + "source/rtp_utility.cc", + "source/rtp_utility.h", + "source/ssrc_database.cc", + "source/ssrc_database.h", + "source/tmmbr_help.cc", + "source/tmmbr_help.h", + # Audio Files + "source/dtmf_queue.cc", + "source/dtmf_queue.h", + "source/rtp_receiver_audio.cc", + "source/rtp_receiver_audio.h", + "source/rtp_sender_audio.cc", + "source/rtp_sender_audio.h", + # Video Files + "source/fec_private_tables_random.h", + "source/fec_private_tables_bursty.h", + "source/forward_error_correction.cc", + "source/forward_error_correction.h", + "source/forward_error_correction_internal.cc", + "source/forward_error_correction_internal.h", + "source/producer_fec.cc", + "source/producer_fec.h", + "source/rtp_packet_history.cc", + "source/rtp_packet_history.h", + "source/rtp_payload_registry.cc", + "source/rtp_receiver_strategy.cc", + "source/rtp_receiver_strategy.h", + "source/rtp_receiver_video.cc", + "source/rtp_receiver_video.h", + "source/rtp_sender_video.cc", + "source/rtp_sender_video.h", + "source/video_codec_information.h", + "source/rtp_format_vp8.cc", + "source/rtp_format_vp8.h", + "source/rtp_format_video_generic.h", + "source/vp8_partition_aggregator.cc", + "source/vp8_partition_aggregator.h", + # Mocks + "mocks/mock_rtp_rtcp.h", + "source/mock/mock_rtp_payload_strategy.h", + ] + + deps = [ + "../../system_wrappers", + "../pacing", + "../remote_bitrate_estimator", + ] + + if (is_win) { + cflags = [ + # TODO(jschuh): Bug 1348: fix this warning. + "/wd4267", # size_t to int truncations + ] + } +} diff --git a/webrtc/modules/rtp_rtcp/OWNERS b/webrtc/modules/rtp_rtcp/OWNERS index c96856422..cafe4c193 100644 --- a/webrtc/modules/rtp_rtcp/OWNERS +++ b/webrtc/modules/rtp_rtcp/OWNERS @@ -2,4 +2,6 @@ pwestin@webrtc.org stefan@webrtc.org henrik.lundin@webrtc.org mflodman@webrtc.org -asapersson@webrtc.org \ No newline at end of file +asapersson@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/rtp_rtcp/interface/fec_receiver.h b/webrtc/modules/rtp_rtcp/interface/fec_receiver.h index 97b200f07..e2ef4b1e9 100644 --- a/webrtc/modules/rtp_rtcp/interface/fec_receiver.h +++ b/webrtc/modules/rtp_rtcp/interface/fec_receiver.h @@ -18,7 +18,7 @@ namespace webrtc { class FecReceiver { public: - static FecReceiver* Create(int32_t id, RtpData* callback); + static FecReceiver* Create(RtpData* callback); virtual ~FecReceiver() {} diff --git a/webrtc/modules/rtp_rtcp/interface/remote_ntp_time_estimator.h b/webrtc/modules/rtp_rtcp/interface/remote_ntp_time_estimator.h new file mode 100644 index 000000000..25f0f2ecf --- /dev/null +++ b/webrtc/modules/rtp_rtcp/interface/remote_ntp_time_estimator.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014 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_MODULES_RTP_RTCP_INTERFACE_REMOTE_NTP_TIME_ESTIMATOR_H_ +#define WEBRTC_MODULES_RTP_RTCP_INTERFACE_REMOTE_NTP_TIME_ESTIMATOR_H_ + +#include "webrtc/system_wrappers/interface/rtp_to_ntp.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class Clock; +class RtpRtcp; +class TimestampExtrapolator; + +// RemoteNtpTimeEstimator can be used to estimate a given RTP timestamp's NTP +// time in local timebase. +// Note that it needs to be trained with at least 2 RTCP SR (by calling +// |UpdateRtcpTimestamp|) before it can be used. +class RemoteNtpTimeEstimator { + public: + explicit RemoteNtpTimeEstimator(Clock* clock); + + ~RemoteNtpTimeEstimator(); + + // Updates the estimator with the timestamp from newly received RTCP SR for + // |ssrc|. The RTCP SR is read from |rtp_rtcp|. + bool UpdateRtcpTimestamp(uint32_t ssrc, RtpRtcp* rtp_rtcp); + + // Estimates the NTP timestamp in local timebase from |rtp_timestamp|. + // Returns the NTP timestamp in ms when success. -1 if failed. + int64_t Estimate(uint32_t rtp_timestamp); + + private: + Clock* clock_; + scoped_ptr ts_extrapolator_; + RtcpList rtcp_list_; + DISALLOW_COPY_AND_ASSIGN(RemoteNtpTimeEstimator); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_RTP_RTCP_INTERFACE_REMOTE_NTP_TIME_ESTIMATOR_H_ diff --git a/webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h b/webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h index 3ea4dcd1b..965f4b024 100644 --- a/webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h +++ b/webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h @@ -54,8 +54,7 @@ class RTPPayloadStrategy { class RTPPayloadRegistry { public: // The registry takes ownership of the strategy. - RTPPayloadRegistry(const int32_t id, - RTPPayloadStrategy* rtp_payload_strategy); + RTPPayloadRegistry(RTPPayloadStrategy* rtp_payload_strategy); ~RTPPayloadRegistry(); int32_t RegisterReceivePayload( @@ -76,10 +75,10 @@ class RTPPayloadRegistry { const uint32_t rate, int8_t* payload_type) const; - void SetRtxStatus(bool enable, uint32_t ssrc); - bool RtxEnabled() const; + void SetRtxSsrc(uint32_t ssrc); + void SetRtxPayloadType(int payload_type); bool IsRtx(const RTPHeader& header) const; @@ -153,7 +152,6 @@ class RTPPayloadRegistry { scoped_ptr crit_sect_; ModuleRTPUtility::PayloadTypeMap payload_type_map_; - int32_t id_; scoped_ptr rtp_payload_strategy_; int8_t red_payload_type_; int8_t ulpfec_payload_type_; diff --git a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h index cd8550c8e..95c565f01 100644 --- a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h +++ b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h @@ -213,7 +213,7 @@ class RtpRtcp : public Module { * * return -1 on failure else 0 */ - virtual int32_t SetSSRC(const uint32_t ssrc) = 0; + virtual void SetSSRC(const uint32_t ssrc) = 0; /* * Get CSRC @@ -249,10 +249,14 @@ class RtpRtcp : public Module { virtual int32_t SetCSRCStatus(const bool include) = 0; /* - * Turn on/off sending RTX (RFC 4588) on a specific SSRC. + * Turn on/off sending RTX (RFC 4588). The modes can be set as a combination + * of values of the enumerator RtxMode. */ - virtual int32_t SetRTXSendStatus(int modes, bool set_ssrc, - uint32_t ssrc) = 0; + virtual void SetRTXSendStatus(int modes) = 0; + + // Sets the SSRC to use when sending RTX packets. This doesn't enable RTX, + // only the SSRC is set. + virtual void SetRtxSsrc(uint32_t ssrc) = 0; // Sets the payload type to use when sending RTX packets. Note that this // doesn't enable RTX, only the payload type is set. @@ -261,8 +265,8 @@ class RtpRtcp : public Module { /* * Get status of sending RTX (RFC 4588) on a specific SSRC. */ - virtual int32_t RTXSendStatus(int* modes, uint32_t* ssrc, - int* payloadType) const = 0; + virtual void RTXSendStatus(int* modes, uint32_t* ssrc, + int* payloadType) const = 0; /* * sends kRtcpByeCode when going from true to false diff --git a/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h index 2215ac0ad..03156c79d 100644 --- a/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h +++ b/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h @@ -77,17 +77,19 @@ class MockRtpRtcp : public RtpRtcp { MOCK_CONST_METHOD0(SSRC, uint32_t()); MOCK_METHOD1(SetSSRC, - int32_t(const uint32_t ssrc)); + void(const uint32_t ssrc)); MOCK_CONST_METHOD1(CSRCs, int32_t(uint32_t arrOfCSRC[kRtpCsrcSize])); MOCK_METHOD2(SetCSRCs, int32_t(const uint32_t arrOfCSRC[kRtpCsrcSize], const uint8_t arrLength)); MOCK_METHOD1(SetCSRCStatus, int32_t(const bool include)); - MOCK_METHOD3(SetRTXSendStatus, - int32_t(int modes, bool setSSRC, uint32_t ssrc)); + MOCK_METHOD1(SetRTXSendStatus, + void(int modes)); MOCK_CONST_METHOD3(RTXSendStatus, - int32_t(int* modes, uint32_t* ssrc, int* payload_type)); + void(int* modes, uint32_t* ssrc, int* payload_type)); + MOCK_METHOD1(SetRtxSsrc, + void(uint32_t)); MOCK_METHOD1(SetRtxSendPayloadType, void(int)); MOCK_METHOD1(SetSendingStatus, diff --git a/webrtc/modules/rtp_rtcp/source/OWNERS b/webrtc/modules/rtp_rtcp/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc index 20be2d5dd..0d6c174a3 100644 --- a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc +++ b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc @@ -16,20 +16,19 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" // RFC 5109 namespace webrtc { -FecReceiver* FecReceiver::Create(int32_t id, RtpData* callback) { - return new FecReceiverImpl(id, callback); +FecReceiver* FecReceiver::Create(RtpData* callback) { + return new FecReceiverImpl(callback); } -FecReceiverImpl::FecReceiverImpl(const int32_t id, RtpData* callback) - : id_(id), - crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), +FecReceiverImpl::FecReceiverImpl(RtpData* callback) + : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), recovered_packet_callback_(callback), - fec_(new ForwardErrorCorrection(id)) {} + fec_(new ForwardErrorCorrection()) {} FecReceiverImpl::~FecReceiverImpl() { while (!received_packet_list_.empty()) { @@ -103,8 +102,7 @@ int32_t FecReceiverImpl::AddReceivedRedPacket( if (timestamp_offset != 0) { // |timestampOffset| should be 0. However, it's possible this is the first // location a corrupt payload can be caught, so don't assert. - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "Corrupt payload found in %s", __FUNCTION__); + LOG(LS_WARNING) << "Corrupt payload found."; delete received_packet; return -1; } diff --git a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h index 03421235c..b876bedc9 100644 --- a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h +++ b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h @@ -25,7 +25,7 @@ class CriticalSectionWrapper; class FecReceiverImpl : public FecReceiver { public: - FecReceiverImpl(const int32_t id, RtpData* callback); + FecReceiverImpl(RtpData* callback); virtual ~FecReceiverImpl(); virtual int32_t AddReceivedRedPacket(const RTPHeader& rtp_header, @@ -36,7 +36,6 @@ class FecReceiverImpl : public FecReceiver { virtual int32_t ProcessReceivedFec() OVERRIDE; private: - int id_; scoped_ptr crit_sect_; RtpData* recovered_packet_callback_; ForwardErrorCorrection* fec_; diff --git a/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc b/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc index 2e8846c3b..0b1244941 100644 --- a/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc @@ -39,8 +39,8 @@ class MockRtpData : public RtpData { class ReceiverFecTest : public ::testing::Test { protected: virtual void SetUp() { - fec_ = new ForwardErrorCorrection(0); - receiver_fec_ = FecReceiver::Create(0, &rtp_data_callback_); + fec_ = new ForwardErrorCorrection(); + receiver_fec_ = FecReceiver::Create(&rtp_data_callback_); generator_ = new FrameGenerator(); } diff --git a/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc b/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc index af2cb9e83..31303c8ad 100644 --- a/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc +++ b/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc @@ -20,7 +20,7 @@ #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" #include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h" #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -82,9 +82,8 @@ ForwardErrorCorrection::ReceivedPacket::~ReceivedPacket() {} ForwardErrorCorrection::RecoveredPacket::RecoveredPacket() {} ForwardErrorCorrection::RecoveredPacket::~RecoveredPacket() {} -ForwardErrorCorrection::ForwardErrorCorrection(int32_t id) - : id_(id), - generated_fec_packets_(kMaxMediaPackets), +ForwardErrorCorrection::ForwardErrorCorrection() + : generated_fec_packets_(kMaxMediaPackets), fec_packet_received_(false) {} ForwardErrorCorrection::~ForwardErrorCorrection() {} @@ -112,43 +111,23 @@ int32_t ForwardErrorCorrection::GenerateFEC(const PacketList& media_packet_list, bool use_unequal_protection, FecMaskType fec_mask_type, PacketList* fec_packet_list) { - if (media_packet_list.empty()) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s media packet list is empty", __FUNCTION__); - return -1; - } - if (!fec_packet_list->empty()) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s FEC packet list is not empty", __FUNCTION__); - return -1; - } const uint16_t num_media_packets = media_packet_list.size(); - bool l_bit = (num_media_packets > 8 * kMaskSizeLBitClear); - int num_maskBytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear; + + // Sanity check arguments. + assert(num_media_packets > 0); + assert(num_important_packets >= 0 && + num_important_packets <= num_media_packets); + assert(fec_packet_list->empty()); if (num_media_packets > kMaxMediaPackets) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s can only protect %d media packets per frame; %d requested", - __FUNCTION__, kMaxMediaPackets, num_media_packets); + LOG(LS_WARNING) << "Can't protect " << num_media_packets + << " media packets per frame. Max is " << kMaxMediaPackets; return -1; } - // Error checking on the number of important packets. - // Can't have more important packets than media packets. - if (num_important_packets > num_media_packets) { - WEBRTC_TRACE( - kTraceError, kTraceRtpRtcp, id_, - "Number of important packets (%d) greater than number of media " - "packets (%d)", - num_important_packets, num_media_packets); - return -1; - } - if (num_important_packets < 0) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "Number of important packets (%d) less than zero", - num_important_packets); - return -1; - } + bool l_bit = (num_media_packets > 8 * kMaskSizeLBitClear); + int num_maskBytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear; + // Do some error checking on the media packets. PacketList::const_iterator media_list_it = media_packet_list.begin(); while (media_list_it != media_packet_list.end()) { @@ -156,20 +135,16 @@ int32_t ForwardErrorCorrection::GenerateFEC(const PacketList& media_packet_list, assert(media_packet); if (media_packet->length < kRtpHeaderSize) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s media packet (%d bytes) is smaller than RTP header", - __FUNCTION__, media_packet->length); + LOG(LS_WARNING) << "Media packet " << media_packet->length << " bytes " + << "is smaller than RTP header."; return -1; } // Ensure our FEC packets will fit in a typical MTU. if (media_packet->length + PacketOverhead() + kTransportOverhead > IP_PACKET_SIZE) { - WEBRTC_TRACE( - kTraceError, kTraceRtpRtcp, id_, - "%s media packet (%d bytes) with overhead is larger than MTU(%d)", - __FUNCTION__, media_packet->length, IP_PACKET_SIZE); - return -1; + LOG(LS_WARNING) << "Media packet " << media_packet->length << " bytes " + << "with overhead is larger than " << IP_PACKET_SIZE; } media_list_it++; } @@ -582,9 +557,7 @@ void ForwardErrorCorrection::InsertFECPacket( } if (fec_packet->protected_pkt_list.empty()) { // All-zero packet mask; we can discard this FEC packet. - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "FEC packet %u has an all-zero packet mask.", - fec_packet->seq_num, __FUNCTION__); + LOG(LS_WARNING) << "FEC packet has an all-zero packet mask."; delete fec_packet; } else { AssignRecoveredPackets(fec_packet, recovered_packet_list); diff --git a/webrtc/modules/rtp_rtcp/source/forward_error_correction.h b/webrtc/modules/rtp_rtcp/source/forward_error_correction.h index 8910fe477..bb790f356 100644 --- a/webrtc/modules/rtp_rtcp/source/forward_error_correction.h +++ b/webrtc/modules/rtp_rtcp/source/forward_error_correction.h @@ -117,8 +117,7 @@ class ForwardErrorCorrection { typedef std::list ReceivedPacketList; typedef std::list RecoveredPacketList; - // \param[in] id Module ID - ForwardErrorCorrection(int32_t id); + ForwardErrorCorrection(); virtual ~ForwardErrorCorrection(); @@ -304,7 +303,6 @@ class ForwardErrorCorrection { static void DiscardOldPackets(RecoveredPacketList* recovered_packet_list); static uint16_t ParseSequenceNumber(uint8_t* packet); - int32_t id_; std::vector generated_fec_packets_; FecPacketList fec_packet_list_; bool fec_packet_received_; diff --git a/webrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc b/webrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc index 8c6cc5434..209af40bc 100644 --- a/webrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc @@ -164,7 +164,7 @@ class RtxLoopBackTransport : public webrtc::Transport { class RtpRtcpRtxNackTest : public ::testing::Test { protected: RtpRtcpRtxNackTest() - : rtp_payload_registry_(0, RTPPayloadStrategy::CreateStrategy(false)), + : rtp_payload_registry_(RTPPayloadStrategy::CreateStrategy(false)), rtp_rtcp_module_(NULL), transport_(kTestSsrc + 1), receiver_(), @@ -188,7 +188,7 @@ class RtpRtcpRtxNackTest : public ::testing::Test { kTestId, &fake_clock, &receiver_, rtp_feedback_.get(), &rtp_payload_registry_)); - EXPECT_EQ(0, rtp_rtcp_module_->SetSSRC(kTestSsrc)); + rtp_rtcp_module_->SetSSRC(kTestSsrc); EXPECT_EQ(0, rtp_rtcp_module_->SetRTCPStatus(kRtcpCompound)); rtp_receiver_->SetNACKStatus(kNackRtcp); EXPECT_EQ(0, rtp_rtcp_module_->SetStorePacketsStatus(true, 600)); @@ -253,9 +253,9 @@ class RtpRtcpRtxNackTest : public ::testing::Test { } void RunRtxTest(RtxMode rtx_method, int loss) { - rtp_payload_registry_.SetRtxStatus(true, kTestSsrc + 1); - EXPECT_EQ(0, rtp_rtcp_module_->SetRTXSendStatus(rtx_method, true, - kTestSsrc + 1)); + rtp_payload_registry_.SetRtxSsrc(kTestSsrc + 1); + rtp_rtcp_module_->SetRTXSendStatus(rtx_method); + rtp_rtcp_module_->SetRtxSsrc(kTestSsrc + 1); transport_.DropEveryNthPacket(loss); uint32_t timestamp = 3000; uint16_t nack_list[kVideoNackListSize]; diff --git a/webrtc/modules/rtp_rtcp/source/producer_fec_unittest.cc b/webrtc/modules/rtp_rtcp/source/producer_fec_unittest.cc index ada7d7026..baa382794 100644 --- a/webrtc/modules/rtp_rtcp/source/producer_fec_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/producer_fec_unittest.cc @@ -39,7 +39,7 @@ void VerifyHeader(uint16_t seq_num, class ProducerFecTest : public ::testing::Test { protected: virtual void SetUp() { - fec_ = new ForwardErrorCorrection(0); + fec_ = new ForwardErrorCorrection(); producer_ = new ProducerFec(fec_); generator_ = new FrameGenerator; } diff --git a/webrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc b/webrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc new file mode 100644 index 000000000..0d71c26b6 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014 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 "webrtc/modules/rtp_rtcp/interface/remote_ntp_time_estimator.h" + +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h" +#include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/system_wrappers/interface/timestamp_extrapolator.h" + +namespace webrtc { + +// TODO(wu): Refactor this class so that it can be shared with +// vie_sync_module.cc. +RemoteNtpTimeEstimator::RemoteNtpTimeEstimator(Clock* clock) + : clock_(clock), + ts_extrapolator_( + new TimestampExtrapolator(clock_->TimeInMilliseconds())) { +} + +RemoteNtpTimeEstimator::~RemoteNtpTimeEstimator() {} + +bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(uint32_t ssrc, + RtpRtcp* rtp_rtcp) { + assert(rtp_rtcp); + uint16_t rtt = 0; + rtp_rtcp->RTT(ssrc, &rtt, NULL, NULL, NULL); + if (rtt == 0) { + // Waiting for valid rtt. + return true; + } + // Update RTCP list + uint32_t ntp_secs = 0; + uint32_t ntp_frac = 0; + uint32_t rtp_timestamp = 0; + if (0 != rtp_rtcp->RemoteNTP(&ntp_secs, + &ntp_frac, + NULL, + NULL, + &rtp_timestamp)) { + // Waiting for RTCP. + return true; + } + bool new_rtcp_sr = false; + if (!UpdateRtcpList( + ntp_secs, ntp_frac, rtp_timestamp, &rtcp_list_, &new_rtcp_sr)) { + return false; + } + if (!new_rtcp_sr) { + // No new RTCP SR since last time this function was called. + return true; + } + // Update extrapolator with the new arrival time. + // The extrapolator assumes the TimeInMilliseconds time. + int64_t receiver_arrival_time_ms = clock_->TimeInMilliseconds(); + int64_t sender_send_time_ms = Clock::NtpToMs(ntp_secs, ntp_frac); + int64_t sender_arrival_time_90k = (sender_send_time_ms + rtt / 2) * 90; + ts_extrapolator_->Update(receiver_arrival_time_ms, sender_arrival_time_90k); + return true; +} + +int64_t RemoteNtpTimeEstimator::Estimate(uint32_t rtp_timestamp) { + if (rtcp_list_.size() < 2) { + // We need two RTCP SR reports to calculate NTP. + return -1; + } + int64_t sender_capture_ntp_ms = 0; + if (!RtpToNtpMs(rtp_timestamp, rtcp_list_, &sender_capture_ntp_ms)) { + return -1; + } + uint32_t timestamp = sender_capture_ntp_ms * 90; + int64_t receiver_capture_ms = + ts_extrapolator_->ExtrapolateLocalTime(timestamp); + int64_t ntp_offset = + clock_->CurrentNtpInMilliseconds() - clock_->TimeInMilliseconds(); + return receiver_capture_ms + ntp_offset; +} + +} // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc b/webrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc new file mode 100644 index 000000000..63cedf03a --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc @@ -0,0 +1,112 @@ +/* +* Copyright (c) 2014 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 "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/common_types.h" +#include "webrtc/modules/rtp_rtcp/interface/remote_ntp_time_estimator.h" +#include "webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgPointee; + +namespace webrtc { + +static const int kTestRtt = 10; +static const int64_t kLocalClockInitialTimeMs = 123; +static const int64_t kRemoteClockInitialTimeMs = 345; +static const uint32_t kTimestampOffset = 567; +static const int kTestSsrc = 789; + +class RemoteNtpTimeEstimatorTest : public ::testing::Test { + protected: + RemoteNtpTimeEstimatorTest() + : local_clock_(kLocalClockInitialTimeMs * 1000), + remote_clock_(kRemoteClockInitialTimeMs * 1000), + estimator_(&local_clock_) {} + ~RemoteNtpTimeEstimatorTest() {} + + void AdvanceTimeMilliseconds(int64_t ms) { + local_clock_.AdvanceTimeMilliseconds(ms); + remote_clock_.AdvanceTimeMilliseconds(ms); + } + + uint32_t GetRemoteTimestamp() { + return static_cast(remote_clock_.TimeInMilliseconds()) * 90 + + kTimestampOffset; + } + + void SendRtcpSr() { + uint32_t rtcp_timestamp = GetRemoteTimestamp(); + uint32_t ntp_seconds; + uint32_t ntp_fractions; + remote_clock_.CurrentNtp(ntp_seconds, ntp_fractions); + + AdvanceTimeMilliseconds(kTestRtt / 2); + ReceiveRtcpSr(rtcp_timestamp, ntp_seconds, ntp_fractions); + } + + void UpdateRtcpTimestamp(MockRtpRtcp* rtp_rtcp, bool expected_result) { + if (rtp_rtcp) { + EXPECT_CALL(*rtp_rtcp, RTT(_, _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(kTestRtt), + Return(0))); + } + EXPECT_EQ(expected_result, + estimator_.UpdateRtcpTimestamp(kTestSsrc, rtp_rtcp)); + } + + void ReceiveRtcpSr(uint32_t rtcp_timestamp, + uint32_t ntp_seconds, + uint32_t ntp_fractions) { + EXPECT_CALL(rtp_rtcp_, RemoteNTP(_, _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<0>(ntp_seconds), + SetArgPointee<1>(ntp_fractions), + SetArgPointee<4>(rtcp_timestamp), + Return(0))); + + UpdateRtcpTimestamp(&rtp_rtcp_, true); + } + + SimulatedClock local_clock_; + SimulatedClock remote_clock_; + MockRtpRtcp rtp_rtcp_; + RemoteNtpTimeEstimator estimator_; +}; + +TEST_F(RemoteNtpTimeEstimatorTest, Estimate) { + // Failed without any RTCP SR, where RemoteNTP returns without valid NTP. + EXPECT_CALL(rtp_rtcp_, RemoteNTP(_, _, _, _, _)).WillOnce(Return(0)); + UpdateRtcpTimestamp(&rtp_rtcp_, false); + + AdvanceTimeMilliseconds(1000); + // Remote peer sends first RTCP SR. + SendRtcpSr(); + + // Remote sends a RTP packet. + AdvanceTimeMilliseconds(15); + uint32_t rtp_timestamp = GetRemoteTimestamp(); + int64_t capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds(); + + // Local peer needs at least 2 RTCP SR to calculate the capture time. + const int64_t kNotEnoughRtcpSr = -1; + EXPECT_EQ(kNotEnoughRtcpSr, estimator_.Estimate(rtp_timestamp)); + + AdvanceTimeMilliseconds(800); + // Remote sends second RTCP SR. + SendRtcpSr(); + + // Local peer gets enough RTCP SR to calculate the capture time. + EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp)); +} + +} // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet.cc b/webrtc/modules/rtp_rtcp/source/rtcp_packet.cc index 0c3197fe1..f6d3bd3d7 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_packet.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet.cc @@ -11,30 +11,115 @@ #include "webrtc/modules/rtp_rtcp/source/rtcp_packet.h" #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" + +using webrtc::RTCPUtility::kBtDlrr; +using webrtc::RTCPUtility::kBtReceiverReferenceTime; +using webrtc::RTCPUtility::kBtVoipMetric; + +using webrtc::RTCPUtility::PT_APP; +using webrtc::RTCPUtility::PT_BYE; +using webrtc::RTCPUtility::PT_IJ; +using webrtc::RTCPUtility::PT_PSFB; +using webrtc::RTCPUtility::PT_RR; +using webrtc::RTCPUtility::PT_RTPFB; +using webrtc::RTCPUtility::PT_SDES; +using webrtc::RTCPUtility::PT_SR; +using webrtc::RTCPUtility::PT_XR; + +using webrtc::RTCPUtility::RTCPPacketAPP; +using webrtc::RTCPUtility::RTCPPacketBYE; +using webrtc::RTCPUtility::RTCPPacketPSFBAPP; +using webrtc::RTCPUtility::RTCPPacketPSFBFIR; +using webrtc::RTCPUtility::RTCPPacketPSFBFIRItem; +using webrtc::RTCPUtility::RTCPPacketPSFBPLI; +using webrtc::RTCPUtility::RTCPPacketPSFBREMBItem; +using webrtc::RTCPUtility::RTCPPacketPSFBRPSI; +using webrtc::RTCPUtility::RTCPPacketPSFBSLI; +using webrtc::RTCPUtility::RTCPPacketPSFBSLIItem; +using webrtc::RTCPUtility::RTCPPacketReportBlockItem; +using webrtc::RTCPUtility::RTCPPacketRR; +using webrtc::RTCPUtility::RTCPPacketRTPFBNACK; +using webrtc::RTCPUtility::RTCPPacketRTPFBNACKItem; +using webrtc::RTCPUtility::RTCPPacketRTPFBTMMBN; +using webrtc::RTCPUtility::RTCPPacketRTPFBTMMBNItem; +using webrtc::RTCPUtility::RTCPPacketRTPFBTMMBR; +using webrtc::RTCPUtility::RTCPPacketRTPFBTMMBRItem; +using webrtc::RTCPUtility::RTCPPacketSR; +using webrtc::RTCPUtility::RTCPPacketXRDLRRReportBlockItem; +using webrtc::RTCPUtility::RTCPPacketXRReceiverReferenceTimeItem; +using webrtc::RTCPUtility::RTCPPacketXR; +using webrtc::RTCPUtility::RTCPPacketXRVOIPMetricItem; namespace webrtc { namespace rtcp { namespace { -void AssignUWord8(uint8_t* buffer, uint16_t* offset, uint8_t value) { +// Unused SSRC of media source, set to 0. +const uint32_t kUnusedMediaSourceSsrc0 = 0; + +void AssignUWord8(uint8_t* buffer, size_t* offset, uint8_t value) { buffer[(*offset)++] = value; } -void AssignUWord16(uint8_t* buffer, uint16_t* offset, uint16_t value) { +void AssignUWord16(uint8_t* buffer, size_t* offset, uint16_t value) { ModuleRTPUtility::AssignUWord16ToBuffer(buffer + *offset, value); *offset += 2; } -void AssignUWord24(uint8_t* buffer, uint16_t* offset, uint32_t value) { +void AssignUWord24(uint8_t* buffer, size_t* offset, uint32_t value) { ModuleRTPUtility::AssignUWord24ToBuffer(buffer + *offset, value); *offset += 3; } -void AssignUWord32(uint8_t* buffer, uint16_t* offset, uint32_t value) { +void AssignUWord32(uint8_t* buffer, size_t* offset, uint32_t value) { ModuleRTPUtility::AssignUWord32ToBuffer(buffer + *offset, value); *offset += 4; } +void ComputeMantissaAnd6bitBase2Exponent(uint32_t input_base10, + uint8_t bits_mantissa, + uint32_t* mantissa, + uint8_t* exp) { + // input_base10 = mantissa * 2^exp + assert(bits_mantissa <= 32); + uint32_t mantissa_max = (1 << bits_mantissa) - 1; + uint8_t exponent = 0; + for (uint32_t i = 0; i < 64; ++i) { + if (input_base10 <= (mantissa_max << i)) { + exponent = i; + break; + } + } + *exp = exponent; + *mantissa = (input_base10 >> exponent); +} + +size_t BlockToHeaderLength(size_t length_in_bytes) { + // Length in 32-bit words minus 1. + assert(length_in_bytes > 0); + assert(length_in_bytes % 4 == 0); + return (length_in_bytes / 4) - 1; +} + // From RFC 3550, RTP: A Transport Protocol for Real-Time Applications. // -// Sender report +// RTP header format. +// 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| RC/FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateHeader(uint8_t count_or_format, // Depends on packet type. + uint8_t packet_type, + size_t length, + uint8_t* buffer, + size_t* pos) { + assert(length <= 0xffff); + const uint8_t kVersion = 2; + AssignUWord8(buffer, pos, (kVersion << 6) + count_or_format); + AssignUWord8(buffer, pos, packet_type); + AssignUWord16(buffer, pos, length); +} + +// Sender report (SR) (RFC 3550). // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -53,13 +138,11 @@ void AssignUWord32(uint8_t* buffer, uint16_t* offset, uint32_t value) { // | sender's octet count | // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -void CreateSenderReport(const RTCPUtility::RTCPPacketSR& sr, +void CreateSenderReport(const RTCPPacketSR& sr, + size_t length, uint8_t* buffer, - uint16_t* pos) { - const uint16_t kLength = 6 + (6 * sr.NumberOfReportBlocks); - AssignUWord8(buffer, pos, 0x80 + sr.NumberOfReportBlocks); - AssignUWord8(buffer, pos, RTCPUtility::PT_SR); - AssignUWord16(buffer, pos, kLength); + size_t* pos) { + CreateHeader(sr.NumberOfReportBlocks, PT_SR, length, buffer, pos); AssignUWord32(buffer, pos, sr.SenderSSRC); AssignUWord32(buffer, pos, sr.NTPMostSignificant); AssignUWord32(buffer, pos, sr.NTPLeastSignificant); @@ -68,7 +151,7 @@ void CreateSenderReport(const RTCPUtility::RTCPPacketSR& sr, AssignUWord32(buffer, pos, sr.SenderOctetCount); } -// Receiver report, header (RFC 3550). +// Receiver report (RR), header (RFC 3550). // // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -77,13 +160,11 @@ void CreateSenderReport(const RTCPUtility::RTCPPacketSR& sr, // | SSRC of packet sender | // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -void CreateReceiverReport(const RTCPUtility::RTCPPacketRR& rr, +void CreateReceiverReport(const RTCPPacketRR& rr, + size_t length, uint8_t* buffer, - uint16_t* pos) { - const uint16_t kLength = 1 + (6 * rr.NumberOfReportBlocks); - AssignUWord8(buffer, pos, 0x80 + rr.NumberOfReportBlocks); - AssignUWord8(buffer, pos, RTCPUtility::PT_RR); - AssignUWord16(buffer, pos, kLength); + size_t* pos) { + CreateHeader(rr.NumberOfReportBlocks, PT_RR, length, buffer, pos); AssignUWord32(buffer, pos, rr.SenderSSRC); } @@ -104,17 +185,89 @@ void CreateReceiverReport(const RTCPUtility::RTCPPacketRR& rr, // | delay since last SR (DLSR) | // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -void CreateReportBlock( - const RTCPUtility::RTCPPacketReportBlockItem& report_block, - uint8_t* buffer, - uint16_t* pos) { - AssignUWord32(buffer, pos, report_block.SSRC); - AssignUWord8(buffer, pos, report_block.FractionLost); - AssignUWord24(buffer, pos, report_block.CumulativeNumOfPacketsLost); - AssignUWord32(buffer, pos, report_block.ExtendedHighestSequenceNumber); - AssignUWord32(buffer, pos, report_block.Jitter); - AssignUWord32(buffer, pos, report_block.LastSR); - AssignUWord32(buffer, pos, report_block.DelayLastSR); +void CreateReportBlocks(const std::vector& blocks, + uint8_t* buffer, + size_t* pos) { + for (std::vector::const_iterator + it = blocks.begin(); it != blocks.end(); ++it) { + AssignUWord32(buffer, pos, (*it).SSRC); + AssignUWord8(buffer, pos, (*it).FractionLost); + AssignUWord24(buffer, pos, (*it).CumulativeNumOfPacketsLost); + AssignUWord32(buffer, pos, (*it).ExtendedHighestSequenceNumber); + AssignUWord32(buffer, pos, (*it).Jitter); + AssignUWord32(buffer, pos, (*it).LastSR); + AssignUWord32(buffer, pos, (*it).DelayLastSR); + } +} + +// Transmission Time Offsets in RTP Streams (RFC 5450). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// hdr |V=2|P| RC | PT=IJ=195 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | inter-arrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// . . +// | inter-arrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateIj(const std::vector& ij_items, + uint8_t* buffer, + size_t* pos) { + size_t length = ij_items.size(); + CreateHeader(length, PT_IJ, length, buffer, pos); + for (std::vector::const_iterator it = ij_items.begin(); + it != ij_items.end(); ++it) { + AssignUWord32(buffer, pos, *it); + } +} + +// Source Description (SDES) (RFC 3550). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// header |V=2|P| SC | PT=SDES=202 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_1 | +// 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_2 | +// 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Canonical End-Point Identifier SDES Item (CNAME) +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | CNAME=1 | length | user and domain name ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateSdes(const std::vector& chunks, + size_t length, + uint8_t* buffer, + size_t* pos) { + CreateHeader(chunks.size(), PT_SDES, length, buffer, pos); + const uint8_t kSdesItemType = 1; + for (std::vector::const_iterator it = chunks.begin(); + it != chunks.end(); ++it) { + AssignUWord32(buffer, pos, (*it).ssrc); + AssignUWord8(buffer, pos, kSdesItemType); + AssignUWord8(buffer, pos, (*it).name.length()); + memcpy(buffer + *pos, (*it).name.data(), (*it).name.length()); + *pos += (*it).name.length(); + memset(buffer + *pos, 0, (*it).null_octets); + *pos += (*it).null_octets; + } } // Bye packet (BYE) (RFC 3550). @@ -130,14 +283,12 @@ void CreateReportBlock( // (opt) | length | reason for leaving ... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -void CreateBye(const RTCPUtility::RTCPPacketBYE& bye, +void CreateBye(const RTCPPacketBYE& bye, const std::vector& csrcs, + size_t length, uint8_t* buffer, - uint16_t* pos) { - const uint8_t kNumSsrcAndCsrcs = 1 + csrcs.size(); - AssignUWord8(buffer, pos, 0x80 + kNumSsrcAndCsrcs); - AssignUWord8(buffer, pos, RTCPUtility::PT_BYE); - AssignUWord16(buffer, pos, kNumSsrcAndCsrcs); + size_t* pos) { + CreateHeader(length, PT_BYE, length, buffer, pos); AssignUWord32(buffer, pos, bye.SenderSSRC); for (std::vector::const_iterator it = csrcs.begin(); it != csrcs.end(); ++it) { @@ -145,6 +296,32 @@ void CreateBye(const RTCPUtility::RTCPPacketBYE& bye, } } +// Application-Defined packet (APP) (RFC 3550). +// +// 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 ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateApp(const RTCPPacketAPP& app, + uint32_t ssrc, + size_t length, + uint8_t* buffer, + size_t* pos) { + CreateHeader(app.SubType, PT_APP, length, buffer, pos); + AssignUWord32(buffer, pos, ssrc); + AssignUWord32(buffer, pos, app.Name); + memcpy(buffer + *pos, app.Data, app.Size); + *pos += app.Size; +} + // RFC 4585: Feedback format. // // Common packet format: @@ -161,6 +338,105 @@ void CreateBye(const RTCPUtility::RTCPPacketBYE& bye, // : Feedback Control Information (FCI) : // : // + +// Picture loss indication (PLI) (RFC 4585). +// +// FCI: no feedback control information. + +void CreatePli(const RTCPPacketPSFBPLI& pli, + size_t length, + uint8_t* buffer, + size_t* pos) { + const uint8_t kFmt = 1; + CreateHeader(kFmt, PT_PSFB, length, buffer, pos); + AssignUWord32(buffer, pos, pli.SenderSSRC); + AssignUWord32(buffer, pos, pli.MediaSSRC); +} + +// Slice loss indication (SLI) (RFC 4585). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | First | Number | PictureID | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateSli(const RTCPPacketPSFBSLI& sli, + const RTCPPacketPSFBSLIItem& sli_item, + size_t length, + uint8_t* buffer, + size_t* pos) { + const uint8_t kFmt = 2; + CreateHeader(kFmt, PT_PSFB, length, buffer, pos); + AssignUWord32(buffer, pos, sli.SenderSSRC); + AssignUWord32(buffer, pos, sli.MediaSSRC); + + AssignUWord8(buffer, pos, sli_item.FirstMB >> 5); + AssignUWord8(buffer, pos, (sli_item.FirstMB << 3) + + ((sli_item.NumberOfMB >> 10) & 0x07)); + AssignUWord8(buffer, pos, sli_item.NumberOfMB >> 2); + AssignUWord8(buffer, pos, (sli_item.NumberOfMB << 6) + sli_item.PictureId); +} + +// Generic NACK (RFC 4585). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PID | BLP | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateNack(const RTCPPacketRTPFBNACK& nack, + const std::vector& nack_fields, + size_t length, + uint8_t* buffer, + size_t* pos) { + const uint8_t kFmt = 1; + CreateHeader(kFmt, PT_RTPFB, length, buffer, pos); + AssignUWord32(buffer, pos, nack.SenderSSRC); + AssignUWord32(buffer, pos, nack.MediaSSRC); + for (std::vector::const_iterator + it = nack_fields.begin(); it != nack_fields.end(); ++it) { + AssignUWord16(buffer, pos, (*it).PacketID); + AssignUWord16(buffer, pos, (*it).BitMask); + } +} + +// Reference picture selection indication (RPSI) (RFC 4585). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PB |0| Payload Type| Native RPSI bit string | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | defined per codec ... | Padding (0) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateRpsi(const RTCPPacketPSFBRPSI& rpsi, + uint8_t padding_bytes, + size_t length, + uint8_t* buffer, + size_t* pos) { + // Native bit string should be a multiple of 8 bits. + assert(rpsi.NumberOfValidBits % 8 == 0); + const uint8_t kFmt = 3; + CreateHeader(kFmt, PT_PSFB, length, buffer, pos); + AssignUWord32(buffer, pos, rpsi.SenderSSRC); + AssignUWord32(buffer, pos, rpsi.MediaSSRC); + AssignUWord8(buffer, pos, padding_bytes * 8); + AssignUWord8(buffer, pos, rpsi.PayloadType); + memcpy(buffer + *pos, rpsi.NativeBitString, rpsi.NumberOfValidBits / 8); + *pos += rpsi.NumberOfValidBits / 8; + memset(buffer + *pos, 0, padding_bytes); + *pos += padding_bytes; +} + // Full intra request (FIR) (RFC 5104). // // FCI: @@ -173,28 +449,275 @@ void CreateBye(const RTCPUtility::RTCPPacketBYE& bye, // | Seq nr. | Reserved | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -void CreateFirRequest(const RTCPUtility::RTCPPacketPSFBFIR& fir, - const RTCPUtility::RTCPPacketPSFBFIRItem& fir_item, - uint8_t* buffer, - uint16_t* pos) { - const uint16_t kLength = 4; +void CreateFir(const RTCPPacketPSFBFIR& fir, + const RTCPPacketPSFBFIRItem& fir_item, + size_t length, + uint8_t* buffer, + size_t* pos) { const uint8_t kFmt = 4; - AssignUWord8(buffer, pos, 0x80 + kFmt); - AssignUWord8(buffer, pos, RTCPUtility::PT_PSFB); - AssignUWord16(buffer, pos, kLength); + CreateHeader(kFmt, PT_PSFB, length, buffer, pos); AssignUWord32(buffer, pos, fir.SenderSSRC); - AssignUWord32(buffer, pos, 0); + AssignUWord32(buffer, pos, kUnusedMediaSourceSsrc0); AssignUWord32(buffer, pos, fir_item.SSRC); AssignUWord8(buffer, pos, fir_item.CommandSequenceNumber); AssignUWord24(buffer, pos, 0); } -void AppendReportBlocks(const std::vector& report_blocks, - uint8_t* buffer, - uint16_t* pos) { - for (std::vector::const_iterator it = report_blocks.begin(); - it != report_blocks.end(); ++it) { - (*it)->Create(buffer, pos); +void CreateTmmbrItem(const RTCPPacketRTPFBTMMBRItem& tmmbr_item, + uint8_t* buffer, + size_t* pos) { + uint32_t bitrate_bps = tmmbr_item.MaxTotalMediaBitRate * 1000; + uint32_t mantissa = 0; + uint8_t exp = 0; + ComputeMantissaAnd6bitBase2Exponent(bitrate_bps, 17, &mantissa, &exp); + + AssignUWord32(buffer, pos, tmmbr_item.SSRC); + AssignUWord8(buffer, pos, (exp << 2) + ((mantissa >> 15) & 0x03)); + AssignUWord8(buffer, pos, mantissa >> 7); + AssignUWord8(buffer, pos, (mantissa << 1) + + ((tmmbr_item.MeasuredOverhead >> 8) & 0x01)); + AssignUWord8(buffer, pos, tmmbr_item.MeasuredOverhead); +} + +// Temporary Maximum Media Stream Bit Rate Request (TMMBR) (RFC 5104). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateTmmbr(const RTCPPacketRTPFBTMMBR& tmmbr, + const RTCPPacketRTPFBTMMBRItem& tmmbr_item, + size_t length, + uint8_t* buffer, + size_t* pos) { + const uint8_t kFmt = 3; + CreateHeader(kFmt, PT_RTPFB, length, buffer, pos); + AssignUWord32(buffer, pos, tmmbr.SenderSSRC); + AssignUWord32(buffer, pos, kUnusedMediaSourceSsrc0); + CreateTmmbrItem(tmmbr_item, buffer, pos); +} + +// Temporary Maximum Media Stream Bit Rate Notification (TMMBN) (RFC 5104). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateTmmbn(const RTCPPacketRTPFBTMMBN& tmmbn, + const std::vector& tmmbn_items, + size_t length, + uint8_t* buffer, + size_t* pos) { + const uint8_t kFmt = 4; + CreateHeader(kFmt, PT_RTPFB, length, buffer, pos); + AssignUWord32(buffer, pos, tmmbn.SenderSSRC); + AssignUWord32(buffer, pos, kUnusedMediaSourceSsrc0); + for (uint8_t i = 0; i < tmmbn_items.size(); ++i) { + CreateTmmbrItem(tmmbn_items[i], buffer, pos); + } +} + +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +// +// 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| FMT=15 | PT=206 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Unique identifier 'R' 'E' 'M' 'B' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Num SSRC | BR Exp | BR Mantissa | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC feedback | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... | + +void CreateRemb(const RTCPPacketPSFBAPP& remb, + const RTCPPacketPSFBREMBItem& remb_item, + size_t length, + uint8_t* buffer, + size_t* pos) { + uint32_t mantissa = 0; + uint8_t exp = 0; + ComputeMantissaAnd6bitBase2Exponent(remb_item.BitRate, 18, &mantissa, &exp); + + const uint8_t kFmt = 15; + CreateHeader(kFmt, PT_PSFB, length, buffer, pos); + AssignUWord32(buffer, pos, remb.SenderSSRC); + AssignUWord32(buffer, pos, kUnusedMediaSourceSsrc0); + AssignUWord8(buffer, pos, 'R'); + AssignUWord8(buffer, pos, 'E'); + AssignUWord8(buffer, pos, 'M'); + AssignUWord8(buffer, pos, 'B'); + AssignUWord8(buffer, pos, remb_item.NumberOfSSRCs); + AssignUWord8(buffer, pos, (exp << 2) + ((mantissa >> 16) & 0x03)); + AssignUWord8(buffer, pos, mantissa >> 8); + AssignUWord8(buffer, pos, mantissa); + for (uint8_t i = 0; i < remb_item.NumberOfSSRCs; ++i) { + AssignUWord32(buffer, pos, remb_item.SSRCs[i]); + } +} + +// From RFC 3611: RTP Control Protocol Extended Reports (RTCP XR). +// +// Format for XR packets: +// +// 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|reserved | PT=XR=207 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : report blocks : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateXrHeader(const RTCPPacketXR& header, + size_t length, + uint8_t* buffer, + size_t* pos) { + CreateHeader(0U, PT_XR, length, buffer, pos); + AssignUWord32(buffer, pos, header.OriginatorSSRC); +} + +void CreateXrBlockHeader(uint8_t block_type, + uint16_t block_length, + uint8_t* buffer, + size_t* pos) { + AssignUWord8(buffer, pos, block_type); + AssignUWord8(buffer, pos, 0); + AssignUWord16(buffer, pos, block_length); +} + +// Receiver Reference Time Report Block (RFC 3611). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=4 | reserved | block length = 2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, most significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, least significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateRrtr(const std::vector& rrtrs, + uint8_t* buffer, + size_t* pos) { + const uint16_t kBlockLength = 2; + for (std::vector::const_iterator it = + rrtrs.begin(); it != rrtrs.end(); ++it) { + CreateXrBlockHeader(kBtReceiverReferenceTime, kBlockLength, buffer, pos); + AssignUWord32(buffer, pos, (*it).NTPMostSignificant); + AssignUWord32(buffer, pos, (*it).NTPLeastSignificant); + } +} + +// DLRR Report Block (RFC 3611). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=5 | reserved | block length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_1 (SSRC of first receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// | last RR (LRR) | 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | delay since last RR (DLRR) | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_2 (SSRC of second receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// : ... : 2 + +void CreateDlrr(const std::vector& dlrrs, + uint8_t* buffer, + size_t* pos) { + for (std::vector::const_iterator it = dlrrs.begin(); + it != dlrrs.end(); ++it) { + if ((*it).empty()) { + continue; + } + uint16_t block_length = 3 * (*it).size(); + CreateXrBlockHeader(kBtDlrr, block_length, buffer, pos); + for (Xr::DlrrBlock::const_iterator it_block = (*it).begin(); + it_block != (*it).end(); ++it_block) { + AssignUWord32(buffer, pos, (*it_block).SSRC); + AssignUWord32(buffer, pos, (*it_block).LastRR); + AssignUWord32(buffer, pos, (*it_block).DelayLastRR); + } + } +} + +// VoIP Metrics Report Block (RFC 3611). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=7 | reserved | block length = 8 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | loss rate | discard rate | burst density | gap density | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | burst duration | gap duration | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | round trip delay | end system delay | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | signal level | noise level | RERL | Gmin | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | R factor | ext. R factor | MOS-LQ | MOS-CQ | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | RX config | reserved | JB nominal | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | JB maximum | JB abs max | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void CreateVoipMetric(const std::vector& metrics, + uint8_t* buffer, + size_t* pos) { + const uint16_t kBlockLength = 8; + for (std::vector::const_iterator it = + metrics.begin(); it != metrics.end(); ++it) { + CreateXrBlockHeader(kBtVoipMetric, kBlockLength, buffer, pos); + AssignUWord32(buffer, pos, (*it).SSRC); + AssignUWord8(buffer, pos, (*it).lossRate); + AssignUWord8(buffer, pos, (*it).discardRate); + AssignUWord8(buffer, pos, (*it).burstDensity); + AssignUWord8(buffer, pos, (*it).gapDensity); + AssignUWord16(buffer, pos, (*it).burstDuration); + AssignUWord16(buffer, pos, (*it).gapDuration); + AssignUWord16(buffer, pos, (*it).roundTripDelay); + AssignUWord16(buffer, pos, (*it).endSystemDelay); + AssignUWord8(buffer, pos, (*it).signalLevel); + AssignUWord8(buffer, pos, (*it).noiseLevel); + AssignUWord8(buffer, pos, (*it).RERL); + AssignUWord8(buffer, pos, (*it).Gmin); + AssignUWord8(buffer, pos, (*it).Rfactor); + AssignUWord8(buffer, pos, (*it).extRfactor); + AssignUWord8(buffer, pos, (*it).MOSLQ); + AssignUWord8(buffer, pos, (*it).MOSCQ); + AssignUWord8(buffer, pos, (*it).RXconfig); + AssignUWord8(buffer, pos, 0); + AssignUWord16(buffer, pos, (*it).JBnominal); + AssignUWord16(buffer, pos, (*it).JBmax); + AssignUWord16(buffer, pos, (*it).JBabsMax); } } } // namespace @@ -205,105 +728,363 @@ void RtcpPacket::Append(RtcpPacket* packet) { } RawPacket RtcpPacket::Build() const { - uint16_t len = 0; + size_t length = 0; uint8_t packet[IP_PACKET_SIZE]; - CreateAndAddAppended(packet, &len, IP_PACKET_SIZE); - return RawPacket(packet, len); + CreateAndAddAppended(packet, &length, IP_PACKET_SIZE); + return RawPacket(packet, length); } -void RtcpPacket::Build(uint8_t* packet, uint16_t* len, uint16_t max_len) const { - *len = 0; - CreateAndAddAppended(packet, len, max_len); +void RtcpPacket::Build(uint8_t* packet, + size_t* length, + size_t max_length) const { + *length = 0; + CreateAndAddAppended(packet, length, max_length); } void RtcpPacket::CreateAndAddAppended(uint8_t* packet, - uint16_t* len, - uint16_t max_len) const { - Create(packet, len, max_len); + size_t* length, + size_t max_length) const { + Create(packet, length, max_length); for (std::vector::const_iterator it = appended_packets_.begin(); it != appended_packets_.end(); ++it) { - (*it)->CreateAndAddAppended(packet, len, max_len); + (*it)->CreateAndAddAppended(packet, length, max_length); } } -void Empty::Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const { +void Empty::Create(uint8_t* packet, size_t* length, size_t max_length) const { } void SenderReport::Create(uint8_t* packet, - uint16_t* len, - uint16_t max_len) const { - if (*len + Length() > max_len) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Max packet size reached, skipped SR."); + size_t* length, + size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; return; } - CreateSenderReport(sr_, packet, len); - AppendReportBlocks(report_blocks_, packet, len); + CreateSenderReport(sr_, BlockToHeaderLength(BlockLength()), packet, length); + CreateReportBlocks(report_blocks_, packet, length); } void SenderReport::WithReportBlock(ReportBlock* block) { assert(block); if (report_blocks_.size() >= kMaxNumberOfReportBlocks) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Max report block size reached."); + LOG(LS_WARNING) << "Max report blocks reached."; return; } - report_blocks_.push_back(block); + report_blocks_.push_back(block->report_block_); sr_.NumberOfReportBlocks = report_blocks_.size(); } void ReceiverReport::Create(uint8_t* packet, - uint16_t* len, - uint16_t max_len) const { - if (*len + Length() > max_len) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Max packet size reached, skipped RR."); + size_t* length, + size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; return; } - CreateReceiverReport(rr_, packet, len); - AppendReportBlocks(report_blocks_, packet, len); + CreateReceiverReport(rr_, BlockToHeaderLength(BlockLength()), packet, length); + CreateReportBlocks(report_blocks_, packet, length); } void ReceiverReport::WithReportBlock(ReportBlock* block) { assert(block); if (report_blocks_.size() >= kMaxNumberOfReportBlocks) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Max report block size reached."); + LOG(LS_WARNING) << "Max report blocks reached."; return; } - report_blocks_.push_back(block); + report_blocks_.push_back(block->report_block_); rr_.NumberOfReportBlocks = report_blocks_.size(); } -void Bye::Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const { - if (*len + Length() > max_len) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Max packet size reached, skipped BYE."); +void Ij::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateIj(ij_items_, packet, length); +} + +void Ij::WithJitterItem(uint32_t jitter) { + if (ij_items_.size() >= kMaxNumberOfIjItems) { + LOG(LS_WARNING) << "Max inter-arrival jitter items reached."; return; } - CreateBye(bye_, csrcs_, packet, len); + ij_items_.push_back(jitter); +} + +void Sdes::Create(uint8_t* packet, size_t* length, size_t max_length) const { + assert(!chunks_.empty()); + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateSdes(chunks_, BlockToHeaderLength(BlockLength()), packet, length); +} + +void Sdes::WithCName(uint32_t ssrc, std::string cname) { + assert(cname.length() <= 0xff); + if (chunks_.size() >= kMaxNumberOfChunks) { + LOG(LS_WARNING) << "Max SDES chunks reached."; + return; + } + // In each chunk, the list of items must be terminated by one or more null + // octets. The next chunk must start on a 32-bit boundary. + // CNAME (1 byte) | length (1 byte) | name | padding. + int null_octets = 4 - ((2 + cname.length()) % 4); + Chunk chunk; + chunk.ssrc = ssrc; + chunk.name = cname; + chunk.null_octets = null_octets; + chunks_.push_back(chunk); +} + +size_t Sdes::BlockLength() const { + // Header (4 bytes). + // Chunk: + // SSRC/CSRC (4 bytes) | CNAME (1 byte) | length (1 byte) | name | padding. + size_t length = kHeaderLength; + for (std::vector::const_iterator it = chunks_.begin(); + it != chunks_.end(); ++it) { + length += 6 + (*it).name.length() + (*it).null_octets; + } + assert(length % 4 == 0); + return length; +} + +void Bye::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateBye(bye_, csrcs_, BlockToHeaderLength(BlockLength()), packet, length); } void Bye::WithCsrc(uint32_t csrc) { if (csrcs_.size() >= kMaxNumberOfCsrcs) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Max CSRC size reached."); + LOG(LS_WARNING) << "Max CSRC size reached."; return; } csrcs_.push_back(csrc); } -void Fir::Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const { - if (*len + Length() > max_len) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Max packet size reached, skipped FIR."); +void App::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; return; } - CreateFirRequest(fir_, fir_item_, packet, len); + CreateApp(app_, ssrc_, BlockToHeaderLength(BlockLength()), packet, length); } -void ReportBlock::Create(uint8_t* packet, uint16_t* len) const { - CreateReportBlock(report_block_, packet, len); +void Pli::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreatePli(pli_, BlockToHeaderLength(BlockLength()), packet, length); } + +void Sli::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateSli(sli_, sli_item_, BlockToHeaderLength(BlockLength()), packet, + length); +} + +void Nack::Create(uint8_t* packet, size_t* length, size_t max_length) const { + assert(!nack_fields_.empty()); + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateNack(nack_, nack_fields_, BlockToHeaderLength(BlockLength()), packet, + length); +} + +void Nack::WithList(const uint16_t* nack_list, int length) { + assert(nack_list); + assert(nack_fields_.empty()); + int i = 0; + while (i < length) { + uint16_t pid = nack_list[i++]; + // Bitmask specifies losses in any of the 16 packets following the pid. + uint16_t bitmask = 0; + while (i < length) { + int shift = static_cast(nack_list[i] - pid) - 1; + if (shift >= 0 && shift <= 15) { + bitmask |= (1 << shift); + ++i; + } else { + break; + } + } + RTCPUtility::RTCPPacketRTPFBNACKItem item; + item.PacketID = pid; + item.BitMask = bitmask; + nack_fields_.push_back(item); + } +} + +void Rpsi::Create(uint8_t* packet, size_t* length, size_t max_length) const { + assert(rpsi_.NumberOfValidBits > 0); + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateRpsi(rpsi_, padding_bytes_, BlockToHeaderLength(BlockLength()), packet, + length); +} + +void Rpsi::WithPictureId(uint64_t picture_id) { + const uint32_t kPidBits = 7; + const uint64_t k7MsbZeroMask = 0x1ffffffffffffff; + uint8_t required_bytes = 0; + uint64_t shifted_pid = picture_id; + do { + ++required_bytes; + shifted_pid = (shifted_pid >> kPidBits) & k7MsbZeroMask; + } while (shifted_pid > 0); + + // Convert picture id to native bit string (natively defined by the video + // codec). + int pos = 0; + for (int i = required_bytes - 1; i > 0; i--) { + rpsi_.NativeBitString[pos++] = + 0x80 | static_cast(picture_id >> (i * kPidBits)); + } + rpsi_.NativeBitString[pos++] = static_cast(picture_id & 0x7f); + rpsi_.NumberOfValidBits = pos * 8; + + // Calculate padding bytes (to reach next 32-bit boundary, 1, 2 or 3 bytes). + padding_bytes_ = 4 - ((2 + required_bytes) % 4); + if (padding_bytes_ == 4) { + padding_bytes_ = 0; + } +} + +void Fir::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateFir(fir_, fir_item_, BlockToHeaderLength(BlockLength()), packet, + length); +} + +void Remb::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateRemb(remb_, remb_item_, BlockToHeaderLength(BlockLength()), packet, + length); +} + +void Remb::AppliesTo(uint32_t ssrc) { + if (remb_item_.NumberOfSSRCs >= kMaxNumberOfSsrcs) { + LOG(LS_WARNING) << "Max number of REMB feedback SSRCs reached."; + return; + } + remb_item_.SSRCs[remb_item_.NumberOfSSRCs++] = ssrc; +} + +void Tmmbr::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateTmmbr(tmmbr_, tmmbr_item_, BlockToHeaderLength(BlockLength()), packet, + length); +} + +void Tmmbn::WithTmmbr(uint32_t ssrc, uint32_t bitrate_kbps, uint16_t overhead) { + assert(overhead <= 0x1ff); + if (tmmbn_items_.size() >= kMaxNumberOfTmmbrs) { + LOG(LS_WARNING) << "Max TMMBN size reached."; + return; + } + RTCPPacketRTPFBTMMBRItem tmmbn_item; + tmmbn_item.SSRC = ssrc; + tmmbn_item.MaxTotalMediaBitRate = bitrate_kbps; + tmmbn_item.MeasuredOverhead = overhead; + tmmbn_items_.push_back(tmmbn_item); +} + +void Tmmbn::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateTmmbn(tmmbn_, tmmbn_items_, BlockToHeaderLength(BlockLength()), packet, + length); +} + +void Xr::Create(uint8_t* packet, size_t* length, size_t max_length) const { + if (*length + BlockLength() > max_length) { + LOG(LS_WARNING) << "Max packet size reached."; + return; + } + CreateXrHeader(xr_header_, BlockToHeaderLength(BlockLength()), packet, + length); + CreateRrtr(rrtr_blocks_, packet, length); + CreateDlrr(dlrr_blocks_, packet, length); + CreateVoipMetric(voip_metric_blocks_, packet, length); +} + +void Xr::WithRrtr(Rrtr* rrtr) { + assert(rrtr); + if (rrtr_blocks_.size() >= kMaxNumberOfRrtrBlocks) { + LOG(LS_WARNING) << "Max RRTR blocks reached."; + return; + } + rrtr_blocks_.push_back(rrtr->rrtr_block_); +} + +void Xr::WithDlrr(Dlrr* dlrr) { + assert(dlrr); + if (dlrr_blocks_.size() >= kMaxNumberOfDlrrBlocks) { + LOG(LS_WARNING) << "Max DLRR blocks reached."; + return; + } + dlrr_blocks_.push_back(dlrr->dlrr_block_); +} + +void Xr::WithVoipMetric(VoipMetric* voip_metric) { + assert(voip_metric); + if (voip_metric_blocks_.size() >= kMaxNumberOfVoipMetricBlocks) { + LOG(LS_WARNING) << "Max Voip Metric blocks reached."; + return; + } + voip_metric_blocks_.push_back(voip_metric->metric_); +} + +size_t Xr::DlrrLength() const { + const size_t kBlockHeaderLen = 4; + const size_t kSubBlockLen = 12; + size_t length = 0; + for (std::vector::const_iterator it = dlrr_blocks_.begin(); + it != dlrr_blocks_.end(); ++it) { + if (!(*it).empty()) { + length += kBlockHeaderLen + kSubBlockLen * (*it).size(); + } + } + return length; +} + +void Dlrr::WithDlrrItem(uint32_t ssrc, + uint32_t last_rr, + uint32_t delay_last_rr) { + if (dlrr_block_.size() >= kMaxNumberOfDlrrItems) { + LOG(LS_WARNING) << "Max DLRR items reached."; + return; + } + RTCPPacketXRDLRRReportBlockItem dlrr; + dlrr.SSRC = ssrc; + dlrr.LastRR = last_rr; + dlrr.DelayLastRR = delay_last_rr; + dlrr_block_.push_back(dlrr); +} + } // namespace rtcp } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet.h b/webrtc/modules/rtp_rtcp/source/rtcp_packet.h index 13ad808b9..0ea98a280 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_packet.h +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet.h @@ -12,6 +12,8 @@ #ifndef WEBRTC_MODULES_RTP_RTCP_RTCP_PACKET_H_ #define WEBRTC_MODULES_RTP_RTCP_RTCP_PACKET_H_ +#include +#include #include #include "webrtc/modules/rtp_rtcp/source/rtcp_utility.h" @@ -21,8 +23,13 @@ namespace webrtc { namespace rtcp { +enum { kCommonFbFmtLength = 12 }; +enum { kReportBlockLength = 24 }; + +class Dlrr; class RawPacket; -class ReportBlock; +class Rrtr; +class VoipMetric; // Class for building RTCP packets. // @@ -40,9 +47,9 @@ class ReportBlock; // fir.To(234) // fir.WithCommandSeqNum(123); // -// uint16_t len = 0; // Builds an intra frame request +// size_t length = 0; // Builds an intra frame request // uint8_t packet[kPacketSize]; // with sequence number 123. -// fir.Build(packet, &len, kPacketSize); +// fir.Build(packet, &length, kPacketSize); // // RawPacket packet = fir.Build(); // Returns a RawPacket holding // // the built rtcp packet. @@ -59,33 +66,94 @@ class RtcpPacket { RawPacket Build() const; - void Build(uint8_t* packet, uint16_t* len, uint16_t max_len) const; + void Build(uint8_t* packet, size_t* length, size_t max_length) const; protected: - RtcpPacket() {} + RtcpPacket() : kHeaderLength(4) {} virtual void Create( - uint8_t* packet, uint16_t* len, uint16_t max_len) const = 0; + uint8_t* packet, size_t* length, size_t max_length) const = 0; - void CreateAndAddAppended( - uint8_t* packet, uint16_t* len, uint16_t max_len) const; + const size_t kHeaderLength; private: + void CreateAndAddAppended( + uint8_t* packet, size_t* length, size_t max_length) const; + std::vector appended_packets_; }; class Empty : public RtcpPacket { public: - Empty() {} + Empty() : RtcpPacket() {} virtual ~Empty() {} protected: - virtual void Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const; + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(Empty); }; -//// From RFC 3550, RTP: A Transport Protocol for Real-Time Applications. +// From RFC 3550, RTP: A Transport Protocol for Real-Time Applications. // +// RTCP report block (RFC 3550). +// +// 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 +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_1 (SSRC of first source) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | fraction lost | cumulative number of packets lost | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | extended highest sequence number received | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | interarrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | last SR (LSR) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | delay since last SR (DLSR) | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + +class ReportBlock { + public: + ReportBlock() { + // TODO(asapersson): Consider adding a constructor to struct. + memset(&report_block_, 0, sizeof(report_block_)); + } + + ~ReportBlock() {} + + void To(uint32_t ssrc) { + report_block_.SSRC = ssrc; + } + void WithFractionLost(uint8_t fraction_lost) { + report_block_.FractionLost = fraction_lost; + } + void WithCumulativeLost(uint32_t cumulative_lost) { + report_block_.CumulativeNumOfPacketsLost = cumulative_lost; + } + void WithExtHighestSeqNum(uint32_t ext_highest_seq_num) { + report_block_.ExtendedHighestSequenceNumber = ext_highest_seq_num; + } + void WithJitter(uint32_t jitter) { + report_block_.Jitter = jitter; + } + void WithLastSr(uint32_t last_sr) { + report_block_.LastSR = last_sr; + } + void WithDelayLastSr(uint32_t delay_last_sr) { + report_block_.DelayLastSR = delay_last_sr; + } + + private: + friend class SenderReport; + friend class ReceiverReport; + RTCPUtility::RTCPPacketReportBlockItem report_block_; +}; + // RTCP sender report (RFC 3550). // // 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 @@ -109,8 +177,7 @@ class Empty : public RtcpPacket { class SenderReport : public RtcpPacket { public: - SenderReport() - : RtcpPacket() { + SenderReport() : RtcpPacket() { memset(&sr_, 0, sizeof(sr_)); } @@ -136,20 +203,24 @@ class SenderReport : public RtcpPacket { } void WithReportBlock(ReportBlock* block); - enum { kMaxNumberOfReportBlocks = 0x1f }; - protected: - virtual void Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const; + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; private: - uint16_t Length() const { - const uint16_t kSrBlockLen = 28; - const uint16_t kReportBlockLen = 24; - return kSrBlockLen + report_blocks_.size() * kReportBlockLen; + enum { kMaxNumberOfReportBlocks = 0x1f }; + + size_t BlockLength() const { + const size_t kSrHeaderLength = 8; + const size_t kSenderInfoLength = 20; + return kSrHeaderLength + kSenderInfoLength + + report_blocks_.size() * kReportBlockLength; } RTCPUtility::RTCPPacketSR sr_; - std::vector report_blocks_; + std::vector report_blocks_; + + DISALLOW_COPY_AND_ASSIGN(SenderReport); }; // @@ -167,8 +238,7 @@ class SenderReport : public RtcpPacket { class ReceiverReport : public RtcpPacket { public: - ReceiverReport() - : RtcpPacket() { + ReceiverReport() : RtcpPacket() { memset(&rr_, 0, sizeof(rr_)); } @@ -179,75 +249,119 @@ class ReceiverReport : public RtcpPacket { } void WithReportBlock(ReportBlock* block); - enum { kMaxNumberOfReportBlocks = 0x1f }; - protected: - virtual void Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const; + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; private: - uint16_t Length() const { - const uint16_t kRrBlockLen = 8; - const uint16_t kReportBlockLen = 24; - return kRrBlockLen + report_blocks_.size() * kReportBlockLen; + enum { kMaxNumberOfReportBlocks = 0x1f }; + + size_t BlockLength() const { + const size_t kRrHeaderLength = 8; + return kRrHeaderLength + report_blocks_.size() * kReportBlockLength; } RTCPUtility::RTCPPacketRR rr_; - std::vector report_blocks_; + std::vector report_blocks_; + + DISALLOW_COPY_AND_ASSIGN(ReceiverReport); }; +// Transmission Time Offsets in RTP Streams (RFC 5450). // -// RTCP report block (RFC 3550). +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// hdr |V=2|P| RC | PT=IJ=195 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | inter-arrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// . . +// | inter-arrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // -// 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 -// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -// | SSRC_1 (SSRC of first source) | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | fraction lost | cumulative number of packets lost | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | extended highest sequence number received | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | interarrival jitter | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | last SR (LSR) | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | delay since last SR (DLSR) | -// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// If present, this RTCP packet must be placed after a receiver report +// (inside a compound RTCP packet), and MUST have the same value for RC +// (reception report count) as the receiver report. -class ReportBlock { +class Ij : public RtcpPacket { public: - ReportBlock() { - memset(&report_block_, 0, sizeof(report_block_)); - } + Ij() : RtcpPacket() {} - ~ReportBlock() {} + virtual ~Ij() {} - void To(uint32_t ssrc) { - report_block_.SSRC = ssrc; - } - void WithFractionLost(uint8_t fraction_lost) { - report_block_.FractionLost = fraction_lost; - } - void WithCumPacketsLost(uint32_t cum_packets_lost) { - report_block_.CumulativeNumOfPacketsLost = cum_packets_lost; - } - void WithExtHighestSeqNum(uint32_t ext_highest_seq_num) { - report_block_.ExtendedHighestSequenceNumber = ext_highest_seq_num; - } - void WithJitter(uint32_t jitter) { - report_block_.Jitter = jitter; - } - void WithLastSr(uint32_t last_sr) { - report_block_.LastSR = last_sr; - } - void WithDelayLastSr(uint32_t delay_last_sr) { - report_block_.DelayLastSR = delay_last_sr; + void WithJitterItem(uint32_t jitter); + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + enum { kMaxNumberOfIjItems = 0x1f }; + + size_t BlockLength() const { + return kHeaderLength + 4 * ij_items_.size(); } - void Create(uint8_t* array, uint16_t* cur_pos) const; + std::vector ij_items_; + + DISALLOW_COPY_AND_ASSIGN(Ij); +}; + +// Source Description (SDES) (RFC 3550). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// header |V=2|P| SC | PT=SDES=202 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_1 | +// 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_2 | +// 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Canonical End-Point Identifier SDES Item (CNAME) +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | CNAME=1 | length | user and domain name ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Sdes : public RtcpPacket { + public: + Sdes() : RtcpPacket() {} + + virtual ~Sdes() {} + + void WithCName(uint32_t ssrc, std::string cname); + + struct Chunk { + uint32_t ssrc; + std::string name; + int null_octets; + }; + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; private: - RTCPUtility::RTCPPacketReportBlockItem report_block_; + enum { kMaxNumberOfChunks = 0x1f }; + + size_t BlockLength() const; + + std::vector chunks_; + + DISALLOW_COPY_AND_ASSIGN(Sdes); }; // @@ -266,8 +380,7 @@ class ReportBlock { class Bye : public RtcpPacket { public: - Bye() - : RtcpPacket() { + Bye() : RtcpPacket() { memset(&bye_, 0, sizeof(bye_)); } @@ -278,19 +391,79 @@ class Bye : public RtcpPacket { } void WithCsrc(uint32_t csrc); - enum { kMaxNumberOfCsrcs = 0x1f - 1 }; - protected: - virtual void Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const; + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; private: - uint16_t Length() const { - const uint16_t kByeBlockLen = 8 + 4*csrcs_.size(); - return kByeBlockLen; + enum { kMaxNumberOfCsrcs = 0x1f - 1 }; + + size_t BlockLength() const { + size_t source_count = 1 + csrcs_.size(); + return kHeaderLength + 4 * source_count; } RTCPUtility::RTCPPacketBYE bye_; std::vector csrcs_; + + DISALLOW_COPY_AND_ASSIGN(Bye); +}; + +// Application-Defined packet (APP) (RFC 3550). +// +// 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 ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class App : public RtcpPacket { + public: + App() + : RtcpPacket(), + ssrc_(0) { + memset(&app_, 0, sizeof(app_)); + } + + virtual ~App() {} + + void From(uint32_t ssrc) { + ssrc_ = ssrc; + } + void WithSubType(uint8_t subtype) { + assert(subtype <= 0x1f); + app_.SubType = subtype; + } + void WithName(uint32_t name) { + app_.Name = name; + } + void WithData(const uint8_t* data, uint16_t data_length) { + assert(data); + assert(data_length <= kRtcpAppCode_DATA_SIZE); + assert(data_length % 4 == 0); + memcpy(app_.Data, data, data_length); + app_.Size = data_length; + } + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + size_t BlockLength() const { + return 12 + app_.Size; + } + + uint32_t ssrc_; + RTCPUtility::RTCPPacketAPP app_; + + DISALLOW_COPY_AND_ASSIGN(App); }; // RFC 4585: Feedback format. @@ -309,6 +482,182 @@ class Bye : public RtcpPacket { // : Feedback Control Information (FCI) : // : +// Picture loss indication (PLI) (RFC 4585). +// +// FCI: no feedback control information. + +class Pli : public RtcpPacket { + public: + Pli() : RtcpPacket() { + memset(&pli_, 0, sizeof(pli_)); + } + + virtual ~Pli() {} + + void From(uint32_t ssrc) { + pli_.SenderSSRC = ssrc; + } + void To(uint32_t ssrc) { + pli_.MediaSSRC = ssrc; + } + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + size_t BlockLength() const { + return kCommonFbFmtLength; + } + + RTCPUtility::RTCPPacketPSFBPLI pli_; + + DISALLOW_COPY_AND_ASSIGN(Pli); +}; + +// Slice loss indication (SLI) (RFC 4585). +// +// FCI: +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | First | Number | PictureID | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Sli : public RtcpPacket { + public: + Sli() : RtcpPacket() { + memset(&sli_, 0, sizeof(sli_)); + memset(&sli_item_, 0, sizeof(sli_item_)); + } + + virtual ~Sli() {} + + void From(uint32_t ssrc) { + sli_.SenderSSRC = ssrc; + } + void To(uint32_t ssrc) { + sli_.MediaSSRC = ssrc; + } + void WithFirstMb(uint16_t first_mb) { + assert(first_mb <= 0x1fff); + sli_item_.FirstMB = first_mb; + } + void WithNumberOfMb(uint16_t number_mb) { + assert(number_mb <= 0x1fff); + sli_item_.NumberOfMB = number_mb; + } + void WithPictureId(uint8_t picture_id) { + assert(picture_id <= 0x3f); + sli_item_.PictureId = picture_id; + } + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + size_t BlockLength() const { + const size_t kFciLength = 4; + return kCommonFbFmtLength + kFciLength; + } + + RTCPUtility::RTCPPacketPSFBSLI sli_; + RTCPUtility::RTCPPacketPSFBSLIItem sli_item_; + + DISALLOW_COPY_AND_ASSIGN(Sli); +}; + +// Generic NACK (RFC 4585). +// +// FCI: +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PID | BLP | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Nack : public RtcpPacket { + public: + Nack() : RtcpPacket() { + memset(&nack_, 0, sizeof(nack_)); + } + + virtual ~Nack() {} + + void From(uint32_t ssrc) { + nack_.SenderSSRC = ssrc; + } + void To(uint32_t ssrc) { + nack_.MediaSSRC = ssrc; + } + void WithList(const uint16_t* nack_list, int length); + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + size_t BlockLength() const { + size_t fci_length = 4 * nack_fields_.size(); + return kCommonFbFmtLength + fci_length; + } + + RTCPUtility::RTCPPacketRTPFBNACK nack_; + std::vector nack_fields_; + + DISALLOW_COPY_AND_ASSIGN(Nack); +}; + +// Reference picture selection indication (RPSI) (RFC 4585). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PB |0| Payload Type| Native RPSI bit string | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | defined per codec ... | Padding (0) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Rpsi : public RtcpPacket { + public: + Rpsi() + : RtcpPacket(), + padding_bytes_(0) { + memset(&rpsi_, 0, sizeof(rpsi_)); + } + + virtual ~Rpsi() {} + + void From(uint32_t ssrc) { + rpsi_.SenderSSRC = ssrc; + } + void To(uint32_t ssrc) { + rpsi_.MediaSSRC = ssrc; + } + void WithPayloadType(uint8_t payload) { + assert(payload <= 0x7f); + rpsi_.PayloadType = payload; + } + void WithPictureId(uint64_t picture_id); + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + size_t BlockLength() const { + size_t fci_length = 2 + (rpsi_.NumberOfValidBits / 8) + padding_bytes_; + return kCommonFbFmtLength + fci_length; + } + + uint8_t padding_bytes_; + RTCPUtility::RTCPPacketPSFBRPSI rpsi_; + + DISALLOW_COPY_AND_ASSIGN(Rpsi); +}; // Full intra request (FIR) (RFC 5104). // @@ -324,8 +673,7 @@ class Bye : public RtcpPacket { class Fir : public RtcpPacket { public: - Fir() - : RtcpPacket() { + Fir() : RtcpPacket() { memset(&fir_, 0, sizeof(fir_)); memset(&fir_item_, 0, sizeof(fir_item_)); } @@ -343,22 +691,371 @@ class Fir : public RtcpPacket { } protected: - virtual void Create(uint8_t* packet, uint16_t* len, uint16_t max_len) const; + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; private: - uint16_t Length() const { - const uint16_t kFirBlockLen = 20; - return kFirBlockLen; + size_t BlockLength() const { + const size_t kFciLength = 8; + return kCommonFbFmtLength + kFciLength; } RTCPUtility::RTCPPacketPSFBFIR fir_; RTCPUtility::RTCPPacketPSFBFIRItem fir_item_; }; +// Temporary Maximum Media Stream Bit Rate Request (TMMBR) (RFC 5104). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Tmmbr : public RtcpPacket { + public: + Tmmbr() : RtcpPacket() { + memset(&tmmbr_, 0, sizeof(tmmbr_)); + memset(&tmmbr_item_, 0, sizeof(tmmbr_item_)); + } + + virtual ~Tmmbr() {} + + void From(uint32_t ssrc) { + tmmbr_.SenderSSRC = ssrc; + } + void To(uint32_t ssrc) { + tmmbr_item_.SSRC = ssrc; + } + void WithBitrateKbps(uint32_t bitrate_kbps) { + tmmbr_item_.MaxTotalMediaBitRate = bitrate_kbps; + } + void WithOverhead(uint16_t overhead) { + assert(overhead <= 0x1ff); + tmmbr_item_.MeasuredOverhead = overhead; + } + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + size_t BlockLength() const { + const size_t kFciLen = 8; + return kCommonFbFmtLength + kFciLen; + } + + RTCPUtility::RTCPPacketRTPFBTMMBR tmmbr_; + RTCPUtility::RTCPPacketRTPFBTMMBRItem tmmbr_item_; + + DISALLOW_COPY_AND_ASSIGN(Tmmbr); +}; + +// Temporary Maximum Media Stream Bit Rate Notification (TMMBN) (RFC 5104). +// +// FCI: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Tmmbn : public RtcpPacket { + public: + Tmmbn() : RtcpPacket() { + memset(&tmmbn_, 0, sizeof(tmmbn_)); + } + + virtual ~Tmmbn() {} + + void From(uint32_t ssrc) { + tmmbn_.SenderSSRC = ssrc; + } + void WithTmmbr(uint32_t ssrc, uint32_t bitrate_kbps, uint16_t overhead); + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + enum { kMaxNumberOfTmmbrs = 50 }; + + size_t BlockLength() const { + const size_t kFciLen = 8; + return kCommonFbFmtLength + kFciLen * tmmbn_items_.size(); + } + + RTCPUtility::RTCPPacketRTPFBTMMBN tmmbn_; + std::vector tmmbn_items_; + + DISALLOW_COPY_AND_ASSIGN(Tmmbn); +}; + +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +// +// 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| FMT=15 | PT=206 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Unique identifier 'R' 'E' 'M' 'B' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Num SSRC | BR Exp | BR Mantissa | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC feedback | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... + +class Remb : public RtcpPacket { + public: + Remb() : RtcpPacket() { + memset(&remb_, 0, sizeof(remb_)); + memset(&remb_item_, 0, sizeof(remb_item_)); + } + + virtual ~Remb() {} + + void From(uint32_t ssrc) { + remb_.SenderSSRC = ssrc; + } + void AppliesTo(uint32_t ssrc); + + void WithBitrateBps(uint32_t bitrate_bps) { + remb_item_.BitRate = bitrate_bps; + } + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + enum { kMaxNumberOfSsrcs = 0xff }; + + size_t BlockLength() const { + return (remb_item_.NumberOfSSRCs + 5) * 4; + } + + RTCPUtility::RTCPPacketPSFBAPP remb_; + RTCPUtility::RTCPPacketPSFBREMBItem remb_item_; + + DISALLOW_COPY_AND_ASSIGN(Remb); +}; + +// From RFC 3611: RTP Control Protocol Extended Reports (RTCP XR). +// +// Format for XR packets: +// +// 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|reserved | PT=XR=207 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : report blocks : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Xr : public RtcpPacket { + public: + typedef std::vector DlrrBlock; + Xr() : RtcpPacket() { + memset(&xr_header_, 0, sizeof(xr_header_)); + } + + virtual ~Xr() {} + + void From(uint32_t ssrc) { + xr_header_.OriginatorSSRC = ssrc; + } + void WithRrtr(Rrtr* rrtr); + void WithDlrr(Dlrr* dlrr); + void WithVoipMetric(VoipMetric* voip_metric); + + protected: + virtual void Create( + uint8_t* packet, size_t* length, size_t max_length) const OVERRIDE; + + private: + enum { kMaxNumberOfRrtrBlocks = 50 }; + enum { kMaxNumberOfDlrrBlocks = 50 }; + enum { kMaxNumberOfVoipMetricBlocks = 50 }; + + size_t BlockLength() const { + const size_t kXrHeaderLength = 8; + return kXrHeaderLength + RrtrLength() + DlrrLength() + VoipMetricLength(); + } + + size_t RrtrLength() const { + const size_t kRrtrBlockLength = 12; + return kRrtrBlockLength * rrtr_blocks_.size(); + } + + size_t DlrrLength() const; + + size_t VoipMetricLength() const { + const size_t kVoipMetricBlockLength = 36; + return kVoipMetricBlockLength * voip_metric_blocks_.size(); + } + + RTCPUtility::RTCPPacketXR xr_header_; + std::vector rrtr_blocks_; + std::vector dlrr_blocks_; + std::vector voip_metric_blocks_; + + DISALLOW_COPY_AND_ASSIGN(Xr); +}; + +// Receiver Reference Time Report Block (RFC 3611). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=4 | reserved | block length = 2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, most significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, least significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class Rrtr { + public: + Rrtr() { + memset(&rrtr_block_, 0, sizeof(rrtr_block_)); + } + ~Rrtr() {} + + void WithNtpSec(uint32_t sec) { + rrtr_block_.NTPMostSignificant = sec; + } + void WithNtpFrac(uint32_t frac) { + rrtr_block_.NTPLeastSignificant = frac; + } + + private: + friend class Xr; + RTCPUtility::RTCPPacketXRReceiverReferenceTimeItem rrtr_block_; + + DISALLOW_COPY_AND_ASSIGN(Rrtr); +}; + +// DLRR Report Block (RFC 3611). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=5 | reserved | block length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_1 (SSRC of first receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// | last RR (LRR) | 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | delay since last RR (DLRR) | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_2 (SSRC of second receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// : ... : 2 + +class Dlrr { + public: + Dlrr() {} + ~Dlrr() {} + + void WithDlrrItem(uint32_t ssrc, uint32_t last_rr, uint32_t delay_last_rr); + + private: + friend class Xr; + enum { kMaxNumberOfDlrrItems = 100 }; + + std::vector dlrr_block_; + + DISALLOW_COPY_AND_ASSIGN(Dlrr); +}; + +// VoIP Metrics Report Block (RFC 3611). +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=7 | reserved | block length = 8 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | loss rate | discard rate | burst density | gap density | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | burst duration | gap duration | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | round trip delay | end system delay | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | signal level | noise level | RERL | Gmin | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | R factor | ext. R factor | MOS-LQ | MOS-CQ | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | RX config | reserved | JB nominal | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | JB maximum | JB abs max | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class VoipMetric { + public: + VoipMetric() { + memset(&metric_, 0, sizeof(metric_)); + } + ~VoipMetric() {} + + void To(uint32_t ssrc) { metric_.SSRC = ssrc; } + void LossRate(uint8_t loss_rate) { metric_.lossRate = loss_rate; } + void DiscardRate(uint8_t discard_rate) { metric_.discardRate = discard_rate; } + void BurstDensity(uint8_t burst_density) { + metric_.burstDensity = burst_density; + } + void GapDensity(uint8_t gap_density) { metric_.gapDensity = gap_density; } + void BurstDuration(uint16_t burst_duration) { + metric_.burstDuration = burst_duration; + } + void GapDuration(uint16_t gap_duration) { + metric_.gapDuration = gap_duration; + } + void RoundTripDelay(uint16_t round_trip_delay) { + metric_.roundTripDelay = round_trip_delay; + } + void EndSystemDelay(uint16_t end_system_delay) { + metric_.endSystemDelay = end_system_delay; + } + void SignalLevel(uint8_t signal_level) { metric_.signalLevel = signal_level; } + void NoiseLevel(uint8_t noise_level) { metric_.noiseLevel = noise_level; } + void Rerl(uint8_t rerl) { metric_.RERL = rerl; } + void Gmin(uint8_t gmin) { metric_.Gmin = gmin; } + void Rfactor(uint8_t rfactor) { metric_.Rfactor = rfactor; } + void ExtRfactor(uint8_t extrfactor) { metric_.extRfactor = extrfactor; } + void MosLq(uint8_t moslq) { metric_.MOSLQ = moslq; } + void MosCq(uint8_t moscq) { metric_.MOSCQ = moscq; } + void RxConfig(uint8_t rxconfig) { metric_.RXconfig = rxconfig; } + void JbNominal(uint16_t jbnominal) { metric_.JBnominal = jbnominal; } + void JbMax(uint16_t jbmax) { metric_.JBmax = jbmax; } + void JbAbsMax(uint16_t jbabsmax) { metric_.JBabsMax = jbabsmax; } + + private: + friend class Xr; + RTCPUtility::RTCPPacketXRVOIPMetricItem metric_; + + DISALLOW_COPY_AND_ASSIGN(VoipMetric); +}; + // Class holding a RTCP packet. // // Takes a built rtcp packet. -// RawPacket raw_packet(buffer, len); +// RawPacket raw_packet(buffer, length); // // To access the raw packet: // raw_packet.buffer(); - pointer to the raw packet @@ -366,22 +1063,22 @@ class Fir : public RtcpPacket { class RawPacket { public: - RawPacket(const uint8_t* buffer, uint16_t len) { - assert(len <= IP_PACKET_SIZE); - memcpy(packet_, buffer, len); - packet_length_ = len; + RawPacket(const uint8_t* packet, size_t length) { + assert(length <= IP_PACKET_SIZE); + memcpy(buffer_, packet, length); + buffer_length_ = length; } const uint8_t* buffer() { - return packet_; + return buffer_; } - uint16_t buffer_length() const { - return packet_length_; + size_t buffer_length() const { + return buffer_length_; } private: - uint16_t packet_length_; - uint8_t packet_[IP_PACKET_SIZE]; + size_t buffer_length_; + uint8_t buffer_[IP_PACKET_SIZE]; }; } // namespace rtcp diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc index 0114acd89..095022efc 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc @@ -15,13 +15,28 @@ #include "webrtc/modules/rtp_rtcp/source/rtcp_packet.h" #include "webrtc/test/rtcp_packet_parser.h" +using webrtc::rtcp::App; using webrtc::rtcp::Bye; +using webrtc::rtcp::Dlrr; using webrtc::rtcp::Empty; using webrtc::rtcp::Fir; +using webrtc::rtcp::Ij; +using webrtc::rtcp::Nack; +using webrtc::rtcp::Pli; +using webrtc::rtcp::Sdes; using webrtc::rtcp::SenderReport; +using webrtc::rtcp::Sli; using webrtc::rtcp::RawPacket; using webrtc::rtcp::ReceiverReport; +using webrtc::rtcp::Remb; using webrtc::rtcp::ReportBlock; +using webrtc::rtcp::Rpsi; +using webrtc::rtcp::Rrtr; +using webrtc::rtcp::SenderReport; +using webrtc::rtcp::Tmmbn; +using webrtc::rtcp::Tmmbr; +using webrtc::rtcp::VoipMetric; +using webrtc::rtcp::Xr; using webrtc::test::RtcpPacketParser; namespace webrtc { @@ -45,7 +60,7 @@ TEST(RtcpPacketTest, RrWithOneReportBlock) { ReportBlock rb; rb.To(kRemoteSsrc); rb.WithFractionLost(55); - rb.WithCumPacketsLost(0x111111); + rb.WithCumulativeLost(0x111111); rb.WithExtHighestSeqNum(0x22222222); rb.WithJitter(0x33333333); rb.WithLastSr(0x44444444); @@ -152,6 +167,279 @@ TEST(RtcpPacketTest, SrWithTwoReportBlocks) { EXPECT_EQ(1, parser.report_blocks_per_ssrc(kRemoteSsrc + 1)); } +TEST(RtcpPacketTest, IjNoItem) { + Ij ij; + + RawPacket packet = ij.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.ij()->num_packets()); + EXPECT_EQ(0, parser.ij_item()->num_packets()); +} + +TEST(RtcpPacketTest, IjOneItem) { + Ij ij; + ij.WithJitterItem(0x11111111); + + RawPacket packet = ij.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.ij()->num_packets()); + EXPECT_EQ(1, parser.ij_item()->num_packets()); + EXPECT_EQ(0x11111111U, parser.ij_item()->Jitter()); +} + +TEST(RtcpPacketTest, IjTwoItems) { + Ij ij; + ij.WithJitterItem(0x11111111); + ij.WithJitterItem(0x22222222); + + RawPacket packet = ij.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.ij()->num_packets()); + EXPECT_EQ(2, parser.ij_item()->num_packets()); + EXPECT_EQ(0x22222222U, parser.ij_item()->Jitter()); +} + +TEST(RtcpPacketTest, AppWithNoData) { + App app; + app.WithSubType(30); + uint32_t name = 'n' << 24; + name += 'a' << 16; + name += 'm' << 8; + name += 'e'; + app.WithName(name); + + RawPacket packet = app.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.app()->num_packets()); + EXPECT_EQ(30U, parser.app()->SubType()); + EXPECT_EQ(name, parser.app()->Name()); + EXPECT_EQ(0, parser.app_item()->num_packets()); +} + +TEST(RtcpPacketTest, App) { + App app; + app.From(kSenderSsrc); + app.WithSubType(30); + uint32_t name = 'n' << 24; + name += 'a' << 16; + name += 'm' << 8; + name += 'e'; + app.WithName(name); + const char kData[] = {'t', 'e', 's', 't', 'd', 'a', 't', 'a'}; + const size_t kDataLength = sizeof(kData) / sizeof(kData[0]); + app.WithData((const uint8_t*)kData, kDataLength); + + RawPacket packet = app.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.app()->num_packets()); + EXPECT_EQ(30U, parser.app()->SubType()); + EXPECT_EQ(name, parser.app()->Name()); + EXPECT_EQ(1, parser.app_item()->num_packets()); + EXPECT_EQ(kDataLength, parser.app_item()->DataLength()); + EXPECT_EQ(0, strncmp(kData, (const char*)parser.app_item()->Data(), + parser.app_item()->DataLength())); +} + +TEST(RtcpPacketTest, SdesWithOneChunk) { + Sdes sdes; + sdes.WithCName(kSenderSsrc, "alice@host"); + + RawPacket packet = sdes.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.sdes()->num_packets()); + EXPECT_EQ(1, parser.sdes_chunk()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.sdes_chunk()->Ssrc()); + EXPECT_EQ("alice@host", parser.sdes_chunk()->Cname()); +} + +TEST(RtcpPacketTest, SdesWithMultipleChunks) { + Sdes sdes; + sdes.WithCName(kSenderSsrc, "a"); + sdes.WithCName(kSenderSsrc + 1, "ab"); + sdes.WithCName(kSenderSsrc + 2, "abc"); + sdes.WithCName(kSenderSsrc + 3, "abcd"); + sdes.WithCName(kSenderSsrc + 4, "abcde"); + sdes.WithCName(kSenderSsrc + 5, "abcdef"); + + RawPacket packet = sdes.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.sdes()->num_packets()); + EXPECT_EQ(6, parser.sdes_chunk()->num_packets()); + EXPECT_EQ(kSenderSsrc + 5, parser.sdes_chunk()->Ssrc()); + EXPECT_EQ("abcdef", parser.sdes_chunk()->Cname()); +} + +TEST(RtcpPacketTest, CnameItemWithEmptyString) { + Sdes sdes; + sdes.WithCName(kSenderSsrc, ""); + + RawPacket packet = sdes.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.sdes()->num_packets()); + EXPECT_EQ(1, parser.sdes_chunk()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.sdes_chunk()->Ssrc()); + EXPECT_EQ("", parser.sdes_chunk()->Cname()); +} + +TEST(RtcpPacketTest, Pli) { + Pli pli; + pli.From(kSenderSsrc); + pli.To(kRemoteSsrc); + + RawPacket packet = pli.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.pli()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.pli()->Ssrc()); + EXPECT_EQ(kRemoteSsrc, parser.pli()->MediaSsrc()); +} + +TEST(RtcpPacketTest, Sli) { + const uint16_t kFirstMb = 7777; + const uint16_t kNumberOfMb = 6666; + const uint8_t kPictureId = 60; + Sli sli; + sli.From(kSenderSsrc); + sli.To(kRemoteSsrc); + sli.WithFirstMb(kFirstMb); + sli.WithNumberOfMb(kNumberOfMb); + sli.WithPictureId(kPictureId); + + RawPacket packet = sli.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.sli()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.sli()->Ssrc()); + EXPECT_EQ(kRemoteSsrc, parser.sli()->MediaSsrc()); + EXPECT_EQ(1, parser.sli_item()->num_packets()); + EXPECT_EQ(kFirstMb, parser.sli_item()->FirstMb()); + EXPECT_EQ(kNumberOfMb, parser.sli_item()->NumberOfMb()); + EXPECT_EQ(kPictureId, parser.sli_item()->PictureId()); +} + +TEST(RtcpPacketTest, Nack) { + Nack nack; + const uint16_t kList[] = {0, 1, 3, 8, 16}; + const uint16_t kListLength = sizeof(kList) / sizeof(kList[0]); + nack.From(kSenderSsrc); + nack.To(kRemoteSsrc); + nack.WithList(kList, kListLength); + RawPacket packet = nack.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.nack()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.nack()->Ssrc()); + EXPECT_EQ(kRemoteSsrc, parser.nack()->MediaSsrc()); + EXPECT_EQ(1, parser.nack_item()->num_packets()); + std::vector seqs = parser.nack_item()->last_nack_list(); + EXPECT_EQ(kListLength, seqs.size()); + for (size_t i = 0; i < kListLength; ++i) { + EXPECT_EQ(kList[i], seqs[i]); + } +} + +TEST(RtcpPacketTest, NackWithWrap) { + Nack nack; + const uint16_t kList[] = {65500, 65516, 65534, 65535, 0, 1, 3, 20, 100}; + const uint16_t kListLength = sizeof(kList) / sizeof(kList[0]); + nack.From(kSenderSsrc); + nack.To(kRemoteSsrc); + nack.WithList(kList, kListLength); + RawPacket packet = nack.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.nack()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.nack()->Ssrc()); + EXPECT_EQ(kRemoteSsrc, parser.nack()->MediaSsrc()); + EXPECT_EQ(4, parser.nack_item()->num_packets()); + std::vector seqs = parser.nack_item()->last_nack_list(); + EXPECT_EQ(kListLength, seqs.size()); + for (size_t i = 0; i < kListLength; ++i) { + EXPECT_EQ(kList[i], seqs[i]); + } +} + +TEST(RtcpPacketTest, Rpsi) { + Rpsi rpsi; + // 1000001 (7 bits = 1 byte in native string). + const uint64_t kPictureId = 0x41; + const uint16_t kNumberOfValidBytes = 1; + rpsi.WithPayloadType(100); + rpsi.WithPictureId(kPictureId); + + RawPacket packet = rpsi.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(100, parser.rpsi()->PayloadType()); + EXPECT_EQ(kNumberOfValidBytes * 8, parser.rpsi()->NumberOfValidBits()); + EXPECT_EQ(kPictureId, parser.rpsi()->PictureId()); +} + +TEST(RtcpPacketTest, RpsiWithTwoByteNativeString) { + Rpsi rpsi; + // |1 0000001 (7 bits = 1 byte in native string). + const uint64_t kPictureId = 0x81; + const uint16_t kNumberOfValidBytes = 2; + rpsi.WithPictureId(kPictureId); + + RawPacket packet = rpsi.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(kNumberOfValidBytes * 8, parser.rpsi()->NumberOfValidBits()); + EXPECT_EQ(kPictureId, parser.rpsi()->PictureId()); +} + +TEST(RtcpPacketTest, RpsiWithThreeByteNativeString) { + Rpsi rpsi; + // 10000|00 100000|0 1000000 (7 bits = 1 byte in native string). + const uint64_t kPictureId = 0x102040; + const uint16_t kNumberOfValidBytes = 3; + rpsi.WithPictureId(kPictureId); + + RawPacket packet = rpsi.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(kNumberOfValidBytes * 8, parser.rpsi()->NumberOfValidBits()); + EXPECT_EQ(kPictureId, parser.rpsi()->PictureId()); +} + +TEST(RtcpPacketTest, RpsiWithFourByteNativeString) { + Rpsi rpsi; + // 1000|001 00001|01 100001|1 1000010 (7 bits = 1 byte in native string). + const uint64_t kPictureId = 0x84161C2; + const uint16_t kNumberOfValidBytes = 4; + rpsi.WithPictureId(kPictureId); + + RawPacket packet = rpsi.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(kNumberOfValidBytes * 8, parser.rpsi()->NumberOfValidBits()); + EXPECT_EQ(kPictureId, parser.rpsi()->PictureId()); +} + +TEST(RtcpPacketTest, RpsiWithMaxPictureId) { + Rpsi rpsi; + // 1 1111111| 1111111 1|111111 11|11111 111|1111 1111|111 11111| + // 11 111111|1 1111111 (7 bits = 1 byte in native string). + const uint64_t kPictureId = 0xffffffffffffffff; + const uint16_t kNumberOfValidBytes = 10; + rpsi.WithPictureId(kPictureId); + + RawPacket packet = rpsi.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(kNumberOfValidBytes * 8, parser.rpsi()->NumberOfValidBits()); + EXPECT_EQ(kPictureId, parser.rpsi()->PictureId()); +} + TEST(RtcpPacketTest, Fir) { Fir fir; fir.From(kSenderSsrc); @@ -256,11 +544,11 @@ TEST(RtcpPacketTest, BuildWithInputBuffer) { rr.WithReportBlock(&rb); rr.Append(&fir); - const uint16_t kRrLength = 8; - const uint16_t kReportBlockLength = 24; - const uint16_t kFirLength = 20; + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; + const size_t kFirLength = 20; - uint16_t len = 0; + size_t len = 0; uint8_t packet[kRrLength + kReportBlockLength + kFirLength]; rr.Build(packet, &len, kRrLength + kReportBlockLength + kFirLength); @@ -277,16 +565,16 @@ TEST(RtcpPacketTest, BuildWithTooSmallBuffer) { rr.From(kSenderSsrc); rr.WithReportBlock(&rb); - const uint16_t kRrLength = 8; - const uint16_t kReportBlockLength = 24; + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; // No packet. - uint16_t len = 0; + size_t len = 0; uint8_t packet[kRrLength + kReportBlockLength - 1]; rr.Build(packet, &len, kRrLength + kReportBlockLength - 1); RtcpPacketParser parser; parser.Parse(packet, len); - EXPECT_EQ(0, len); + EXPECT_EQ(0U, len); } TEST(RtcpPacketTest, BuildWithTooSmallBuffer_LastBlockFits) { @@ -297,10 +585,10 @@ TEST(RtcpPacketTest, BuildWithTooSmallBuffer_LastBlockFits) { rr.WithReportBlock(&rb); rr.Append(&fir); - const uint16_t kRrLength = 8; - const uint16_t kReportBlockLength = 24; + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; - uint16_t len = 0; + size_t len = 0; uint8_t packet[kRrLength + kReportBlockLength - 1]; rr.Build(packet, &len, kRrLength + kReportBlockLength - 1); RtcpPacketParser parser; @@ -309,4 +597,307 @@ TEST(RtcpPacketTest, BuildWithTooSmallBuffer_LastBlockFits) { EXPECT_EQ(0, parser.report_block()->num_packets()); EXPECT_EQ(1, parser.fir()->num_packets()); } + +TEST(RtcpPacketTest, Remb) { + Remb remb; + remb.From(kSenderSsrc); + remb.AppliesTo(kRemoteSsrc); + remb.AppliesTo(kRemoteSsrc + 1); + remb.AppliesTo(kRemoteSsrc + 2); + remb.WithBitrateBps(261011); + + RawPacket packet = remb.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.psfb_app()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.psfb_app()->Ssrc()); + EXPECT_EQ(1, parser.remb_item()->num_packets()); + EXPECT_EQ(261011, parser.remb_item()->last_bitrate_bps()); + std::vector ssrcs = parser.remb_item()->last_ssrc_list(); + EXPECT_EQ(kRemoteSsrc, ssrcs[0]); + EXPECT_EQ(kRemoteSsrc + 1, ssrcs[1]); + EXPECT_EQ(kRemoteSsrc + 2, ssrcs[2]); +} + +TEST(RtcpPacketTest, Tmmbr) { + Tmmbr tmmbr; + tmmbr.From(kSenderSsrc); + tmmbr.To(kRemoteSsrc); + tmmbr.WithBitrateKbps(312); + tmmbr.WithOverhead(60); + + RawPacket packet = tmmbr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.tmmbr()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.tmmbr()->Ssrc()); + EXPECT_EQ(1, parser.tmmbr_item()->num_packets()); + EXPECT_EQ(312U, parser.tmmbr_item()->BitrateKbps()); + EXPECT_EQ(60U, parser.tmmbr_item()->Overhead()); +} + +TEST(RtcpPacketTest, TmmbnWithNoItem) { + Tmmbn tmmbn; + tmmbn.From(kSenderSsrc); + + RawPacket packet = tmmbn.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.tmmbn()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.tmmbn()->Ssrc()); + EXPECT_EQ(0, parser.tmmbn_items()->num_packets()); +} + +TEST(RtcpPacketTest, TmmbnWithOneItem) { + Tmmbn tmmbn; + tmmbn.From(kSenderSsrc); + tmmbn.WithTmmbr(kRemoteSsrc, 312, 60); + + RawPacket packet = tmmbn.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.tmmbn()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.tmmbn()->Ssrc()); + EXPECT_EQ(1, parser.tmmbn_items()->num_packets()); + EXPECT_EQ(kRemoteSsrc, parser.tmmbn_items()->Ssrc(0)); + EXPECT_EQ(312U, parser.tmmbn_items()->BitrateKbps(0)); + EXPECT_EQ(60U, parser.tmmbn_items()->Overhead(0)); +} + +TEST(RtcpPacketTest, TmmbnWithTwoItems) { + Tmmbn tmmbn; + tmmbn.From(kSenderSsrc); + tmmbn.WithTmmbr(kRemoteSsrc, 312, 60); + tmmbn.WithTmmbr(kRemoteSsrc + 1, 1288, 40); + + RawPacket packet = tmmbn.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.tmmbn()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.tmmbn()->Ssrc()); + EXPECT_EQ(2, parser.tmmbn_items()->num_packets()); + EXPECT_EQ(kRemoteSsrc, parser.tmmbn_items()->Ssrc(0)); + EXPECT_EQ(312U, parser.tmmbn_items()->BitrateKbps(0)); + EXPECT_EQ(60U, parser.tmmbn_items()->Overhead(0)); + EXPECT_EQ(kRemoteSsrc + 1, parser.tmmbn_items()->Ssrc(1)); + EXPECT_EQ(1288U, parser.tmmbn_items()->BitrateKbps(1)); + EXPECT_EQ(40U, parser.tmmbn_items()->Overhead(1)); +} + +TEST(RtcpPacketTest, XrWithNoReportBlocks) { + Xr xr; + xr.From(kSenderSsrc); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); +} + +TEST(RtcpPacketTest, XrWithRrtr) { + Rrtr rrtr; + rrtr.WithNtpSec(0x11111111); + rrtr.WithNtpFrac(0x22222222); + Xr xr; + xr.From(kSenderSsrc); + xr.WithRrtr(&rrtr); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(1, parser.rrtr()->num_packets()); + EXPECT_EQ(0x11111111U, parser.rrtr()->NtpSec()); + EXPECT_EQ(0x22222222U, parser.rrtr()->NtpFrac()); +} + +TEST(RtcpPacketTest, XrWithTwoRrtrBlocks) { + Rrtr rrtr1; + rrtr1.WithNtpSec(0x11111111); + rrtr1.WithNtpFrac(0x22222222); + Rrtr rrtr2; + rrtr2.WithNtpSec(0x33333333); + rrtr2.WithNtpFrac(0x44444444); + Xr xr; + xr.From(kSenderSsrc); + xr.WithRrtr(&rrtr1); + xr.WithRrtr(&rrtr2); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(2, parser.rrtr()->num_packets()); + EXPECT_EQ(0x33333333U, parser.rrtr()->NtpSec()); + EXPECT_EQ(0x44444444U, parser.rrtr()->NtpFrac()); +} + +TEST(RtcpPacketTest, XrWithDlrrWithOneSubBlock) { + Dlrr dlrr; + dlrr.WithDlrrItem(0x11111111, 0x22222222, 0x33333333); + Xr xr; + xr.From(kSenderSsrc); + xr.WithDlrr(&dlrr); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(1, parser.dlrr()->num_packets()); + EXPECT_EQ(1, parser.dlrr_items()->num_packets()); + EXPECT_EQ(0x11111111U, parser.dlrr_items()->Ssrc(0)); + EXPECT_EQ(0x22222222U, parser.dlrr_items()->LastRr(0)); + EXPECT_EQ(0x33333333U, parser.dlrr_items()->DelayLastRr(0)); +} + +TEST(RtcpPacketTest, XrWithDlrrWithTwoSubBlocks) { + Dlrr dlrr; + dlrr.WithDlrrItem(0x11111111, 0x22222222, 0x33333333); + dlrr.WithDlrrItem(0x44444444, 0x55555555, 0x66666666); + Xr xr; + xr.From(kSenderSsrc); + xr.WithDlrr(&dlrr); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(1, parser.dlrr()->num_packets()); + EXPECT_EQ(2, parser.dlrr_items()->num_packets()); + EXPECT_EQ(0x11111111U, parser.dlrr_items()->Ssrc(0)); + EXPECT_EQ(0x22222222U, parser.dlrr_items()->LastRr(0)); + EXPECT_EQ(0x33333333U, parser.dlrr_items()->DelayLastRr(0)); + EXPECT_EQ(0x44444444U, parser.dlrr_items()->Ssrc(1)); + EXPECT_EQ(0x55555555U, parser.dlrr_items()->LastRr(1)); + EXPECT_EQ(0x66666666U, parser.dlrr_items()->DelayLastRr(1)); +} + +TEST(RtcpPacketTest, XrWithTwoDlrrBlocks) { + Dlrr dlrr1; + dlrr1.WithDlrrItem(0x11111111, 0x22222222, 0x33333333); + Dlrr dlrr2; + dlrr2.WithDlrrItem(0x44444444, 0x55555555, 0x66666666); + Xr xr; + xr.From(kSenderSsrc); + xr.WithDlrr(&dlrr1); + xr.WithDlrr(&dlrr2); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(2, parser.dlrr()->num_packets()); + EXPECT_EQ(2, parser.dlrr_items()->num_packets()); + EXPECT_EQ(0x11111111U, parser.dlrr_items()->Ssrc(0)); + EXPECT_EQ(0x22222222U, parser.dlrr_items()->LastRr(0)); + EXPECT_EQ(0x33333333U, parser.dlrr_items()->DelayLastRr(0)); + EXPECT_EQ(0x44444444U, parser.dlrr_items()->Ssrc(1)); + EXPECT_EQ(0x55555555U, parser.dlrr_items()->LastRr(1)); + EXPECT_EQ(0x66666666U, parser.dlrr_items()->DelayLastRr(1)); +} + +TEST(RtcpPacketTest, XrWithVoipMetric) { + VoipMetric metric; + metric.To(kRemoteSsrc); + metric.LossRate(1); + metric.DiscardRate(2); + metric.BurstDensity(3); + metric.GapDensity(4); + metric.BurstDuration(0x1111); + metric.GapDuration(0x2222); + metric.RoundTripDelay(0x3333); + metric.EndSystemDelay(0x4444); + metric.SignalLevel(5); + metric.NoiseLevel(6); + metric.Rerl(7); + metric.Gmin(8); + metric.Rfactor(9); + metric.ExtRfactor(10); + metric.MosLq(11); + metric.MosCq(12); + metric.RxConfig(13); + metric.JbNominal(0x5555); + metric.JbMax(0x6666); + metric.JbAbsMax(0x7777); + + Xr xr; + xr.From(kSenderSsrc); + xr.WithVoipMetric(&metric); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(1, parser.voip_metric()->num_packets()); + EXPECT_EQ(kRemoteSsrc, parser.voip_metric()->Ssrc()); + EXPECT_EQ(1, parser.voip_metric()->LossRate()); + EXPECT_EQ(2, parser.voip_metric()->DiscardRate()); + EXPECT_EQ(3, parser.voip_metric()->BurstDensity()); + EXPECT_EQ(4, parser.voip_metric()->GapDensity()); + EXPECT_EQ(0x1111, parser.voip_metric()->BurstDuration()); + EXPECT_EQ(0x2222, parser.voip_metric()->GapDuration()); + EXPECT_EQ(0x3333, parser.voip_metric()->RoundTripDelay()); + EXPECT_EQ(0x4444, parser.voip_metric()->EndSystemDelay()); + EXPECT_EQ(5, parser.voip_metric()->SignalLevel()); + EXPECT_EQ(6, parser.voip_metric()->NoiseLevel()); + EXPECT_EQ(7, parser.voip_metric()->Rerl()); + EXPECT_EQ(8, parser.voip_metric()->Gmin()); + EXPECT_EQ(9, parser.voip_metric()->Rfactor()); + EXPECT_EQ(10, parser.voip_metric()->ExtRfactor()); + EXPECT_EQ(11, parser.voip_metric()->MosLq()); + EXPECT_EQ(12, parser.voip_metric()->MosCq()); + EXPECT_EQ(13, parser.voip_metric()->RxConfig()); + EXPECT_EQ(0x5555, parser.voip_metric()->JbNominal()); + EXPECT_EQ(0x6666, parser.voip_metric()->JbMax()); + EXPECT_EQ(0x7777, parser.voip_metric()->JbAbsMax()); +} + +TEST(RtcpPacketTest, XrWithMultipleReportBlocks) { + Rrtr rrtr; + Dlrr dlrr; + dlrr.WithDlrrItem(1, 2, 3); + VoipMetric metric; + Xr xr; + xr.From(kSenderSsrc); + xr.WithRrtr(&rrtr); + xr.WithDlrr(&dlrr); + xr.WithVoipMetric(&metric); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(1, parser.rrtr()->num_packets()); + EXPECT_EQ(1, parser.dlrr()->num_packets()); + EXPECT_EQ(1, parser.dlrr_items()->num_packets()); + EXPECT_EQ(1, parser.voip_metric()->num_packets()); +} + +TEST(RtcpPacketTest, DlrrWithoutItemNotIncludedInPacket) { + Rrtr rrtr; + Dlrr dlrr; + VoipMetric metric; + Xr xr; + xr.From(kSenderSsrc); + xr.WithRrtr(&rrtr); + xr.WithDlrr(&dlrr); + xr.WithVoipMetric(&metric); + + RawPacket packet = xr.Build(); + RtcpPacketParser parser; + parser.Parse(packet.buffer(), packet.buffer_length()); + EXPECT_EQ(1, parser.xr_header()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.xr_header()->Ssrc()); + EXPECT_EQ(1, parser.rrtr()->num_packets()); + EXPECT_EQ(0, parser.dlrr()->num_packets()); + EXPECT_EQ(1, parser.voip_metric()->num_packets()); +} } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc b/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc index 4012a81cd..896bd5f4d 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc @@ -18,7 +18,7 @@ #include "webrtc/modules/rtp_rtcp/source/rtcp_utility.h" #include "webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -57,7 +57,6 @@ RTCPReceiver::RTCPReceiver(const int32_t id, Clock* clock, _lastIncreasedSequenceNumberMs(0), stats_callback_(NULL) { memset(&_remoteSenderInfo, 0, sizeof(_remoteSenderInfo)); - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id, "%s created", __FUNCTION__); } RTCPReceiver::~RTCPReceiver() { @@ -82,8 +81,6 @@ RTCPReceiver::~RTCPReceiver() { delete first->second; _receivedCnameMap.erase(first); } - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, _id, - "%s deleted", __FUNCTION__); } void @@ -178,8 +175,7 @@ int32_t RTCPReceiver::ResetRTT(const uint32_t remoteSSRC) { RTCPReportBlockInformation* reportBlock = GetReportBlockInformation(remoteSSRC); if (reportBlock == NULL) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "\tfailed to GetReportBlockInformation(%u)", remoteSSRC); + LOG(LS_WARNING) << "Failed to reset rtt for ssrc " << remoteSSRC; return -1; } reportBlock->RTT = 0; @@ -282,22 +278,14 @@ bool RTCPReceiver::LastReceivedXrReferenceTimeInfo( return true; } -int32_t -RTCPReceiver::SenderInfoReceived(RTCPSenderInfo* senderInfo) const -{ - if(senderInfo == NULL) - { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); - return -1; - } - CriticalSectionScoped lock(_criticalSectionRTCPReceiver); - if(_lastReceivedSRNTPsecs == 0) - { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, "%s No received SR", __FUNCTION__); - return -1; - } - memcpy(senderInfo, &(_remoteSenderInfo), sizeof(RTCPSenderInfo)); - return 0; +int32_t RTCPReceiver::SenderInfoReceived(RTCPSenderInfo* senderInfo) const { + assert(senderInfo); + CriticalSectionScoped lock(_criticalSectionRTCPReceiver); + if (_lastReceivedSRNTPsecs == 0) { + return -1; + } + memcpy(senderInfo, &(_remoteSenderInfo), sizeof(RTCPSenderInfo)); + return 0; } // statistics @@ -518,8 +506,8 @@ void RTCPReceiver::HandleReportBlock( RTCPReportBlockInformation* reportBlock = CreateReportBlockInformation(remoteSSRC); if (reportBlock == NULL) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "\tfailed to CreateReportBlockInformation(%u)", remoteSSRC); + LOG(LS_WARNING) << "Failed to CreateReportBlockInformation(" + << remoteSSRC << ")"; return; } @@ -779,9 +767,6 @@ int32_t RTCPReceiver::BoundingSet(bool &tmmbrOwner, TMMBRSet* boundingSetRec) { } RTCPReceiveInformation* receiveInfo = receiveInfoIt->second; if (receiveInfo == NULL) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "%s failed to get RTCPReceiveInformation", - __FUNCTION__); return -1; } if (receiveInfo->TmmbnBoundingSet.lengthOfSet() > 0) { @@ -1348,8 +1333,7 @@ int32_t RTCPReceiver::UpdateTMMBR() { TMMBRSet* boundingSet = NULL; numBoundingSet = FindTMMBRBoundingSet(boundingSet); if (numBoundingSet == -1) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, - "Failed to find TMMBR bounding set."); + LOG(LS_WARNING) << "Failed to find TMMBR bounding set."; return -1; } // Set bounding set @@ -1369,8 +1353,6 @@ int32_t RTCPReceiver::UpdateTMMBR() { CriticalSectionScoped lock(_criticalSectionFeedbacks); if (_cbRtcpBandwidthObserver) { _cbRtcpBandwidthObserver->OnReceivedEstimatedBitrate(bitrate * 1000); - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, - "Set TMMBR request:%d kbps", bitrate); } } return 0; @@ -1395,9 +1377,6 @@ void RTCPReceiver::TriggerCallbacksFromRTCPPacket( // Process TMMBR and REMB first to avoid multiple callbacks // to OnNetworkChanged. if (rtcpPacketInformation.rtcpPacketTypeFlags & kRtcpTmmbr) { - WEBRTC_TRACE(kTraceStateInfo, kTraceRtpRtcp, _id, - "SIG [RTCP] Incoming TMMBR to id:%d", _id); - // Might trigger a OnReceivedBandwidthEstimateUpdate. UpdateTMMBR(); } @@ -1412,9 +1391,8 @@ void RTCPReceiver::TriggerCallbacksFromRTCPPacket( } if (rtcpPacketInformation.rtcpPacketTypeFlags & kRtcpNack) { if (rtcpPacketInformation.nackSequenceNumbers.size() > 0) { - WEBRTC_TRACE(kTraceStateInfo, kTraceRtpRtcp, _id, - "SIG [RTCP] Incoming NACK length:%d", - rtcpPacketInformation.nackSequenceNumbers.size()); + LOG(LS_VERBOSE) << "Incoming NACK length: " + << rtcpPacketInformation.nackSequenceNumbers.size(); _rtpRtcp.OnReceivedNACK(rtcpPacketInformation.nackSequenceNumbers); } } @@ -1429,13 +1407,11 @@ void RTCPReceiver::TriggerCallbacksFromRTCPPacket( if ((rtcpPacketInformation.rtcpPacketTypeFlags & kRtcpPli) || (rtcpPacketInformation.rtcpPacketTypeFlags & kRtcpFir)) { if (rtcpPacketInformation.rtcpPacketTypeFlags & kRtcpPli) { - WEBRTC_TRACE(kTraceStateInfo, kTraceRtpRtcp, _id, - "SIG [RTCP] Incoming PLI from SSRC:0x%x", - rtcpPacketInformation.remoteSSRC); + LOG(LS_VERBOSE) << "Incoming PLI from SSRC " + << rtcpPacketInformation.remoteSSRC; } else { - WEBRTC_TRACE(kTraceStateInfo, kTraceRtpRtcp, _id, - "SIG [RTCP] Incoming FIR from SSRC:0x%x", - rtcpPacketInformation.remoteSSRC); + LOG(LS_VERBOSE) << "Incoming FIR from SSRC " + << rtcpPacketInformation.remoteSSRC; } _cbRtcpIntraFrameObserver->OnReceivedIntraFrameRequest(local_ssrc); } @@ -1450,9 +1426,8 @@ void RTCPReceiver::TriggerCallbacksFromRTCPPacket( } if (_cbRtcpBandwidthObserver) { if (rtcpPacketInformation.rtcpPacketTypeFlags & kRtcpRemb) { - WEBRTC_TRACE(kTraceStateInfo, kTraceRtpRtcp, _id, - "SIG [RTCP] Incoming REMB:%d", - rtcpPacketInformation.receiverEstimatedMaxBitrate); + LOG(LS_VERBOSE) << "Incoming REMB: " + << rtcpPacketInformation.receiverEstimatedMaxBitrate; _cbRtcpBandwidthObserver->OnReceivedEstimatedBitrate( rtcpPacketInformation.receiverEstimatedMaxBitrate); } @@ -1548,9 +1523,6 @@ int32_t RTCPReceiver::TMMBRReceived(const uint32_t size, while (receiveInfoIt != _receivedInfoMap.end()) { RTCPReceiveInformation* receiveInfo = receiveInfoIt->second; if(receiveInfo == NULL) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "%s failed to get RTCPReceiveInformation", - __FUNCTION__); return -1; } num += receiveInfo->TmmbrSet.lengthOfSet(); diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h b/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h index 949beb9c7..0ca43fa53 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h +++ b/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h @@ -12,10 +12,10 @@ #define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_RECEIVER_HELP_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" // RTCPReportBlock #include "webrtc/modules/rtp_rtcp/source/rtcp_utility.h" #include "webrtc/modules/rtp_rtcp/source/tmmbr_help.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc b/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc index 6e6edf816..d73de9c42 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc @@ -19,7 +19,7 @@ #include "webrtc/common_types.h" #include "webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -161,8 +161,6 @@ RTCPSender::RTCPSender(const int32_t id, memset(_CNAME, 0, sizeof(_CNAME)); memset(_lastSendReport, 0, sizeof(_lastSendReport)); memset(_lastRTCPTime, 0, sizeof(_lastRTCPTime)); - - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id, "%s created", __FUNCTION__); } RTCPSender::~RTCPSender() { @@ -187,8 +185,6 @@ RTCPSender::~RTCPSender() { } delete _criticalSectionTransport; delete _criticalSectionRTCPSender; - - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, _id, "%s deleted", __FUNCTION__); } int32_t @@ -427,7 +423,8 @@ RTCPSender::SetCameraDelay(const int32_t delayMS) CriticalSectionScoped lock(_criticalSectionRTCPSender); if(delayMS > 1000 || delayMS < -1000) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument, delay can't be larger than 1 sec", __FUNCTION__); + LOG(LS_WARNING) << "Delay can't be larger than 1 second: " + << delayMS << " ms"; return -1; } _cameraDelayMS = delayMS; @@ -631,15 +628,10 @@ int32_t RTCPSender::AddReportBlock( uint32_t SSRC, std::map* report_blocks, const RTCPReportBlock* reportBlock) { - if (reportBlock == NULL) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "%s invalid argument", __FUNCTION__); - return -1; - } + assert(reportBlock); if (report_blocks->size() >= RTCP_MAX_REPORT_BLOCKS) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "%s invalid argument", __FUNCTION__); + LOG(LS_WARNING) << "Too many report blocks."; return -1; } std::map::iterator it = @@ -677,7 +669,7 @@ int32_t RTCPSender::BuildSR(const FeedbackState& feedback_state, // sanity if(pos + 52 >= IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); + LOG(LS_WARNING) << "Failed to build Sender Report."; return -2; } uint32_t RTPtime; @@ -760,8 +752,7 @@ int32_t RTCPSender::BuildSDEC(uint8_t* rtcpbuffer, int& pos) { // sanity if(pos + 12 + lengthCname >= IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "%s invalid argument", __FUNCTION__); + LOG(LS_WARNING) << "Failed to build SDEC."; return -2; } // SDEC Source Description @@ -913,7 +904,9 @@ RTCPSender::BuildExtendedJitterReport( { if (external_report_blocks_.size() > 0) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, "Not implemented."); + // TODO(andresp): Remove external report blocks since they are not + // supported. + LOG(LS_ERROR) << "Handling of external report blocks not implemented."; return 0; } @@ -1317,7 +1310,7 @@ RTCPSender::BuildTMMBN(uint8_t* rtcpbuffer, int& pos) // sanity if(pos + 12 + boundingSet->lengthOfSet()*8 >= IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); + LOG(LS_WARNING) << "Failed to build TMMBN."; return -2; } uint8_t FMT = 4; @@ -1384,12 +1377,12 @@ RTCPSender::BuildAPP(uint8_t* rtcpbuffer, int& pos) // sanity if(_appData == NULL) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, "%s invalid state", __FUNCTION__); + LOG(LS_WARNING) << "Failed to build app specific."; return -1; } if(pos + 12 + _appLength >= IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); + LOG(LS_WARNING) << "Failed to build app specific."; return -2; } rtcpbuffer[pos++]=(uint8_t)0x80 + _appSubType; @@ -1425,7 +1418,7 @@ RTCPSender::BuildNACK(uint8_t* rtcpbuffer, // sanity if(pos + 16 >= IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); + LOG(LS_WARNING) << "Failed to build NACK."; return -2; } @@ -1478,8 +1471,7 @@ RTCPSender::BuildNACK(uint8_t* rtcpbuffer, numOfNackFields++; } if (i != nackSize) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, - "Nack list to large for one packet."); + LOG(LS_WARNING) << "Nack list to large for one packet."; } rtcpbuffer[nackSizePos] = static_cast(2 + numOfNackFields); *nackString = stringBuilder.GetResult(); @@ -1715,8 +1707,7 @@ int32_t RTCPSender::SendRTCP(const FeedbackState& feedback_state, CriticalSectionScoped lock(_criticalSectionRTCPSender); if(_method == kRtcpOff) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, - "%s invalid state", __FUNCTION__); + LOG(LS_WARNING) << "Can't send rtcp if it is disabled."; return -1; } } @@ -2128,13 +2119,7 @@ int32_t RTCPSender::SetCSRCs(const uint32_t arrOfCSRC[kRtpCsrcSize], const uint8_t arrLength) { - if(arrLength > kRtpCsrcSize) - { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); - assert(false); - return -1; - } - + assert(arrLength <= kRtpCsrcSize); CriticalSectionScoped lock(_criticalSectionRTCPSender); for(int i = 0; i < arrLength;i++) @@ -2153,7 +2138,7 @@ RTCPSender::SetApplicationSpecificData(const uint8_t subType, { if(length %4 != 0) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); + LOG(LS_ERROR) << "Failed to SetApplicationSpecificData."; return -1; } CriticalSectionScoped lock(_criticalSectionRTCPSender); @@ -2199,17 +2184,10 @@ int32_t RTCPSender::WriteAllReportBlocksToBuffer( uint8_t& numberOfReportBlocks, const uint32_t NTPsec, const uint32_t NTPfrac) { - // sanity one block - if(pos + 24 >= IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "%s invalid argument", __FUNCTION__); - return -1; - } numberOfReportBlocks = external_report_blocks_.size(); numberOfReportBlocks += internal_report_blocks_.size(); if ((pos + numberOfReportBlocks * 24) >= IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "%s invalid argument", __FUNCTION__); + LOG(LS_WARNING) << "Can't fit all report blocks."; return -1; } pos = WriteReportBlocksToBuffer(rtcpbuffer, pos, internal_report_blocks_); diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc index 8474390d2..dfb655c51 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc @@ -278,7 +278,7 @@ class RtcpSenderTest : public ::testing::Test { : over_use_detector_options_(), clock_(1335900000), rtp_payload_registry_(new RTPPayloadRegistry( - 0, RTPPayloadStrategy::CreateStrategy(false))), + RTPPayloadStrategy::CreateStrategy(false))), remote_bitrate_observer_(), remote_bitrate_estimator_( RemoteBitrateEstimatorFactory().Create( diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_utility.cc b/webrtc/modules/rtp_rtcp/source/rtcp_utility.cc index 705a38b01..9acab735e 100644 --- a/webrtc/modules/rtp_rtcp/source/rtcp_utility.cc +++ b/webrtc/modules/rtp_rtcp/source/rtcp_utility.cc @@ -1266,31 +1266,27 @@ RTCPUtility::RTCPParserV2::ParseFBCommon(const RTCPCommonHeader& header) } } -bool -RTCPUtility::RTCPParserV2::ParseRPSIItem() -{ - // RFC 4585 6.3.3. Reference Picture Selection Indication (RPSI) - /* - 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 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | PB |0| Payload Type| Native RPSI bit string | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | defined per codec ... | Padding (0) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ +bool RTCPUtility::RTCPParserV2::ParseRPSIItem() { + + // RFC 4585 6.3.3. Reference Picture Selection Indication (RPSI). + // + // 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 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | PB |0| Payload Type| Native RPSI bit string | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | defined per codec ... | Padding (0) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ const ptrdiff_t length = _ptrRTCPBlockEnd - _ptrRTCPData; - if (length < 4) - { + if (length < 4) { _state = State_TopLevel; EndCurrentBlock(); return false; } - if(length > 2+RTCP_RPSI_DATA_SIZE) - { + if (length > 2 + RTCP_RPSI_DATA_SIZE) { _state = State_TopLevel; EndCurrentBlock(); @@ -1299,12 +1295,14 @@ RTCPUtility::RTCPParserV2::ParseRPSIItem() _packetType = kRtcpPsfbRpsiCode; - uint8_t paddingBits = *_ptrRTCPData++; + uint8_t padding_bits = *_ptrRTCPData++; _packet.RPSI.PayloadType = *_ptrRTCPData++; - memcpy(_packet.RPSI.NativeBitString, _ptrRTCPData, length-2); + memcpy(_packet.RPSI.NativeBitString, _ptrRTCPData, length - 2); + _ptrRTCPData += length - 2; - _packet.RPSI.NumberOfValidBits = uint16_t(length-2)*8 - paddingBits; + _packet.RPSI.NumberOfValidBits = + static_cast(length - 2) * 8 - padding_bits; return true; } diff --git a/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc index 904156e94..fa847625e 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc @@ -41,7 +41,7 @@ template void ClearList(std::list* my_list) { class RtpFecTest : public ::testing::Test { protected: RtpFecTest() - : fec_(new ForwardErrorCorrection(0)), ssrc_(rand()), fec_seq_num_(0) {} + : fec_(new ForwardErrorCorrection()), ssrc_(rand()), fec_seq_num_(0) {} ForwardErrorCorrection* fec_; int ssrc_; @@ -86,43 +86,6 @@ class RtpFecTest : public ::testing::Test { void TearDown(); }; -// TODO(marpan): Consider adding table for input/output to simplify tests. - -TEST_F(RtpFecTest, HandleIncorrectInputs) { - int kNumImportantPackets = 0; - bool kUseUnequalProtection = false; - uint8_t kProtectionFactor = 60; - - // Media packet list is empty. - EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, kProtectionFactor, - kNumImportantPackets, kUseUnequalProtection, - webrtc::kFecMaskBursty, &fec_packet_list_)); - - int num_media_packets = 10; - ConstructMediaPackets(num_media_packets); - - kNumImportantPackets = -1; - // Number of important packets below 0. - EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, kProtectionFactor, - kNumImportantPackets, kUseUnequalProtection, - webrtc::kFecMaskBursty, &fec_packet_list_)); - - kNumImportantPackets = 12; - // Number of important packets greater than number of media packets. - EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, kProtectionFactor, - kNumImportantPackets, kUseUnequalProtection, - webrtc::kFecMaskBursty, &fec_packet_list_)); - - num_media_packets = kMaxNumberMediaPackets + 1; - ConstructMediaPackets(num_media_packets); - - kNumImportantPackets = 0; - // Number of media packet is above maximum allowed (kMaxNumberMediaPackets). - EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, kProtectionFactor, - kNumImportantPackets, kUseUnequalProtection, - webrtc::kFecMaskBursty, &fec_packet_list_)); -} - TEST_F(RtpFecTest, FecRecoveryNoLoss) { const int kNumImportantPackets = 0; const bool kUseUnequalProtection = false; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h b/webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h index 650c0fad5..e4d3dc083 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h @@ -28,8 +28,8 @@ #include #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/interface/module_common_types.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h b/webrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h index 13eb0e101..e146492c7 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h @@ -18,9 +18,9 @@ #ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_TEST_HELPER_H_ #define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_TEST_HELPER_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h b/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h index b41653c8b..edffe8aec 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_header_extension.h @@ -62,6 +62,8 @@ class RtpHeaderExtensionMap { int32_t Deregister(const RTPExtensionType type); + bool IsRegistered(RTPExtensionType type) const; + int32_t GetType(const uint8_t id, RTPExtensionType* type) const; int32_t GetId(const RTPExtensionType type, uint8_t* id) const; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_header_parser.cc b/webrtc/modules/rtp_rtcp/source/rtp_header_parser.cc index d04872582..bb24d4dbf 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_header_parser.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_header_parser.cc @@ -13,7 +13,6 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" -#include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { @@ -60,8 +59,6 @@ bool RtpHeaderParserImpl::Parse(const uint8_t* packet, int length, const bool valid_rtpheader = rtp_parser.Parse(*header, &map); if (!valid_rtpheader) { - WEBRTC_TRACE(kTraceDebug, kTraceRtpRtcp, -1, - "IncomingPacket invalid RTP header"); return false; } return true; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_packet_history.cc b/webrtc/modules/rtp_rtcp/source/rtp_packet_history.cc index 73b232606..e3515f445 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_packet_history.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_packet_history.cc @@ -18,7 +18,7 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -33,13 +33,21 @@ RTPPacketHistory::RTPPacketHistory(Clock* clock) } RTPPacketHistory::~RTPPacketHistory() { - Free(); + { + CriticalSectionScoped cs(critsect_); + Free(); + } delete critsect_; } void RTPPacketHistory::SetStorePacketsStatus(bool enable, uint16_t number_to_store) { + CriticalSectionScoped cs(critsect_); if (enable) { + if (store_) { + LOG(LS_WARNING) << "Purging packet history in order to re-set status."; + Free(); + } Allocate(number_to_store); } else { Free(); @@ -48,16 +56,7 @@ void RTPPacketHistory::SetStorePacketsStatus(bool enable, void RTPPacketHistory::Allocate(uint16_t number_to_store) { assert(number_to_store > 0); - CriticalSectionScoped cs(critsect_); - if (store_) { - if (number_to_store != stored_packets_.size()) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "SetStorePacketsStatus already set, number: %d", - number_to_store); - } - return; - } - + assert(!store_); store_ = true; stored_packets_.resize(number_to_store); stored_seq_nums_.resize(number_to_store); @@ -68,7 +67,6 @@ void RTPPacketHistory::Allocate(uint16_t number_to_store) { } void RTPPacketHistory::Free() { - CriticalSectionScoped cs(critsect_); if (!store_) { return; } @@ -133,8 +131,8 @@ int32_t RTPPacketHistory::PutRTPPacket(const uint8_t* packet, VerifyAndAllocatePacketLength(max_packet_length); if (packet_length > max_packet_length_) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, -1, - "Failed to store RTP packet, length: %d", packet_length); + LOG(LS_WARNING) << "Failed to store RTP packet with length: " + << packet_length; return -1; } @@ -159,46 +157,6 @@ int32_t RTPPacketHistory::PutRTPPacket(const uint8_t* packet, return 0; } -int32_t RTPPacketHistory::ReplaceRTPHeader(const uint8_t* packet, - uint16_t sequence_number, - uint16_t rtp_header_length) { - CriticalSectionScoped cs(critsect_); - if (!store_) { - return 0; - } - - assert(packet); - assert(rtp_header_length > 3); - - if (rtp_header_length > max_packet_length_) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "Failed to replace RTP packet, length: %d", rtp_header_length); - return -1; - } - - int32_t index = 0; - bool found = FindSeqNum(sequence_number, &index); - if (!found) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "No match for getting seqNum %u", sequence_number); - return -1; - } - - uint16_t length = stored_lengths_.at(index); - if (length == 0 || length > max_packet_length_) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "No match for getting seqNum %u, len %d", sequence_number, length); - return -1; - } - assert(stored_seq_nums_[index] == sequence_number); - - // Update RTP header. - std::vector >::iterator it = - stored_packets_.begin() + index; - std::copy(packet, packet + rtp_header_length, it->begin()); - return 0; -} - bool RTPPacketHistory::HasRTPPacket(uint16_t sequence_number) const { CriticalSectionScoped cs(critsect_); if (!store_) { @@ -225,6 +183,7 @@ bool RTPPacketHistory::GetPacketAndSetSendTime(uint16_t sequence_number, uint8_t* packet, uint16_t* packet_length, int64_t* stored_time_ms) { + assert(*packet_length >= max_packet_length_); CriticalSectionScoped cs(critsect_); if (!store_) { return false; @@ -233,21 +192,15 @@ bool RTPPacketHistory::GetPacketAndSetSendTime(uint16_t sequence_number, int32_t index = 0; bool found = FindSeqNum(sequence_number, &index); if (!found) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "No match for getting seqNum %u", sequence_number); + LOG(LS_WARNING) << "No match for getting seqNum " << sequence_number; return false; } uint16_t length = stored_lengths_.at(index); - if (length == 0 || length > max_packet_length_) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "No match for getting seqNum %u, len %d", sequence_number, length); - return false; - } - - if (length > *packet_length) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Input buffer too short for packet %u", sequence_number); + assert(length <= max_packet_length_); + if (length == 0) { + LOG(LS_WARNING) << "No match for getting seqNum " << sequence_number + << ", len " << length; return false; } @@ -255,8 +208,6 @@ bool RTPPacketHistory::GetPacketAndSetSendTime(uint16_t sequence_number, int64_t now = clock_->TimeInMilliseconds(); if (min_elapsed_time_ms > 0 && ((now - stored_send_times_.at(index)) < min_elapsed_time_ms)) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "Skip getting packet %u, packet recently resent.", sequence_number); return false; } diff --git a/webrtc/modules/rtp_rtcp/source/rtp_packet_history.h b/webrtc/modules/rtp_rtcp/source/rtp_packet_history.h index 785e4992b..190e5057b 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_packet_history.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_packet_history.h @@ -18,6 +18,7 @@ #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" #include "webrtc/typedefs.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" namespace webrtc { @@ -40,14 +41,6 @@ class RTPPacketHistory { int64_t capture_time_ms, StorageType type); - // Replaces the stored RTP packet with matching sequence number with the - // RTP header of the provided packet. - // Note: Calling this function assumes that the RTP header length should not - // have changed since the packet was stored. - int32_t ReplaceRTPHeader(const uint8_t* packet, - uint16_t sequence_number, - uint16_t rtp_header_length); - // Gets stored RTP packet corresponding to the input sequence number. // The packet is copied to the buffer pointed to by ptr_rtp_packet. // The rtp_packet_length should show the available buffer size. @@ -74,8 +67,8 @@ class RTPPacketHistory { private: void GetPacket(int index, uint8_t* packet, uint16_t* packet_length, int64_t* stored_time_ms) const; - void Allocate(uint16_t number_to_store); - void Free(); + void Allocate(uint16_t number_to_store) EXCLUSIVE_LOCKS_REQUIRED(*critsect_); + void Free() EXCLUSIVE_LOCKS_REQUIRED(*critsect_); void VerifyAndAllocatePacketLength(uint16_t packet_length); bool FindSeqNum(uint16_t sequence_number, int32_t* index) const; int FindBestFittingPacket(uint16_t size) const; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc index 1682b7c33..7eb22ff69 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc @@ -103,19 +103,6 @@ TEST_F(RtpPacketHistoryTest, PutRtpPacket_TooLargePacketLength) { kAllowRetransmission)); } -TEST_F(RtpPacketHistoryTest, GetRtpPacket_TooSmallBuffer) { - hist_->SetStorePacketsStatus(true, 10); - uint16_t len = 0; - int64_t capture_time_ms = fake_clock_.TimeInMilliseconds(); - CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); - EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, - capture_time_ms, kAllowRetransmission)); - uint16_t len_out = len - 1; - int64_t time; - EXPECT_FALSE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_, - &len_out, &time)); -} - TEST_F(RtpPacketHistoryTest, GetRtpPacket_NotStored) { hist_->SetStorePacketsStatus(true, 10); uint16_t len = kMaxPacketLength; @@ -155,42 +142,6 @@ TEST_F(RtpPacketHistoryTest, GetRtpPacket) { } } -TEST_F(RtpPacketHistoryTest, ReplaceRtpHeader) { - hist_->SetStorePacketsStatus(true, 10); - - uint16_t len = 0; - int64_t capture_time_ms = 1; - CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); - // Replace should fail, packet is not stored. - EXPECT_EQ(-1, hist_->ReplaceRTPHeader(packet_, kSeqNum, len)); - EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, - capture_time_ms, kAllowRetransmission)); - - // Create modified packet and replace. - len = 0; - CreateRtpPacket(kSeqNum, kSsrc + 1, kPayload + 2, kTimestamp, packet_, &len); - EXPECT_EQ(0, hist_->ReplaceRTPHeader(packet_, kSeqNum, len)); - - uint16_t len_out = kMaxPacketLength; - int64_t time; - EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 0, false, packet_out_, - &len_out, &time)); - EXPECT_EQ(len, len_out); - EXPECT_EQ(capture_time_ms, time); - for (int i = 0; i < len; i++) { - EXPECT_EQ(packet_[i], packet_out_[i]); - } - - // Replace should fail, too large length. - EXPECT_EQ(-1, hist_->ReplaceRTPHeader(packet_, kSeqNum, - kMaxPacketLength + 1)); - - // Replace should fail, packet is not stored. - len = 0; - CreateRtpPacket(kSeqNum + 1, kSsrc, kPayload, kTimestamp, packet_, &len); - EXPECT_EQ(-1, hist_->ReplaceRTPHeader(packet_, kSeqNum + 1, len)); -} - TEST_F(RtpPacketHistoryTest, NoCaptureTime) { hist_->SetStorePacketsStatus(true, 10); uint16_t len = 0; @@ -236,10 +187,10 @@ TEST_F(RtpPacketHistoryTest, MinResendTime) { capture_time_ms, kAllowRetransmission)); int64_t time; + len = kMaxPacketLength; EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 100, false, packet_, &len, &time)); fake_clock_.AdvanceTimeMilliseconds(100); - // Time has elapsed. len = kMaxPacketLength; EXPECT_TRUE(hist_->GetPacketAndSetSendTime(kSeqNum, 100, false, packet_, &len, diff --git a/webrtc/modules/rtp_rtcp/source/rtp_payload_registry.cc b/webrtc/modules/rtp_rtcp/source/rtp_payload_registry.cc index 1c3b990c5..db2e4cd31 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_payload_registry.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_payload_registry.cc @@ -10,15 +10,13 @@ #include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { RTPPayloadRegistry::RTPPayloadRegistry( - const int32_t id, RTPPayloadStrategy* rtp_payload_strategy) : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - id_(id), rtp_payload_strategy_(rtp_payload_strategy), red_payload_type_(-1), ulpfec_payload_type_(-1), @@ -60,9 +58,8 @@ int32_t RTPPayloadRegistry::RegisterReceivePayload( case 77: // 205 Transport layer FB message. case 78: // 206 Payload-specific FB message. case 79: // 207 Extended report. - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s invalid payloadtype:%d", - __FUNCTION__, payload_type); + LOG(LS_ERROR) << "Can't register invalid receiver payload type: " + << payload_type; return -1; default: break; @@ -94,9 +91,7 @@ int32_t RTPPayloadRegistry::RegisterReceivePayload( return 0; } } - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s invalid argument payload_type:%d already registered", - __FUNCTION__, payload_type); + LOG(LS_ERROR) << "Payload type already registered: " << payload_type; return -1; } @@ -138,14 +133,8 @@ int32_t RTPPayloadRegistry::DeRegisterReceivePayload( const int8_t payload_type) { CriticalSectionScoped cs(crit_sect_.get()); ModuleRTPUtility::PayloadTypeMap::iterator it = - payload_type_map_.find(payload_type); - - if (it == payload_type_map_.end()) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s failed to find payload_type:%d", - __FUNCTION__, payload_type); - return -1; - } + payload_type_map_.find(payload_type); + assert(it != payload_type_map_.end()); delete it->second; payload_type_map_.erase(it); return 0; @@ -194,11 +183,7 @@ int32_t RTPPayloadRegistry::ReceivePayloadType( const uint8_t channels, const uint32_t rate, int8_t* payload_type) const { - if (payload_type == NULL) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s invalid argument", __FUNCTION__); - return -1; - } + assert(payload_type); size_t payload_name_length = strlen(payload_name); CriticalSectionScoped cs(crit_sect_.get()); @@ -243,12 +228,6 @@ int32_t RTPPayloadRegistry::ReceivePayloadType( return -1; } -void RTPPayloadRegistry::SetRtxStatus(bool enable, uint32_t ssrc) { - CriticalSectionScoped cs(crit_sect_.get()); - rtx_ = enable; - ssrc_rtx_ = ssrc; -} - bool RTPPayloadRegistry::RtxEnabled() const { CriticalSectionScoped cs(crit_sect_.get()); return rtx_; @@ -296,17 +275,24 @@ bool RTPPayloadRegistry::RestoreOriginalPacket(uint8_t** restored_packet, (*restored_packet)[1] |= kRtpMarkerBitMask; // Marker bit is set. } } else { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "Incorrect RTX configuration, dropping packet."); + LOG(LS_WARNING) << "Incorrect RTX configuration, dropping packet."; return false; } } return true; } +void RTPPayloadRegistry::SetRtxSsrc(uint32_t ssrc) { + CriticalSectionScoped cs(crit_sect_.get()); + ssrc_rtx_ = ssrc; + rtx_ = true; +} + void RTPPayloadRegistry::SetRtxPayloadType(int payload_type) { CriticalSectionScoped cs(crit_sect_.get()); + assert(payload_type >= 0); payload_type_rtx_ = payload_type; + rtx_ = true; } bool RTPPayloadRegistry::IsRed(const RTPHeader& header) const { diff --git a/webrtc/modules/rtp_rtcp/source/rtp_payload_registry_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_payload_registry_unittest.cc index 96fa80ad8..c03ffcd1f 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_payload_registry_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_payload_registry_unittest.cc @@ -32,8 +32,7 @@ class RtpPayloadRegistryTest : public ::testing::Test { void SetUp() { // Note: the payload registry takes ownership of the strategy. mock_payload_strategy_ = new testing::NiceMock(); - rtp_payload_registry_.reset( - new RTPPayloadRegistry(123, mock_payload_strategy_)); + rtp_payload_registry_.reset(new RTPPayloadRegistry(mock_payload_strategy_)); } protected: diff --git a/webrtc/modules/rtp_rtcp/source/rtp_receiver_audio.cc b/webrtc/modules/rtp_rtcp/source/rtp_receiver_audio.cc index 134548518..c8104cc37 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_receiver_audio.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_receiver_audio.cc @@ -15,7 +15,7 @@ #include // memcpy() #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -277,11 +277,8 @@ int32_t RTPReceiverAudio::InvokeOnInitializeDecoder( specific_payload.Audio.frequency, specific_payload.Audio.channels, specific_payload.Audio.rate)) { - WEBRTC_TRACE(kTraceError, - kTraceRtpRtcp, - id, - "Failed to create video decoder for payload type:%d", - payload_type); + LOG(LS_ERROR) << "Failed to create decoder for payload type: " + << payload_name << "/" << payload_type; return -1; } return 0; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc b/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc index 9a276819a..d92618f2d 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc @@ -18,7 +18,7 @@ #include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h" #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" #include "webrtc/modules/rtp_rtcp/source/rtp_receiver_strategy.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -39,7 +39,7 @@ RtpReceiver* RtpReceiver::CreateVideoReceiver( return new RtpReceiverImpl( id, clock, NullObjectRtpAudioFeedback(), incoming_messages_callback, rtp_payload_registry, - RTPReceiverStrategy::CreateVideoStrategy(id, incoming_payload_callback)); + RTPReceiverStrategy::CreateVideoStrategy(incoming_payload_callback)); } RtpReceiver* RtpReceiver::CreateAudioReceiver( @@ -87,8 +87,6 @@ RtpReceiverImpl::RtpReceiverImpl(int32_t id, assert(incoming_messages_callback); memset(current_remote_csrc_, 0, sizeof(current_remote_csrc_)); - - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id, "%s created", __FUNCTION__); } RtpReceiverImpl::~RtpReceiverImpl() { @@ -96,7 +94,6 @@ RtpReceiverImpl::~RtpReceiverImpl() { cb_rtp_feedback_->OnIncomingCSRCChanged(id_, current_remote_csrc_[i], false); } - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id_, "%s deleted", __FUNCTION__); } RTPReceiverStrategy* RtpReceiverImpl::GetMediaReceiver() const { @@ -127,9 +124,8 @@ int32_t RtpReceiverImpl::RegisterReceivePayload( if (created_new_payload) { if (rtp_media_receiver_->OnNewPayloadTypeCreated(payload_name, payload_type, frequency) != 0) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s failed to register payload", - __FUNCTION__); + LOG(LS_ERROR) << "Failed to register payload: " << payload_name << "/" + << payload_type; return -1; } } @@ -182,19 +178,12 @@ bool RtpReceiverImpl::IncomingRtpPacket( PayloadUnion payload_specific, bool in_order) { // Sanity check. - if (payload_length < 0) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s invalid argument", - __FUNCTION__); - return false; - } - int8_t first_payload_byte = 0; - if (payload_length > 0) { - first_payload_byte = payload[0]; - } + assert(payload_length >= 0); + // Trigger our callbacks. CheckSSRCChanged(rtp_header); + int8_t first_payload_byte = payload_length > 0 ? payload[0] : 0; bool is_red = false; bool should_reset_statistics = false; @@ -205,14 +194,9 @@ bool RtpReceiverImpl::IncomingRtpPacket( &should_reset_statistics) == -1) { if (payload_length == 0) { // OK, keep-alive packet. - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "%s received keepalive", - __FUNCTION__); return true; } - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "%s received invalid payloadtype", - __FUNCTION__); + LOG(LS_WARNING) << "Receiving invalid payload type."; return false; } @@ -347,9 +331,8 @@ void RtpReceiverImpl::CheckSSRCChanged(const RTPHeader& rtp_header) { id_, rtp_header.payloadType, payload_name, rtp_header.payload_type_frequency, channels, rate)) { // New stream, same codec. - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "Failed to create decoder for payload type:%d", - rtp_header.payloadType); + LOG(LS_ERROR) << "Failed to create decoder for payload type: " + << rtp_header.payloadType; } } } diff --git a/webrtc/modules/rtp_rtcp/source/rtp_receiver_strategy.h b/webrtc/modules/rtp_rtcp/source/rtp_receiver_strategy.h index d8a225796..09c9b6fc3 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_receiver_strategy.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_receiver_strategy.h @@ -26,8 +26,7 @@ class TelephoneEventHandler; // This class is not thread-safe and must be protected by its caller. class RTPReceiverStrategy { public: - static RTPReceiverStrategy* CreateVideoStrategy(int32_t id, - RtpData* data_callback); + static RTPReceiverStrategy* CreateVideoStrategy(RtpData* data_callback); static RTPReceiverStrategy* CreateAudioStrategy( int32_t id, RtpData* data_callback, RtpAudioFeedback* incoming_messages_callback); diff --git a/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.cc b/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.cc index b733cdb4b..5bb519f62 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.cc @@ -17,19 +17,18 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h" #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { RTPReceiverStrategy* RTPReceiverStrategy::CreateVideoStrategy( - int32_t id, RtpData* data_callback) { - return new RTPReceiverVideo(id, data_callback); + RtpData* data_callback) { + return new RTPReceiverVideo(data_callback); } -RTPReceiverVideo::RTPReceiverVideo(int32_t id, RtpData* data_callback) - : RTPReceiverStrategy(data_callback), - id_(id) {} +RTPReceiverVideo::RTPReceiverVideo(RtpData* data_callback) + : RTPReceiverStrategy(data_callback) {} RTPReceiverVideo::~RTPReceiverVideo() { } @@ -93,11 +92,8 @@ int32_t RTPReceiverVideo::InvokeOnInitializeDecoder( // For video we just go with default values. if (-1 == callback->OnInitializeDecoder( id, payload_type, payload_name, kVideoPayloadTypeFrequency, 1, 0)) { - WEBRTC_TRACE(kTraceError, - kTraceRtpRtcp, - id, - "Failed to create video decoder for payload type:%d", - payload_type); + LOG(LS_ERROR) << "Failed to created decoder for payload type: " + << payload_type; return -1; } return 0; @@ -111,13 +107,6 @@ int32_t RTPReceiverVideo::ParseVideoCodecSpecific( RtpVideoCodecTypes video_type, int64_t now_ms, bool is_first_packet) { - WEBRTC_TRACE(kTraceStream, - kTraceRtpRtcp, - id_, - "%s(timestamp:%u)", - __FUNCTION__, - rtp_header->header.timestamp); - switch (rtp_header->type.Video.codec) { case kRtpVideoGeneric: rtp_header->type.Video.isFirstPacket = is_first_packet; @@ -170,13 +159,8 @@ int32_t RTPReceiverVideo::ReceiveVp8Codec(WebRtcRTPHeader* rtp_header, const uint8_t* payload_data, uint16_t payload_data_length) { ModuleRTPUtility::RTPPayload parsed_packet; - uint32_t id; - { - CriticalSectionScoped cs(crit_sect_.get()); - id = id_; - } ModuleRTPUtility::RTPPayloadParser rtp_payload_parser( - kRtpVideoVp8, payload_data, payload_data_length, id); + kRtpVideoVp8, payload_data, payload_data_length); if (!rtp_payload_parser.Parse(parsed_packet)) return -1; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.h b/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.h index ab69b40ee..4d81cb397 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_receiver_video.h @@ -22,7 +22,7 @@ namespace webrtc { class RTPReceiverVideo : public RTPReceiverStrategy { public: - RTPReceiverVideo(const int32_t id, RtpData* data_callback); + RTPReceiverVideo(RtpData* data_callback); virtual ~RTPReceiverVideo(); @@ -80,8 +80,6 @@ class RTPReceiverVideo : public RTPReceiverStrategy { RtpVideoCodecTypes video_type, int64_t now_ms, bool is_first_packet); - - int32_t id_; }; } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp.gypi b/webrtc/modules/rtp_rtcp/source/rtp_rtcp.gypi index 0a8c901e5..dcd659880 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp.gypi +++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp.gypi @@ -20,6 +20,7 @@ # Common '../interface/fec_receiver.h', '../interface/receive_statistics.h', + '../interface/remote_ntp_time_estimator.h', '../interface/rtp_header_parser.h', '../interface/rtp_payload_registry.h', '../interface/rtp_receiver.h', @@ -32,6 +33,7 @@ 'fec_receiver_impl.h', 'receive_statistics_impl.cc', 'receive_statistics_impl.h', + 'remote_ntp_time_estimator.cc', 'rtp_header_parser.cc', 'rtp_rtcp_config.h', 'rtp_rtcp_impl.cc', diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index 575da60a3..70fe71743 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -17,11 +17,6 @@ #include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace.h" -#ifdef MATLAB -#include "webrtc/modules/rtp_rtcp/test/BWEStandAlone/MatlabPlot.h" -extern MatlabEngine eng; // Global variable defined elsewhere. -#endif - #ifdef _WIN32 // Disable warning C4355: 'this' : used in base member initializer list. #pragma warning(disable : 4355) @@ -66,7 +61,9 @@ ModuleRtpRtcpImpl::ModuleRtpRtcpImpl(const Configuration& configuration) configuration.outgoing_transport, configuration.audio_messages, configuration.paced_sender), - rtcp_sender_(configuration.id, configuration.audio, configuration.clock, + rtcp_sender_(configuration.id, + configuration.audio, + configuration.clock, configuration.receive_statistics), rtcp_receiver_(configuration.id, configuration.clock, this), clock_(configuration.clock), @@ -83,15 +80,13 @@ ModuleRtpRtcpImpl::ModuleRtpRtcpImpl(const Configuration& configuration) CriticalSectionWrapper::CreateCriticalSection()), default_module_( static_cast(configuration.default_module)), + padding_index_(-1), // Start padding at the first child module. nack_method_(kNackOff), nack_last_time_sent_full_(0), nack_last_seq_number_sent_(0), simulcast_(false), key_frame_req_method_(kKeyFrameReqFirRtp), remote_bitrate_(configuration.remote_bitrate_estimator), -#ifdef MATLAB - , plot1_(NULL), -#endif rtt_stats_(configuration.rtt_stats), critical_section_rtt_(CriticalSectionWrapper::CreateCriticalSection()), rtt_ms_(0) { @@ -110,13 +105,9 @@ ModuleRtpRtcpImpl::ModuleRtpRtcpImpl(const Configuration& configuration) uint32_t SSRC = rtp_sender_.SSRC(); rtcp_sender_.SetSSRC(SSRC); SetRtcpReceiverSsrcs(SSRC); - - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id_, "%s created", __FUNCTION__); } ModuleRtpRtcpImpl::~ModuleRtpRtcpImpl() { - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id_, "%s deleted", __FUNCTION__); - // All child modules MUST be deleted before deleting the default. assert(child_modules_.empty()); @@ -125,21 +116,9 @@ ModuleRtpRtcpImpl::~ModuleRtpRtcpImpl() { if (default_module_) { default_module_->DeRegisterChildModule(this); } -#ifdef MATLAB - if (plot1_) { - eng.DeletePlot(plot1_); - plot1_ = NULL; - } -#endif } void ModuleRtpRtcpImpl::RegisterChildModule(RtpRtcp* module) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "RegisterChildModule(module:0x%x)", - module); - CriticalSectionScoped lock( critical_section_module_ptrs_.get()); CriticalSectionScoped double_lock( @@ -153,17 +132,12 @@ void ModuleRtpRtcpImpl::RegisterChildModule(RtpRtcp* module) { } void ModuleRtpRtcpImpl::DeRegisterChildModule(RtpRtcp* remove_module) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "DeRegisterChildModule(module:0x%x)", remove_module); - CriticalSectionScoped lock( critical_section_module_ptrs_.get()); CriticalSectionScoped double_lock( critical_section_module_ptrs_feedback_.get()); - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RtpRtcp* module = *it; if (module == remove_module) { @@ -263,16 +237,18 @@ int32_t ModuleRtpRtcpImpl::Process() { return 0; } -int32_t ModuleRtpRtcpImpl::SetRTXSendStatus(int mode, bool set_ssrc, - uint32_t ssrc) { - rtp_sender_.SetRTXStatus(mode, set_ssrc, ssrc); - return 0; +void ModuleRtpRtcpImpl::SetRTXSendStatus(int mode) { + rtp_sender_.SetRTXStatus(mode); } -int32_t ModuleRtpRtcpImpl::RTXSendStatus(int* mode, uint32_t* ssrc, - int* payload_type) const { +void ModuleRtpRtcpImpl::RTXSendStatus(int* mode, + uint32_t* ssrc, + int* payload_type) const { rtp_sender_.RTXStatus(mode, ssrc, payload_type); - return 0; +} + +void ModuleRtpRtcpImpl::SetRtxSsrc(uint32_t ssrc) { + rtp_sender_.SetRtxSsrc(ssrc); } void ModuleRtpRtcpImpl::SetRtxSendPayloadType(int payload_type) { @@ -282,29 +258,12 @@ void ModuleRtpRtcpImpl::SetRtxSendPayloadType(int payload_type) { int32_t ModuleRtpRtcpImpl::IncomingRtcpPacket( const uint8_t* rtcp_packet, const uint16_t length) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "IncomingRtcpPacket(packet_length:%u)", length); - // Minimum RTP is 12 bytes. - // Minimum RTCP is 8 bytes (RTCP BYE). - if (length == 8) { - WEBRTC_TRACE(kTraceDebug, kTraceRtpRtcp, -1, - "IncomingRtcpPacket invalid length"); - return false; - } - // Check RTP version. - const uint8_t version = rtcp_packet[0] >> 6; - if (version != 2) { - WEBRTC_TRACE(kTraceDebug, kTraceRtpRtcp, -1, - "IncomingRtcpPacket invalid RTP version"); - return false; - } // Allow receive of non-compound RTCP packets. RTCPUtility::RTCPParserV2 rtcp_parser(rtcp_packet, length, true); const bool valid_rtcpheader = rtcp_parser.IsValid(); if (!valid_rtcpheader) { - WEBRTC_TRACE(kTraceDebug, kTraceRtpRtcp, id_, - "IncomingRtcpPacket invalid RTCP packet"); + LOG(LS_WARNING) << "Incoming invalid RTCP packet"; return -1; } RTCPHelp::RTCPPacketInformation rtcp_packet_information; @@ -318,14 +277,6 @@ int32_t ModuleRtpRtcpImpl::IncomingRtcpPacket( int32_t ModuleRtpRtcpImpl::RegisterSendPayload( const CodecInst& voice_codec) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "RegisterSendPayload(pl_name:%s pl_type:%d frequency:%u)", - voice_codec.plname, - voice_codec.pltype, - voice_codec.plfreq); - return rtp_sender_.RegisterPayload( voice_codec.plname, voice_codec.pltype, @@ -336,13 +287,6 @@ int32_t ModuleRtpRtcpImpl::RegisterSendPayload( int32_t ModuleRtpRtcpImpl::RegisterSendPayload( const VideoCodec& video_codec) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "RegisterSendPayload(pl_name:%s pl_type:%d)", - video_codec.plName, - video_codec.plType); - send_video_codec_ = video_codec; { // simulcast_ is accessed when accessing child_modules_, so this write needs @@ -359,11 +303,6 @@ int32_t ModuleRtpRtcpImpl::RegisterSendPayload( int32_t ModuleRtpRtcpImpl::DeRegisterSendPayload( const int8_t payload_type) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "DeRegisterSendPayload(%d)", payload_type); - return rtp_sender_.DeRegisterSendPayload(payload_type); } @@ -372,58 +311,37 @@ int8_t ModuleRtpRtcpImpl::SendPayloadType() const { } uint32_t ModuleRtpRtcpImpl::StartTimestamp() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "StartTimestamp()"); - return rtp_sender_.StartTimestamp(); } // Configure start timestamp, default is a random number. int32_t ModuleRtpRtcpImpl::SetStartTimestamp( const uint32_t timestamp) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetStartTimestamp(%d)", - timestamp); rtcp_sender_.SetStartTimestamp(timestamp); rtp_sender_.SetStartTimestamp(timestamp, true); return 0; // TODO(pwestin): change to void. } uint16_t ModuleRtpRtcpImpl::SequenceNumber() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SequenceNumber()"); - return rtp_sender_.SequenceNumber(); } // Set SequenceNumber, default is a random number. int32_t ModuleRtpRtcpImpl::SetSequenceNumber( const uint16_t seq_num) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetSequenceNumber(%d)", - seq_num); - rtp_sender_.SetSequenceNumber(seq_num); return 0; // TODO(pwestin): change to void. } uint32_t ModuleRtpRtcpImpl::SSRC() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SSRC()"); - return rtp_sender_.SSRC(); } // Configure SSRC, default is a random number. -int32_t ModuleRtpRtcpImpl::SetSSRC(const uint32_t ssrc) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SetSSRC(%d)", ssrc); - +void ModuleRtpRtcpImpl::SetSSRC(const uint32_t ssrc) { rtp_sender_.SetSSRC(ssrc); rtcp_sender_.SetSSRC(ssrc); SetRtcpReceiverSsrcs(ssrc); - - return 0; // TODO(pwestin): change to void. } int32_t ModuleRtpRtcpImpl::SetCSRCStatus(const bool include) { @@ -434,25 +352,17 @@ int32_t ModuleRtpRtcpImpl::SetCSRCStatus(const bool include) { int32_t ModuleRtpRtcpImpl::CSRCs( uint32_t arr_of_csrc[kRtpCsrcSize]) const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "CSRCs()"); - return rtp_sender_.CSRCs(arr_of_csrc); } int32_t ModuleRtpRtcpImpl::SetCSRCs( const uint32_t arr_of_csrc[kRtpCsrcSize], const uint8_t arr_length) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetCSRCs(arr_length:%d)", - arr_length); - if (IsDefaultModule()) { // For default we need to update all child modules too. CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RtpRtcp* module = *it; if (module) { @@ -461,10 +371,6 @@ int32_t ModuleRtpRtcpImpl::SetCSRCs( it++; } } else { - for (int i = 0; i < arr_length; ++i) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "\tidx:%d CSRC:%u", i, - arr_of_csrc[i]); - } rtcp_sender_.SetCSRCs(arr_of_csrc, arr_length); rtp_sender_.SetCSRCs(arr_of_csrc, arr_length); } @@ -472,35 +378,23 @@ int32_t ModuleRtpRtcpImpl::SetCSRCs( } uint32_t ModuleRtpRtcpImpl::PacketCountSent() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "PacketCountSent()"); return rtp_sender_.Packets(); } uint32_t ModuleRtpRtcpImpl::ByteCountSent() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "ByteCountSent()"); return rtp_sender_.Bytes(); } int ModuleRtpRtcpImpl::CurrentSendFrequencyHz() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "CurrentSendFrequencyHz()"); return rtp_sender_.SendPayloadFrequency(); } int32_t ModuleRtpRtcpImpl::SetSendingStatus(const bool sending) { - if (sending) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetSendingStatus(sending)"); - } else { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetSendingStatus(stopped)"); - } if (rtcp_sender_.Sending() != sending) { // Sends RTCP BYE when going from true to false RTCPSender::FeedbackState feedback_state(this); if (rtcp_sender_.SetSendingStatus(feedback_state, sending) != 0) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "Failed to send RTCP BYE"); + LOG(LS_WARNING) << "Failed to send RTCP BYE"; } collision_detected_ = false; @@ -525,31 +419,21 @@ int32_t ModuleRtpRtcpImpl::SetSendingStatus(const bool sending) { } bool ModuleRtpRtcpImpl::Sending() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "Sending()"); return rtcp_sender_.Sending(); } int32_t ModuleRtpRtcpImpl::SetSendingMediaStatus(const bool sending) { - if (sending) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetSendingMediaStatus(sending)"); - } else { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetSendingMediaStatus(stopped)"); - } rtp_sender_.SetSendingMediaStatus(sending); return 0; } bool ModuleRtpRtcpImpl::SendingMedia() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "Sending()"); - if (!IsDefaultModule()) { return rtp_sender_.SendingMedia(); } CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::const_iterator it = child_modules_.begin(); + std::vector::const_iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RTPSender& rtp_sender = (*it)->rtp_sender_; if (rtp_sender.SendingMedia()) { @@ -569,13 +453,6 @@ int32_t ModuleRtpRtcpImpl::SendOutgoingData( uint32_t payload_size, const RTPFragmentationHeader* fragmentation, const RTPVideoHeader* rtp_video_hdr) { - WEBRTC_TRACE( - kTraceStream, - kTraceRtpRtcp, - id_, - "SendOutgoingData(frame_type:%d payload_type:%d time_stamp:%u size:%u)", - frame_type, payload_type, time_stamp, payload_size); - rtcp_sender_.SetLastRtpTime(time_stamp, capture_time_ms); if (!IsDefaultModule()) { @@ -601,7 +478,7 @@ int32_t ModuleRtpRtcpImpl::SendOutgoingData( return -1; } int idx = 0; - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); for (; idx < rtp_video_hdr->simulcastIdx; ++it) { if (it == child_modules_.end()) { return -1; @@ -619,11 +496,6 @@ int32_t ModuleRtpRtcpImpl::SendOutgoingData( if (it == child_modules_.end()) { return -1; } - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SendOutgoingData(SimulcastIdx:%u size:%u, ssrc:0x%x)", - idx, payload_size, (*it)->rtp_sender_.SSRC()); return (*it)->SendOutgoingData(frame_type, payload_type, time_stamp, @@ -633,7 +505,7 @@ int32_t ModuleRtpRtcpImpl::SendOutgoingData( fragmentation, rtp_video_hdr); } else { - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); // Send to all "child" modules while (it != child_modules_.end()) { if ((*it)->SendingMedia()) { @@ -656,13 +528,6 @@ bool ModuleRtpRtcpImpl::TimeToSendPacket(uint32_t ssrc, uint16_t sequence_number, int64_t capture_time_ms, bool retransmission) { - WEBRTC_TRACE( - kTraceStream, - kTraceRtpRtcp, - id_, - "TimeToSendPacket(ssrc:0x%x sequence_number:%u capture_time_ms:%ll)", - ssrc, sequence_number, capture_time_ms); - if (!IsDefaultModule()) { // Don't send from default module. if (SendingMedia() && ssrc == rtp_sender_.SSRC()) { @@ -671,7 +536,7 @@ bool ModuleRtpRtcpImpl::TimeToSendPacket(uint32_t ssrc, } } else { CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); while (it != child_modules_.end()) { if ((*it)->SendingMedia() && ssrc == (*it)->rtp_sender_.SSRC()) { return (*it)->rtp_sender_.TimeToSendPacket(sequence_number, @@ -686,9 +551,6 @@ bool ModuleRtpRtcpImpl::TimeToSendPacket(uint32_t ssrc, } int ModuleRtpRtcpImpl::TimeToSendPadding(int bytes) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, "TimeToSendPadding(bytes: %d)", - bytes); - if (!IsDefaultModule()) { // Don't send from default module. if (SendingMedia()) { @@ -696,13 +558,15 @@ int ModuleRtpRtcpImpl::TimeToSendPadding(int bytes) { } } else { CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::iterator it = child_modules_.begin(); - while (it != child_modules_.end()) { + // Decide what media stream to pad on based on a round-robin scheme. + for (size_t i = 0; i < child_modules_.size(); ++i) { + padding_index_ = (padding_index_ + 1) % child_modules_.size(); // Send padding on one of the modules sending media. - if ((*it)->SendingMedia()) { - return (*it)->rtp_sender_.TimeToSendPadding(bytes); + if (child_modules_[padding_index_]->SendingMedia() && + child_modules_[padding_index_]->rtp_sender_.GetTargetBitrate() > 0) { + return child_modules_[padding_index_]->rtp_sender_.TimeToSendPadding( + bytes); } - ++it; } } return 0; @@ -721,24 +585,17 @@ bool ModuleRtpRtcpImpl::GetSendSideDelay(int* avg_send_delay_ms, } uint16_t ModuleRtpRtcpImpl::MaxPayloadLength() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "MaxPayloadLength()"); return rtp_sender_.MaxPayloadLength(); } uint16_t ModuleRtpRtcpImpl::MaxDataPayloadLength() const { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "MaxDataPayloadLength()"); - // Assuming IP/UDP. uint16_t min_data_payload_length = IP_PACKET_SIZE - 28; if (IsDefaultModule()) { // For default we need to update all child modules too. CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::const_iterator it = - child_modules_.begin(); + std::vector::const_iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RtpRtcp* module = *it; if (module) { @@ -763,13 +620,6 @@ int32_t ModuleRtpRtcpImpl::SetTransportOverhead( const bool tcp, const bool ipv6, const uint8_t authentication_overhead) { - WEBRTC_TRACE( - kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetTransportOverhead(TCP:%d, IPV6:%d authentication_overhead:%u)", - tcp, ipv6, authentication_overhead); - uint16_t packet_overhead = 0; if (ipv6) { packet_overhead = 40; @@ -801,11 +651,8 @@ int32_t ModuleRtpRtcpImpl::SetTransportOverhead( } int32_t ModuleRtpRtcpImpl::SetMaxTransferUnit(const uint16_t mtu) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SetMaxTransferUnit(%u)", - mtu); if (mtu > IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "Invalid in argument to SetMaxTransferUnit(%u)", mtu); + LOG(LS_ERROR) << "Invalid mtu: " << mtu; return -1; } return rtp_sender_.SetMaxPayloadLength(mtu - packet_overhead_, @@ -813,7 +660,6 @@ int32_t ModuleRtpRtcpImpl::SetMaxTransferUnit(const uint16_t mtu) { } RTCPMethod ModuleRtpRtcpImpl::RTCP() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "RTCP()"); if (rtcp_sender_.Status() != kRtcpOff) { return rtcp_receiver_.Status(); } @@ -822,8 +668,6 @@ RTCPMethod ModuleRtpRtcpImpl::RTCP() const { // Configure RTCP status i.e on/off. int32_t ModuleRtpRtcpImpl::SetRTCPStatus(const RTCPMethod method) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SetRTCPStatus(%d)", - method); if (rtcp_sender_.SetRTCPStatus(method) == 0) { return rtcp_receiver_.SetRTCPStatus(method); } @@ -837,34 +681,26 @@ uint32_t ModuleRtpRtcpImpl::LastSendReport( } int32_t ModuleRtpRtcpImpl::SetCNAME(const char c_name[RTCP_CNAME_SIZE]) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SetCNAME(%s)", c_name); return rtcp_sender_.SetCNAME(c_name); } int32_t ModuleRtpRtcpImpl::CNAME(char c_name[RTCP_CNAME_SIZE]) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "CNAME()"); return rtcp_sender_.CNAME(c_name); } int32_t ModuleRtpRtcpImpl::AddMixedCNAME( const uint32_t ssrc, const char c_name[RTCP_CNAME_SIZE]) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "AddMixedCNAME(SSRC:%u)", ssrc); return rtcp_sender_.AddMixedCNAME(ssrc, c_name); } int32_t ModuleRtpRtcpImpl::RemoveMixedCNAME(const uint32_t ssrc) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "RemoveMixedCNAME(SSRC:%u)", ssrc); return rtcp_sender_.RemoveMixedCNAME(ssrc); } int32_t ModuleRtpRtcpImpl::RemoteCNAME( const uint32_t remote_ssrc, char c_name[RTCP_CNAME_SIZE]) const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "RemoteCNAME(SSRC:%u)", remote_ssrc); return rtcp_receiver_.CNAME(remote_ssrc, c_name); } @@ -874,7 +710,6 @@ int32_t ModuleRtpRtcpImpl::RemoteNTP( uint32_t* rtcp_arrival_time_secs, uint32_t* rtcp_arrival_time_frac, uint32_t* rtcp_timestamp) const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "RemoteNTP()"); return rtcp_receiver_.NTP(received_ntpsecs, received_ntpfrac, rtcp_arrival_time_secs, @@ -888,21 +723,21 @@ int32_t ModuleRtpRtcpImpl::RTT(const uint32_t remote_ssrc, uint16_t* avg_rtt, uint16_t* min_rtt, uint16_t* max_rtt) const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "RTT()"); - return rtcp_receiver_.RTT(remote_ssrc, rtt, avg_rtt, min_rtt, max_rtt); + int32_t ret = rtcp_receiver_.RTT(remote_ssrc, rtt, avg_rtt, min_rtt, max_rtt); + if (rtt && *rtt == 0) { + // Try to get RTT from RtcpRttStats class. + *rtt = static_cast(rtt_ms()); + } + return ret; } // Reset RoundTripTime statistics. int32_t ModuleRtpRtcpImpl::ResetRTT(const uint32_t remote_ssrc) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "ResetRTT(SSRC:%u)", - remote_ssrc); return rtcp_receiver_.ResetRTT(remote_ssrc); } // Reset RTP data counters for the sending side. int32_t ModuleRtpRtcpImpl::ResetSendDataCountersRTP() { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "ResetSendDataCountersRTP()"); rtp_sender_.ResetDataCounters(); return 0; // TODO(pwestin): change to void. } @@ -910,8 +745,6 @@ int32_t ModuleRtpRtcpImpl::ResetSendDataCountersRTP() { // Force a send of an RTCP packet. // Normal SR and RR are triggered via the process function. int32_t ModuleRtpRtcpImpl::SendRTCP(uint32_t rtcp_packet_type) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SendRTCP(0x%x)", - rtcp_packet_type); RTCPSender::FeedbackState feedback_state(this); return rtcp_sender_.SendRTCP(feedback_state, rtcp_packet_type); } @@ -921,23 +754,16 @@ int32_t ModuleRtpRtcpImpl::SetRTCPApplicationSpecificData( const uint32_t name, const uint8_t* data, const uint16_t length) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetRTCPApplicationSpecificData(sub_type:%d name:0x%x)", - sub_type, name); return rtcp_sender_.SetApplicationSpecificData(sub_type, name, data, length); } // (XR) VOIP metric. int32_t ModuleRtpRtcpImpl::SetRTCPVoIPMetrics( const RTCPVoIPMetric* voip_metric) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SetRTCPVoIPMetrics()"); - return rtcp_sender_.SetRTCPVoIPMetrics(voip_metric); } void ModuleRtpRtcpImpl::SetRtcpXrRrtrStatus(bool enable) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetRtcpXrRrtrStatus(%s)", enable ? "true" : "false"); return rtcp_sender_.SendRtcpXrReceiverReferenceTime(enable); } @@ -948,7 +774,6 @@ bool ModuleRtpRtcpImpl::RtcpXrRrtrStatus() const { int32_t ModuleRtpRtcpImpl::DataCountersRTP( uint32_t* bytes_sent, uint32_t* packets_sent) const { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, "DataCountersRTP()"); if (bytes_sent) { *bytes_sent = rtp_sender_.Bytes(); } @@ -959,27 +784,23 @@ int32_t ModuleRtpRtcpImpl::DataCountersRTP( } int32_t ModuleRtpRtcpImpl::RemoteRTCPStat(RTCPSenderInfo* sender_info) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "RemoteRTCPStat()"); return rtcp_receiver_.SenderInfoReceived(sender_info); } // Received RTCP report. int32_t ModuleRtpRtcpImpl::RemoteRTCPStat( std::vector* receive_blocks) const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "RemoteRTCPStat()"); return rtcp_receiver_.StatisticsReceived(receive_blocks); } int32_t ModuleRtpRtcpImpl::AddRTCPReportBlock( const uint32_t ssrc, const RTCPReportBlock* report_block) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "AddRTCPReportBlock()"); return rtcp_sender_.AddExternalReportBlock(ssrc, report_block); } int32_t ModuleRtpRtcpImpl::RemoveRTCPReportBlock( const uint32_t ssrc) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "RemoveRTCPReportBlock()"); return rtcp_sender_.RemoveExternalReportBlock(ssrc); } @@ -992,44 +813,25 @@ void ModuleRtpRtcpImpl::GetRtcpPacketTypeCounters( // (REMB) Receiver Estimated Max Bitrate. bool ModuleRtpRtcpImpl::REMB() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "REMB()"); return rtcp_sender_.REMB(); } int32_t ModuleRtpRtcpImpl::SetREMBStatus(const bool enable) { - if (enable) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetREMBStatus(enable)"); - } else { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetREMBStatus(disable)"); - } return rtcp_sender_.SetREMBStatus(enable); } int32_t ModuleRtpRtcpImpl::SetREMBData(const uint32_t bitrate, const uint8_t number_of_ssrc, const uint32_t* ssrc) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetREMBData(bitrate:%d,?,?)", bitrate); return rtcp_sender_.SetREMBData(bitrate, number_of_ssrc, ssrc); } // (IJ) Extended jitter report. bool ModuleRtpRtcpImpl::IJ() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "IJ()"); return rtcp_sender_.IJ(); } int32_t ModuleRtpRtcpImpl::SetIJStatus(const bool enable) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetIJStatus(%s)", enable ? "true" : "false"); return rtcp_sender_.SetIJStatus(enable); } @@ -1046,23 +848,14 @@ int32_t ModuleRtpRtcpImpl::DeregisterSendRtpHeaderExtension( // (TMMBR) Temporary Max Media Bit Rate. bool ModuleRtpRtcpImpl::TMMBR() const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "TMMBR()"); return rtcp_sender_.TMMBR(); } int32_t ModuleRtpRtcpImpl::SetTMMBRStatus(const bool enable) { - if (enable) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetTMMBRStatus(enable)"); - } else { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetTMMBRStatus(disable)"); - } return rtcp_sender_.SetTMMBRStatus(enable); } int32_t ModuleRtpRtcpImpl::SetTMMBN(const TMMBRSet* bounding_set) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SetTMMBN()"); uint32_t max_bitrate_kbit = rtp_sender_.MaxConfiguredBitrateVideo() / 1000; return rtcp_sender_.SetTMMBN(bounding_set, max_bitrate_kbit); @@ -1070,32 +863,18 @@ int32_t ModuleRtpRtcpImpl::SetTMMBN(const TMMBRSet* bounding_set) { // Returns the currently configured retransmission mode. int ModuleRtpRtcpImpl::SelectiveRetransmissions() const { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SelectiveRetransmissions()"); return rtp_sender_.SelectiveRetransmissions(); } // Enable or disable a retransmission mode, which decides which packets will // be retransmitted if NACKed. int ModuleRtpRtcpImpl::SetSelectiveRetransmissions(uint8_t settings) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetSelectiveRetransmissions(%u)", - settings); return rtp_sender_.SetSelectiveRetransmissions(settings); } // Send a Negative acknowledgment packet. int32_t ModuleRtpRtcpImpl::SendNACK(const uint16_t* nack_list, const uint16_t size) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SendNACK(size:%u)", size); - // Use RTT from RtcpRttStats class if provided. uint16_t rtt = rtt_ms(); if (rtt == 0) { @@ -1149,14 +928,6 @@ int32_t ModuleRtpRtcpImpl::SendNACK(const uint16_t* nack_list, int32_t ModuleRtpRtcpImpl::SetStorePacketsStatus( const bool enable, const uint16_t number_to_store) { - if (enable) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetStorePacketsStatus(enable, number_to_store:%d)", - number_to_store); - } else { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetStorePacketsStatus(disable)"); - } rtp_sender_.SetStorePacketsStatus(enable, number_to_store); return 0; // TODO(pwestin): change to void. } @@ -1180,19 +951,11 @@ int32_t ModuleRtpRtcpImpl::SendTelephoneEventOutband( const uint8_t key, const uint16_t time_ms, const uint8_t level) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SendTelephoneEventOutband(key:%u, time_ms:%u, level:%u)", key, - time_ms, level); return rtp_sender_.SendTelephoneEvent(key, time_ms, level); } bool ModuleRtpRtcpImpl::SendTelephoneEventActive( int8_t& telephone_event) const { - - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SendTelephoneEventActive()"); return rtp_sender_.SendTelephoneEventActive(&telephone_event); } @@ -1200,40 +963,23 @@ bool ModuleRtpRtcpImpl::SendTelephoneEventActive( // packet in silence (CNG). int32_t ModuleRtpRtcpImpl::SetAudioPacketSize( const uint16_t packet_size_samples) { - - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetAudioPacketSize(%u)", - packet_size_samples); return rtp_sender_.SetAudioPacketSize(packet_size_samples); } int32_t ModuleRtpRtcpImpl::SetAudioLevel( const uint8_t level_d_bov) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetAudioLevel(level_d_bov:%u)", - level_d_bov); return rtp_sender_.SetAudioLevel(level_d_bov); } // Set payload type for Redundant Audio Data RFC 2198. int32_t ModuleRtpRtcpImpl::SetSendREDPayloadType( const int8_t payload_type) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetSendREDPayloadType(%d)", - payload_type); return rtp_sender_.SetRED(payload_type); } // Get payload type for Redundant Audio Data RFC 2198. int32_t ModuleRtpRtcpImpl::SendREDPayloadType( int8_t& payload_type) const { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "SendREDPayloadType()"); return rtp_sender_.RED(&payload_type); } @@ -1243,52 +989,41 @@ RtpVideoCodecTypes ModuleRtpRtcpImpl::SendVideoCodec() const { void ModuleRtpRtcpImpl::SetTargetSendBitrate( const std::vector& stream_bitrates) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, - "SetTargetSendBitrate: %ld streams", stream_bitrates.size()); if (IsDefaultModule()) { CriticalSectionScoped lock(critical_section_module_ptrs_.get()); if (simulcast_) { - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); for (size_t i = 0; it != child_modules_.end() && i < stream_bitrates.size(); ++it) { if ((*it)->SendingMedia()) { RTPSender& rtp_sender = (*it)->rtp_sender_; - rtp_sender.SetTargetSendBitrate(stream_bitrates[i]); + rtp_sender.SetTargetBitrate(stream_bitrates[i]); ++i; } } } else { if (stream_bitrates.size() > 1) return; - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); for (; it != child_modules_.end(); ++it) { RTPSender& rtp_sender = (*it)->rtp_sender_; - rtp_sender.SetTargetSendBitrate(stream_bitrates[0]); + rtp_sender.SetTargetBitrate(stream_bitrates[0]); } } } else { if (stream_bitrates.size() > 1) return; - rtp_sender_.SetTargetSendBitrate(stream_bitrates[0]); + rtp_sender_.SetTargetBitrate(stream_bitrates[0]); } } int32_t ModuleRtpRtcpImpl::SetKeyFrameRequestMethod( const KeyFrameRequestMethod method) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetKeyFrameRequestMethod(method:%u)", - method); key_frame_req_method_ = method; return 0; } int32_t ModuleRtpRtcpImpl::RequestKeyFrame() { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "RequestKeyFrame"); switch (key_frame_req_method_) { case kKeyFrameReqFirRtp: return rtp_sender_.SendRTPIntraRequest(); @@ -1302,25 +1037,15 @@ int32_t ModuleRtpRtcpImpl::RequestKeyFrame() { int32_t ModuleRtpRtcpImpl::SendRTCPSliceLossIndication( const uint8_t picture_id) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SendRTCPSliceLossIndication (picture_id:%d)", - picture_id); RTCPSender::FeedbackState feedback_state(this); return rtcp_sender_.SendRTCP( feedback_state, kRtcpSli, 0, 0, false, picture_id); } int32_t ModuleRtpRtcpImpl::SetCameraDelay(const int32_t delay_ms) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetCameraDelay(%d)", - delay_ms); if (IsDefaultModule()) { CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RtpRtcp* module = *it; if (module) { @@ -1337,18 +1062,6 @@ int32_t ModuleRtpRtcpImpl::SetGenericFECStatus( const bool enable, const uint8_t payload_type_red, const uint8_t payload_type_fec) { - if (enable) { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetGenericFECStatus(enable, %u)", - payload_type_red); - } else { - WEBRTC_TRACE(kTraceModuleCall, - kTraceRtpRtcp, - id_, - "SetGenericFECStatus(disable)"); - } return rtp_sender_.SetGenericFECStatus(enable, payload_type_red, payload_type_fec); @@ -1358,13 +1071,11 @@ int32_t ModuleRtpRtcpImpl::GenericFECStatus( bool& enable, uint8_t& payload_type_red, uint8_t& payload_type_fec) { - WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "GenericFECStatus()"); - bool child_enabled = false; if (IsDefaultModule()) { // For default we need to check all child modules too. CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RtpRtcp* module = *it; if (module) { @@ -1398,7 +1109,7 @@ int32_t ModuleRtpRtcpImpl::SetFecParameters( // For default we need to update all child modules too. CriticalSectionScoped lock(critical_section_module_ptrs_.get()); - std::list::iterator it = child_modules_.begin(); + std::vector::iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RtpRtcp* module = *it; if (module) { @@ -1452,8 +1163,7 @@ void ModuleRtpRtcpImpl::BitrateSent(uint32_t* total_rate, if (nack_rate != NULL) *nack_rate = 0; - std::list::const_iterator it = - child_modules_.begin(); + std::vector::const_iterator it = child_modules_.begin(); while (it != child_modules_.end()) { RtpRtcp* module = *it; if (module) { diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h index d8cbc80d5..55826b6fe 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -21,10 +21,6 @@ #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/test/testsupport/gtest_prod_util.h" -#ifdef MATLAB -class MatlabPlot; -#endif - namespace webrtc { class ModuleRtpRtcpImpl : public RtpRtcp { @@ -80,7 +76,7 @@ class ModuleRtpRtcpImpl : public RtpRtcp { virtual uint32_t SSRC() const OVERRIDE; // Configure SSRC, default is a random number. - virtual int32_t SetSSRC(const uint32_t ssrc) OVERRIDE; + virtual void SetSSRC(const uint32_t ssrc) OVERRIDE; virtual int32_t CSRCs(uint32_t arr_of_csrc[kRtpCsrcSize]) const OVERRIDE; @@ -95,13 +91,12 @@ class ModuleRtpRtcpImpl : public RtpRtcp { virtual uint32_t ByteCountSent() const; - virtual int32_t SetRTXSendStatus(const int mode, - const bool set_ssrc, - const uint32_t ssrc) OVERRIDE; + virtual void SetRTXSendStatus(const int mode) OVERRIDE; - virtual int32_t RTXSendStatus(int* mode, uint32_t* ssrc, - int* payloadType) const OVERRIDE; + virtual void RTXSendStatus(int* mode, uint32_t* ssrc, + int* payloadType) const OVERRIDE; + virtual void SetRtxSsrc(uint32_t ssrc) OVERRIDE; virtual void SetRtxSendPayloadType(int payload_type) OVERRIDE; @@ -427,7 +422,8 @@ class ModuleRtpRtcpImpl : public RtpRtcp { scoped_ptr critical_section_module_ptrs_; scoped_ptr critical_section_module_ptrs_feedback_; ModuleRtpRtcpImpl* default_module_; - std::list child_modules_; + std::vector child_modules_; + size_t padding_index_; // Send side NACKMethod nack_method_; @@ -440,10 +436,6 @@ class ModuleRtpRtcpImpl : public RtpRtcp { RemoteBitrateEstimator* remote_bitrate_; -#ifdef MATLAB - MatlabPlot* plot1_; -#endif - RtcpRttStats* rtt_stats_; // The processed RTT from RtcpRttStats. diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc index 30ed7157b..eb76cfe7c 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc @@ -12,13 +12,22 @@ #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/common_types.h" +#include "webrtc/modules/pacing/include/mock/mock_paced_sender.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" #include "webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h" +#include "webrtc/system_wrappers/interface/scoped_vector.h" + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SaveArg; namespace webrtc { namespace { const uint32_t kSenderSsrc = 0x12345; const uint32_t kReceiverSsrc = 0x23456; +const uint32_t kSenderRtxSsrc = 0x32345; const uint32_t kOneWayNetworkDelayMs = 100; class RtcpRttStatsTestImpl : public RtcpRttStats { @@ -107,11 +116,11 @@ class RtpRtcpImplTest : public ::testing::Test { receiver_(&clock_) { // Send module. EXPECT_EQ(0, sender_.impl_->SetSendingStatus(true)); - EXPECT_EQ(0, sender_.impl_->SetSSRC(kSenderSsrc)); + sender_.impl_->SetSSRC(kSenderSsrc); sender_.impl_->SetRemoteSSRC(kReceiverSsrc); // Receive module. EXPECT_EQ(0, receiver_.impl_->SetSendingStatus(false)); - EXPECT_EQ(0, receiver_.impl_->SetSSRC(kReceiverSsrc)); + receiver_.impl_->SetSSRC(kReceiverSsrc); receiver_.impl_->SetRemoteSSRC(kSenderSsrc); // Transport settings. sender_.transport_.SetRtpRtcpModule(receiver_.impl_.get()); @@ -215,4 +224,245 @@ TEST_F(RtpRtcpImplTest, RtcpPacketTypeCounter_FirAndPli) { EXPECT_EQ(2U, sender_.RtcpReceived().fir_packets); EXPECT_EQ(1U, sender_.RtcpReceived().pli_packets); } + +class RtpSendingTestTransport : public Transport { + public: + void ResetCounters() { bytes_received_.clear(); } + + virtual int SendPacket(int channel, const void* data, int length) { + RTPHeader header; + scoped_ptr parser(RtpHeaderParser::Create()); + EXPECT_TRUE( + parser->Parse(static_cast(data), length, &header)); + bytes_received_[header.ssrc] += length; + ++packets_received_[header.ssrc]; + return length; + } + + virtual int SendRTCPPacket(int channel, const void* data, int length) { + return length; + } + + int GetPacketsReceived(uint32_t ssrc) const { + std::map::const_iterator it = packets_received_.find(ssrc); + if (it == packets_received_.end()) + return 0; + return it->second; + } + + int GetBytesReceived(uint32_t ssrc) const { + std::map::const_iterator it = bytes_received_.find(ssrc); + if (it == bytes_received_.end()) + return 0; + return it->second; + } + + int GetTotalBytesReceived() const { + int sum = 0; + for (std::map::const_iterator it = bytes_received_.begin(); + it != bytes_received_.end(); + ++it) { + sum += it->second; + } + return sum; + } + + private: + std::map bytes_received_; + std::map packets_received_; +}; + +class RtpSendingTest : public ::testing::Test { + protected: + // Map from SSRC to number of received packets and bytes. + typedef std::map > PaddingMap; + + RtpSendingTest() { + // Send module. + RtpRtcp::Configuration config; + config.audio = false; + config.clock = Clock::GetRealTimeClock(); + config.outgoing_transport = &transport_; + config.receive_statistics = receive_statistics_.get(); + config.rtt_stats = &rtt_stats_; + config.paced_sender = &pacer_; + memset(&codec_, 0, sizeof(VideoCodec)); + codec_.plType = 100; + strncpy(codec_.plName, "VP8", 3); + codec_.numberOfSimulcastStreams = 3; + codec_.simulcastStream[0].width = 320; + codec_.simulcastStream[0].height = 180; + codec_.simulcastStream[0].maxBitrate = 300; + codec_.simulcastStream[1].width = 640; + codec_.simulcastStream[1].height = 360; + codec_.simulcastStream[1].maxBitrate = 600; + codec_.simulcastStream[2].width = 1280; + codec_.simulcastStream[2].height = 720; + codec_.simulcastStream[2].maxBitrate = 1200; + // We need numberOfSimulcastStreams + 1 RTP modules since we need one + // default module. + for (int i = 0; i < codec_.numberOfSimulcastStreams + 1; ++i) { + RtpRtcp* sender = RtpRtcp::CreateRtpRtcp(config); + EXPECT_EQ(0, sender->RegisterSendPayload(codec_)); + EXPECT_EQ(0, sender->SetSendingStatus(true)); + EXPECT_EQ(0, sender->SetSendingMediaStatus(true)); + sender->SetSSRC(kSenderSsrc + i); + sender->SetRemoteSSRC(kReceiverSsrc + i); + senders_.push_back(sender); + config.default_module = senders_[0]; + } + std::vector bitrates; + bitrates.push_back(codec_.simulcastStream[0].maxBitrate); + bitrates.push_back(codec_.simulcastStream[1].maxBitrate); + bitrates.push_back(codec_.simulcastStream[2].maxBitrate); + senders_[0]->SetTargetSendBitrate(bitrates); + } + + ~RtpSendingTest() { + for (int i = senders_.size() - 1; i >= 0; --i) { + delete senders_[i]; + } + } + + void SendFrameOnSender(int sender_index, + const uint8_t* payload, + size_t length) { + RTPVideoHeader rtp_video_header = { + codec_.simulcastStream[sender_index].width, + codec_.simulcastStream[sender_index].height, + true, + 0, + kRtpVideoVp8, + {}}; + uint32_t seq_num = 0; + uint32_t ssrc = 0; + int64_t capture_time_ms = 0; + bool retransmission = false; + EXPECT_CALL(pacer_, SendPacket(_, _, _, _, _, _)) + .WillRepeatedly(DoAll(SaveArg<1>(&ssrc), + SaveArg<2>(&seq_num), + SaveArg<3>(&capture_time_ms), + SaveArg<5>(&retransmission), + Return(true))); + EXPECT_EQ(0, + senders_[sender_index]->SendOutgoingData(kVideoFrameKey, + codec_.plType, + 0, + 0, + payload, + length, + NULL, + &rtp_video_header)); + EXPECT_TRUE(senders_[sender_index]->TimeToSendPacket( + ssrc, seq_num, capture_time_ms, retransmission)); + } + + void ExpectPadding(const PaddingMap& expected_padding) { + int expected_total_bytes = 0; + for (PaddingMap::const_iterator it = expected_padding.begin(); + it != expected_padding.end(); + ++it) { + int packets_received = transport_.GetBytesReceived(it->first); + if (it->second.first > 0) { + EXPECT_GE(packets_received, it->second.first) + << "On SSRC: " << it->first; + } + int bytes_received = transport_.GetBytesReceived(it->first); + expected_total_bytes += bytes_received; + if (it->second.second > 0) { + EXPECT_GE(bytes_received, it->second.second) + << "On SSRC: " << it->first; + } else { + EXPECT_EQ(0, bytes_received) << "On SSRC: " << it->first; + } + } + EXPECT_EQ(expected_total_bytes, transport_.GetTotalBytesReceived()); + } + + scoped_ptr receive_statistics_; + RtcpRttStatsTestImpl rtt_stats_; + std::vector senders_; + RtpSendingTestTransport transport_; + NiceMock pacer_; + VideoCodec codec_; +}; + +TEST_F(RtpSendingTest, RoundRobinPadding) { + // We have to send on an SSRC to be allowed to pad, since a marker bit must + // be sent prior to padding packets. + const uint8_t payload[200] = {0}; + for (int i = 0; i < codec_.numberOfSimulcastStreams; ++i) { + SendFrameOnSender(i + 1, payload, sizeof(payload)); + } + transport_.ResetCounters(); + senders_[0]->TimeToSendPadding(500); + PaddingMap expected_padding; + expected_padding[kSenderSsrc + 1] = std::make_pair(2, 500); + expected_padding[kSenderSsrc + 2] = std::make_pair(0, 0); + expected_padding[kSenderSsrc + 3] = std::make_pair(0, 0); + ExpectPadding(expected_padding); + senders_[0]->TimeToSendPadding(1000); + expected_padding[kSenderSsrc + 2] = std::make_pair(4, 1000); + ExpectPadding(expected_padding); + senders_[0]->TimeToSendPadding(1500); + expected_padding[kSenderSsrc + 3] = std::make_pair(6, 1500); + ExpectPadding(expected_padding); +} + +TEST_F(RtpSendingTest, RoundRobinPaddingRtx) { + // Enable RTX to allow padding to be sent prior to media. + for (int i = 1; i < codec_.numberOfSimulcastStreams + 1; ++i) { + senders_[i]->SetRtxSendPayloadType(96); + senders_[i]->SetRtxSsrc(kSenderRtxSsrc + i); + senders_[i]->SetRTXSendStatus(kRtxRetransmitted); + } + transport_.ResetCounters(); + senders_[0]->TimeToSendPadding(500); + PaddingMap expected_padding; + expected_padding[kSenderSsrc + 1] = std::make_pair(0, 0); + expected_padding[kSenderSsrc + 2] = std::make_pair(0, 0); + expected_padding[kSenderSsrc + 3] = std::make_pair(0, 0); + expected_padding[kSenderRtxSsrc + 1] = std::make_pair(2, 500); + expected_padding[kSenderRtxSsrc + 2] = std::make_pair(0, 0); + expected_padding[kSenderRtxSsrc + 3] = std::make_pair(0, 0); + ExpectPadding(expected_padding); + senders_[0]->TimeToSendPadding(1000); + expected_padding[kSenderRtxSsrc + 2] = std::make_pair(4, 500); + ExpectPadding(expected_padding); + senders_[0]->TimeToSendPadding(1500); + + expected_padding[kSenderRtxSsrc + 3] = std::make_pair(6, 500); + ExpectPadding(expected_padding); +} + +TEST_F(RtpSendingTest, RoundRobinPaddingRtxRedundantPayloads) { + for (int i = 1; i < codec_.numberOfSimulcastStreams + 1; ++i) { + senders_[i]->SetRtxSendPayloadType(96); + senders_[i]->SetRtxSsrc(kSenderRtxSsrc + i); + senders_[i]->SetRTXSendStatus(kRtxRetransmitted | kRtxRedundantPayloads); + senders_[i]->SetStorePacketsStatus(true, 100); + } + // First send payloads so that we have something to retransmit. + const size_t kPayloadSize = 500; + const uint8_t payload[kPayloadSize] = {0}; + for (int i = 0; i < codec_.numberOfSimulcastStreams; ++i) { + SendFrameOnSender(i + 1, payload, sizeof(payload)); + } + transport_.ResetCounters(); + senders_[0]->TimeToSendPadding(500); + PaddingMap expected_padding; + expected_padding[kSenderSsrc + 1] = std::make_pair(0, 0); + expected_padding[kSenderSsrc + 2] = std::make_pair(0, 0); + expected_padding[kSenderSsrc + 3] = std::make_pair(0, 0); + expected_padding[kSenderRtxSsrc + 1] = std::make_pair(1, 500); + expected_padding[kSenderRtxSsrc + 2] = std::make_pair(0, 0); + expected_padding[kSenderRtxSsrc + 3] = std::make_pair(0, 0); + ExpectPadding(expected_padding); + senders_[0]->TimeToSendPadding(1000); + expected_padding[kSenderRtxSsrc + 2] = std::make_pair(2, 1000); + ExpectPadding(expected_padding); + senders_[0]->TimeToSendPadding(1500); + expected_padding[kSenderRtxSsrc + 3] = std::make_pair(3, 1500); + ExpectPadding(expected_padding); +} } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc index 29e4616e1..74947693b 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc @@ -15,7 +15,7 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_sender_audio.h" #include "webrtc/modules/rtp_rtcp/source/rtp_sender_video.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -89,7 +89,7 @@ RTPSender::RTPSender(const int32_t id, rtx_(kRtxOff), payload_type_rtx_(-1), target_bitrate_critsect_(CriticalSectionWrapper::CreateCriticalSection()), - target_bitrate_kbps_(0) { + target_bitrate_(0) { memset(nack_byte_count_times_, 0, sizeof(nack_byte_count_times_)); memset(nack_byte_count_, 0, sizeof(nack_byte_count_)); memset(csrcs_, 0, sizeof(csrcs_)); @@ -105,9 +105,8 @@ RTPSender::RTPSender(const int32_t id, audio_ = new RTPSenderAudio(id, clock_, this); audio_->RegisterAudioCallback(audio_feedback); } else { - video_ = new RTPSenderVideo(id, clock_, this); + video_ = new RTPSenderVideo(clock_, this); } - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id, "%s created", __FUNCTION__); } RTPSender::~RTPSender() { @@ -126,12 +125,16 @@ RTPSender::~RTPSender() { } delete audio_; delete video_; +} - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id_, "%s deleted", __FUNCTION__); +void RTPSender::SetTargetBitrate(uint32_t bitrate) { + CriticalSectionScoped cs(target_bitrate_critsect_.get()); + target_bitrate_ = bitrate; } -void RTPSender::SetTargetSendBitrate(const uint32_t bits) { - SetTargetBitrateKbps(static_cast(bits / 1000)); +uint32_t RTPSender::GetTargetBitrate() { + CriticalSectionScoped cs(target_bitrate_critsect_.get()); + return target_bitrate_; } uint16_t RTPSender::ActualSendBitrateKbit() const { @@ -290,16 +293,12 @@ int32_t RTPSender::SetMaxPayloadLength( const uint16_t packet_over_head) { // Sanity check. if (max_payload_length < 100 || max_payload_length > IP_PACKET_SIZE) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, "%s invalid argument", - __FUNCTION__); + LOG(LS_ERROR) << "Invalid max payload length: " << max_payload_length; return -1; } CriticalSectionScoped cs(send_critsect_); max_payload_length_ = max_payload_length; packet_over_head_ = packet_over_head; - - WEBRTC_TRACE(kTraceInfo, kTraceRtpRtcp, id_, "SetMaxPayloadLength to %d.", - max_payload_length); return 0; } @@ -319,16 +318,14 @@ uint16_t RTPSender::MaxPayloadLength() const { uint16_t RTPSender::PacketOverHead() const { return packet_over_head_; } -void RTPSender::SetRTXStatus(int mode, bool set_ssrc, uint32_t ssrc) { +void RTPSender::SetRTXStatus(int mode) { CriticalSectionScoped cs(send_critsect_); rtx_ = mode; - if (rtx_ != kRtxOff) { - if (set_ssrc) { - ssrc_rtx_ = ssrc; - } else { - ssrc_rtx_ = ssrc_db_.CreateSSRC(); // Can't be 0. - } - } +} + +void RTPSender::SetRtxSsrc(uint32_t ssrc) { + CriticalSectionScoped cs(send_critsect_); + ssrc_rtx_ = ssrc; } void RTPSender::RTXStatus(int* mode, uint32_t* ssrc, @@ -339,7 +336,6 @@ void RTPSender::RTXStatus(int* mode, uint32_t* ssrc, *payload_type = payload_type_rtx_; } - void RTPSender::SetRtxPayloadType(int payload_type) { CriticalSectionScoped cs(send_critsect_); payload_type_rtx_ = payload_type; @@ -350,8 +346,7 @@ int32_t RTPSender::CheckPayloadType(const int8_t payload_type, CriticalSectionScoped cs(send_critsect_); if (payload_type < 0) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, "\tinvalid payload_type (%d)", - payload_type); + LOG(LS_ERROR) << "Invalid payload_type " << payload_type; return -1; } if (audio_configured_) { @@ -373,8 +368,7 @@ int32_t RTPSender::CheckPayloadType(const int8_t payload_type, std::map::iterator it = payload_type_map_.find(payload_type); if (it == payload_type_map_.end()) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "\tpayloadType:%d not registered", payload_type); + LOG(LS_WARNING) << "Payload type " << payload_type << " not registered."; return -1; } payload_type_ = payload_type; @@ -403,9 +397,7 @@ int32_t RTPSender::SendOutgoingData( } RtpVideoCodecTypes video_type = kRtpVideoGeneric; if (CheckPayloadType(payload_type, &video_type) != 0) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "%s invalid argument failed to find payload_type:%d", - __FUNCTION__, payload_type); + LOG(LS_ERROR) << "Don't send data with unknown payload type."; return -1; } @@ -478,8 +470,8 @@ bool RTPSender::SendPaddingAccordingToBitrate( // Current bitrate since last estimate(1 second) averaged with the // estimate since then, to get the most up to date bitrate. uint32_t current_bitrate = bitrate_sent_.BitrateNow(); - uint16_t target_bitrate_kbps = GetTargetBitrateKbps(); - int bitrate_diff = target_bitrate_kbps * 1000 - current_bitrate; + uint32_t target_bitrate = GetTargetBitrate(); + int bitrate_diff = target_bitrate - current_bitrate; if (bitrate_diff <= 0) { return true; } @@ -490,7 +482,7 @@ bool RTPSender::SendPaddingAccordingToBitrate( } else { bytes = (bitrate_diff / 8); // Cap at 200 ms of target send data. - int bytes_cap = target_bitrate_kbps * 25; // 1000 / 8 / 5. + int bytes_cap = target_bitrate / 1000 * 25; // 1000 / 8 / 5. if (bytes > bytes_cap) { bytes = bytes_cap; } @@ -616,8 +608,6 @@ int32_t RTPSender::ReSendPacket(uint16_t packet_id, uint32_t min_resend_time) { RTPHeader header; if (!rtp_parser.Parse(header)) { assert(false); - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, id_, - "Failed to parse RTP header of packet to be retransmitted."); return -1; } if (!paced_sender_->SendPacket(PacedSender::kHighPriority, @@ -644,10 +634,9 @@ bool RTPSender::SendPacketToNetwork(const uint8_t *packet, uint32_t size) { } TRACE_EVENT_INSTANT2("webrtc_rtp", "RTPSender::SendPacketToNetwork", "size", size, "sent", bytes_sent); - // TODO(pwesin): Add a separate bitrate for sent bitrate after pacer. + // TODO(pwestin): Add a separate bitrate for sent bitrate after pacer. if (bytes_sent <= 0) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "Transport failed to send packet"); + LOG(LS_WARNING) << "Transport failed to send packet"; return false; } return true; @@ -672,15 +661,12 @@ void RTPSender::OnReceivedNACK( "num_seqnum", nack_sequence_numbers.size(), "avg_rtt", avg_rtt); const int64_t now = clock_->TimeInMilliseconds(); uint32_t bytes_re_sent = 0; - uint16_t target_bitrate_kbps = GetTargetBitrateKbps(); + uint32_t target_bitrate = GetTargetBitrate(); // Enough bandwidth to send NACK? if (!ProcessNACKBitRate(now)) { - WEBRTC_TRACE(kTraceStream, - kTraceRtpRtcp, - id_, - "NACK bitrate reached. Skip sending NACK response. Target %d", - target_bitrate_kbps); + LOG(LS_INFO) << "NACK bitrate reached. Skip sending NACK response. Target " + << target_bitrate; return; } @@ -695,16 +681,15 @@ void RTPSender::OnReceivedNACK( continue; } else if (bytes_sent < 0) { // Failed to send one Sequence number. Give up the rest in this nack. - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, - "Failed resending RTP packet %d, Discard rest of packets", - *it); + LOG(LS_WARNING) << "Failed resending RTP packet " << *it + << ", Discard rest of packets"; break; } // Delay bandwidth estimate (RTT * BW). - if (target_bitrate_kbps != 0 && avg_rtt) { + if (target_bitrate != 0 && avg_rtt) { // kbits/s * ms = bits => bits/8 = bytes uint32_t target_bytes = - (static_cast(target_bitrate_kbps) * avg_rtt) >> 3; + (static_cast(target_bitrate / 1000) * avg_rtt) >> 3; if (bytes_re_sent > target_bytes) { break; // Ignore the rest of the packets in the list. } @@ -719,33 +704,33 @@ void RTPSender::OnReceivedNACK( bool RTPSender::ProcessNACKBitRate(const uint32_t now) { uint32_t num = 0; - int32_t byte_count = 0; - const uint32_t avg_interval = 1000; - uint16_t target_bitrate_kbps = GetTargetBitrateKbps(); + int byte_count = 0; + const uint32_t kAvgIntervalMs = 1000; + uint32_t target_bitrate = GetTargetBitrate(); CriticalSectionScoped cs(send_critsect_); - if (target_bitrate_kbps == 0) { + if (target_bitrate == 0) { return true; } for (num = 0; num < NACK_BYTECOUNT_SIZE; ++num) { - if ((now - nack_byte_count_times_[num]) > avg_interval) { + if ((now - nack_byte_count_times_[num]) > kAvgIntervalMs) { // Don't use data older than 1sec. break; } else { byte_count += nack_byte_count_[num]; } } - int32_t time_interval = avg_interval; + uint32_t time_interval = kAvgIntervalMs; if (num == NACK_BYTECOUNT_SIZE) { // More than NACK_BYTECOUNT_SIZE nack messages has been received // during the last msg_interval. - time_interval = now - nack_byte_count_times_[num - 1]; - if (time_interval < 0) { - time_interval = avg_interval; + if (nack_byte_count_times_[num - 1] <= now) { + time_interval = now - nack_byte_count_times_[num - 1]; } } - return (byte_count * 8) < (target_bitrate_kbps * time_interval); + return (byte_count * 8) < + static_cast(target_bitrate / 1000 * time_interval); } void RTPSender::UpdateNACKBitRate(const uint32_t bytes, @@ -820,18 +805,9 @@ bool RTPSender::PrepareAndSendPacket(uint8_t* buffer, int64_t now_ms = clock_->TimeInMilliseconds(); int64_t diff_ms = now_ms - capture_time_ms; - bool updated_transmission_time_offset = - UpdateTransmissionTimeOffset(buffer_to_send_ptr, length, rtp_header, - diff_ms); - bool updated_abs_send_time = - UpdateAbsoluteSendTime(buffer_to_send_ptr, length, rtp_header, now_ms); - if (updated_transmission_time_offset || updated_abs_send_time) { - // Update stored packet in case of receiving a re-transmission request. - packet_history_.ReplaceRTPHeader(buffer_to_send_ptr, - rtp_header.sequenceNumber, - rtp_header.headerLength); - } - + UpdateTransmissionTimeOffset(buffer_to_send_ptr, length, rtp_header, + diff_ms); + UpdateAbsoluteSendTime(buffer_to_send_ptr, length, rtp_header, now_ms); bool ret = SendPacketToNetwork(buffer_to_send_ptr, length); UpdateRtpStats(buffer_to_send_ptr, length, rtp_header, send_over_rtx, is_retransmit); @@ -911,8 +887,13 @@ int RTPSender::TimeToSendPadding(int bytes) { int bytes_sent = SendRedundantPayloads(payload_type, bytes); bytes -= bytes_sent; if (bytes > 0) { - int padding_sent = SendPadData(payload_type, timestamp, capture_time_ms, - bytes, kDontStore, true, true); + int padding_sent = SendPadData(payload_type, + timestamp, + capture_time_ms, + bytes, + kDontStore, + true, + rtx_ == kRtxOff); bytes_sent += padding_sent; } return bytes_sent; @@ -1248,55 +1229,50 @@ uint8_t RTPSender::BuildAbsoluteSendTimeExtension(uint8_t* data_buffer) const { return kAbsoluteSendTimeLength; } -bool RTPSender::UpdateTransmissionTimeOffset( +void RTPSender::UpdateTransmissionTimeOffset( uint8_t *rtp_packet, const uint16_t rtp_packet_length, const RTPHeader &rtp_header, const int64_t time_diff_ms) const { CriticalSectionScoped cs(send_critsect_); - + // Get id. + uint8_t id = 0; + if (rtp_header_extension_map_.GetId(kRtpExtensionTransmissionTimeOffset, + &id) != 0) { + // Not registered. + return; + } // Get length until start of header extension block. int extension_block_pos = rtp_header_extension_map_.GetLengthUntilBlockStartInBytes( kRtpExtensionTransmissionTimeOffset); if (extension_block_pos < 0) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update transmission time offset, not registered."); - return false; + LOG(LS_WARNING) + << "Failed to update transmission time offset, not registered."; + return; } int block_pos = 12 + rtp_header.numCSRCs + extension_block_pos; if (rtp_packet_length < block_pos + kTransmissionTimeOffsetLength || rtp_header.headerLength < block_pos + kTransmissionTimeOffsetLength) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update transmission time offset, invalid length."); - return false; + LOG(LS_WARNING) + << "Failed to update transmission time offset, invalid length."; + return; } // Verify that header contains extension. if (!((rtp_packet[12 + rtp_header.numCSRCs] == 0xBE) && (rtp_packet[12 + rtp_header.numCSRCs + 1] == 0xDE))) { - WEBRTC_TRACE( - kTraceStream, kTraceRtpRtcp, id_, - "Failed to update transmission time offset, hdr extension not found."); - return false; - } - // Get id. - uint8_t id = 0; - if (rtp_header_extension_map_.GetId(kRtpExtensionTransmissionTimeOffset, - &id) != 0) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update transmission time offset, no id."); - return false; + LOG(LS_WARNING) << "Failed to update transmission time offset, hdr " + "extension not found."; + return; } // Verify first byte in block. const uint8_t first_block_byte = (id << 4) + 2; if (rtp_packet[block_pos] != first_block_byte) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update transmission time offset."); - return false; + LOG(LS_WARNING) << "Failed to update transmission time offset."; + return; } // Update transmission offset field (converting to a 90 kHz timestamp). ModuleRTPUtility::AssignUWord24ToBuffer(rtp_packet + block_pos + 1, time_diff_ms * 90); // RTP timestamp. - return true; } bool RTPSender::UpdateAudioLevel(uint8_t *rtp_packet, @@ -1306,97 +1282,85 @@ bool RTPSender::UpdateAudioLevel(uint8_t *rtp_packet, const uint8_t dBov) const { CriticalSectionScoped cs(send_critsect_); + // Get id. + uint8_t id = 0; + if (rtp_header_extension_map_.GetId(kRtpExtensionAudioLevel, &id) != 0) { + // Not registered. + return false; + } // Get length until start of header extension block. int extension_block_pos = rtp_header_extension_map_.GetLengthUntilBlockStartInBytes( kRtpExtensionAudioLevel); if (extension_block_pos < 0) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update audio level, not registered."); + // The feature is not enabled. return false; } int block_pos = 12 + rtp_header.numCSRCs + extension_block_pos; if (rtp_packet_length < block_pos + kAudioLevelLength || rtp_header.headerLength < block_pos + kAudioLevelLength) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update audio level, invalid length."); + LOG(LS_WARNING) << "Failed to update audio level, invalid length."; return false; } // Verify that header contains extension. if (!((rtp_packet[12 + rtp_header.numCSRCs] == 0xBE) && (rtp_packet[12 + rtp_header.numCSRCs + 1] == 0xDE))) { - WEBRTC_TRACE( - kTraceStream, kTraceRtpRtcp, id_, - "Failed to update audio level, hdr extension not found."); - return false; - } - // Get id. - uint8_t id = 0; - if (rtp_header_extension_map_.GetId(kRtpExtensionAudioLevel, &id) != 0) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update audio level, no id."); + LOG(LS_WARNING) << "Failed to update audio level, hdr extension not found."; return false; } // Verify first byte in block. const uint8_t first_block_byte = (id << 4) + 0; if (rtp_packet[block_pos] != first_block_byte) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update audio level."); + LOG(LS_WARNING) << "Failed to update audio level."; return false; } rtp_packet[block_pos + 1] = (is_voiced ? 0x80 : 0x00) + (dBov & 0x7f); return true; } -bool RTPSender::UpdateAbsoluteSendTime( +void RTPSender::UpdateAbsoluteSendTime( uint8_t *rtp_packet, const uint16_t rtp_packet_length, const RTPHeader &rtp_header, const int64_t now_ms) const { CriticalSectionScoped cs(send_critsect_); + // Get id. + uint8_t id = 0; + if (rtp_header_extension_map_.GetId(kRtpExtensionAbsoluteSendTime, + &id) != 0) { + // Not registered. + return; + } // Get length until start of header extension block. int extension_block_pos = rtp_header_extension_map_.GetLengthUntilBlockStartInBytes( kRtpExtensionAbsoluteSendTime); if (extension_block_pos < 0) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update absolute send time, not registered."); - return false; + // The feature is not enabled. + return; } int block_pos = 12 + rtp_header.numCSRCs + extension_block_pos; if (rtp_packet_length < block_pos + kAbsoluteSendTimeLength || rtp_header.headerLength < block_pos + kAbsoluteSendTimeLength) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update absolute send time, invalid length."); - return false; + LOG(LS_WARNING) << "Failed to update absolute send time, invalid length."; + return; } // Verify that header contains extension. if (!((rtp_packet[12 + rtp_header.numCSRCs] == 0xBE) && (rtp_packet[12 + rtp_header.numCSRCs + 1] == 0xDE))) { - WEBRTC_TRACE( - kTraceStream, kTraceRtpRtcp, id_, - "Failed to update absolute send time, hdr extension not found."); - return false; - } - // Get id. - uint8_t id = 0; - if (rtp_header_extension_map_.GetId(kRtpExtensionAbsoluteSendTime, - &id) != 0) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update absolute send time, no id."); - return false; + LOG(LS_WARNING) + << "Failed to update absolute send time, hdr extension not found."; + return; } // Verify first byte in block. const uint8_t first_block_byte = (id << 4) + 2; if (rtp_packet[block_pos] != first_block_byte) { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, id_, - "Failed to update absolute send time."); - return false; + LOG(LS_WARNING) << "Failed to update absolute send time."; + return; } // Update absolute send time field (convert ms to 24-bit unsigned with 18 bit // fractional part). ModuleRTPUtility::AssignUWord24ToBuffer(rtp_packet + block_pos + 1, ((now_ms << 18) / 1000) & 0x00ffffff); - return true; } void RTPSender::SetSendingStatus(bool enabled) { @@ -1705,14 +1669,4 @@ void RTPSender::BitrateUpdated(const BitrateStatistics& stats) { bitrate_callback_->Notify(stats, ssrc_); } } - -void RTPSender::SetTargetBitrateKbps(uint16_t bitrate_kbps) { - CriticalSectionScoped cs(target_bitrate_critsect_.get()); - target_bitrate_kbps_ = bitrate_kbps; -} - -uint16_t RTPSender::GetTargetBitrateKbps() { - CriticalSectionScoped cs(target_bitrate_critsect_.get()); - return target_bitrate_kbps_; -} } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.h b/webrtc/modules/rtp_rtcp/source/rtp_sender.h index fc2c8217f..17b026cad 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.h @@ -83,7 +83,8 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { // was sent within the statistics window. bool GetSendSideDelay(int* avg_send_delay_ms, int* max_send_delay_ms) const; - void SetTargetSendBitrate(const uint32_t bits); + void SetTargetBitrate(uint32_t bitrate); + uint32_t GetTargetBitrate(); virtual uint16_t MaxDataPayloadLength() const OVERRIDE; // with RTP and FEC headers. @@ -158,19 +159,11 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { uint8_t BuildAudioLevelExtension(uint8_t* data_buffer) const; uint8_t BuildAbsoluteSendTimeExtension(uint8_t* data_buffer) const; - bool UpdateTransmissionTimeOffset(uint8_t *rtp_packet, - const uint16_t rtp_packet_length, - const RTPHeader &rtp_header, - const int64_t time_diff_ms) const; bool UpdateAudioLevel(uint8_t *rtp_packet, const uint16_t rtp_packet_length, const RTPHeader &rtp_header, const bool is_voiced, const uint8_t dBov) const; - bool UpdateAbsoluteSendTime(uint8_t *rtp_packet, - const uint16_t rtp_packet_length, - const RTPHeader &rtp_header, - const int64_t now_ms) const; bool TimeToSendPacket(uint16_t sequence_number, int64_t capture_time_ms, bool retransmission); @@ -192,10 +185,12 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { bool ProcessNACKBitRate(const uint32_t now); // RTX. - void SetRTXStatus(int mode, bool set_ssrc, uint32_t ssrc); + void SetRTXStatus(int mode); void RTXStatus(int* mode, uint32_t* ssrc, int* payload_type) const; + void SetRtxSsrc(uint32_t ssrc); + void SetRtxPayloadType(int payloadType); // Functions wrapping RTPSenderInterface. @@ -319,6 +314,15 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { void UpdateDelayStatistics(int64_t capture_time_ms, int64_t now_ms); + void UpdateTransmissionTimeOffset(uint8_t *rtp_packet, + const uint16_t rtp_packet_length, + const RTPHeader &rtp_header, + const int64_t time_diff_ms) const; + void UpdateAbsoluteSendTime(uint8_t *rtp_packet, + const uint16_t rtp_packet_length, + const RTPHeader &rtp_header, + const int64_t now_ms) const; + void UpdateRtpStats(const uint8_t* buffer, uint32_t size, const RTPHeader& header, @@ -326,9 +330,6 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { bool is_retransmit); bool IsFecPacket(const uint8_t* buffer, const RTPHeader& header) const; - void SetTargetBitrateKbps(uint16_t bitrate_kbps); - uint16_t GetTargetBitrateKbps(); - Clock* clock_; Bitrate bitrate_sent_; @@ -396,7 +397,7 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { // that by the time the function returns there is no guarantee // that the target bitrate is still valid. scoped_ptr target_bitrate_critsect_; - uint16_t target_bitrate_kbps_ GUARDED_BY(target_bitrate_critsect_); + uint16_t target_bitrate_ GUARDED_BY(target_bitrate_critsect_); }; } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc index 8b72ce02b..18482890f 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc @@ -444,7 +444,7 @@ TEST_F(RtpSenderTest, TrafficSmoothingWithExtensions) { kRtpExtensionTransmissionTimeOffset, kTransmissionTimeOffsetExtensionId)); EXPECT_EQ(0, rtp_sender_->RegisterRtpHeaderExtension( kRtpExtensionAbsoluteSendTime, kAbsoluteSendTimeExtensionId)); - rtp_sender_->SetTargetSendBitrate(300000); + rtp_sender_->SetTargetBitrate(300000); int64_t capture_time_ms = fake_clock_.TimeInMilliseconds(); int32_t rtp_length = rtp_sender_->BuildRTPheader(packet_, kPayload, @@ -498,7 +498,7 @@ TEST_F(RtpSenderTest, TrafficSmoothingRetransmits) { kRtpExtensionTransmissionTimeOffset, kTransmissionTimeOffsetExtensionId)); EXPECT_EQ(0, rtp_sender_->RegisterRtpHeaderExtension( kRtpExtensionAbsoluteSendTime, kAbsoluteSendTimeExtensionId)); - rtp_sender_->SetTargetSendBitrate(300000); + rtp_sender_->SetTargetBitrate(300000); int64_t capture_time_ms = fake_clock_.TimeInMilliseconds(); int32_t rtp_length = rtp_sender_->BuildRTPheader(packet_, kPayload, @@ -580,7 +580,7 @@ TEST_F(RtpSenderTest, SendPadding) { kAbsoluteSendTimeExtensionId); webrtc::RTPHeader rtp_header; - rtp_sender_->SetTargetSendBitrate(300000); + rtp_sender_->SetTargetBitrate(300000); int64_t capture_time_ms = fake_clock_.TimeInMilliseconds(); int32_t rtp_length = rtp_sender_->BuildRTPheader(packet_, kPayload, @@ -687,8 +687,8 @@ TEST_F(RtpSenderTest, SendRedundantPayloads) { rtp_header_len += 4; // 4 bytes extension. rtp_header_len += 4; // 4 extra bytes common to all extension headers. - rtp_sender_->SetRTXStatus(kRtxRetransmitted | kRtxRedundantPayloads, true, - 1234); + rtp_sender_->SetRTXStatus(kRtxRetransmitted | kRtxRedundantPayloads); + rtp_sender_->SetRtxSsrc(1234); // Create and set up parser. scoped_ptr rtp_parser( @@ -698,7 +698,7 @@ TEST_F(RtpSenderTest, SendRedundantPayloads) { kTransmissionTimeOffsetExtensionId); rtp_parser->RegisterRtpHeaderExtension(kRtpExtensionAbsoluteSendTime, kAbsoluteSendTimeExtensionId); - rtp_sender_->SetTargetSendBitrate(300000); + rtp_sender_->SetTargetBitrate(300000); const size_t kNumPayloadSizes = 10; const int kPayloadSizes[kNumPayloadSizes] = {500, 550, 600, 650, 700, 750, 800, 850, 900, 950}; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc index 10bc252bb..5d8ae1665 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -20,7 +20,7 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h" #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -31,11 +31,9 @@ struct RtpPacket { ForwardErrorCorrection::Packet* pkt; }; -RTPSenderVideo::RTPSenderVideo(const int32_t id, - Clock* clock, +RTPSenderVideo::RTPSenderVideo(Clock* clock, RTPSenderInterface* rtpSender) - : _id(id), - _rtpSender(*rtpSender), + : _rtpSender(*rtpSender), _sendVideoCritsect(CriticalSectionWrapper::CreateCriticalSection()), _videoType(kRtpVideoGeneric), _videoCodecInformation(NULL), @@ -43,7 +41,7 @@ RTPSenderVideo::RTPSenderVideo(const int32_t id, _retransmissionSettings(kRetransmitBaseLayer), // Generic FEC - _fec(id), + _fec(), _fecEnabled(false), _payloadTypeRED(-1), _payloadTypeFEC(-1), @@ -329,8 +327,6 @@ RTPSenderVideo::SendVideo(const RtpVideoCodecTypes videoType, { return retVal; } - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, "%s(timestamp:%u)", - __FUNCTION__, captureTimeStamp); return 0; } @@ -476,9 +472,9 @@ RTPSenderVideo::SendVP8(const FrameType frameType, rtpHeaderLength, captureTimeStamp, capture_time_ms, storage, protect)) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "RTPSenderVideo::SendVP8 failed to send packet number" - " %d", _rtpSender.SequenceNumber()); + LOG(LS_WARNING) + << "RTPSenderVideo::SendVP8 failed to send packet number " + << _rtpSender.SequenceNumber(); } } TRACE_EVENT_ASYNC_END1("webrtc", "Video", capture_time_ms, diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender_video.h b/webrtc/modules/rtp_rtcp/source/rtp_sender_video.h index 4c406d750..daa730e8c 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender_video.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender_video.h @@ -31,7 +31,7 @@ struct RtpPacket; class RTPSenderVideo { public: - RTPSenderVideo(const int32_t id, Clock* clock, + RTPSenderVideo(Clock* clock, RTPSenderInterface* rtpSender); virtual ~RTPSenderVideo(); @@ -112,7 +112,6 @@ class RTPSenderVideo const RTPVideoTypeHeader* rtpTypeHdr); private: - int32_t _id; RTPSenderInterface& _rtpSender; CriticalSectionWrapper* _sendVideoCritsect; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_utility.cc b/webrtc/modules/rtp_rtcp/source/rtp_utility.cc index 2aa218bad..c1f3c6427 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_utility.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_utility.cc @@ -30,7 +30,7 @@ #endif #include "webrtc/system_wrappers/interface/tick_util.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #if (defined(_DEBUG) && defined(_WIN32) && (_MSC_VER >= 1400)) #define DEBUG_PRINT(...) \ @@ -464,22 +464,22 @@ void RTPHeaderParser::ParseOneByteExtensionHeader( ptr++; if (id == 15) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Ext id: 15 encountered, parsing terminated."); + LOG(LS_WARNING) + << "RTP extension header 15 encountered. Terminate parsing."; return; } RTPExtensionType type; if (ptrExtensionMap->GetType(id, &type) != 0) { // If we encounter an unknown extension, just skip over it. - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "Failed to find extension id: %d", id); + LOG(LS_WARNING) << "Failed to find extension id: " + << static_cast(id); } else { switch (type) { case kRtpExtensionTransmissionTimeOffset: { if (len != 2) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Incorrect transmission time offset len: %d", len); + LOG(LS_WARNING) << "Incorrect transmission time offset len: " + << len; return; } // 0 1 2 3 @@ -502,8 +502,7 @@ void RTPHeaderParser::ParseOneByteExtensionHeader( } case kRtpExtensionAudioLevel: { if (len != 0) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Incorrect audio level len: %d", len); + LOG(LS_WARNING) << "Incorrect audio level len: " << len; return; } // 0 1 2 3 @@ -525,8 +524,7 @@ void RTPHeaderParser::ParseOneByteExtensionHeader( } case kRtpExtensionAbsoluteSendTime: { if (len != 2) { - WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, - "Incorrect absolute send time len: %d", len); + LOG(LS_WARNING) << "Incorrect absolute send time len: " << len; return; } // 0 1 2 3 @@ -543,8 +541,7 @@ void RTPHeaderParser::ParseOneByteExtensionHeader( break; } default: { - WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, - "Extension type not implemented."); + LOG(LS_WARNING) << "Extension type not implemented: " << type; return; } } @@ -570,17 +567,12 @@ uint8_t RTPHeaderParser::ParsePaddingBytes( return num_zero_bytes; } -// RTP payload parser RTPPayloadParser::RTPPayloadParser(const RtpVideoCodecTypes videoType, const uint8_t* payloadData, - uint16_t payloadDataLength, - int32_t id) - : - _id(id), - _dataPtr(payloadData), - _dataLength(payloadDataLength), - _videoType(videoType) { -} + uint16_t payloadDataLength) + : _dataPtr(payloadData), + _dataLength(payloadDataLength), + _videoType(videoType) {} RTPPayloadParser::~RTPPayloadParser() { } @@ -655,8 +647,7 @@ bool RTPPayloadParser::ParseVP8(RTPPayload& parsedPacket) const { } if (dataLength <= 0) { - WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, - "Error parsing VP8 payload descriptor; payload too short"); + LOG(LS_ERROR) << "Error parsing VP8 payload descriptor!"; return false; } diff --git a/webrtc/modules/rtp_rtcp/source/rtp_utility.h b/webrtc/modules/rtp_rtcp/source/rtp_utility.h index 8002273c3..732301f6f 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_utility.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_utility.h @@ -166,8 +166,8 @@ namespace ModuleRTPUtility public: RTPPayloadParser(const RtpVideoCodecTypes payloadType, const uint8_t* payloadData, - const uint16_t payloadDataLength, // Length w/o padding. - const int32_t id); + // Length w/o padding. + const uint16_t payloadDataLength); ~RTPPayloadParser(); @@ -202,7 +202,6 @@ namespace ModuleRTPUtility int dataLength) const; private: - int32_t _id; const uint8_t* _dataPtr; const uint16_t _dataLength; const RtpVideoCodecTypes _videoType; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc index 02a89fc4f..d33eaf4c8 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc @@ -76,7 +76,7 @@ TEST(ParseVP8Test, BasicHeader) { payload[0] = 0x14; // Binary 0001 0100; S = 1, PartID = 4. payload[1] = 0x01; // P frame. - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 4, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 4); RTPPayload parsedPacket; ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); @@ -97,7 +97,7 @@ TEST(ParseVP8Test, PictureID) { payload[1] = 0x80; payload[2] = 17; - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10); RTPPayload parsedPacket; ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); @@ -117,7 +117,7 @@ TEST(ParseVP8Test, PictureID) { // Re-use payload, but change to long PictureID. payload[2] = 0x80 | 17; payload[3] = 17; - RTPPayloadParser rtpPayloadParser2(kRtpVideoVp8, payload, 10, 0); + RTPPayloadParser rtpPayloadParser2(kRtpVideoVp8, payload, 10); ASSERT_TRUE(rtpPayloadParser2.Parse(parsedPacket)); @@ -136,7 +136,7 @@ TEST(ParseVP8Test, Tl0PicIdx) { payload[1] = 0x40; payload[2] = 17; - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 13, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 13); RTPPayload parsedPacket; ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); @@ -159,7 +159,7 @@ TEST(ParseVP8Test, TIDAndLayerSync) { payload[1] = 0x20; payload[2] = 0x80; // TID(2) + LayerSync(false) - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10); RTPPayload parsedPacket; ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); @@ -183,7 +183,7 @@ TEST(ParseVP8Test, KeyIdx) { payload[1] = 0x10; // K = 1. payload[2] = 0x11; // KEYIDX = 17 decimal. - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10); RTPPayload parsedPacket; ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); @@ -209,7 +209,7 @@ TEST(ParseVP8Test, MultipleExtensions) { payload[4] = 42; // Tl0PicIdx. payload[5] = 0x40 | 0x20 | 0x11; // TID(1) + LayerSync(true) + KEYIDX(17). - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 10); RTPPayload parsedPacket; ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); @@ -236,7 +236,7 @@ TEST(ParseVP8Test, TooShortHeader) { payload[2] = 0x80 | 17; // ... but only 2 bytes PictureID is provided. payload[3] = 17; // PictureID, low 8 bits. - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 4, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, payload, 4); RTPPayload parsedPacket; EXPECT_FALSE(rtpPayloadParser.Parse(parsedPacket)); @@ -258,7 +258,7 @@ TEST(ParseVP8Test, TestWithPacketizer) { ASSERT_EQ(0, packetizer.NextPacket(packet, &send_bytes, &last)); ASSERT_TRUE(last); - RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, packet, send_bytes, 0); + RTPPayloadParser rtpPayloadParser(kRtpVideoVp8, packet, send_bytes); RTPPayload parsedPacket; ASSERT_TRUE(rtpPayloadParser.Parse(parsedPacket)); diff --git a/webrtc/modules/rtp_rtcp/source/ssrc_database.cc b/webrtc/modules/rtp_rtcp/source/ssrc_database.cc index 1e57970c0..df09b01bd 100644 --- a/webrtc/modules/rtp_rtcp/source/ssrc_database.cc +++ b/webrtc/modules/rtp_rtcp/source/ssrc_database.cc @@ -14,7 +14,6 @@ #include #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" #ifdef _WIN32 #include @@ -185,8 +184,6 @@ SSRCDatabase::SSRCDatabase() _ssrcVector = new uint32_t[10]; #endif _critSect = CriticalSectionWrapper::CreateCriticalSection(); - - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, -1, "%s created", __FUNCTION__); } SSRCDatabase::~SSRCDatabase() @@ -197,8 +194,6 @@ SSRCDatabase::~SSRCDatabase() _ssrcMap.clear(); #endif delete _critSect; - - WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, -1, "%s deleted", __FUNCTION__); } uint32_t SSRCDatabase::GenerateRandom() diff --git a/webrtc/modules/rtp_rtcp/source/vp8_partition_aggregator.h b/webrtc/modules/rtp_rtcp/source/vp8_partition_aggregator.h index 2ca5f287a..cbb1207b8 100644 --- a/webrtc/modules/rtp_rtcp/source/vp8_partition_aggregator.h +++ b/webrtc/modules/rtp_rtcp/source/vp8_partition_aggregator.h @@ -13,8 +13,8 @@ #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/interface/module_common_types.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/typedefs.h" namespace webrtc { diff --git a/webrtc/modules/rtp_rtcp/test/OWNERS b/webrtc/modules/rtp_rtcp/test/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/test/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc b/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc index 4f43e6ac4..ac2c5ca01 100644 --- a/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc +++ b/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc @@ -34,7 +34,7 @@ class RtpRtcpAPITest : public ::testing::Test { configuration.clock = &fake_clock; module = RtpRtcp::CreateRtpRtcp(configuration); rtp_payload_registry_.reset(new RTPPayloadRegistry( - test_id, RTPPayloadStrategy::CreateStrategy(true))); + RTPPayloadStrategy::CreateStrategy(true))); rtp_receiver_.reset(RtpReceiver::CreateAudioReceiver( test_id, &fake_clock, NULL, NULL, NULL, rtp_payload_registry_.get())); } @@ -80,7 +80,7 @@ TEST_F(RtpRtcpAPITest, MTU) { } TEST_F(RtpRtcpAPITest, SSRC) { - EXPECT_EQ(0, module->SetSSRC(test_ssrc)); + module->SetSSRC(test_ssrc); EXPECT_EQ(test_ssrc, module->SSRC()); } @@ -119,21 +119,22 @@ TEST_F(RtpRtcpAPITest, RtxSender) { int rtx_mode = kRtxOff; const int kRtxPayloadType = 119; int payload_type = -1; - EXPECT_EQ(0, module->SetRTXSendStatus(kRtxRetransmitted, true, 1)); + module->SetRTXSendStatus(kRtxRetransmitted); module->SetRtxSendPayloadType(kRtxPayloadType); - EXPECT_EQ(0, module->RTXSendStatus(&rtx_mode, &ssrc, &payload_type)); + module->SetRtxSsrc(1); + module->RTXSendStatus(&rtx_mode, &ssrc, &payload_type); EXPECT_EQ(kRtxRetransmitted, rtx_mode); EXPECT_EQ(1u, ssrc); EXPECT_EQ(kRtxPayloadType, payload_type); rtx_mode = kRtxOff; - EXPECT_EQ(0, module->SetRTXSendStatus(kRtxOff, true, 0)); + module->SetRTXSendStatus(kRtxOff); payload_type = -1; module->SetRtxSendPayloadType(kRtxPayloadType); - EXPECT_EQ(0, module->RTXSendStatus(&rtx_mode, &ssrc, &payload_type)); + module->RTXSendStatus(&rtx_mode, &ssrc, &payload_type); EXPECT_EQ(kRtxOff, rtx_mode); - EXPECT_EQ(kRtxPayloadType ,payload_type); - EXPECT_EQ(0, module->SetRTXSendStatus(kRtxRetransmitted, false, 1)); - EXPECT_EQ(0, module->RTXSendStatus(&rtx_mode, &ssrc, &payload_type)); + EXPECT_EQ(kRtxPayloadType, payload_type); + module->SetRTXSendStatus(kRtxRetransmitted); + module->RTXSendStatus(&rtx_mode, &ssrc, &payload_type); EXPECT_EQ(kRtxRetransmitted, rtx_mode); EXPECT_EQ(kRtxPayloadType, payload_type); } @@ -141,7 +142,8 @@ TEST_F(RtpRtcpAPITest, RtxSender) { TEST_F(RtpRtcpAPITest, RtxReceiver) { const uint32_t kRtxSsrc = 1; const int kRtxPayloadType = 119; - rtp_payload_registry_->SetRtxStatus(true, kRtxSsrc); + EXPECT_FALSE(rtp_payload_registry_->RtxEnabled()); + rtp_payload_registry_->SetRtxSsrc(kRtxSsrc); rtp_payload_registry_->SetRtxPayloadType(kRtxPayloadType); EXPECT_TRUE(rtp_payload_registry_->RtxEnabled()); RTPHeader rtx_header; @@ -150,8 +152,7 @@ TEST_F(RtpRtcpAPITest, RtxReceiver) { EXPECT_TRUE(rtp_payload_registry_->IsRtx(rtx_header)); rtx_header.ssrc = 0; EXPECT_FALSE(rtp_payload_registry_->IsRtx(rtx_header)); - rtp_payload_registry_->SetRtxStatus(false, kRtxSsrc); - EXPECT_FALSE(rtp_payload_registry_->RtxEnabled()); rtx_header.ssrc = kRtxSsrc; - EXPECT_FALSE(rtp_payload_registry_->IsRtx(rtx_header)); + rtx_header.payloadType = 0; + EXPECT_TRUE(rtp_payload_registry_->IsRtx(rtx_header)); } diff --git a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_audio.cc b/webrtc/modules/rtp_rtcp/test/testAPI/test_api_audio.cc index 786865832..8ae4c55ef 100644 --- a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_audio.cc +++ b/webrtc/modules/rtp_rtcp/test/testAPI/test_api_audio.cc @@ -122,9 +122,9 @@ class RtpRtcpAudioTest : public ::testing::Test { receive_statistics2_.reset(ReceiveStatistics::Create(&fake_clock)); rtp_payload_registry1_.reset(new RTPPayloadRegistry( - test_id, RTPPayloadStrategy::CreateStrategy(true))); + RTPPayloadStrategy::CreateStrategy(true))); rtp_payload_registry2_.reset(new RTPPayloadRegistry( - test_id, RTPPayloadStrategy::CreateStrategy(true))); + RTPPayloadStrategy::CreateStrategy(true))); RtpRtcp::Configuration configuration; configuration.id = test_id; @@ -189,7 +189,7 @@ class RtpRtcpAudioTest : public ::testing::Test { }; TEST_F(RtpRtcpAudioTest, Basic) { - EXPECT_EQ(0, module1->SetSSRC(test_ssrc)); + module1->SetSSRC(test_ssrc); EXPECT_EQ(0, module1->SetStartTimestamp(test_timestamp)); // Test detection at the end of a DTMF tone. @@ -260,7 +260,7 @@ TEST_F(RtpRtcpAudioTest, RED) { voice_codec.channels, (voice_codec.rate < 0) ? 0 : voice_codec.rate)); - EXPECT_EQ(0, module1->SetSSRC(test_ssrc)); + module1->SetSSRC(test_ssrc); EXPECT_EQ(0, module1->SetStartTimestamp(test_timestamp)); EXPECT_EQ(0, module1->SetSendingStatus(true)); @@ -333,7 +333,7 @@ TEST_F(RtpRtcpAudioTest, DTMF) { voice_codec.channels, (voice_codec.rate < 0) ? 0 : voice_codec.rate)); - EXPECT_EQ(0, module1->SetSSRC(test_ssrc)); + module1->SetSSRC(test_ssrc); EXPECT_EQ(0, module1->SetStartTimestamp(test_timestamp)); EXPECT_EQ(0, module1->SetSendingStatus(true)); diff --git a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_rtcp.cc b/webrtc/modules/rtp_rtcp/test/testAPI/test_api_rtcp.cc index 1e715187e..9d0505ef7 100644 --- a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_rtcp.cc +++ b/webrtc/modules/rtp_rtcp/test/testAPI/test_api_rtcp.cc @@ -116,9 +116,9 @@ class RtpRtcpRtcpTest : public ::testing::Test { configuration.intra_frame_callback = myRTCPFeedback1; rtp_payload_registry1_.reset(new RTPPayloadRegistry( - test_id, RTPPayloadStrategy::CreateStrategy(true))); + RTPPayloadStrategy::CreateStrategy(true))); rtp_payload_registry2_.reset(new RTPPayloadRegistry( - test_id, RTPPayloadStrategy::CreateStrategy(true))); + RTPPayloadStrategy::CreateStrategy(true))); module1 = RtpRtcp::CreateRtpRtcp(configuration); @@ -152,8 +152,8 @@ class RtpRtcpRtcpTest : public ::testing::Test { EXPECT_EQ(0, module1->SetRTCPStatus(kRtcpCompound)); EXPECT_EQ(0, module2->SetRTCPStatus(kRtcpCompound)); - EXPECT_EQ(0, module2->SetSSRC(test_ssrc + 1)); - EXPECT_EQ(0, module1->SetSSRC(test_ssrc)); + module2->SetSSRC(test_ssrc + 1); + module1->SetSSRC(test_ssrc); EXPECT_EQ(0, module1->SetSequenceNumber(test_sequence_number)); EXPECT_EQ(0, module1->SetStartTimestamp(test_timestamp)); EXPECT_EQ(0, module1->SetCSRCs(test_CSRC, 2)); @@ -280,7 +280,6 @@ TEST_F(RtpRtcpRtcpTest, RTCP) { reportBlock.lastSR = 6; // Set report blocks. - EXPECT_EQ(-1, module1->AddRTCPReportBlock(test_CSRC[0], NULL)); EXPECT_EQ(0, module1->AddRTCPReportBlock(test_CSRC[0], &reportBlock)); reportBlock.lastSR= 7; @@ -319,7 +318,6 @@ TEST_F(RtpRtcpRtcpTest, RTCP) { // get all report blocks std::vector report_blocks; - EXPECT_EQ(-1, module1->RemoteRTCPStat(NULL)); EXPECT_EQ(0, module1->RemoteRTCPStat(&report_blocks)); ASSERT_EQ(1u, report_blocks.size()); const RTCPReportBlock& reportBlockReceived = report_blocks[0]; diff --git a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_video.cc b/webrtc/modules/rtp_rtcp/test/testAPI/test_api_video.cc index 6f065d580..94d1e52ef 100644 --- a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_video.cc +++ b/webrtc/modules/rtp_rtcp/test/testAPI/test_api_video.cc @@ -28,7 +28,7 @@ class RtpRtcpVideoTest : public ::testing::Test { protected: RtpRtcpVideoTest() : test_id_(123), - rtp_payload_registry_(0, RTPPayloadStrategy::CreateStrategy(false)), + rtp_payload_registry_(RTPPayloadStrategy::CreateStrategy(false)), test_ssrc_(3456), test_timestamp_(4567), test_sequence_number_(2345), @@ -51,7 +51,7 @@ class RtpRtcpVideoTest : public ::testing::Test { test_id_, &fake_clock, receiver_, NULL, &rtp_payload_registry_)); EXPECT_EQ(0, video_module_->SetRTCPStatus(kRtcpCompound)); - EXPECT_EQ(0, video_module_->SetSSRC(test_ssrc_)); + video_module_->SetSSRC(test_ssrc_); rtp_receiver_->SetNACKStatus(kNackRtcp); EXPECT_EQ(0, video_module_->SetStorePacketsStatus(true, 600)); EXPECT_EQ(0, video_module_->SetSendingStatus(true)); diff --git a/webrtc/modules/rtp_rtcp/test/testFec/OWNERS b/webrtc/modules/rtp_rtcp/test/testFec/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/rtp_rtcp/test/testFec/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc b/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc index 8896b6857..fc11fbeec 100644 --- a/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc +++ b/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc @@ -105,9 +105,7 @@ TEST(FecTest, FecTest) { ASSERT_EQ(12, kMaxMediaPackets[1]) << "Max media packets for bursty mode not " << "equal to 12."; - uint32_t id = 0; - ForwardErrorCorrection fec(id); - + ForwardErrorCorrection fec; ForwardErrorCorrection::PacketList mediaPacketList; ForwardErrorCorrection::PacketList fecPacketList; ForwardErrorCorrection::ReceivedPacketList toDecodeList; diff --git a/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc b/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc index cc1ee84e3..2fb09683e 100644 --- a/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc +++ b/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc @@ -198,7 +198,7 @@ class FecPacketMaskMetricsTest : public ::testing::Test { int RecoveredMediaPackets(int num_media_packets, int num_fec_packets, uint8_t* state) { - scoped_array state_tmp( + scoped_ptr state_tmp( new uint8_t[num_media_packets + num_fec_packets]); memcpy(state_tmp.get(), state, num_media_packets + num_fec_packets); int num_recovered_packets = 0; @@ -392,7 +392,7 @@ class FecPacketMaskMetricsTest : public ::testing::Test { // (which containes the code size parameters/protection length). void ComputeMetricsForCode(CodeType code_type, int code_index) { - scoped_array prob_weight(new double[kNumLossModels]); + scoped_ptr prob_weight(new double[kNumLossModels]); memset(prob_weight.get() , 0, sizeof(double) * kNumLossModels); MetricsFecCode metrics_code; SetMetricsZero(&metrics_code); @@ -400,7 +400,7 @@ class FecPacketMaskMetricsTest : public ::testing::Test { int num_media_packets = code_params_[code_index].num_media_packets; int num_fec_packets = code_params_[code_index].num_fec_packets; int tot_num_packets = num_media_packets + num_fec_packets; - scoped_array state(new uint8_t[tot_num_packets]); + scoped_ptr state(new uint8_t[tot_num_packets]); memset(state.get() , 0, tot_num_packets); int num_loss_configurations = static_cast(pow(2.0f, tot_num_packets)); diff --git a/webrtc/modules/utility/BUILD.gn b/webrtc/modules/utility/BUILD.gn new file mode 100644 index 000000000..825120460 --- /dev/null +++ b/webrtc/modules/utility/BUILD.gn @@ -0,0 +1,48 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("utility") { + sources = [ + "interface/audio_frame_operations.h", + "interface/file_player.h", + "interface/file_recorder.h", + "interface/helpers_android.h", + "interface/process_thread.h", + "interface/rtp_dump.h", + "source/audio_frame_operations.cc", + "source/coder.cc", + "source/coder.h", + "source/file_player_impl.cc", + "source/file_player_impl.h", + "source/file_recorder_impl.cc", + "source/file_recorder_impl.h", + "source/helpers_android.cc", + "source/process_thread_impl.cc", + "source/process_thread_impl.h", + "source/rtp_dump_impl.cc", + "source/rtp_dump_impl.h", + ] + + deps = [ + "../../common_audio", + "../../system_wrappers", + "../audio_coding", + "../media_file", + ] + if (enable_video) { + sources += [ + "source/frame_scaler.cc", + "source/video_coder.cc", + "source/video_frames_queue.cc", + ] + + deps += [ "../video_coding" ] + } +} diff --git a/webrtc/modules/utility/OWNERS b/webrtc/modules/utility/OWNERS index 674c73801..dbdb53e34 100644 --- a/webrtc/modules/utility/OWNERS +++ b/webrtc/modules/utility/OWNERS @@ -1,4 +1,6 @@ henrike@webrtc.org pwestin@webrtc.org asapersson@webrtc.org -perkj@webrtc.org \ No newline at end of file +perkj@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/utility/interface/mock/mock_process_thread.h b/webrtc/modules/utility/interface/mock/mock_process_thread.h new file mode 100644 index 000000000..fc0c1fb1c --- /dev/null +++ b/webrtc/modules/utility/interface/mock/mock_process_thread.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014 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_MODULES_UTILITY_INTERFACE_MOCK_PROCESS_THREAD_H_ +#define WEBRTC_MODULES_UTILITY_INTERFACE_MOCK_PROCESS_THREAD_H_ + +#include "webrtc/modules/utility/interface/process_thread.h" + +#include "testing/gmock/include/gmock/gmock.h" + +namespace webrtc { + +class MockProcessThread : public ProcessThread { + public: + MOCK_METHOD0(Start, int32_t()); + MOCK_METHOD0(Stop, int32_t()); + MOCK_METHOD1(RegisterModule, int32_t(Module* module)); + MOCK_METHOD1(DeRegisterModule, int32_t(const Module* module)); +}; + +} // namespace webrtc +#endif // WEBRTC_MODULES_UTILITY_INTERFACE_MOCK_PROCESS_THREAD_H_ diff --git a/webrtc/modules/utility/source/OWNERS b/webrtc/modules/utility/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/utility/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/utility/source/audio_frame_operations.cc b/webrtc/modules/utility/source/audio_frame_operations.cc index 18dba52b6..e3b001047 100644 --- a/webrtc/modules/utility/source/audio_frame_operations.cc +++ b/webrtc/modules/utility/source/audio_frame_operations.cc @@ -72,7 +72,6 @@ void AudioFrameOperations::SwapStereoChannels(AudioFrame* frame) { void AudioFrameOperations::Mute(AudioFrame& frame) { memset(frame.data_, 0, sizeof(int16_t) * frame.samples_per_channel_ * frame.num_channels_); - frame.energy_ = 0; } int AudioFrameOperations::Scale(float left, float right, AudioFrame& frame) { diff --git a/webrtc/modules/utility/source/audio_frame_operations_unittest.cc b/webrtc/modules/utility/source/audio_frame_operations_unittest.cc index 34c08a89e..f4d881cf8 100644 --- a/webrtc/modules/utility/source/audio_frame_operations_unittest.cc +++ b/webrtc/modules/utility/source/audio_frame_operations_unittest.cc @@ -142,17 +142,13 @@ TEST_F(AudioFrameOperationsTest, SwapStereoChannelsFailsOnMono) { TEST_F(AudioFrameOperationsTest, MuteSucceeds) { SetFrameData(&frame_, 1000, 1000); - frame_.energy_ = 1000 * 1000 * frame_.samples_per_channel_ * - frame_.num_channels_; AudioFrameOperations::Mute(frame_); AudioFrame muted_frame; muted_frame.samples_per_channel_ = 320; muted_frame.num_channels_ = 2; SetFrameData(&muted_frame, 0, 0); - muted_frame.energy_ = 0; VerifyFramesAreEqual(muted_frame, frame_); - EXPECT_EQ(muted_frame.energy_, frame_.energy_); } // TODO(andrew): should not allow negative scales. diff --git a/webrtc/modules/utility/source/file_player_impl.cc b/webrtc/modules/utility/source/file_player_impl.cc index 9240e6469..8049245fb 100644 --- a/webrtc/modules/utility/source/file_player_impl.cc +++ b/webrtc/modules/utility/source/file_player_impl.cc @@ -9,7 +9,7 @@ */ #include "webrtc/modules/utility/source/file_player_impl.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #ifdef WEBRTC_MODULE_UTILITY_VIDEO #include "frame_scaler.h" @@ -35,8 +35,6 @@ FilePlayer* FilePlayer::CreateFilePlayer(uint32_t instanceID, #ifdef WEBRTC_MODULE_UTILITY_VIDEO return new VideoFilePlayerImpl(instanceID, fileFormat); #else - WEBRTC_TRACE(kTraceError, kTraceFile, -1, - "Invalid file format: %d", kFileFormatAviFile); assert(false); return NULL; #endif @@ -114,10 +112,9 @@ int32_t FilePlayerImpl::Get10msAudioFromFile( { if(_codec.plfreq == 0) { - WEBRTC_TRACE(kTraceWarning, kTraceVoice, _instanceID, - "FilePlayerImpl::Get10msAudioFromFile() playing not started!\ - codecFreq = %d, wantedFreq = %d", - _codec.plfreq, frequencyInHz); + LOG(LS_WARNING) << "Get10msAudioFromFile() playing not started!" + << " codec freq = " << _codec.plfreq + << ", wanted freq = " << frequencyInHz; return -1; } @@ -175,8 +172,7 @@ int32_t FilePlayerImpl::Get10msAudioFromFile( if(_resampler.ResetIfNeeded(unresampledAudioFrame.sample_rate_hz_, frequencyInHz, kResamplerSynchronous)) { - WEBRTC_TRACE(kTraceWarning, kTraceVoice, _instanceID, - "FilePlayerImpl::Get10msAudioFromFile() unexpected codec"); + LOG(LS_WARNING) << "Get10msAudioFromFile() unexpected codec."; // New sampling frequency. Update state. outLen = frequencyInHz / 100; @@ -214,8 +210,7 @@ int32_t FilePlayerImpl::SetAudioScaling(float scaleFactor) _scaling = scaleFactor; return 0; } - WEBRTC_TRACE(kTraceWarning, kTraceVoice, _instanceID, - "FilePlayerImpl::SetAudioScaling() not allowed scale factor"); + LOG(LS_WARNING) << "SetAudioScaling() non-allowed scale factor."; return -1; } @@ -255,9 +250,8 @@ int32_t FilePlayerImpl::StartPlayingFile(const char* fileName, codecInstL16.pacsize = 160; } else { - WEBRTC_TRACE(kTraceError, kTraceVoice, _instanceID, - "FilePlayerImpl::StartPlayingFile() sample frequency\ - specifed not supported for PCM format."); + LOG(LS_ERROR) << "StartPlayingFile() sample frequency not " + << "supported for PCM format."; return -1; } @@ -266,12 +260,8 @@ int32_t FilePlayerImpl::StartPlayingFile(const char* fileName, startPosition, stopPosition) == -1) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingFile() failed to initialize file\ - %s playout.", fileName); + LOG(LS_WARNING) << "StartPlayingFile() failed to initialize " + << "pcm file " << fileName; return -1; } SetAudioScaling(volumeScaling); @@ -280,13 +270,8 @@ int32_t FilePlayerImpl::StartPlayingFile(const char* fileName, if (_fileModule.StartPlayingAudioFile(fileName, notification, loop, _fileFormat, codecInst) == -1) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingPreEncodedFile() failed to\ - initialize pre-encoded file %s playout.", - fileName); + LOG(LS_WARNING) << "StartPlayingFile() failed to initialize " + << "pre-encoded file " << fileName; return -1; } } else @@ -297,12 +282,8 @@ int32_t FilePlayerImpl::StartPlayingFile(const char* fileName, startPosition, stopPosition) == -1) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingFile() failed to initialize file\ - %s playout.", fileName); + LOG(LS_WARNING) << "StartPlayingFile() failed to initialize file " + << fileName; return -1; } SetAudioScaling(volumeScaling); @@ -350,12 +331,8 @@ int32_t FilePlayerImpl::StartPlayingFile(InStream& sourceStream, codecInstL16.pacsize = 160; }else { - WEBRTC_TRACE( - kTraceError, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingFile() sample frequency specifed\ - not supported for PCM format."); + LOG(LS_ERROR) << "StartPlayingFile() sample frequency not " + << "supported for PCM format."; return -1; } if (_fileModule.StartPlayingAudioStream(sourceStream, notification, @@ -363,12 +340,8 @@ int32_t FilePlayerImpl::StartPlayingFile(InStream& sourceStream, startPosition, stopPosition) == -1) { - WEBRTC_TRACE( - kTraceError, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingFile() failed to initialize stream\ - playout."); + LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream " + << "playout."; return -1; } @@ -377,12 +350,8 @@ int32_t FilePlayerImpl::StartPlayingFile(InStream& sourceStream, if (_fileModule.StartPlayingAudioStream(sourceStream, notification, _fileFormat, codecInst) == -1) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingFile() failed to initialize stream\ - playout."); + LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream " + << "playout."; return -1; } } else { @@ -392,9 +361,8 @@ int32_t FilePlayerImpl::StartPlayingFile(InStream& sourceStream, startPosition, stopPosition) == -1) { - WEBRTC_TRACE(kTraceError, kTraceVoice, _instanceID, - "FilePlayerImpl::StartPlayingFile() failed to initialize\ - stream playout."); + LOG(LS_ERROR) << "StartPlayingFile() failed to initialize stream " + << "playout."; return -1; } } @@ -430,23 +398,14 @@ int32_t FilePlayerImpl::SetUpAudioDecoder() { if ((_fileModule.codec_info(_codec) == -1)) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingFile() failed to retrieve Codec info\ - of file data."); + LOG(LS_WARNING) << "Failed to retrieve codec info of file data."; return -1; } if( STR_CASE_CMP(_codec.plname, "L16") != 0 && _audioDecoder.SetDecodeCodec(_codec,AMRFileStorage) == -1) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FilePlayerImpl::StartPlayingFile() codec %s not supported", - _codec.plname); + LOG(LS_WARNING) << "SetUpAudioDecoder() codec " << _codec.plname + << " not supported."; return -1; } _numberOf10MsPerFrame = _codec.pacsize / (_codec.plfreq / 100); @@ -458,7 +417,7 @@ int32_t FilePlayerImpl::SetUpAudioDecoder() VideoFilePlayerImpl::VideoFilePlayerImpl(uint32_t instanceID, FileFormats fileFormat) : FilePlayerImpl(instanceID, fileFormat), - video_decoder_(new VideoCoder(instanceID)), + video_decoder_(new VideoCoder()), video_codec_info_(), _decodedVideoFrames(0), _encodedData(*new EncodedVideoData()), @@ -522,7 +481,7 @@ int32_t VideoFilePlayerImpl::StopPlayingFile() CriticalSectionScoped lock( _critSec); _decodedVideoFrames = 0; - video_decoder_.reset(new VideoCoder(_instanceID)); + video_decoder_.reset(new VideoCoder()); return FilePlayerImpl::StopPlayingFile(); } @@ -627,12 +586,7 @@ int32_t VideoFilePlayerImpl::TimeUntilNextVideoFrame() reinterpret_cast< int8_t*>(_encodedData.payloadData), encodedBufferLengthInBytes) != 0) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVideo, - _instanceID, - "FilePlayerImpl::TimeUntilNextVideoFrame() error reading\ - video data"); + LOG(LS_WARNING) << "Error reading video data."; return -1; } _encodedData.payloadSize = encodedBufferLengthInBytes; @@ -685,23 +639,16 @@ int32_t VideoFilePlayerImpl::SetUpVideoDecoder() { if (_fileModule.VideoCodecInst(video_codec_info_) != 0) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVideo, - _instanceID, - "FilePlayerImpl::SetVideoDecoder() failed to retrieve Codec info of\ - file data."); + LOG(LS_WARNING) << "SetVideoDecoder() failed to retrieve codec info of " + << "file data."; return -1; } int32_t useNumberOfCores = 1; if (video_decoder_->SetDecodeCodec(video_codec_info_, useNumberOfCores) != 0) { - WEBRTC_TRACE(kTraceWarning, - kTraceVideo, - _instanceID, - "FilePlayerImpl::SetUpVideoDecoder() codec %s not supported", - video_codec_info_.plName); + LOG(LS_WARNING) << "SetUpVideoDecoder() codec " + << video_codec_info_.plName << " not supported."; return -1; } diff --git a/webrtc/modules/utility/source/file_player_unittests.cc b/webrtc/modules/utility/source/file_player_unittests.cc new file mode 100644 index 000000000..d430d9f59 --- /dev/null +++ b/webrtc/modules/utility/source/file_player_unittests.cc @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014 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. + */ + +// Unit tests for FilePlayer. + +#include "webrtc/modules/utility/interface/file_player.h" + +#include +#include + +#include "gflags/gflags.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/md5digest.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/test/testsupport/fileutils.h" + +DEFINE_bool(file_player_output, false, "Generate reference files."); + +namespace webrtc { + +class FilePlayerTest : public ::testing::Test { + protected: + static const uint32_t kId = 0; + static const FileFormats kFileFormat = kFileFormatWavFile; + static const int kSampleRateHz = 8000; + + FilePlayerTest() + : player_(FilePlayer::CreateFilePlayer(kId, kFileFormat)), + output_file_(NULL) {} + + virtual void SetUp() OVERRIDE { + if (FLAGS_file_player_output) { + std::string output_file = + webrtc::test::OutputPath() + "file_player_unittest_out.pcm"; + output_file_ = fopen(output_file.c_str(), "wb"); + ASSERT_TRUE(output_file_ != NULL); + } + } + + virtual void TearDown() OVERRIDE { + if (output_file_) + fclose(output_file_); + } + + ~FilePlayerTest() { FilePlayer::DestroyFilePlayer(player_); } + + void PlayFileAndCheck(const std::string& input_file, + const std::string& ref_checksum, + int output_length_ms) { + const float kScaling = 1; + ASSERT_EQ(0, + player_->StartPlayingFile( + input_file.c_str(), false, 0, kScaling, 0, 0, NULL)); + rtc::Md5Digest checksum; + for (int i = 0; i < output_length_ms / 10; ++i) { + int16_t out[10 * kSampleRateHz / 1000] = {0}; + int num_samples; + EXPECT_EQ(0, + player_->Get10msAudioFromFile(out, num_samples, kSampleRateHz)); + checksum.Update(out, num_samples * sizeof(out[0])); + if (FLAGS_file_player_output) { + ASSERT_EQ(static_cast(num_samples), + fwrite(out, sizeof(out[0]), num_samples, output_file_)); + } + } + char checksum_result[rtc::Md5Digest::kSize]; + EXPECT_EQ(rtc::Md5Digest::kSize, + checksum.Finish(checksum_result, rtc::Md5Digest::kSize)); + EXPECT_EQ(ref_checksum, + rtc::hex_encode(checksum_result, sizeof(checksum_result))); + } + + FilePlayer* player_; + FILE* output_file_; +}; + +TEST_F(FilePlayerTest, PlayWavPcmuFile) { + const std::string kFileName = + test::ResourcePath("utility/encapsulated_pcmu_8khz", "wav"); + // The file is longer than this, but keeping the output shorter limits the + // runtime for the test. + const int kOutputLengthMs = 10000; + const std::string kRefChecksum = "c74e7fd432d439b1311e1c16815b3e9a"; + + PlayFileAndCheck(kFileName, kRefChecksum, kOutputLengthMs); +} + +TEST_F(FilePlayerTest, PlayWavPcm16File) { + const std::string kFileName = + test::ResourcePath("utility/encapsulated_pcm16b_8khz", "wav"); + // The file is longer than this, but keeping the output shorter limits the + // runtime for the test. + const int kOutputLengthMs = 10000; + const std::string kRefChecksum = "e41d7e1dac8aeae9f21e8e03cd7ecd71"; + + PlayFileAndCheck(kFileName, kRefChecksum, kOutputLengthMs); +} + +} // namespace webrtc diff --git a/webrtc/modules/utility/source/file_recorder_impl.cc b/webrtc/modules/utility/source/file_recorder_impl.cc index 7e13c369e..264b867a2 100644 --- a/webrtc/modules/utility/source/file_recorder_impl.cc +++ b/webrtc/modules/utility/source/file_recorder_impl.cc @@ -12,7 +12,7 @@ #include "webrtc/engine_configurations.h" #include "webrtc/modules/media_file/interface/media_file.h" #include "webrtc/modules/utility/source/file_recorder_impl.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #ifdef WEBRTC_MODULE_UTILITY_VIDEO #include "critical_section_wrapper.h" @@ -38,8 +38,6 @@ FileRecorder* FileRecorder::CreateFileRecorder(uint32_t instanceID, #ifdef WEBRTC_MODULE_UTILITY_VIDEO return new AviRecorder(instanceID, fileFormat); #else - WEBRTC_TRACE(kTraceError, kTraceFile, -1, - "Invalid file format: %d", kFileFormatAviFile); assert(false); return NULL; #endif @@ -115,13 +113,8 @@ int32_t FileRecorderImpl::StartRecordingAudioFile( } if( retVal != 0) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FileRecorder::StartRecording() failed to initialize file %s for\ - recording.", - fileName); + LOG(LS_WARNING) << "Failed to initialize file " << fileName + << " for recording."; if(IsRecording()) { @@ -152,12 +145,7 @@ int32_t FileRecorderImpl::StartRecordingAudioFile( } if( retVal != 0) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FileRecorder::StartRecording() failed to initialize outStream for\ - recording."); + LOG(LS_WARNING) << "Failed to initialize outStream for recording."; if(IsRecording()) { @@ -184,12 +172,8 @@ int32_t FileRecorderImpl::RecordAudioToFile( { if (codec_info_.plfreq == 0) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FileRecorder::RecordAudioToFile() recording audio is not turned\ - on"); + LOG(LS_WARNING) << "RecordAudioToFile() recording audio is not " + << "turned on."; return -1; } AudioFrame tempAudioFrame; @@ -250,13 +234,9 @@ int32_t FileRecorderImpl::RecordAudioToFile( if (_audioEncoder.Encode(*ptrAudioFrame, _audioBuffer, encodedLenInBytes) == -1) { - WEBRTC_TRACE( - kTraceWarning, - kTraceVoice, - _instanceID, - "FileRecorder::RecordAudioToFile() codec %s not supported or\ - failed to encode stream", - codec_info_.plname); + LOG(LS_WARNING) << "RecordAudioToFile() codec " + << codec_info_.plname + << " not supported or failed to encode stream."; return -1; } } else { @@ -309,12 +289,8 @@ int32_t FileRecorderImpl::SetUpAudioEncoder() { if(_audioEncoder.SetEncodeCodec(codec_info_,_amrFormat) == -1) { - WEBRTC_TRACE( - kTraceError, - kTraceVoice, - _instanceID, - "FileRecorder::StartRecording() codec %s not supported", - codec_info_.plname); + LOG(LS_ERROR) << "SetUpAudioEncoder() codec " + << codec_info_.plname << " not supported."; return -1; } } @@ -352,7 +328,7 @@ AviRecorder::AviRecorder(uint32_t instanceID, FileFormats fileFormat) _writtenAudioMS(0), _writtenVideoMS(0) { - _videoEncoder = new VideoCoder(instanceID); + _videoEncoder = new VideoCoder(); _frameScaler = new FrameScaler(); _videoFramesQueue = new VideoFramesQueue(); _thread = ThreadWrapper::CreateThread(Run, this, kNormalPriority, @@ -600,8 +576,8 @@ bool AviRecorder::Process() error = EncodeAndWriteVideoToFile( *frameToProcess); if( error != 0) { - WEBRTC_TRACE(kTraceError, kTraceVideo, _instanceID, - "AviRecorder::Process() error writing to file."); + LOG(LS_ERROR) << "AviRecorder::Process() error writing to " + << "file."; break; } else { uint32_t frameLengthMS = 1000 / @@ -640,8 +616,7 @@ bool AviRecorder::Process() error = EncodeAndWriteVideoToFile( *frameToProcess); if(error != 0) { - WEBRTC_TRACE(kTraceError, kTraceVideo, _instanceID, - "AviRecorder::Process() error writing to file."); + LOG(LS_ERROR) << "AviRecorder::Process() error writing to file."; } else { _writtenVideoMS += frameLengthMS; } @@ -692,17 +667,12 @@ int32_t AviRecorder::EncodeAndWriteVideoToFile(I420VideoFrame& videoFrame) (int8_t*)(_videoEncodedData.payloadData), _videoEncodedData.payloadSize)) { - WEBRTC_TRACE(kTraceError, kTraceVideo, _instanceID, - "Error writing AVI file"); + LOG(LS_ERROR) << "Error writing AVI file."; return -1; } } else { - WEBRTC_TRACE( - kTraceError, - kTraceVideo, - _instanceID, - "FileRecorder::RecordVideoToFile() frame dropped by encoder bitrate\ - likely to low."); + LOG(LS_ERROR) << "FileRecorder::RecordVideoToFile() frame dropped by " + << "encoder, bitrate likely too low."; } return 0; } diff --git a/webrtc/modules/utility/source/frame_scaler.cc b/webrtc/modules/utility/source/frame_scaler.cc index ed127a671..50ccf8adc 100644 --- a/webrtc/modules/utility/source/frame_scaler.cc +++ b/webrtc/modules/utility/source/frame_scaler.cc @@ -13,7 +13,6 @@ #ifdef WEBRTC_MODULE_UTILITY_VIDEO #include "webrtc/common_video/libyuv/include/scaler.h" -#include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { diff --git a/webrtc/modules/utility/source/process_thread_impl.cc b/webrtc/modules/utility/source/process_thread_impl.cc index dd5c42cf4..bf7db3bc8 100644 --- a/webrtc/modules/utility/source/process_thread_impl.cc +++ b/webrtc/modules/utility/source/process_thread_impl.cc @@ -10,7 +10,7 @@ #include "webrtc/modules/interface/module.h" #include "webrtc/modules/utility/source/process_thread_impl.h" -#include "webrtc/system_wrappers/interface/trace.h" + namespace webrtc { ProcessThread::~ProcessThread() @@ -32,14 +32,12 @@ ProcessThreadImpl::ProcessThreadImpl() _critSectModules(CriticalSectionWrapper::CreateCriticalSection()), _thread(NULL) { - WEBRTC_TRACE(kTraceMemory, kTraceUtility, -1, "%s created", __FUNCTION__); } ProcessThreadImpl::~ProcessThreadImpl() { delete _critSectModules; delete &_timeEvent; - WEBRTC_TRACE(kTraceMemory, kTraceUtility, -1, "%s deleted", __FUNCTION__); } int32_t ProcessThreadImpl::Start() @@ -101,9 +99,7 @@ int32_t ProcessThreadImpl::RegisterModule(Module* module) } _modules.push_front(module); - WEBRTC_TRACE(kTraceInfo, kTraceUtility, -1, - "number of registered modules has increased to %d", - _modules.size()); + // Wake the thread calling ProcessThreadImpl::Process() to update the // waiting time. The waiting time for the just registered module may be // shorter than all other registered modules. @@ -119,9 +115,6 @@ int32_t ProcessThreadImpl::DeRegisterModule(const Module* module) if(module == *iter) { _modules.erase(iter); - WEBRTC_TRACE(kTraceInfo, kTraceUtility, -1, - "number of registered modules has decreased to %d", - _modules.size()); return 0; } } diff --git a/webrtc/modules/utility/source/rtp_dump_impl.cc b/webrtc/modules/utility/source/rtp_dump_impl.cc index 39316f478..547df332f 100644 --- a/webrtc/modules/utility/source/rtp_dump_impl.cc +++ b/webrtc/modules/utility/source/rtp_dump_impl.cc @@ -14,7 +14,7 @@ #include #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #if defined(_WIN32) #include @@ -71,7 +71,6 @@ RtpDumpImpl::RtpDumpImpl() _file(*FileWrapper::Create()), _startTime(0) { - WEBRTC_TRACE(kTraceMemory, kTraceUtility, -1, "%s created", __FUNCTION__); } RtpDump::~RtpDump() @@ -84,7 +83,6 @@ RtpDumpImpl::~RtpDumpImpl() _file.CloseFile(); delete &_file; delete _critSect; - WEBRTC_TRACE(kTraceMemory, kTraceUtility, -1, "%s deleted", __FUNCTION__); } int32_t RtpDumpImpl::Start(const char* fileNameUTF8) @@ -100,8 +98,7 @@ int32_t RtpDumpImpl::Start(const char* fileNameUTF8) _file.CloseFile(); if (_file.OpenFile(fileNameUTF8, false, false, false) == -1) { - WEBRTC_TRACE(kTraceError, kTraceUtility, -1, - "failed to open the specified file"); + LOG(LS_ERROR) << "Failed to open file."; return -1; } @@ -113,8 +110,7 @@ int32_t RtpDumpImpl::Start(const char* fileNameUTF8) sprintf(magic, "#!rtpplay%s \n", RTPFILE_VERSION); if (_file.WriteText(magic) == -1) { - WEBRTC_TRACE(kTraceError, kTraceUtility, -1, - "error writing to file"); + LOG(LS_ERROR) << "Error writing to file."; return -1; } @@ -129,8 +125,7 @@ int32_t RtpDumpImpl::Start(const char* fileNameUTF8) memset(dummyHdr, 0, 16); if (!_file.Write(dummyHdr, sizeof(dummyHdr))) { - WEBRTC_TRACE(kTraceError, kTraceUtility, -1, - "error writing to file"); + LOG(LS_ERROR) << "Error writing to file."; return -1; } return 0; @@ -198,14 +193,12 @@ int32_t RtpDumpImpl::DumpPacket(const uint8_t* packet, uint16_t packetLength) if (!_file.Write(&hdr, sizeof(hdr))) { - WEBRTC_TRACE(kTraceError, kTraceUtility, -1, - "error writing to file"); + LOG(LS_ERROR) << "Error writing to file."; return -1; } if (!_file.Write(packet, packetLength)) { - WEBRTC_TRACE(kTraceError, kTraceUtility, -1, - "error writing to file"); + LOG(LS_ERROR) << "Error writing to file."; return -1; } diff --git a/webrtc/modules/utility/source/video_coder.cc b/webrtc/modules/utility/source/video_coder.cc index 267ed8104..5096acecd 100644 --- a/webrtc/modules/utility/source/video_coder.cc +++ b/webrtc/modules/utility/source/video_coder.cc @@ -13,10 +13,7 @@ #include "webrtc/modules/utility/source/video_coder.h" namespace webrtc { -VideoCoder::VideoCoder(uint32_t instanceID) - : _vcm(VideoCodingModule::Create(instanceID)), - _decodedVideo(0) -{ +VideoCoder::VideoCoder() : _vcm(VideoCodingModule::Create()), _decodedVideo(0) { _vcm->InitializeSender(); _vcm->InitializeReceiver(); diff --git a/webrtc/modules/utility/source/video_coder.h b/webrtc/modules/utility/source/video_coder.h index cb8bfa5a1..8e4344be1 100644 --- a/webrtc/modules/utility/source/video_coder.h +++ b/webrtc/modules/utility/source/video_coder.h @@ -20,7 +20,7 @@ namespace webrtc { class VideoCoder : public VCMPacketizationCallback, public VCMReceiveCallback { public: - VideoCoder(uint32_t instanceID); + VideoCoder(); ~VideoCoder(); int32_t SetEncodeCodec(VideoCodec& videoCodecInst, diff --git a/webrtc/modules/utility/source/video_frames_queue.cc b/webrtc/modules/utility/source/video_frames_queue.cc index 53b446501..9ade8b51a 100644 --- a/webrtc/modules/utility/source/video_frames_queue.cc +++ b/webrtc/modules/utility/source/video_frames_queue.cc @@ -16,8 +16,8 @@ #include "webrtc/common_video/interface/texture_video_frame.h" #include "webrtc/modules/interface/module_common_types.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/tick_util.h" -#include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { VideoFramesQueue::VideoFramesQueue() @@ -38,12 +38,7 @@ VideoFramesQueue::~VideoFramesQueue() { int32_t VideoFramesQueue::AddFrame(const I420VideoFrame& newFrame) { if (newFrame.native_handle() != NULL) { - _incomingFrames.push_back(new TextureVideoFrame( - static_cast(newFrame.native_handle()), - newFrame.width(), - newFrame.height(), - newFrame.timestamp(), - newFrame.render_time_ms())); + _incomingFrames.push_back(newFrame.CloneFrame()); return 0; } @@ -56,16 +51,9 @@ int32_t VideoFramesQueue::AddFrame(const I420VideoFrame& newFrame) { if (!ptrFrameToAdd) { if (_emptyFrames.size() + _incomingFrames.size() > KMaxNumberOfFrames) { - WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, -1, - "%s: too many frames, limit: %d", __FUNCTION__, - KMaxNumberOfFrames); + LOG(LS_WARNING) << "Too many frames, limit: " << KMaxNumberOfFrames; return -1; } - - WEBRTC_TRACE(kTraceMemory, kTraceVideoRenderer, -1, - "%s: allocating buffer %d", __FUNCTION__, - _emptyFrames.size() + _incomingFrames.size()); - ptrFrameToAdd = new I420VideoFrame(); } ptrFrameToAdd->CopyFrame(newFrame); @@ -87,8 +75,10 @@ I420VideoFrame* VideoFramesQueue::FrameToRecord() { // List is traversed beginning to end. If ptrRenderFrame is not // NULL it must be the first, and thus oldest, VideoFrame in the // queue. It can be recycled. - ReturnFrame(ptrRenderFrame); - iter = _incomingFrames.erase(iter); + if (ptrRenderFrame) { + ReturnFrame(ptrRenderFrame); + _incomingFrames.pop_front(); + } ptrRenderFrame = ptrOldestFrameInList; } else { // All VideoFrames following this one will be even newer. No match diff --git a/webrtc/modules/video_capture/BUILD.gn b/webrtc/modules/video_capture/BUILD.gn new file mode 100644 index 000000000..fd3fe574d --- /dev/null +++ b/webrtc/modules/video_capture/BUILD.gn @@ -0,0 +1,13 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("video_capture") { + # TODO(glaznev): Implement. +} diff --git a/webrtc/modules/video_capture/OWNERS b/webrtc/modules/video_capture/OWNERS index 3b02126fe..ba8976508 100644 --- a/webrtc/modules/video_capture/OWNERS +++ b/webrtc/modules/video_capture/OWNERS @@ -1,5 +1,15 @@ fischman@webrtc.org +glaznev@webrtc.org mallinath@webrtc.org mflodman@webrtc.org perkj@webrtc.org wu@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/video_capture/android/device_info_android.cc b/webrtc/modules/video_capture/android/device_info_android.cc index 10c277eeb..4a80fe272 100644 --- a/webrtc/modules/video_capture/android/device_info_android.cc +++ b/webrtc/modules/video_capture/android/device_info_android.cc @@ -21,36 +21,40 @@ #include "webrtc/system_wrappers/interface/ref_count.h" #include "webrtc/system_wrappers/interface/trace.h" -namespace webrtc -{ +namespace webrtc { -namespace videocapturemodule -{ +namespace videocapturemodule { -static std::string ResolutionsToString( - const std::vector >& pairs) { +// Helper for storing lists of pairs of ints. Used e.g. for resolutions & FPS +// ranges. +typedef std::pair IntPair; +typedef std::vector IntPairs; + +static std::string IntPairsToString(const IntPairs& pairs, char separator) { std::stringstream stream; for (size_t i = 0; i < pairs.size(); ++i) { if (i > 0) stream << ", "; - stream << "(" << pairs[i].first << "x" << pairs[i].second << ")"; + stream << "(" << pairs[i].first << separator << pairs[i].second << ")"; } return stream.str(); } struct AndroidCameraInfo { std::string name; - int min_mfps, max_mfps; // FPS*1000. bool front_facing; int orientation; - std::vector > resolutions; // Pairs are: (width,height). + IntPairs resolutions; // Pairs are: (width,height). + // Pairs are (min,max) in units of FPS*1000 ("milli-frame-per-second"). + IntPairs mfpsRanges; std::string ToString() { std::stringstream stream; - stream << "Name: [" << name << "], mfps: [" << min_mfps << ":" << max_mfps + stream << "Name: [" << name << "], MFPS ranges: [" + << IntPairsToString(mfpsRanges, ':') << "], front_facing: " << front_facing << ", orientation: " << orientation << ", resolutions: [" - << ResolutionsToString(resolutions) << "]"; + << IntPairsToString(resolutions, 'x') << "]"; return stream.str(); } }; @@ -120,8 +124,6 @@ void DeviceInfoAndroid::Initialize(JNIEnv* jni) { const Json::Value& camera = cameras[i]; AndroidCameraInfo info; info.name = camera["name"].asString(); - info.min_mfps = camera["min_mfps"].asInt(); - info.max_mfps = camera["max_mfps"].asInt(); info.front_facing = camera["front_facing"].asBool(); info.orientation = camera["orientation"].asInt(); Json::Value sizes = camera["sizes"]; @@ -130,10 +132,23 @@ void DeviceInfoAndroid::Initialize(JNIEnv* jni) { info.resolutions.push_back(std::make_pair( size["width"].asInt(), size["height"].asInt())); } + Json::Value mfpsRanges = camera["mfpsRanges"]; + for (Json::ArrayIndex j = 0; j < mfpsRanges.size(); ++j) { + const Json::Value& mfpsRange = mfpsRanges[j]; + info.mfpsRanges.push_back(std::make_pair(mfpsRange["min_mfps"].asInt(), + mfpsRange["max_mfps"].asInt())); + } g_camera_info->push_back(info); } } +void DeviceInfoAndroid::DeInitialize() { + if (g_camera_info) { + delete g_camera_info; + g_camera_info = NULL; + } +} + VideoCaptureModule::DeviceInfo* VideoCaptureImpl::CreateDeviceInfo( const int32_t id) { return new videocapturemodule::DeviceInfoAndroid(id); @@ -187,14 +202,17 @@ int32_t DeviceInfoAndroid::CreateCapabilityMap( return -1; for (size_t i = 0; i < info->resolutions.size(); ++i) { - const std::pair& size = info->resolutions[i]; - VideoCaptureCapability cap; - cap.width = size.first; - cap.height = size.second; - cap.maxFPS = info->max_mfps / 1000; - cap.expectedCaptureDelay = kExpectedCaptureDelay; - cap.rawType = kVideoNV21; - _captureCapabilities.push_back(cap); + for (size_t j = 0; j < info->mfpsRanges.size(); ++j) { + const IntPair& size = info->resolutions[i]; + const IntPair& mfpsRange = info->mfpsRanges[j]; + VideoCaptureCapability cap; + cap.width = size.first; + cap.height = size.second; + cap.maxFPS = mfpsRange.second / 1000; + cap.expectedCaptureDelay = kExpectedCaptureDelay; + cap.rawType = kVideoNV21; + _captureCapabilities.push_back(cap); + } } return _captureCapabilities.size(); } @@ -210,13 +228,22 @@ int32_t DeviceInfoAndroid::GetOrientation( return 0; } -void DeviceInfoAndroid::GetFpsRange(const char* deviceUniqueIdUTF8, - int* min_mfps, int* max_mfps) { +void DeviceInfoAndroid::GetMFpsRange(const char* deviceUniqueIdUTF8, + int max_fps_to_match, + int* min_mfps, int* max_mfps) { const AndroidCameraInfo* info = FindCameraInfoByName(deviceUniqueIdUTF8); if (info == NULL) return; - *min_mfps = info->min_mfps; - *max_mfps = info->max_mfps; + // Rely on CameraParameters.getSupportedPreviewFpsRange() to sort its return + // value (per its documentation) and return the first (most flexible) range + // whose high end is at least as high as that requested. + for (size_t i = 0; i < info->mfpsRanges.size(); ++i) { + if (info->mfpsRanges[i].second / 1000 >= max_fps_to_match) { + *min_mfps = info->mfpsRanges[i].first; + *max_mfps = info->mfpsRanges[i].second; + return; + } + } } } // namespace videocapturemodule diff --git a/webrtc/modules/video_capture/android/device_info_android.h b/webrtc/modules/video_capture/android/device_info_android.h index d277113e5..542cbba08 100644 --- a/webrtc/modules/video_capture/android/device_info_android.h +++ b/webrtc/modules/video_capture/android/device_info_android.h @@ -24,6 +24,7 @@ namespace videocapturemodule class DeviceInfoAndroid : public DeviceInfoImpl { public: static void Initialize(JNIEnv* env); + static void DeInitialize(); DeviceInfoAndroid(int32_t id); virtual ~DeviceInfoAndroid(); @@ -53,10 +54,12 @@ class DeviceInfoAndroid : public DeviceInfoImpl { virtual int32_t GetOrientation(const char* deviceUniqueIdUTF8, VideoCaptureRotation& orientation); - // Populate |min_mfps| and |max_mfps| with the supported range of the device. - void GetFpsRange(const char* deviceUniqueIdUTF8, - int* min_mfps, - int* max_mfps); + // Populate |min_mfps| and |max_mfps| with the closest supported range of the + // device to |max_fps_to_match|. + void GetMFpsRange(const char* deviceUniqueIdUTF8, + int max_fps_to_match, + int* min_mfps, + int* max_mfps); private: enum { kExpectedCaptureDelay = 190}; diff --git a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java index dfe63adfe..2f571d0ca 100644 --- a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java +++ b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java @@ -11,19 +11,22 @@ package org.webrtc.videoengine; import java.io.IOException; -import java.util.Locale; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.Exchanger; +import android.content.Context; import android.graphics.ImageFormat; -import android.graphics.PixelFormat; -import android.graphics.Rect; import android.graphics.SurfaceTexture; -import android.graphics.YuvImage; -import android.hardware.Camera; import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; import android.util.Log; -import android.view.SurfaceHolder; +import android.view.OrientationEventListener; import android.view.SurfaceHolder.Callback; +import android.view.SurfaceHolder; // Wrapper for android Camera, with support for direct local preview rendering. // Threading notes: this class is called from ViE C++ code, and from Camera & @@ -37,38 +40,110 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { private final static String TAG = "WEBRTC-JC"; + private static SurfaceHolder localPreview; private Camera camera; // Only non-null while capturing. + private CameraThread cameraThread; + private Handler cameraThreadHandler; private final int id; private final Camera.CameraInfo info; + private final OrientationEventListener orientationListener; private final long native_capturer; // |VideoCaptureAndroid*| in C++. - private SurfaceHolder localPreview; - private SurfaceTexture dummySurfaceTexture; + private SurfaceTexture cameraSurfaceTexture; + private int[] cameraGlTextures = null; // Arbitrary queue depth. Higher number means more memory allocated & held, // lower number means more sensitivity to processing time in the client (and // potentially stalling the capturer if it runs out of buffers to write to). private final int numCaptureBuffers = 3; + private double averageDurationMs; + private long lastCaptureTimeMs; + private int frameCount; + + // Requests future capturers to send their frames to |localPreview| directly. + public static void setLocalPreview(SurfaceHolder localPreview) { + // It is a gross hack that this is a class-static. Doing it right would + // mean plumbing this through the C++ API and using it from + // webrtc/examples/android/media_demo's MediaEngine class. + VideoCaptureAndroid.localPreview = localPreview; + } public VideoCaptureAndroid(int id, long native_capturer) { this.id = id; this.native_capturer = native_capturer; this.info = new Camera.CameraInfo(); Camera.getCameraInfo(id, info); + + // Must be the last thing in the ctor since we pass a reference to |this|! + final VideoCaptureAndroid self = this; + orientationListener = new OrientationEventListener(GetContext()) { + @Override public void onOrientationChanged(int degrees) { + if (degrees == OrientationEventListener.ORIENTATION_UNKNOWN) { + return; + } + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + degrees = (info.orientation - degrees + 360) % 360; + } else { // back-facing + degrees = (info.orientation + degrees) % 360; + } + self.OnOrientationChanged(self.native_capturer, degrees); + } + }; + // Don't add any code here; see the comment above |self| above! + } + + // Return the global application context. + private static native Context GetContext(); + // Request frame rotation post-capture. + private native void OnOrientationChanged(long captureObject, int degrees); + + private class CameraThread extends Thread { + private Exchanger handlerExchanger; + public CameraThread(Exchanger handlerExchanger) { + this.handlerExchanger = handlerExchanger; + } + + @Override public void run() { + Looper.prepare(); + exchange(handlerExchanger, new Handler()); + Looper.loop(); + } } // Called by native code. Returns true if capturer is started. // - // Note that this actually opens the camera, which can be a slow operation and - // thus might be done on a background thread, but ViE API needs a - // synchronous success return value so we can't do that. + // Note that this actually opens the camera, and Camera callbacks run on the + // thread that calls open(), so this is done on the CameraThread. Since ViE + // API needs a synchronous success return value we wait for the result. private synchronized boolean startCapture( - int width, int height, int min_mfps, int max_mfps) { + final int width, final int height, + final int min_mfps, final int max_mfps) { Log.d(TAG, "startCapture: " + width + "x" + height + "@" + min_mfps + ":" + max_mfps); + if (cameraThread != null || cameraThreadHandler != null) { + throw new RuntimeException("Camera thread already started!"); + } + Exchanger handlerExchanger = new Exchanger(); + cameraThread = new CameraThread(handlerExchanger); + cameraThread.start(); + cameraThreadHandler = exchange(handlerExchanger, null); + + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + startCaptureOnCameraThread(width, height, min_mfps, max_mfps, result); + } + }); + boolean startResult = exchange(result, false); // |false| is a dummy value. + orientationListener.enable(); + return startResult; + } + + private void startCaptureOnCameraThread( + int width, int height, int min_mfps, int max_mfps, + Exchanger result) { Throwable error = null; try { camera = Camera.open(id); - localPreview = ViERenderer.GetLocalRenderer(); if (localPreview != null) { localPreview.addCallback(this); if (localPreview.getSurface() != null && @@ -78,13 +153,27 @@ private synchronized boolean startCapture( } else { // No local renderer (we only care about onPreviewFrame() buffers, not a // directly-displayed UI element). Camera won't capture without - // setPreview{Texture,Display}, so we create a dummy SurfaceTexture and - // hand it over to Camera, but never listen for frame-ready callbacks, + // setPreview{Texture,Display}, so we create a SurfaceTexture and hand + // it over to Camera, but never listen for frame-ready callbacks, // and never call updateTexImage on it. try { - // "42" because http://goo.gl/KaEn8 - dummySurfaceTexture = new SurfaceTexture(42); - camera.setPreviewTexture(dummySurfaceTexture); + cameraGlTextures = new int[1]; + // Generate one texture pointer and bind it as an external texture. + GLES20.glGenTextures(1, cameraGlTextures, 0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + cameraGlTextures[0]); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + + cameraSurfaceTexture = new SurfaceTexture(cameraGlTextures[0]); + cameraSurfaceTexture.setOnFrameAvailableListener(null); + camera.setPreviewTexture(cameraSurfaceTexture); } catch (IOException e) { throw new RuntimeException(e); } @@ -106,8 +195,11 @@ private synchronized boolean startCapture( camera.addCallbackBuffer(new byte[bufSize]); } camera.setPreviewCallbackWithBuffer(this); + frameCount = 0; + averageDurationMs = 1000 / max_mfps; camera.startPreview(); - return true; + exchange(result, true); + return; } catch (IOException e) { error = e; } catch (RuntimeException e) { @@ -115,14 +207,38 @@ private synchronized boolean startCapture( } Log.e(TAG, "startCapture failed", error); if (camera != null) { - stopCapture(); + Exchanger resultDropper = new Exchanger(); + stopCaptureOnCameraThread(resultDropper); + exchange(resultDropper, false); } - return false; + exchange(result, false); + return; } // Called by native code. Returns true when camera is known to be stopped. private synchronized boolean stopCapture() { Log.d(TAG, "stopCapture"); + orientationListener.disable(); + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + stopCaptureOnCameraThread(result); + } + }); + boolean status = exchange(result, false); // |false| is a dummy value here. + try { + cameraThread.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + cameraThreadHandler = null; + cameraThread = null; + Log.d(TAG, "stopCapture done"); + return status; + } + + private void stopCaptureOnCameraThread( + Exchanger result) { if (camera == null) { throw new RuntimeException("Camera is already stopped!"); } @@ -135,42 +251,81 @@ private synchronized boolean stopCapture() { camera.setPreviewDisplay(null); } else { camera.setPreviewTexture(null); + cameraSurfaceTexture = null; + if (cameraGlTextures != null) { + GLES20.glDeleteTextures(1, cameraGlTextures, 0); + cameraGlTextures = null; + } } camera.release(); camera = null; - return true; + exchange(result, true); + Looper.myLooper().quit(); + return; } catch (IOException e) { error = e; } catch (RuntimeException e) { error = e; } Log.e(TAG, "Failed to stop camera", error); - return false; + exchange(result, false); + Looper.myLooper().quit(); + return; } private native void ProvideCameraFrame( - byte[] data, int length, long captureObject); + byte[] data, int length, long timeStamp, long captureObject); - public synchronized void onPreviewFrame(byte[] data, Camera callbackCamera) { + // Called on cameraThread so must not "synchronized". + @Override + public void onPreviewFrame(byte[] data, Camera callbackCamera) { + if (Thread.currentThread() != cameraThread) { + throw new RuntimeException("Camera callback not on camera thread?!?"); + } if (camera == null) { return; } if (camera != callbackCamera) { throw new RuntimeException("Unexpected camera in callback!"); } - ProvideCameraFrame(data, data.length, native_capturer); + frameCount++; + long captureTimeMs = SystemClock.elapsedRealtime(); + if (frameCount > 1) { + double durationMs = captureTimeMs - lastCaptureTimeMs; + averageDurationMs = 0.9 * averageDurationMs + 0.1 * durationMs; + if ((frameCount % 30) == 0) { + Log.d(TAG, "Camera TS " + captureTimeMs + + ". Duration: " + (int)durationMs + " ms. FPS: " + + (int) (1000 / averageDurationMs + 0.5)); + } + } + lastCaptureTimeMs = captureTimeMs; + ProvideCameraFrame(data, data.length, captureTimeMs, native_capturer); camera.addCallbackBuffer(data); } // Sets the rotation of the preview render window. // Does not affect the captured video image. // Called by native code. - private synchronized void setPreviewRotation(int rotation) { - Log.v(TAG, "setPreviewRotation:" + rotation); - - if (camera == null) { + private synchronized void setPreviewRotation(final int rotation) { + if (camera == null || cameraThreadHandler == null) { return; } + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + setPreviewRotationOnCameraThread(rotation, result); + } + }); + // Use the exchanger below to block this function until + // setPreviewRotationOnCameraThread() completes, holding the synchronized + // lock for the duration. The exchanged value itself is ignored. + exchange(result, null); + } + + private void setPreviewRotationOnCameraThread( + int rotation, Exchanger result) { + Log.v(TAG, "setPreviewRotation:" + rotation); int resultRotation = 0; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { @@ -182,34 +337,70 @@ private synchronized void setPreviewRotation(int rotation) { resultRotation = rotation; } camera.setDisplayOrientation(resultRotation); + exchange(result, null); } + @Override public synchronized void surfaceChanged( SurfaceHolder holder, int format, int width, int height) { Log.d(TAG, "VideoCaptureAndroid::surfaceChanged ignored: " + format + ": " + width + "x" + height); } - public synchronized void surfaceCreated(SurfaceHolder holder) { + @Override + public synchronized void surfaceCreated(final SurfaceHolder holder) { Log.d(TAG, "VideoCaptureAndroid::surfaceCreated"); - if (camera == null) { + if (camera == null || cameraThreadHandler == null) { return; } - try { - camera.setPreviewDisplay(holder); - } catch (IOException e) { + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + setPreviewDisplayOnCameraThread(holder, result); + } + }); + IOException e = exchange(result, null); // |null| is a dummy value here. + if (e != null) { throw new RuntimeException(e); } } + @Override public synchronized void surfaceDestroyed(SurfaceHolder holder) { Log.d(TAG, "VideoCaptureAndroid::surfaceDestroyed"); - if (camera == null) { + if (camera == null || cameraThreadHandler == null) { return; } + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + setPreviewDisplayOnCameraThread(null, result); + } + }); + IOException e = exchange(result, null); // |null| is a dummy value here. + if (e != null) { + throw new RuntimeException(e); + } + } + + private void setPreviewDisplayOnCameraThread( + SurfaceHolder holder, Exchanger result) { try { - camera.setPreviewDisplay(null); + camera.setPreviewDisplay(holder); } catch (IOException e) { + exchange(result, e); + return; + } + exchange(result, null); + return; + } + + // Exchanges |value| with |exchanger|, converting InterruptedExceptions to + // RuntimeExceptions (since we expect never to see these). + private static T exchange(Exchanger exchanger, T value) { + try { + return exchanger.exchange(value); + } catch (InterruptedException e) { throw new RuntimeException(e); } } diff --git a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java index f23e9a8a4..fe207ca3e 100644 --- a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java +++ b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java @@ -10,14 +10,8 @@ package org.webrtc.videoengine; -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.List; -import java.util.Locale; -import android.content.Context; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; @@ -57,17 +51,22 @@ private static String getDeviceInfo() { devices.put(cameraDict); List supportedSizes; List supportedFpsRanges; + Camera camera = null; try { - Camera camera = Camera.open(i); + camera = Camera.open(i); Parameters parameters = camera.getParameters(); supportedSizes = parameters.getSupportedPreviewSizes(); supportedFpsRanges = parameters.getSupportedPreviewFpsRange(); - camera.release(); Log.d(TAG, uniqueName); } catch (RuntimeException e) { - Log.e(TAG, "Failed to open " + uniqueName + ", skipping"); + Log.e(TAG, "Failed to open " + uniqueName + ", skipping", e); continue; + } finally { + if (camera != null) { + camera.release(); + } } + JSONArray sizes = new JSONArray(); for (Size supportedSize : supportedSizes) { JSONObject size = new JSONObject(); @@ -75,18 +74,26 @@ private static String getDeviceInfo() { size.put("height", supportedSize.height); sizes.put(size); } - // Android SDK deals in integral "milliframes per second" - // (i.e. fps*1000, instead of floating-point frames-per-second) so we - // preserve that through the Java->C++->Java round-trip. - int[] mfps = supportedFpsRanges.get(supportedFpsRanges.size() - 1); + + JSONArray mfpsRanges = new JSONArray(); + for (int[] range : supportedFpsRanges) { + JSONObject mfpsRange = new JSONObject(); + // Android SDK deals in integral "milliframes per second" + // (i.e. fps*1000, instead of floating-point frames-per-second) so we + // preserve that through the Java->C++->Java round-trip. + mfpsRange.put("min_mfps", range[Parameters.PREVIEW_FPS_MIN_INDEX]); + mfpsRange.put("max_mfps", range[Parameters.PREVIEW_FPS_MAX_INDEX]); + mfpsRanges.put(mfpsRange); + } + cameraDict.put("name", uniqueName); cameraDict.put("front_facing", isFrontFacing(info)) .put("orientation", info.orientation) .put("sizes", sizes) - .put("min_mfps", mfps[Parameters.PREVIEW_FPS_MIN_INDEX]) - .put("max_mfps", mfps[Parameters.PREVIEW_FPS_MAX_INDEX]); + .put("mfpsRanges", mfpsRanges); } String ret = devices.toString(2); + Log.d(TAG, ret); return ret; } catch (JSONException e) { throw new RuntimeException(e); diff --git a/webrtc/modules/video_capture/android/video_capture_android.cc b/webrtc/modules/video_capture/android/video_capture_android.cc index 2b6d60644..6f0200e62 100644 --- a/webrtc/modules/video_capture/android/video_capture_android.cc +++ b/webrtc/modules/video_capture/android/video_capture_android.cc @@ -10,24 +10,34 @@ #include "webrtc/modules/video_capture/android/video_capture_android.h" +#include "webrtc/base/common.h" #include "webrtc/modules/utility/interface/helpers_android.h" #include "webrtc/modules/video_capture/android/device_info_android.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/logcat_trace_context.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/ref_count.h" #include "webrtc/system_wrappers/interface/trace.h" static JavaVM* g_jvm = NULL; static jclass g_java_capturer_class = NULL; // VideoCaptureAndroid.class. +static jobject g_context = NULL; // Owned android.content.Context. namespace webrtc { +// Called by Java to get the global application context. +jobject JNICALL GetContext(JNIEnv* env, jclass) { + assert(g_context); + return g_context; +} + // Called by Java when the camera has a new frame to deliver. void JNICALL ProvideCameraFrame( JNIEnv* env, jobject, jbyteArray javaCameraFrame, jint length, + jlong timeStamp, jlong context) { webrtc::videocapturemodule::VideoCaptureAndroid* captureModule = reinterpret_cast( @@ -38,25 +48,67 @@ void JNICALL ProvideCameraFrame( env->ReleaseByteArrayElements(javaCameraFrame, cameraFrame, JNI_ABORT); } -int32_t SetCaptureAndroidVM(JavaVM* javaVM) { - g_jvm = javaVM; - AttachThreadScoped ats(g_jvm); - - videocapturemodule::DeviceInfoAndroid::Initialize(ats.env()); - - jclass j_capture_class = - ats.env()->FindClass("org/webrtc/videoengine/VideoCaptureAndroid"); - assert(j_capture_class); - g_java_capturer_class = - reinterpret_cast(ats.env()->NewGlobalRef(j_capture_class)); - assert(g_java_capturer_class); +// Called by Java when the device orientation has changed. +void JNICALL OnOrientationChanged( + JNIEnv* env, jobject, jlong context, jint degrees) { + webrtc::videocapturemodule::VideoCaptureAndroid* captureModule = + reinterpret_cast( + context); + degrees = (360 + degrees) % 360; + assert(degrees >= 0 && degrees < 360); + VideoCaptureRotation rotation = + (degrees <= 45 || degrees > 315) ? kCameraRotate0 : + (degrees > 45 && degrees <= 135) ? kCameraRotate90 : + (degrees > 135 && degrees <= 225) ? kCameraRotate180 : + (degrees > 225 && degrees <= 315) ? kCameraRotate270 : + kCameraRotate0; // Impossible. + int32_t status = + captureModule->VideoCaptureImpl::SetCaptureRotation(rotation); + RTC_UNUSED(status); + assert(status == 0); +} - JNINativeMethod native_method = { - "ProvideCameraFrame", "([BIJ)V", - reinterpret_cast(&ProvideCameraFrame) - }; - if (ats.env()->RegisterNatives(g_java_capturer_class, &native_method, 1) != 0) - assert(false); +int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context) { + if (javaVM) { + assert(!g_jvm); + g_jvm = javaVM; + AttachThreadScoped ats(g_jvm); + g_context = ats.env()->NewGlobalRef(context); + + videocapturemodule::DeviceInfoAndroid::Initialize(ats.env()); + + jclass j_capture_class = + ats.env()->FindClass("org/webrtc/videoengine/VideoCaptureAndroid"); + assert(j_capture_class); + g_java_capturer_class = + reinterpret_cast(ats.env()->NewGlobalRef(j_capture_class)); + assert(g_java_capturer_class); + + JNINativeMethod native_methods[] = { + {"GetContext", + "()Landroid/content/Context;", + reinterpret_cast(&GetContext)}, + {"OnOrientationChanged", + "(JI)V", + reinterpret_cast(&OnOrientationChanged)}, + {"ProvideCameraFrame", + "([BIJJ)V", + reinterpret_cast(&ProvideCameraFrame)}}; + if (ats.env()->RegisterNatives(g_java_capturer_class, + native_methods, 3) != 0) + assert(false); + } else { + if (g_jvm) { + AttachThreadScoped ats(g_jvm); + ats.env()->UnregisterNatives(g_java_capturer_class); + ats.env()->DeleteGlobalRef(g_java_capturer_class); + g_java_capturer_class = NULL; + ats.env()->DeleteGlobalRef(g_context); + g_context = NULL; + videocapturemodule::DeviceInfoAndroid::DeInitialize(); + g_jvm = NULL; + } + } return 0; } @@ -96,18 +148,18 @@ int32_t VideoCaptureAndroid::Init(const int32_t id, return -1; // Store the device name + LOG(LS_INFO) << "VideoCaptureAndroid::Init: " << deviceUniqueIdUTF8; + size_t camera_id = 0; + if (!_deviceInfo.FindCameraIndex(deviceUniqueIdUTF8, &camera_id)) + return -1; _deviceUniqueId = new char[nameLength + 1]; memcpy(_deviceUniqueId, deviceUniqueIdUTF8, nameLength + 1); AttachThreadScoped ats(g_jvm); JNIEnv* env = ats.env(); - jmethodID ctor = env->GetMethodID(g_java_capturer_class, "", "(IJ)V"); assert(ctor); jlong j_this = reinterpret_cast(this); - size_t camera_id = 0; - if (!_deviceInfo.FindCameraIndex(deviceUniqueIdUTF8, &camera_id)) - return -1; _jCapturer = env->NewGlobalRef( env->NewObject(g_java_capturer_class, ctor, camera_id, j_this)); assert(_jCapturer); @@ -143,7 +195,8 @@ int32_t VideoCaptureAndroid::StartCapture( assert(j_start); int min_mfps = 0; int max_mfps = 0; - _deviceInfo.GetFpsRange(_deviceUniqueId, &min_mfps, &max_mfps); + _deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS, + &min_mfps, &max_mfps); bool started = env->CallBooleanMethod(_jCapturer, j_start, _captureCapability.width, _captureCapability.height, @@ -183,9 +236,9 @@ int32_t VideoCaptureAndroid::CaptureSettings( int32_t VideoCaptureAndroid::SetCaptureRotation( VideoCaptureRotation rotation) { - CriticalSectionScoped cs(&_apiCs); - if (VideoCaptureImpl::SetCaptureRotation(rotation) != 0) - return 0; + int32_t status = VideoCaptureImpl::SetCaptureRotation(rotation); + if (status != 0) + return status; AttachThreadScoped ats(g_jvm); JNIEnv* env = ats.env(); diff --git a/webrtc/modules/video_capture/device_info_impl.cc b/webrtc/modules/video_capture/device_info_impl.cc index c205e5166..f5d5edaab 100644 --- a/webrtc/modules/video_capture/device_info_impl.cc +++ b/webrtc/modules/video_capture/device_info_impl.cc @@ -8,11 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include #include #include "webrtc/modules/video_capture/device_info_impl.h" #include "webrtc/modules/video_capture/video_capture_config.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #ifndef abs #define abs(a) (a>=0?a:-a) @@ -75,13 +76,8 @@ int32_t DeviceInfoImpl::GetCapability(const char* deviceUniqueIdUTF8, const uint32_t deviceCapabilityNumber, VideoCaptureCapability& capability) { + assert(deviceUniqueIdUTF8 != NULL); - if (!deviceUniqueIdUTF8) - { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id, - "deviceUniqueIdUTF8 parameter not set in call to GetCapability"); - return -1; - } ReadLockScoped cs(_apiLock); if ((_lastUsedDeviceNameLength != strlen((char*) deviceUniqueIdUTF8)) @@ -111,9 +107,9 @@ int32_t DeviceInfoImpl::GetCapability(const char* deviceUniqueIdUTF8, // Make sure the number is valid if (deviceCapabilityNumber >= (unsigned int) _captureCapabilities.size()) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id, - "deviceCapabilityNumber %d is invalid in call to GetCapability", - deviceCapabilityNumber); + LOG(LS_ERROR) << "Invalid deviceCapabilityNumber " + << deviceCapabilityNumber << ">= number of capabilities (" + << _captureCapabilities.size() << ")."; return -1; } @@ -266,9 +262,9 @@ int32_t DeviceInfoImpl::GetBestMatchedCapability( }// else height not good }//end for - WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id, - "Best camera format: Width %d, Height %d, Frame rate %d, Color format %d", - bestWidth, bestHeight, bestFrameRate, bestRawType); + LOG(LS_VERBOSE) << "Best camera format: " << bestWidth << "x" << bestHeight + << "@" << bestFrameRate + << "fps, color format: " << bestRawType; // Copy the capability if (bestformatIndex < 0) @@ -343,11 +339,10 @@ int32_t DeviceInfoImpl::GetExpectedCaptureDelay( } if (bestDelay > kMaxCaptureDelay) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id, - "Expected capture delay too high. %dms, will use %d", bestDelay, - kMaxCaptureDelay); + LOG(LS_WARNING) << "Expected capture delay (" << bestDelay + << " ms) too high, using " << kMaxCaptureDelay + << " ms."; bestDelay = kMaxCaptureDelay; - } return bestDelay; diff --git a/webrtc/modules/video_capture/ensure_initialized.cc b/webrtc/modules/video_capture/ensure_initialized.cc new file mode 100644 index 000000000..65c9a8dbe --- /dev/null +++ b/webrtc/modules/video_capture/ensure_initialized.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2014 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. + */ + +// Platform-specific initialization bits, if any, go here. + +#if !defined(ANDROID) || !defined(WEBRTC_CHROMIUM_BUILD) + +namespace webrtc { +namespace videocapturemodule { +void EnsureInitialized() {} +} // namespace videocapturemodule +} // namespace webrtc + +#else // !defined(ANDROID) || !defined(WEBRTC_CHROMIUM_BUILD) + +#include +#include + +#include "base/android/jni_android.h" + +// Handy alternative to assert() which suppresses unused-variable warnings when +// assert() is a no-op (i.e. in Release builds). +#ifdef NDEBUG +#define ASSERT(x) if (false && (x)); else +#else +#define ASSERT(x) assert(x) +#endif + +namespace webrtc { + +// Declared in webrtc/modules/video_capture/include/video_capture.h. +int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject g_context); + +namespace videocapturemodule { + +static pthread_once_t g_initialize_once = PTHREAD_ONCE_INIT; + +void EnsureInitializedOnce() { + JNIEnv* jni = ::base::android::AttachCurrentThread(); + jobject context = ::base::android::GetApplicationContext(); + JavaVM* jvm = NULL; + int status = jni->GetJavaVM(&jvm); + ASSERT(status == 0); + status = webrtc::SetCaptureAndroidVM(jvm, context) == 0; + ASSERT(status); +} + +void EnsureInitialized() { + int ret = pthread_once(&g_initialize_once, &EnsureInitializedOnce); + ASSERT(ret == 0); +} + +} // namespace videocapturemodule +} // namespace webrtc + +#endif // ANDROID & WEBRTC_CHROMIUM_BUILD diff --git a/webrtc/modules/video_capture/ensure_initialized.h b/webrtc/modules/video_capture/ensure_initialized.h new file mode 100644 index 000000000..429879537 --- /dev/null +++ b/webrtc/modules/video_capture/ensure_initialized.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 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. + */ + +namespace webrtc { +namespace videocapturemodule { + +// Ensure any necessary initialization of webrtc::videocapturemodule has +// completed. +void EnsureInitialized(); + +} // namespace videocapturemodule. +} // namespace webrtc. diff --git a/webrtc/modules/video_capture/include/mock/mock_video_capture.h b/webrtc/modules/video_capture/include/mock/mock_video_capture.h new file mode 100644 index 000000000..8ad74a238 --- /dev/null +++ b/webrtc/modules/video_capture/include/mock/mock_video_capture.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014 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_MODULES_VIDEO_CAPTURE_INCLUDE_MOCK_MOCK_VIDEO_CAPTURE_H_ +#define WEBRTC_MODULES_VIDEO_CAPTURE_INCLUDE_MOCK_MOCK_VIDEO_CAPTURE_H_ + +#include "webrtc/modules/video_capture/include/video_capture.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace webrtc { + +class MockVideoCaptureModule : public VideoCaptureModule { + public: + // from Module + MOCK_METHOD0(TimeUntilNextProcess, int32_t()); + MOCK_METHOD0(Process, int32_t()); + + // from RefCountedModule + MOCK_METHOD0(AddRef, int32_t()); + MOCK_METHOD0(Release, int32_t()); + + // from VideoCaptureModule + MOCK_METHOD1(RegisterCaptureDataCallback, + void(VideoCaptureDataCallback& dataCallback)); + MOCK_METHOD0(DeRegisterCaptureDataCallback, void()); + MOCK_METHOD1(RegisterCaptureCallback, void(VideoCaptureFeedBack& callBack)); + MOCK_METHOD0(DeRegisterCaptureCallback, void()); + MOCK_METHOD1(StartCapture, int32_t(const VideoCaptureCapability& capability)); + MOCK_METHOD0(StopCapture, int32_t()); + MOCK_CONST_METHOD0(CurrentDeviceName, const char*()); + MOCK_METHOD0(CaptureStarted, bool()); + MOCK_METHOD1(CaptureSettings, int32_t(VideoCaptureCapability& settings)); + MOCK_METHOD1(SetCaptureDelay, void(int32_t delayMS)); + MOCK_METHOD0(CaptureDelay, int32_t()); + MOCK_METHOD1(SetCaptureRotation, int32_t(VideoCaptureRotation rotation)); + MOCK_METHOD1(GetEncodeInterface, + VideoCaptureEncodeInterface*(const VideoCodec& codec)); + MOCK_METHOD1(EnableFrameRateCallback, void(const bool enable)); + MOCK_METHOD1(EnableNoPictureAlarm, void(const bool enable)); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_VIDEO_CAPTURE_INCLUDE_MOCK_MOCK_VIDEO_CAPTURE_H_ diff --git a/webrtc/modules/video_capture/include/video_capture.h b/webrtc/modules/video_capture/include/video_capture.h index 5340cb731..7398af604 100644 --- a/webrtc/modules/video_capture/include/video_capture.h +++ b/webrtc/modules/video_capture/include/video_capture.h @@ -20,8 +20,8 @@ namespace webrtc { -#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) -int32_t SetCaptureAndroidVM(JavaVM* javaVM); +#if defined(ANDROID) +int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context); #endif class VideoCaptureModule: public RefCountedModule { diff --git a/webrtc/modules/video_capture/ios/rtc_video_capture_ios_objc.mm b/webrtc/modules/video_capture/ios/rtc_video_capture_ios_objc.mm index 016a20bfd..641ca2416 100644 --- a/webrtc/modules/video_capture/ios/rtc_video_capture_ios_objc.mm +++ b/webrtc/modules/video_capture/ios/rtc_video_capture_ios_objc.mm @@ -32,8 +32,8 @@ @implementation RTCVideoCaptureIosObjC { AVCaptureSession* _captureSession; int _captureId; AVCaptureConnection* _connection; - BOOL _captureStarting; // Guarded by _captureStartingCondition. - NSCondition* _captureStartingCondition; + BOOL _captureChanging; // Guarded by _captureChangingCondition. + NSCondition* _captureChangingCondition; } @synthesize frameRotation = _framRotation; @@ -43,18 +43,16 @@ - (id)initWithOwner:(VideoCaptureIos*)owner captureId:(int)captureId { _owner = owner; _captureId = captureId; _captureSession = [[AVCaptureSession alloc] init]; - _captureStarting = NO; - _captureStartingCondition = [[NSCondition alloc] init]; + _captureChanging = NO; + _captureChangingCondition = [[NSCondition alloc] init]; - if (!_captureSession || !_captureStartingCondition) { + if (!_captureSession || !_captureChangingCondition) { return nil; } // create and configure a new output (using callbacks) AVCaptureVideoDataOutput* captureOutput = [[AVCaptureVideoDataOutput alloc] init]; - [captureOutput setSampleBufferDelegate:self - queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; NSNumber* val = [NSNumber @@ -90,6 +88,17 @@ - (id)initWithOwner:(VideoCaptureIos*)owner captureId:(int)captureId { return self; } +- (void)directOutputToSelf { + [[self currentOutput] + setSampleBufferDelegate:self + queue:dispatch_get_global_queue( + DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; +} + +- (void)directOutputToNil { + [[self currentOutput] setSampleBufferDelegate:nil queue:NULL]; +} + - (void)statusBarOrientationDidChange:(NSNotification*)notification { [self setRelativeVideoOrientation]; } @@ -99,6 +108,7 @@ - (void)dealloc { } - (BOOL)setCaptureDeviceByUniqueId:(NSString*)uniqueId { + [self waitForCaptureChangeToFinish]; // check to see if the camera is already set if (_captureSession) { NSArray* currentInputs = [NSArray arrayWithArray:[_captureSession inputs]]; @@ -114,6 +124,7 @@ - (BOOL)setCaptureDeviceByUniqueId:(NSString*)uniqueId { } - (BOOL)startCaptureWithCapability:(const VideoCaptureCapability&)capability { + [self waitForCaptureChangeToFinish]; if (!_captureSession) { return NO; } @@ -148,20 +159,23 @@ - (BOOL)startCaptureWithCapability:(const VideoCaptureCapability&)capability { _capability = capability; - NSArray* currentOutputs = [_captureSession outputs]; - if ([currentOutputs count] == 0) { + AVCaptureVideoDataOutput* currentOutput = [self currentOutput]; + if (!currentOutput) return NO; - } - AVCaptureVideoDataOutput* currentOutput = - (AVCaptureVideoDataOutput*)[currentOutputs objectAtIndex:0]; + [self directOutputToSelf]; + _captureChanging = YES; dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { [self startCaptureInBackgroundWithOutput:currentOutput]; }); return YES; } +- (AVCaptureVideoDataOutput*)currentOutput { + return [[_captureSession outputs] firstObject]; +} + - (void)startCaptureInBackgroundWithOutput: (AVCaptureVideoDataOutput*)currentOutput { NSString* captureQuality = @@ -195,11 +209,7 @@ - (void)startCaptureInBackgroundWithOutput: [_captureSession commitConfiguration]; [_captureSession startRunning]; - - [_captureStartingCondition lock]; - _captureStarting = NO; - [_captureStartingCondition signal]; - [_captureStartingCondition unlock]; + [self signalCaptureChangeEnd]; } - (void)setRelativeVideoOrientation { @@ -235,17 +245,26 @@ - (void)onVideoError:(NSNotification*)notification { } - (BOOL)stopCapture { - [self waitForCaptureStartToFinish]; + [self waitForCaptureChangeToFinish]; + [self directOutputToNil]; + if (!_captureSession) { return NO; } - [_captureSession stopRunning]; - + _captureChanging = YES; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^(void) { [self stopCaptureInBackground]; }); return YES; } +- (void)stopCaptureInBackground { + [_captureSession stopRunning]; + [self signalCaptureChangeEnd]; +} + - (BOOL)changeCaptureInputByUniqueId:(NSString*)uniqueId { + [self waitForCaptureChangeToFinish]; NSArray* currentInputs = [_captureSession inputs]; // remove current input if ([currentInputs count] > 0) { @@ -341,11 +360,18 @@ - (void)captureOutput:(AVCaptureOutput*)captureOutput CVPixelBufferUnlockBaseAddress(videoFrame, kFlags); } -- (void)waitForCaptureStartToFinish { - [_captureStartingCondition lock]; - while (_captureStarting) { - [_captureStartingCondition wait]; +- (void)signalCaptureChangeEnd { + [_captureChangingCondition lock]; + _captureChanging = NO; + [_captureChangingCondition signal]; + [_captureChangingCondition unlock]; +} + +- (void)waitForCaptureChangeToFinish { + [_captureChangingCondition lock]; + while (_captureChanging) { + [_captureChangingCondition wait]; } - [_captureStartingCondition unlock]; + [_captureChangingCondition unlock]; } @end diff --git a/webrtc/modules/video_capture/ios/video_capture_ios.mm b/webrtc/modules/video_capture/ios/video_capture_ios.mm index c605025bd..2010f0308 100644 --- a/webrtc/modules/video_capture/ios/video_capture_ios.mm +++ b/webrtc/modules/video_capture/ios/video_capture_ios.mm @@ -34,7 +34,7 @@ } VideoCaptureIos::~VideoCaptureIos() { - if (capture_device_) { + if (is_capturing_) { [capture_device_ stopCapture]; } } diff --git a/webrtc/modules/video_capture/mac/qtkit/video_capture_qtkit_info_objc.mm b/webrtc/modules/video_capture/mac/qtkit/video_capture_qtkit_info_objc.mm index 7b46aec19..2b18e1eca 100644 --- a/webrtc/modules/video_capture/mac/qtkit/video_capture_qtkit_info_objc.mm +++ b/webrtc/modules/video_capture/mac/qtkit/video_capture_qtkit_info_objc.mm @@ -155,11 +155,14 @@ - (NSNumber*)initializeVariables - (void)checkOSSupported { Class osSupportedTest = NSClassFromString(@"QTCaptureSession"); - _OSSupportedInfo = NO; if(nil == osSupportedTest) { + _OSSupportedInfo = NO; + } + else + { + _OSSupportedInfo = YES; } - _OSSupportedInfo = YES; } /// ***** Retrieves the number of capture devices currently available diff --git a/webrtc/modules/video_capture/test/video_capture_unittest.cc b/webrtc/modules/video_capture/test/video_capture_unittest.cc index 98b6aa61a..4c2263d04 100644 --- a/webrtc/modules/video_capture/test/video_capture_unittest.cc +++ b/webrtc/modules/video_capture/test/video_capture_unittest.cc @@ -10,10 +10,14 @@ #include +#include +#include + #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/common_video/interface/i420_video_frame.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/modules/utility/interface/process_thread.h" +#include "webrtc/modules/video_capture/ensure_initialized.h" #include "webrtc/modules/video_capture/include/video_capture.h" #include "webrtc/modules/video_capture/include/video_capture_factory.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" @@ -21,6 +25,7 @@ #include "webrtc/system_wrappers/interface/scoped_refptr.h" #include "webrtc/system_wrappers/interface/sleep.h" #include "webrtc/system_wrappers/interface/tick_util.h" +#include "webrtc/test/testsupport/gtest_disable.h" using webrtc::CriticalSectionWrapper; using webrtc::CriticalSectionScoped; @@ -231,7 +236,9 @@ class VideoCaptureTest : public testing::Test { VideoCaptureTest() : number_of_devices_(0) {} void SetUp() { - device_info_.reset(VideoCaptureFactory::CreateDeviceInfo(5)); + webrtc::videocapturemodule::EnsureInitialized(); + device_info_.reset(VideoCaptureFactory::CreateDeviceInfo(0)); + assert(device_info_.get()); number_of_devices_ = device_info_->NumberOfDevices(); ASSERT_GT(number_of_devices_, 0u); } @@ -258,7 +265,7 @@ class VideoCaptureTest : public testing::Test { void StartCapture(VideoCaptureModule* capture_module, VideoCaptureCapability capability) { - EXPECT_EQ(0, capture_module->StartCapture(capability)); + ASSERT_EQ(0, capture_module->StartCapture(capability)); EXPECT_TRUE(capture_module->CaptureStarted()); VideoCaptureCapability resulting_capability; @@ -289,7 +296,7 @@ TEST_F(VideoCaptureTest, CreateDelete) { capability.rawType = webrtc::kVideoUnknown; #endif capture_observer.SetExpectedCapability(capability); - StartCapture(module.get(), capability); + ASSERT_NO_FATAL_FAILURE(StartCapture(module.get(), capability)); // Less than 4s to start the camera. EXPECT_LE(TickTime::MillisecondTimestamp() - start_time, 4000); @@ -323,17 +330,50 @@ TEST_F(VideoCaptureTest, Capabilities) { int number_of_capabilities = device_info_->NumberOfCapabilities( module->CurrentDeviceName()); EXPECT_GT(number_of_capabilities, 0); + // Key is x, value is vector of maxFPS values at that + // resolution. + typedef std::map > FrameRatesByResolution; + FrameRatesByResolution frame_rates_by_resolution; for (int i = 0; i < number_of_capabilities; ++i) { VideoCaptureCapability capability; EXPECT_EQ(0, device_info_->GetCapability(module->CurrentDeviceName(), i, capability)); + std::ostringstream resolutionStream; + resolutionStream << capability.width << "x" << capability.height; + resolutionStream.flush(); + std::string resolution = resolutionStream.str(); + frame_rates_by_resolution[resolution].push_back(capability.maxFPS); + + // Since Android presents so many resolution/FPS combinations and the test + // runner imposes a timeout, we only actually start the capture and test + // that a frame was captured for 2 frame-rates at each resolution. + if (frame_rates_by_resolution[resolution].size() > 2) + continue; + capture_observer.SetExpectedCapability(capability); - StartCapture(module.get(), capability); - // Make sure 5 frames are captured. - EXPECT_TRUE_WAIT(capture_observer.incoming_frames() >= 5, kTimeOut); + ASSERT_NO_FATAL_FAILURE(StartCapture(module.get(), capability)); + // Make sure at least one frame is captured. + EXPECT_TRUE_WAIT(capture_observer.incoming_frames() >= 1, kTimeOut); EXPECT_EQ(0, module->StopCapture()); } + +#if ANDROID + // There's no reason for this to _necessarily_ be true, but in practice all + // Android devices this test runs on in fact do support multiple capture + // resolutions and multiple frame-rates per captured resolution, so we assert + // this fact here as a regression-test against the time that we only noticed a + // single frame-rate per resolution (bug 2974). If this test starts being run + // on devices for which this is untrue (e.g. Nexus4) then the following should + // probably be wrapped in a base::android::BuildInfo::model()/device() check. + EXPECT_GT(frame_rates_by_resolution.size(), 1U); + for (FrameRatesByResolution::const_iterator it = + frame_rates_by_resolution.begin(); + it != frame_rates_by_resolution.end(); + ++it) { + EXPECT_GT(it->second.size(), 1U) << it->first; + } +#endif // ANDROID } // NOTE: flaky, crashes sometimes. @@ -376,10 +416,12 @@ TEST_F(VideoCaptureTest, DISABLED_TestTwoCameras) { #endif capture_observer2.SetExpectedCapability(capability2); - StartCapture(module1.get(), capability1); - StartCapture(module2.get(), capability2); + ASSERT_NO_FATAL_FAILURE(StartCapture(module1.get(), capability1)); + ASSERT_NO_FATAL_FAILURE(StartCapture(module2.get(), capability2)); EXPECT_TRUE_WAIT(capture_observer1.incoming_frames() >= 5, kTimeOut); EXPECT_TRUE_WAIT(capture_observer2.incoming_frames() >= 5, kTimeOut); + EXPECT_EQ(0, module2->StopCapture()); + EXPECT_EQ(0, module1->StopCapture()); } // Test class for testing external capture and capture feedback information @@ -432,7 +474,7 @@ TEST_F(VideoCaptureExternalTest, TestExternalCapture) { unsigned int length = webrtc::CalcBufferSize(webrtc::kI420, test_frame_.width(), test_frame_.height()); - webrtc::scoped_array test_buffer(new uint8_t[length]); + webrtc::scoped_ptr test_buffer(new uint8_t[length]); webrtc::ExtractBuffer(test_frame_, length, test_buffer.get()); EXPECT_EQ(0, capture_input_interface_->IncomingFrame(test_buffer.get(), length, capture_callback_.capability(), 0)); @@ -509,7 +551,8 @@ TEST_F(VideoCaptureExternalTest, DISABLED_TestExternalCaptureI420) { } // Test frame rate and no picture alarm. -TEST_F(VideoCaptureExternalTest , FrameRate) { +// Flaky on Win32, see webrtc:3270. +TEST_F(VideoCaptureExternalTest, DISABLED_ON_WIN(FrameRate)) { int64_t testTime = 3; TickTime startTime = TickTime::Now(); @@ -517,7 +560,7 @@ TEST_F(VideoCaptureExternalTest , FrameRate) { unsigned int length = webrtc::CalcBufferSize(webrtc::kI420, test_frame_.width(), test_frame_.height()); - webrtc::scoped_array test_buffer(new uint8_t[length]); + webrtc::scoped_ptr test_buffer(new uint8_t[length]); webrtc::ExtractBuffer(test_frame_, length, test_buffer.get()); EXPECT_EQ(0, capture_input_interface_->IncomingFrame(test_buffer.get(), length, capture_callback_.capability(), 0)); @@ -533,7 +576,7 @@ TEST_F(VideoCaptureExternalTest , FrameRate) { unsigned int length = webrtc::CalcBufferSize(webrtc::kI420, test_frame_.width(), test_frame_.height()); - webrtc::scoped_array test_buffer(new uint8_t[length]); + webrtc::scoped_ptr test_buffer(new uint8_t[length]); webrtc::ExtractBuffer(test_frame_, length, test_buffer.get()); EXPECT_EQ(0, capture_input_interface_->IncomingFrame(test_buffer.get(), length, capture_callback_.capability(), 0)); @@ -551,7 +594,7 @@ TEST_F(VideoCaptureExternalTest, Rotation) { unsigned int length = webrtc::CalcBufferSize(webrtc::kI420, test_frame_.width(), test_frame_.height()); - webrtc::scoped_array test_buffer(new uint8_t[length]); + webrtc::scoped_ptr test_buffer(new uint8_t[length]); webrtc::ExtractBuffer(test_frame_, length, test_buffer.get()); EXPECT_EQ(0, capture_input_interface_->IncomingFrame(test_buffer.get(), length, capture_callback_.capability(), 0)); diff --git a/webrtc/modules/video_capture/video_capture.gypi b/webrtc/modules/video_capture/video_capture.gypi index c1526f768..204cdf88d 100644 --- a/webrtc/modules/video_capture/video_capture.gypi +++ b/webrtc/modules/video_capture/video_capture.gypi @@ -60,6 +60,7 @@ 'link_settings': { 'xcode_settings': { 'OTHER_LDFLAGS': [ + '-framework CoreVideo', '-framework QTKit', ], }, @@ -167,11 +168,20 @@ }, ], 'conditions': [ + ['include_tests==1 and build_with_chromium==1 and OS=="android"', { + # Use WebRTC capture code for Android APK tests that are built from a + # Chromium checkout. Normally when built as a part of Chromium the + # Chromium video capture code is used. This overrides the default in + # webrtc/build/common.gypi. + 'variables': { + 'include_internal_video_capture': 1, + }, + }], ['include_tests==1', { 'targets': [ { 'target_name': 'video_capture_tests', - 'type': 'executable', + 'type': '<(gtest_target_type)', 'dependencies': [ 'video_capture_module', 'webrtc_utility', @@ -179,6 +189,8 @@ '<(DEPTH)/testing/gtest.gyp:gtest', ], 'sources': [ + 'ensure_initialized.cc', + 'ensure_initialized.h', 'test/video_capture_unittest.cc', 'test/video_capture_main_mac.mm', ], @@ -198,6 +210,13 @@ '-lX11', ], }], + # TODO(henrike): remove build_with_chromium==1 when the bots are + # using Chromium's buildbots. + ['build_with_chromium==1 and OS=="android"', { + 'dependencies': [ + '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', + ], + }], ['OS=="mac"', { 'dependencies': [ # Link with a special main for mac so we can use the webcam. diff --git a/webrtc/modules/video_capture/video_capture_impl.cc b/webrtc/modules/video_capture/video_capture_impl.cc index e83c7a469..6f179e2da 100644 --- a/webrtc/modules/video_capture/video_capture_impl.cc +++ b/webrtc/modules/video_capture/video_capture_impl.cc @@ -17,9 +17,9 @@ #include "webrtc/modules/video_capture/video_capture_config.h" #include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/ref_count.h" #include "webrtc/system_wrappers/interface/tick_util.h" -#include "webrtc/system_wrappers/interface/trace.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc @@ -260,11 +260,8 @@ int32_t VideoCaptureImpl::IncomingFrame( const VideoCaptureCapability& frameInfo, int64_t captureTime/*=0*/) { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideoCapture, _id, - "IncomingFrame width %d, height %d", (int) frameInfo.width, - (int) frameInfo.height); - - CriticalSectionScoped cs(&_callBackCs); + CriticalSectionScoped cs(&_apiCs); + CriticalSectionScoped cs2(&_callBackCs); const int32_t width = frameInfo.width; const int32_t height = frameInfo.height; @@ -281,8 +278,7 @@ int32_t VideoCaptureImpl::IncomingFrame( CalcBufferSize(commonVideoType, width, abs(height)) != videoFrameLength) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id, - "Wrong incoming frame length."); + LOG(LS_ERROR) << "Wrong incoming frame length."; return -1; } @@ -306,8 +302,8 @@ int32_t VideoCaptureImpl::IncomingFrame( stride_uv, stride_uv); if (ret < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id, - "Failed to allocate I420 frame."); + LOG(LS_ERROR) << "Failed to create empty frame, this should only " + "happen due to bad parameters."; return -1; } const int conversionResult = ConvertToI420(commonVideoType, @@ -319,9 +315,8 @@ int32_t VideoCaptureImpl::IncomingFrame( &_captureFrame); if (conversionResult < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id, - "Failed to convert capture frame from type %d to I420", - frameInfo.rawType); + LOG(LS_ERROR) << "Failed to convert capture frame from type " + << frameInfo.rawType << "to I420."; return -1; } DeliverCapturedFrame(_captureFrame, captureTime); @@ -338,7 +333,8 @@ int32_t VideoCaptureImpl::IncomingFrame( int32_t VideoCaptureImpl::IncomingI420VideoFrame(I420VideoFrame* video_frame, int64_t captureTime) { - CriticalSectionScoped cs(&_callBackCs); + CriticalSectionScoped cs(&_apiCs); + CriticalSectionScoped cs2(&_callBackCs); DeliverCapturedFrame(*video_frame, captureTime); return 0; diff --git a/webrtc/modules/video_capture/video_capture_tests.isolate b/webrtc/modules/video_capture/video_capture_tests.isolate index 30374ce4e..57dd66739 100644 --- a/webrtc/modules/video_capture/video_capture_tests.isolate +++ b/webrtc/modules/video_capture/video_capture_tests.isolate @@ -8,27 +8,25 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../../data/', - '../../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/video_capture_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/video_capture_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/modules/video_coding/BUILD.gn b/webrtc/modules/video_coding/BUILD.gn new file mode 100644 index 000000000..f5a69b181 --- /dev/null +++ b/webrtc/modules/video_coding/BUILD.gn @@ -0,0 +1,86 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("video_coding") { + sources = [ + "main/interface/video_coding.h", + "main/interface/video_coding_defines.h", + "main/source/codec_database.cc", + "main/source/codec_database.h", + "main/source/codec_timer.cc", + "main/source/codec_timer.h", + "main/source/content_metrics_processing.cc", + "main/source/content_metrics_processing.h", + "main/source/decoding_state.cc", + "main/source/decoding_state.h", + "main/source/encoded_frame.cc", + "main/source/encoded_frame.h", + "main/source/er_tables_xor.h", + "main/source/fec_tables_xor.h", + "main/source/frame_buffer.cc", + "main/source/frame_buffer.h", + "main/source/generic_decoder.cc", + "main/source/generic_decoder.h", + "main/source/generic_encoder.cc", + "main/source/generic_encoder.h", + "main/source/inter_frame_delay.cc", + "main/source/inter_frame_delay.h", + "main/source/internal_defines.h", + "main/source/jitter_buffer.cc", + "main/source/jitter_buffer.h", + "main/source/jitter_buffer_common.h", + "main/source/jitter_estimator.cc", + "main/source/jitter_estimator.h", + "main/source/media_opt_util.cc", + "main/source/media_opt_util.h", + "main/source/media_optimization.cc", + "main/source/media_optimization.h", + "main/source/nack_fec_tables.h", + "main/source/packet.cc", + "main/source/packet.h", + "main/source/qm_select_data.h", + "main/source/qm_select.cc", + "main/source/qm_select.h", + "main/source/receiver.cc", + "main/source/receiver.h", + "main/source/rtt_filter.cc", + "main/source/rtt_filter.h", + "main/source/session_info.cc", + "main/source/session_info.h", + "main/source/timestamp_map.cc", + "main/source/timestamp_map.h", + "main/source/timing.cc", + "main/source/timing.h", + "main/source/video_coding_impl.cc", + "main/source/video_coding_impl.h", + "main/source/video_receiver.cc", + "main/source/video_sender.cc", + ] + + deps = [ + ":video_coding_utility", + ":webrtc_i420", + ":webrtc_vp8", + "../../common_video", + "../../system_wrappers", + ] +} + +source_set("video_coding_utility") { + # TODO(stefan): Implement. +} + +source_set("webrtc_i420") { + # TODO(stefan): Implement. +} + +source_set("webrtc_vp8") { + # TODO(stefan): Implement. +} diff --git a/webrtc/modules/video_coding/OWNERS b/webrtc/modules/video_coding/OWNERS index 7183cf213..037de93f3 100644 --- a/webrtc/modules/video_coding/OWNERS +++ b/webrtc/modules/video_coding/OWNERS @@ -2,3 +2,5 @@ stefan@webrtc.org mikhal@webrtc.org marpan@webrtc.org henrik.lundin@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/video_coding/codecs/i420/main/source/OWNERS b/webrtc/modules/video_coding/codecs/i420/main/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_coding/codecs/i420/main/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_coding/codecs/test/OWNERS b/webrtc/modules/video_coding/codecs/test/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_coding/codecs/test/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor.cc index 30ee6a8e0..93738caf1 100644 --- a/webrtc/modules/video_coding/codecs/test/videoprocessor.cc +++ b/webrtc/modules/video_coding/codecs/test/videoprocessor.cc @@ -331,7 +331,7 @@ void VideoProcessorImpl::FrameDecoded(const I420VideoFrame& image) { } // TODO(mikhal): Extracting the buffer for now - need to update test. int length = CalcBufferSize(kI420, up_image.width(), up_image.height()); - scoped_array image_buffer(new uint8_t[length]); + scoped_ptr image_buffer(new uint8_t[length]); length = ExtractBuffer(up_image, length, image_buffer.get()); // Update our copy of the last successful frame: memcpy(last_successful_frame_buffer_, image_buffer.get(), length); @@ -344,7 +344,7 @@ void VideoProcessorImpl::FrameDecoded(const I420VideoFrame& image) { // Update our copy of the last successful frame: // TODO(mikhal): Add as a member function, so won't be allocated per frame. int length = CalcBufferSize(kI420, image.width(), image.height()); - scoped_array image_buffer(new uint8_t[length]); + scoped_ptr image_buffer(new uint8_t[length]); length = ExtractBuffer(image, length, image_buffer.get()); assert(length > 0); memcpy(last_successful_frame_buffer_, image_buffer.get(), length); diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc index 747557fdf..e7b934dc2 100644 --- a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc +++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc @@ -156,7 +156,10 @@ class VideoProcessorIntegrationTest: public testing::Test { // Setup the TestConfig struct for processing of a clip in CIF resolution. config_.input_filename = webrtc::test::ResourcePath("foreman_cif", "yuv"); - config_.output_filename = tmpnam(NULL); + + // Generate an output filename in a safe way. + config_.output_filename = webrtc::test::TempFilename( + webrtc::test::OutputPath(), "videoprocessor_integrationtest"); config_.frame_length_in_bytes = CalcBufferSize(kI420, kCIFWidth, kCIFHeight); config_.verbose = false; @@ -560,8 +563,7 @@ void SetRateControlMetrics(RateControlMetrics* rc_metrics, // Run with no packet loss and fixed bitrate. Quality should be very high. // One key frame (first frame only) in sequence. Setting |key_frame_interval| // to -1 below means no periodic key frames in test. -TEST_F(VideoProcessorIntegrationTest, - DISABLED_ON_ANDROID(ProcessZeroPacketLoss)) { +TEST_F(VideoProcessorIntegrationTest, ProcessZeroPacketLoss) { // Bitrate and frame rate profile. RateProfile rate_profile; SetRateProfilePars(&rate_profile, 0, 500, 30, 0); @@ -572,7 +574,7 @@ TEST_F(VideoProcessorIntegrationTest, SetCodecParameters(&process_settings, 0.0f, -1, 1, false, true, true, false); // Metrics for expected quality. QualityMetrics quality_metrics; - SetQualityMetrics(&quality_metrics, 36.95, 33.0, 0.90, 0.90); + SetQualityMetrics(&quality_metrics, 34.95, 33.0, 0.90, 0.89); // Metrics for rate control. RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 15, 0); @@ -584,8 +586,7 @@ TEST_F(VideoProcessorIntegrationTest, // Run with 5% packet loss and fixed bitrate. Quality should be a bit lower. // One key frame (first frame only) in sequence. -TEST_F(VideoProcessorIntegrationTest, - DISABLED_ON_ANDROID(Process5PercentPacketLoss)) { +TEST_F(VideoProcessorIntegrationTest, Process5PercentPacketLoss) { // Bitrate and frame rate profile. RateProfile rate_profile; SetRateProfilePars(&rate_profile, 0, 500, 30, 0); @@ -608,8 +609,7 @@ TEST_F(VideoProcessorIntegrationTest, // Run with 10% packet loss and fixed bitrate. Quality should be even lower. // One key frame (first frame only) in sequence. -TEST_F(VideoProcessorIntegrationTest, - DISABLED_ON_ANDROID(Process10PercentPacketLoss)) { +TEST_F(VideoProcessorIntegrationTest, Process10PercentPacketLoss) { // Bitrate and frame rate profile. RateProfile rate_profile; SetRateProfilePars(&rate_profile, 0, 500, 30, 0); @@ -630,6 +630,15 @@ TEST_F(VideoProcessorIntegrationTest, rc_metrics); } +// The tests below are currently disabled for Android. For ARM, the encoder +// uses |cpu_speed| = 12, as opposed to default |cpu_speed| <= 6 for x86, +// which leads to significantly different quality. The quality and rate control +// settings in the tests below are defined for encoder speed setting +// |cpu_speed| <= ~6. A number of settings would need to be significantly +// modified for the |cpu_speed| = 12 case. For now, keep the tests below +// disabled on Android. Some quality parameter in the above test has been +// adjusted to also pass for |cpu_speed| <= 12. + // Run with no packet loss, with varying bitrate (3 rate updates): // low to high to medium. Check that quality and encoder response to the new // target rate/per-frame bandwidth (for each rate update) is within limits. diff --git a/webrtc/modules/video_coding/codecs/test_framework/OWNERS b/webrtc/modules/video_coding/codecs/test_framework/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_coding/codecs/test_framework/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_coding/codecs/test_framework/unit_test.cc b/webrtc/modules/video_coding/codecs/test_framework/unit_test.cc index 3b034e01c..ec12a5169 100644 --- a/webrtc/modules/video_coding/codecs/test_framework/unit_test.cc +++ b/webrtc/modules/video_coding/codecs/test_framework/unit_test.cc @@ -565,7 +565,7 @@ UnitTest::Perform() frameLength = WaitForDecodedFrame(); } unsigned int length = CalcBufferSize(kI420, width, height); - scoped_array decoded_buffer(new uint8_t[length]); + scoped_ptr decoded_buffer(new uint8_t[length]); ExtractBuffer(_decodedVideoBuffer, _lengthSourceFrame, decoded_buffer.get()); EXPECT_TRUE(CheckIfBitExact(decoded_buffer.get(), frameLength, _refDecFrame, @@ -645,7 +645,7 @@ UnitTest::Perform() // check that decoded frame matches with reference unsigned int length = CalcBufferSize(kI420, width, height); - scoped_array decoded_buffer(new uint8_t[length]); + scoped_ptr decoded_buffer(new uint8_t[length]); ExtractBuffer(_decodedVideoBuffer, length, decoded_buffer.get()); EXPECT_TRUE(CheckIfBitExact(decoded_buffer.get(), length, _refDecFrame, _lengthSourceFrame) == true); diff --git a/webrtc/modules/video_coding/codecs/tools/OWNERS b/webrtc/modules/video_coding/codecs/tools/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_coding/codecs/tools/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_coding/codecs/tools/video_codecs_tools.gypi b/webrtc/modules/video_coding/codecs/tools/video_codecs_tools.gypi index cdae0afea..8f15b2850 100644 --- a/webrtc/modules/video_coding/codecs/tools/video_codecs_tools.gypi +++ b/webrtc/modules/video_coding/codecs/tools/video_codecs_tools.gypi @@ -17,6 +17,7 @@ 'video_codecs_test_framework', 'webrtc_video_coding', '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', '<(webrtc_root)/test/metrics.gyp:metrics', '<(webrtc_vp8_dir)/vp8.gyp:webrtc_vp8', ], diff --git a/webrtc/modules/video_coding/codecs/vp8/OWNERS b/webrtc/modules/video_coding/codecs/vp8/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_coding/codecs/vp8/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_coding/codecs/vp8/test/vp8_impl_unittest.cc b/webrtc/modules/video_coding/codecs/vp8/test/vp8_impl_unittest.cc index c6985ae3c..3cc8bc330 100644 --- a/webrtc/modules/video_coding/codecs/vp8/test/vp8_impl_unittest.cc +++ b/webrtc/modules/video_coding/codecs/vp8/test/vp8_impl_unittest.cc @@ -22,6 +22,8 @@ namespace webrtc { enum { kMaxWaitEncTimeMs = 100 }; enum { kMaxWaitDecTimeMs = 25 }; +static const uint32_t kTestTimestamp = 123; +static const int64_t kTestNtpTimeMs = 456; // TODO(mikhal): Replace these with mocks. class Vp8UnitTestEncodeCompleteCallback : public webrtc::EncodedImageCallback { @@ -128,6 +130,7 @@ class TestVp8Impl : public ::testing::Test { input_frame_.CreateEmptyFrame(codec_inst_.width, codec_inst_.height, stride_y, stride_uv, stride_uv); + input_frame_.set_timestamp(kTestTimestamp); // Using ConvertToI420 to add stride to the image. EXPECT_EQ(0, ConvertToI420(kI420, source_buffer_.get(), 0, 0, codec_inst_.width, codec_inst_.height, @@ -178,7 +181,7 @@ class TestVp8Impl : public ::testing::Test { scoped_ptr encode_complete_callback_; scoped_ptr decode_complete_callback_; - scoped_array source_buffer_; + scoped_ptr source_buffer_; FILE* source_file_; I420VideoFrame input_frame_; scoped_ptr encoder_; @@ -235,10 +238,13 @@ TEST_F(TestVp8Impl, DISABLED_ON_ANDROID(AlignedStrideEncodeDecode)) { VideoFrameToEncodedImage(encoded_video_frame_, encodedImage); // First frame should be a key frame. encodedImage._frameType = kKeyFrame; + encodedImage.ntp_time_ms_ = kTestNtpTimeMs; EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, decoder_->Decode(encodedImage, false, NULL)); EXPECT_GT(WaitForDecodedFrame(), 0); // Compute PSNR on all planes (faster than SSIM). EXPECT_GT(I420PSNR(&input_frame_, &decoded_video_frame_), 36); + EXPECT_EQ(kTestTimestamp, decoded_video_frame_.timestamp()); + EXPECT_EQ(kTestNtpTimeMs, decoded_video_frame_.ntp_time_ms()); } TEST_F(TestVp8Impl, DISABLED_ON_ANDROID(DecodeWithACompleteKeyFrame)) { diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc index 0a67064fb..4901edff3 100644 --- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc +++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc @@ -214,7 +214,10 @@ int VP8EncoderImpl::InitEncode(const VideoCodec* inst, } config_->g_lag_in_frames = 0; // 0- no frame lagging - if (codec_.width * codec_.height > 1280 * 960 && number_of_cores >= 6) { + if (codec_.width * codec_.height >= 1920 * 1080 && number_of_cores > 8) { + config_->g_threads = 8; // 8 threads for 1080p on high perf machines. + } else if (codec_.width * codec_.height > 1280 * 960 && + number_of_cores >= 6) { config_->g_threads = 3; // 3 threads for 1080p. } else if (codec_.width * codec_.height > 640 * 480 && number_of_cores >= 3) { config_->g_threads = 2; // 2 threads for qHD/HD. @@ -710,7 +713,7 @@ int VP8DecoderImpl::Decode(const EncodedImage& input_image, } img = vpx_codec_get_frame(decoder_, &iter); - ret = ReturnFrame(img, input_image._timeStamp); + ret = ReturnFrame(img, input_image._timeStamp, input_image.ntp_time_ms_); if (ret != 0) { // Reset to avoid requesting key frames too often. if (ret < 0 && propagation_cnt_ > 0) @@ -790,7 +793,9 @@ int VP8DecoderImpl::DecodePartitions( return WEBRTC_VIDEO_CODEC_OK; } -int VP8DecoderImpl::ReturnFrame(const vpx_image_t* img, uint32_t timestamp) { +int VP8DecoderImpl::ReturnFrame(const vpx_image_t* img, + uint32_t timestamp, + int64_t ntp_time_ms) { if (img == NULL) { // Decoder OK and NULL image => No show frame return WEBRTC_VIDEO_CODEC_NO_OUTPUT; @@ -808,6 +813,7 @@ int VP8DecoderImpl::ReturnFrame(const vpx_image_t* img, uint32_t timestamp) { img->stride[VPX_PLANE_U], img->stride[VPX_PLANE_V]); decoded_image_.set_timestamp(timestamp); + decoded_image_.set_ntp_time_ms(ntp_time_ms); int ret = decode_complete_callback_->Decoded(decoded_image_); if (ret != 0) return ret; diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h index 26dd52e6a..56f7219fc 100644 --- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h +++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h @@ -214,7 +214,9 @@ class VP8DecoderImpl : public VP8Decoder { int DecodePartitions(const EncodedImage& input_image, const RTPFragmentationHeader* fragmentation); - int ReturnFrame(const vpx_image_t* img, uint32_t timeStamp); + int ReturnFrame(const vpx_image_t* img, + uint32_t timeStamp, + int64_t ntp_time_ms); I420VideoFrame decoded_image_; DecodedImageCallback* decode_complete_callback_; diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_sequence_coder.cc b/webrtc/modules/video_coding/codecs/vp8/vp8_sequence_coder.cc index 1bd3e1a62..ffa0bcc68 100644 --- a/webrtc/modules/video_coding/codecs/vp8/vp8_sequence_coder.cc +++ b/webrtc/modules/video_coding/codecs/vp8/vp8_sequence_coder.cc @@ -142,7 +142,7 @@ int SequenceCoder(webrtc::test::CommandLineParser& parser) { EXPECT_EQ(0, decoder->InitDecode(&inst, 1)); webrtc::I420VideoFrame input_frame; unsigned int length = webrtc::CalcBufferSize(webrtc::kI420, width, height); - webrtc::scoped_array frame_buffer(new uint8_t[length]); + webrtc::scoped_ptr frame_buffer(new uint8_t[length]); int half_width = (width + 1) / 2; // Set and register callbacks. diff --git a/webrtc/modules/video_coding/main/interface/video_coding.h b/webrtc/modules/video_coding/main/interface/video_coding.h index c0166761e..cad0e5ab8 100644 --- a/webrtc/modules/video_coding/main/interface/video_coding.h +++ b/webrtc/modules/video_coding/main/interface/video_coding.h @@ -72,11 +72,9 @@ class VideoCodingModule : public Module kReferenceSelection }; - static VideoCodingModule* Create(const int32_t id); + static VideoCodingModule* Create(); - static VideoCodingModule* Create(const int32_t id, - Clock* clock, - EventFactory* event_factory); + static VideoCodingModule* Create(Clock* clock, EventFactory* event_factory); static void Destroy(VideoCodingModule* module); diff --git a/webrtc/modules/video_coding/main/source/Android.mk b/webrtc/modules/video_coding/main/source/Android.mk index 9ebdbed9a..a8cf2d0e6 100644 --- a/webrtc/modules/video_coding/main/source/Android.mk +++ b/webrtc/modules/video_coding/main/source/Android.mk @@ -37,7 +37,6 @@ LOCAL_SRC_FILES := \ receiver.cc \ rtt_filter.cc \ session_info.cc \ - timestamp_extrapolator.cc \ timestamp_map.cc \ timing.cc \ video_coding_impl.cc @@ -56,7 +55,7 @@ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/../../../../common_video/vplib/main/interface \ $(LOCAL_PATH)/../../../../common_video/interface \ $(LOCAL_PATH)/../../utility/include \ - $(LOCAL_PATH)/../../../../system_wrappers/interface + $(LOCAL_PATH)/../../../../system_wrappers/interface LOCAL_SHARED_LIBRARIES := \ libcutils \ diff --git a/webrtc/modules/video_coding/main/source/OWNERS b/webrtc/modules/video_coding/main/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_coding/main/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_coding/main/source/codec_database.cc b/webrtc/modules/video_coding/main/source/codec_database.cc index 3ff8c7620..e7a9d91b1 100644 --- a/webrtc/modules/video_coding/main/source/codec_database.cc +++ b/webrtc/modules/video_coding/main/source/codec_database.cc @@ -20,7 +20,7 @@ #include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" #endif #include "webrtc/modules/video_coding/main/source/internal_defines.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -42,9 +42,8 @@ VCMExtDecoderMapItem::VCMExtDecoderMapItem( internal_render_timing(internal_render_timing) { } -VCMCodecDataBase::VCMCodecDataBase(int id) - : id_(id), - number_of_cores_(0), +VCMCodecDataBase::VCMCodecDataBase() + : number_of_cores_(0), max_payload_size_(kDefaultPayloadSize), periodic_key_frames_(false), pending_encoder_reset_(true), @@ -58,8 +57,7 @@ VCMCodecDataBase::VCMCodecDataBase(int id) ptr_decoder_(NULL), current_dec_is_external_(false), dec_map_(), - dec_external_map_() { -} + dec_external_map_() {} VCMCodecDataBase::~VCMCodecDataBase() { ResetSender(); @@ -221,24 +219,14 @@ bool VCMCodecDataBase::SetSendCodec( } else { ptr_encoder_ = CreateEncoder(send_codec->codecType); current_enc_is_external_ = false; + if (!ptr_encoder_) { + return false; + } } encoded_frame_callback->SetPayloadType(send_codec->plType); - if (!ptr_encoder_) { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(id_), - "Failed to create encoder: %s.", - send_codec->plName); - return false; - } if (ptr_encoder_->InitEncode(send_codec, number_of_cores_, max_payload_size_) < 0) { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(id_), - "Failed to initialize encoder: %s.", - send_codec->plName); DeleteEncoder(); return false; } else if (ptr_encoder_->RegisterEncodeCallback(encoded_frame_callback) < 0) { @@ -257,8 +245,6 @@ bool VCMCodecDataBase::SetSendCodec( } bool VCMCodecDataBase::SendCodec(VideoCodec* current_send_codec) const { - WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideoCoding, VCMId(id_), - "SendCodec"); if (!ptr_encoder_) { return false; } @@ -267,8 +253,6 @@ bool VCMCodecDataBase::SendCodec(VideoCodec* current_send_codec) const { } VideoCodecType VCMCodecDataBase::SendCodec() const { - WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideoCoding, VCMId(id_), - "SendCodec type"); if (!ptr_encoder_) { return kVideoCodecUnknown; } @@ -398,7 +382,11 @@ bool VCMCodecDataBase::DeregisterExternalDecoder(uint8_t payload_type) { // Not found return false; } - if (receive_codec_.plType == payload_type) { + // We can't use payload_type to check if the decoder is currently in use, + // because payload type may be out of date (e.g. before we decode the first + // frame after RegisterReceiveCodec) + if (ptr_decoder_ != NULL && + &ptr_decoder_->_decoder == (*it).second->external_decoder_instance) { // Release it if it was registered and in use. ReleaseDecoder(ptr_decoder_); ptr_decoder_ = NULL; @@ -437,12 +425,6 @@ bool VCMCodecDataBase::RegisterReceiveCodec( if (number_of_cores < 0) { return false; } - WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideoCoding, VCMId(id_), - "Codec: %s, Payload type %d, Height %d, Width %d, Bitrate %d," - "Framerate %d.", - receive_codec->plName, receive_codec->plType, - receive_codec->height, receive_codec->width, - receive_codec->startBitrate, receive_codec->maxFramerate); // Check if payload value already exists, if so - erase old and insert new. DeregisterReceiveCodec(receive_codec->plType); if (receive_codec->codecType == kVideoCodecUnknown) { @@ -524,7 +506,7 @@ VCMGenericDecoder* VCMCodecDataBase::CreateDecoderCopy() const { if (!decoder_copy) { return NULL; } - return new VCMGenericDecoder(*decoder_copy, id_, ptr_decoder_->External()); + return new VCMGenericDecoder(*decoder_copy, ptr_decoder_->External()); } void VCMCodecDataBase::ReleaseDecoder(VCMGenericDecoder* decoder) const { @@ -543,8 +525,7 @@ void VCMCodecDataBase::CopyDecoder(const VCMGenericDecoder& decoder) { if (decoder_copy) { VCMDecodedFrameCallback* cb = ptr_decoder_->_callback; ReleaseDecoder(ptr_decoder_); - ptr_decoder_ = new VCMGenericDecoder(*decoder_copy, id_, - decoder.External()); + ptr_decoder_ = new VCMGenericDecoder(*decoder_copy, decoder.External()); if (cb && ptr_decoder_->RegisterDecodeCompleteCallback(cb)) { assert(false); } @@ -569,8 +550,8 @@ VCMGenericDecoder* VCMCodecDataBase::CreateAndInitDecoder( assert(new_codec); const VCMDecoderMapItem* decoder_item = FindDecoderItem(payload_type); if (!decoder_item) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, VCMId(id_), - "Unknown payload type: %u", payload_type); + LOG(LS_ERROR) << "Can't find a decoder associated with payload type: " + << payload_type; return NULL; } VCMGenericDecoder* ptr_decoder = NULL; @@ -579,7 +560,7 @@ VCMGenericDecoder* VCMCodecDataBase::CreateAndInitDecoder( if (external_dec_item) { // External codec. ptr_decoder = new VCMGenericDecoder( - *external_dec_item->external_decoder_instance, id_, true); + *external_dec_item->external_decoder_instance, true); *external = true; } else { // Create decoder. @@ -611,6 +592,7 @@ VCMGenericEncoder* VCMCodecDataBase::CreateEncoder( return new VCMGenericEncoder(*(new I420Encoder)); #endif default: + LOG(LS_WARNING) << "No internal encoder of this type exists."; return NULL; } } @@ -630,11 +612,11 @@ VCMGenericDecoder* VCMCodecDataBase::CreateDecoder(VideoCodecType type) const { switch (type) { #ifdef VIDEOCODEC_VP8 case kVideoCodecVP8: - return new VCMGenericDecoder(*(VP8Decoder::Create()), id_); + return new VCMGenericDecoder(*(VP8Decoder::Create())); #endif #ifdef VIDEOCODEC_I420 case kVideoCodecI420: - return new VCMGenericDecoder(*(new I420Decoder), id_); + return new VCMGenericDecoder(*(new I420Decoder)); #endif default: return NULL; diff --git a/webrtc/modules/video_coding/main/source/codec_database.h b/webrtc/modules/video_coding/main/source/codec_database.h index 2a28ed4fb..f27218f61 100644 --- a/webrtc/modules/video_coding/main/source/codec_database.h +++ b/webrtc/modules/video_coding/main/source/codec_database.h @@ -50,7 +50,7 @@ struct VCMExtDecoderMapItem { class VCMCodecDataBase { public: - explicit VCMCodecDataBase(int id); + VCMCodecDataBase(); ~VCMCodecDataBase(); // Sender Side @@ -174,7 +174,6 @@ class VCMCodecDataBase { const VCMExtDecoderMapItem* FindExternalDecoderItem( uint8_t payload_type) const; - int id_; int number_of_cores_; int max_payload_size_; bool periodic_key_frames_; diff --git a/webrtc/modules/video_coding/main/source/encoded_frame.cc b/webrtc/modules/video_coding/main/source/encoded_frame.cc index 6760762c9..3ccf0b0fd 100644 --- a/webrtc/modules/video_coding/main/source/encoded_frame.cc +++ b/webrtc/modules/video_coding/main/source/encoded_frame.cc @@ -149,17 +149,12 @@ const RTPFragmentationHeader* VCMEncodedFrame::FragmentationHeader() const { return &_fragmentation; } -int32_t -VCMEncodedFrame::VerifyAndAllocate(const uint32_t minimumSize) +void VCMEncodedFrame::VerifyAndAllocate(const uint32_t minimumSize) { if(minimumSize > _size) { // create buffer of sufficient size uint8_t* newBuffer = new uint8_t[minimumSize]; - if (newBuffer == NULL) - { - return -1; - } if(_buffer) { // copy old data @@ -169,7 +164,6 @@ VCMEncodedFrame::VerifyAndAllocate(const uint32_t minimumSize) _buffer = newBuffer; _size = minimumSize; } - return 0; } webrtc::FrameType VCMEncodedFrame::ConvertFrameType(VideoFrameType frameType) diff --git a/webrtc/modules/video_coding/main/source/encoded_frame.h b/webrtc/modules/video_coding/main/source/encoded_frame.h index 3e73be518..dd0f843d2 100644 --- a/webrtc/modules/video_coding/main/source/encoded_frame.h +++ b/webrtc/modules/video_coding/main/source/encoded_frame.h @@ -104,7 +104,7 @@ class VCMEncodedFrame : protected EncodedImage * is copied to the new buffer. * Buffer size is updated to minimumSize. */ - int32_t VerifyAndAllocate(const uint32_t minimumSize); + void VerifyAndAllocate(const uint32_t minimumSize); void Reset(); diff --git a/webrtc/modules/video_coding/main/source/frame_buffer.cc b/webrtc/modules/video_coding/main/source/frame_buffer.cc index 531c7ac11..fce68fb32 100644 --- a/webrtc/modules/video_coding/main/source/frame_buffer.cc +++ b/webrtc/modules/video_coding/main/source/frame_buffer.cc @@ -14,6 +14,7 @@ #include #include "webrtc/modules/video_coding/main/source/packet.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -86,20 +87,7 @@ VCMFrameBuffer::InsertPacket(const VCMPacket& packet, int64_t timeInMs, VCMDecodeErrorMode decode_error_mode, const FrameData& frame_data) { - // Is this packet part of this frame? - if (TimeStamp() && (TimeStamp() != packet.timestamp)) { - return kTimeStampError; - } - - // sanity checks - if (_size + packet.sizeBytes + - (packet.insertStartCode ? kH264StartCodeLengthBytes : 0 ) - > kMaxJBFrameSizeBytes) { - return kSizeError; - } - if (NULL == packet.dataPtr && packet.sizeBytes > 0) { - return kSizeError; - } + assert(!(NULL == packet.dataPtr && packet.sizeBytes > 0)); if (packet.dataPtr != NULL) { _payloadType = packet.payloadType; } @@ -108,6 +96,8 @@ VCMFrameBuffer::InsertPacket(const VCMPacket& packet, // First packet (empty and/or media) inserted into this frame. // store some info and set some initial values. _timeStamp = packet.timestamp; + // We only take the ntp timestamp of the first packet of a frame. + ntp_time_ms_ = packet.ntp_time_ms_; _codec = packet.codec; if (packet.frameType != kFrameEmpty) { // first media packet @@ -126,11 +116,11 @@ VCMFrameBuffer::InsertPacket(const VCMPacket& packet, const uint32_t newSize = _size + increments * kBufferIncStepSizeBytes; if (newSize > kMaxJBFrameSizeBytes) { + LOG(LS_ERROR) << "Failed to insert packet due to frame being too " + "big."; return kSizeError; } - if (VerifyAndAllocate(newSize) == -1) { - return kSizeError; - } + VerifyAndAllocate(newSize); _sessionInfo.UpdateDataPointers(prevBuffer, _buffer); } diff --git a/webrtc/modules/video_coding/main/source/generic_decoder.cc b/webrtc/modules/video_coding/main/source/generic_decoder.cc index 50b1eda70..cb0faf9a9 100644 --- a/webrtc/modules/video_coding/main/source/generic_decoder.cc +++ b/webrtc/modules/video_coding/main/source/generic_decoder.cc @@ -12,8 +12,7 @@ #include "webrtc/modules/video_coding/main/source/generic_decoder.h" #include "webrtc/modules/video_coding/main/source/internal_defines.h" #include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/trace.h" -#include "webrtc/system_wrappers/interface/trace_event.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -59,10 +58,11 @@ int32_t VCMDecodedFrameCallback::Decoded(I420VideoFrame& decodedImage) _timestampMap.Pop(decodedImage.timestamp())); callback = _receiveCallback; } - if (frameInfo == NULL) - { - // The map should never be empty or full if this callback is called. - return WEBRTC_VIDEO_CODEC_ERROR; + + if (frameInfo == NULL) { + LOG(LS_WARNING) << "Too many frames backed up in the decoder, dropping " + "this one."; + return WEBRTC_VIDEO_CODEC_OK; } _timing.StopDecodeTimer( @@ -73,14 +73,7 @@ int32_t VCMDecodedFrameCallback::Decoded(I420VideoFrame& decodedImage) if (callback != NULL) { decodedImage.set_render_time_ms(frameInfo->renderTimeMs); - int32_t callbackReturn = callback->FrameToRender(decodedImage); - if (callbackReturn < 0) - { - WEBRTC_TRACE(webrtc::kTraceDebug, - webrtc::kTraceVideoCoding, - -1, - "Render callback returned error: %d", callbackReturn); - } + callback->FrameToRender(decodedImage); } return WEBRTC_VIDEO_CODEC_OK; } @@ -125,15 +118,15 @@ int32_t VCMDecodedFrameCallback::Pop(uint32_t timestamp) return VCM_OK; } -VCMGenericDecoder::VCMGenericDecoder(VideoDecoder& decoder, int32_t id, bool isExternal) +VCMGenericDecoder::VCMGenericDecoder(VideoDecoder& decoder, bool isExternal) : -_id(id), _callback(NULL), _frameInfos(), _nextFrameInfoIdx(0), _decoder(decoder), _codecType(kVideoCodecUnknown), -_isExternal(isExternal) +_isExternal(isExternal), +_keyFrameDecoded(false) { } @@ -156,11 +149,6 @@ int32_t VCMGenericDecoder::Decode(const VCMEncodedFrame& frame, _frameInfos[_nextFrameInfoIdx].renderTimeMs = frame.RenderTimeMs(); _callback->Map(frame.TimeStamp(), &_frameInfos[_nextFrameInfoIdx]); - WEBRTC_TRACE(webrtc::kTraceDebug, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Decoding timestamp %u", frame.TimeStamp()); - _nextFrameInfoIdx = (_nextFrameInfoIdx + 1) % kDecoderFrameMemoryLength; int32_t ret = _decoder.Decode(frame.EncodedImage(), frame.MissingFrame(), @@ -170,7 +158,8 @@ int32_t VCMGenericDecoder::Decode(const VCMEncodedFrame& frame, if (ret < WEBRTC_VIDEO_CODEC_OK) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, VCMId(_id), "Decoder error: %d\n", ret); + LOG(LS_WARNING) << "Failed to decode frame with timestamp " + << frame.TimeStamp() << ", error code: " << ret; _callback->Pop(frame.TimeStamp()); return ret; } diff --git a/webrtc/modules/video_coding/main/source/generic_decoder.h b/webrtc/modules/video_coding/main/source/generic_decoder.h index e1993fbb9..846d4d3e1 100644 --- a/webrtc/modules/video_coding/main/source/generic_decoder.h +++ b/webrtc/modules/video_coding/main/source/generic_decoder.h @@ -63,7 +63,7 @@ class VCMGenericDecoder { friend class VCMCodecDataBase; public: - VCMGenericDecoder(VideoDecoder& decoder, int32_t id = 0, bool isExternal = false); + VCMGenericDecoder(VideoDecoder& decoder, bool isExternal = false); ~VCMGenericDecoder(); /** @@ -105,17 +105,14 @@ class VCMGenericDecoder bool External() const; -protected: - - int32_t _id; +private: VCMDecodedFrameCallback* _callback; VCMFrameInformation _frameInfos[kDecoderFrameMemoryLength]; - uint32_t _nextFrameInfoIdx; + uint32_t _nextFrameInfoIdx; VideoDecoder& _decoder; VideoCodecType _codecType; bool _isExternal; bool _keyFrameDecoded; - }; } // namespace webrtc diff --git a/webrtc/modules/video_coding/main/source/generic_encoder.cc b/webrtc/modules/video_coding/main/source/generic_encoder.cc index 68296fc98..6fb2c9f81 100644 --- a/webrtc/modules/video_coding/main/source/generic_encoder.cc +++ b/webrtc/modules/video_coding/main/source/generic_encoder.cc @@ -13,6 +13,7 @@ #include "webrtc/modules/video_coding/main/source/generic_encoder.h" #include "webrtc/modules/video_coding/main/source/media_optimization.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { namespace { @@ -83,7 +84,12 @@ VCMGenericEncoder::InitEncode(const VideoCodec* settings, _bitRate = settings->startBitrate * 1000; _frameRate = settings->maxFramerate; _codecType = settings->codecType; - return _encoder.InitEncode(settings, numberOfCores, maxPayloadSize); + if (_encoder.InitEncode(settings, numberOfCores, maxPayloadSize) != 0) { + LOG(LS_ERROR) << "Failed to initialize the encoder associated with " + "payload name: " << settings->plName; + return -1; + } + return 0; } int32_t diff --git a/webrtc/modules/video_coding/main/source/jitter_buffer.cc b/webrtc/modules/video_coding/main/source/jitter_buffer.cc index f11f81b46..d8792f21a 100644 --- a/webrtc/modules/video_coding/main/source/jitter_buffer.cc +++ b/webrtc/modules/video_coding/main/source/jitter_buffer.cc @@ -25,7 +25,6 @@ #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/event_wrapper.h" #include "webrtc/system_wrappers/interface/logging.h" -#include "webrtc/system_wrappers/interface/trace.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -77,10 +76,6 @@ int FrameList::RecycleFramesUntilKeyFrame(FrameList::iterator* key_frame_it, FrameList::iterator it = begin(); while (!empty()) { // Throw at least one frame. - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCoding, -1, - "Recycling: type=%s, low seqnum=%u", - it->second->FrameType() == kVideoFrameKey ? - "key" : "delta", it->second->GetLowSeqNum()); it->second->Reset(); free_frames->push_back(it->second); erase(it++); @@ -128,16 +123,10 @@ void FrameList::Reset(UnorderedFrameList* free_frames) { } VCMJitterBuffer::VCMJitterBuffer(Clock* clock, - EventFactory* event_factory, - int vcm_id, - int receiver_id, - bool master) - : vcm_id_(vcm_id), - receiver_id_(receiver_id), - clock_(clock), + EventFactory* event_factory) + : clock_(clock), running_(false), crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - master_(master), frame_event_(event_factory->CreateEvent()), packet_event_(event_factory->CreateEvent()), max_number_of_frames_(kStartNumberOfFrames), @@ -156,7 +145,7 @@ VCMJitterBuffer::VCMJitterBuffer(Clock* clock, num_consecutive_old_frames_(0), num_consecutive_old_packets_(0), num_discarded_packets_(0), - jitter_estimate_(vcm_id, receiver_id), + jitter_estimate_(), inter_frame_delay_(clock_->TimeInMilliseconds()), rtt_ms_(kDefaultRtt), nack_mode_(kNoNack), @@ -192,10 +181,7 @@ void VCMJitterBuffer::CopyFrom(const VCMJitterBuffer& rhs) { if (this != &rhs) { crit_sect_->Enter(); rhs.crit_sect_->Enter(); - vcm_id_ = rhs.vcm_id_; - receiver_id_ = rhs.receiver_id_; running_ = rhs.running_; - master_ = !rhs.master_; max_number_of_frames_ = rhs.max_number_of_frames_; incoming_frame_rate_ = rhs.incoming_frame_rate_; incoming_frame_count_ = rhs.incoming_frame_count_; @@ -276,10 +262,6 @@ void VCMJitterBuffer::Start() { first_packet_since_reset_ = true; rtt_ms_ = kDefaultRtt; last_decoded_state_.Reset(); - - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), "JB(0x%x): Jitter buffer: start", - this); } void VCMJitterBuffer::Stop() { @@ -300,9 +282,6 @@ void VCMJitterBuffer::Stop() { // Make sure we wake up any threads waiting on these events. frame_event_->Set(); packet_event_->Set(); - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), "JB(0x%x): Jitter buffer: stop", - this); } bool VCMJitterBuffer::Running() const { @@ -327,9 +306,6 @@ void VCMJitterBuffer::Flush() { waiting_for_completion_.latest_packet_time = -1; first_packet_since_reset_ = true; missing_sequence_numbers_.clear(); - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), "JB(0x%x): Jitter buffer: flush", - this); } // Get received key and delta frames @@ -583,6 +559,8 @@ VCMFrameBufferEnum VCMJitterBuffer::GetFrame(const VCMPacket& packet, DropPacketsFromNackList(last_decoded_state_.sequence_num()); if (num_consecutive_old_packets_ > kMaxConsecutiveOldPackets) { + LOG(LS_WARNING) << num_consecutive_old_packets_ << " consecutive old " + "packets received. Flushing the jitter buffer."; Flush(); return kFlushIndicator; } @@ -602,13 +580,13 @@ VCMFrameBufferEnum VCMJitterBuffer::GetFrame(const VCMPacket& packet, VCMFrameBufferEnum ret = kNoError; if (!*frame) { // No free frame! Try to reclaim some... - LOG_F(LS_INFO) << "Unable to get empty frame; Recycling."; + LOG(LS_WARNING) << "Unable to get empty frame; Recycling."; bool found_key_frame = RecycleFramesUntilKeyFrame(); *frame = GetEmptyFrame(); - if (!*frame) - return kGeneralError; - else if (!found_key_frame) + assert(*frame); + if (!found_key_frame) { ret = kFlushIndicator; + } } (*frame)->Reset(); return ret; @@ -650,6 +628,8 @@ VCMFrameBufferEnum VCMJitterBuffer::InsertPacket(const VCMPacket& packet, // Flush if this happens consistently. num_consecutive_old_frames_++; if (num_consecutive_old_frames_ > kMaxConsecutiveOldFrames) { + LOG(LS_WARNING) << num_consecutive_old_packets_ << " consecutive old " + "frames received. Flushing the jitter buffer."; Flush(); return kFlushIndicator; } @@ -702,8 +682,8 @@ VCMFrameBufferEnum VCMJitterBuffer::InsertPacket(const VCMPacket& packet, if (IsPacketRetransmitted(packet)) { frame->IncrementNackCount(); } - if (!UpdateNackList(packet.seqNum)) { - LOG_F(LS_INFO) << "Requesting key frame due to flushed NACK list."; + if (!UpdateNackList(packet.seqNum) && + packet.frameType != kVideoFrameKey) { buffer_return = kFlushIndicator; } latest_received_sequence_number_ = LatestSequenceNumber( @@ -725,15 +705,6 @@ VCMFrameBufferEnum VCMJitterBuffer::InsertPacket(const VCMPacket& packet, } case kCompleteSession: { if (update_decodable_list) { - if (master_) { - // Only trace the primary jitter buffer to make it possible to parse - // and plot the trace file. - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "JB(0x%x) FB(0x%x): Complete frame added to jitter" - "buffer, size:%d type %d", - this, frame, frame->Length(), frame->FrameType()); - } CountFrame(*frame); frame->SetCountedFrame(true); if (continuous) { @@ -960,8 +931,6 @@ uint16_t* VCMJitterBuffer::GetNackList(uint16_t* nack_list_size, incomplete_frames_.begin(), incomplete_frames_.end(), HasNonEmptyState); } - if (have_non_empty_frame) - LOG_F(LS_INFO) << "First frame is not key; Recycling."; bool found_key_frame = RecycleFramesUntilKeyFrame(); if (!found_key_frame) { *request_key_frame = have_non_empty_frame; @@ -977,9 +946,9 @@ uint16_t* VCMJitterBuffer::GetNackList(uint16_t* nack_list_size, int non_continuous_incomplete_duration = NonContinuousOrIncompleteDuration(); if (non_continuous_incomplete_duration > 90 * max_incomplete_time_ms_) { - LOG_F(LS_INFO) << "Too long non-decodable duration: " << - non_continuous_incomplete_duration << " > " << - 90 * max_incomplete_time_ms_; + LOG_F(LS_WARNING) << "Too long non-decodable duration: " + << non_continuous_incomplete_duration << " > " + << 90 * max_incomplete_time_ms_; FrameList::reverse_iterator rit = find_if(incomplete_frames_.rbegin(), incomplete_frames_.rend(), IsKeyFrame); if (rit == incomplete_frames_.rend()) { @@ -1038,10 +1007,12 @@ bool VCMJitterBuffer::UpdateNackList(uint16_t sequence_number) { TRACE_EVENT_INSTANT1("webrtc", "AddNack", "seqnum", i); } if (TooLargeNackList() && !HandleTooLargeNackList()) { + LOG(LS_WARNING) << "Requesting key frame due to too large NACK list."; return false; } if (MissingTooOldPacket(sequence_number) && !HandleTooOldPackets(sequence_number)) { + LOG(LS_WARNING) << "Requesting key frame due to missing too old packets"; return false; } } else { @@ -1058,8 +1029,9 @@ bool VCMJitterBuffer::TooLargeNackList() const { bool VCMJitterBuffer::HandleTooLargeNackList() { // Recycle frames until the NACK list is small enough. It is likely cheaper to // request a key frame than to retransmit this many missing packets. - LOG_F(LS_INFO) << "NACK list has grown too large: " << - missing_sequence_numbers_.size() << " > " << max_nack_list_size_; + LOG_F(LS_WARNING) << "NACK list has grown too large: " + << missing_sequence_numbers_.size() << " > " + << max_nack_list_size_; bool key_frame_found = false; while (TooLargeNackList()) { key_frame_found = RecycleFramesUntilKeyFrame(); @@ -1083,8 +1055,9 @@ bool VCMJitterBuffer::HandleTooOldPackets(uint16_t latest_sequence_number) { bool key_frame_found = false; const uint16_t age_of_oldest_missing_packet = latest_sequence_number - *missing_sequence_numbers_.begin(); - LOG_F(LS_INFO) << "NACK list contains too old sequence numbers: " << - age_of_oldest_missing_packet << " > " << max_packet_age_to_nack_; + LOG_F(LS_WARNING) << "NACK list contains too old sequence numbers: " + << age_of_oldest_missing_packet << " > " + << max_packet_age_to_nack_; while (MissingTooOldPacket(latest_sequence_number)) { key_frame_found = RecycleFramesUntilKeyFrame(); } @@ -1136,10 +1109,6 @@ bool VCMJitterBuffer::TryToIncreaseJitterBufferSize() { frame_buffers_[max_number_of_frames_] = new_frame; free_frames_.push_back(new_frame); ++max_number_of_frames_; - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "JB(0x%x) FB(0x%x): Jitter buffer increased to:%d frames", - this, new_frame, max_number_of_frames_); TRACE_COUNTER1("webrtc", "JBMaxFrames", max_number_of_frames_); return true; } @@ -1161,13 +1130,9 @@ bool VCMJitterBuffer::RecycleFramesUntilKeyFrame() { key_frame_found = key_frame_it != decodable_frames_.end(); } drop_count_ += dropped_frames; - if (dropped_frames) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "Jitter buffer drop count:%u", drop_count_); - } TRACE_EVENT_INSTANT0("webrtc", "JB::RecycleFramesUntilKeyFrame"); if (key_frame_found) { + LOG(LS_INFO) << "Found key frame while dropping frames."; // Reset last decoded state to make sure the next frame decoded is a key // frame, and start NACKing from here. last_decoded_state_.Reset(); @@ -1246,19 +1211,6 @@ void VCMJitterBuffer::UpdateJitterEstimate(const VCMJitterSample& sample, if (sample.latest_packet_time == -1) { return; } - if (incomplete_frame) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), "Received incomplete frame " - "timestamp %u frame size %u at time %u", - sample.timestamp, sample.frame_size, - MaskWord64ToUWord32(sample.latest_packet_time)); - } else { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), "Received complete frame " - "timestamp %u frame size %u at time %u", - sample.timestamp, sample.frame_size, - MaskWord64ToUWord32(sample.latest_packet_time)); - } UpdateJitterEstimate(sample.latest_packet_time, sample.timestamp, sample.frame_size, incomplete_frame); } @@ -1273,23 +1225,6 @@ void VCMJitterBuffer::UpdateJitterEstimate(const VCMFrameBuffer& frame, } // No retransmitted frames should be a part of the jitter // estimate. - if (incomplete_frame) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "Received incomplete frame timestamp %u frame type %d " - "frame size %u at time %u, jitter estimate was %u", - frame.TimeStamp(), frame.FrameType(), frame.Length(), - MaskWord64ToUWord32(frame.LatestPacketTimeMs()), - EstimatedJitterMs()); - } else { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), "Received complete frame " - "timestamp %u frame type %d frame size %u at time %u, " - "jitter estimate was %u", - frame.TimeStamp(), frame.FrameType(), frame.Length(), - MaskWord64ToUWord32(frame.LatestPacketTimeMs()), - EstimatedJitterMs()); - } UpdateJitterEstimate(frame.LatestPacketTimeMs(), frame.TimeStamp(), frame.Length(), incomplete_frame); } @@ -1306,12 +1241,6 @@ void VCMJitterBuffer::UpdateJitterEstimate( return; } int64_t frame_delay; - // Calculate the delay estimate - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "Packet received and sent to jitter estimate with: " - "timestamp=%u wall_clock=%u", timestamp, - MaskWord64ToUWord32(latest_packet_time_ms)); bool not_reordered = inter_frame_delay_.CalculateDelay(timestamp, &frame_delay, latest_packet_time_ms); diff --git a/webrtc/modules/video_coding/main/source/jitter_buffer.h b/webrtc/modules/video_coding/main/source/jitter_buffer.h index 8586f115f..6ed9cfb85 100644 --- a/webrtc/modules/video_coding/main/source/jitter_buffer.h +++ b/webrtc/modules/video_coding/main/source/jitter_buffer.h @@ -16,6 +16,7 @@ #include #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/modules/video_coding/main/interface/video_coding.h" #include "webrtc/modules/video_coding/main/interface/video_coding_defines.h" @@ -23,7 +24,6 @@ #include "webrtc/modules/video_coding/main/source/inter_frame_delay.h" #include "webrtc/modules/video_coding/main/source/jitter_buffer_common.h" #include "webrtc/modules/video_coding/main/source/jitter_estimator.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/typedefs.h" @@ -77,10 +77,7 @@ class FrameList class VCMJitterBuffer { public: VCMJitterBuffer(Clock* clock, - EventFactory* event_factory, - int vcm_id, - int receiver_id, - bool master); + EventFactory* event_factory); virtual ~VCMJitterBuffer(); // Makes |this| a deep copy of |rhs|. @@ -274,13 +271,10 @@ class VCMJitterBuffer { uint16_t EstimatedLowSequenceNumber(const VCMFrameBuffer& frame) const; - int vcm_id_; - int receiver_id_; Clock* clock_; // If we are running (have started) or not. bool running_; CriticalSectionWrapper* crit_sect_; - bool master_; // Event to signal when we have a frame ready for decoder. scoped_ptr frame_event_; // Event to signal when we have received a packet. diff --git a/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc b/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc index e535a8a40..0490658b4 100644 --- a/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc +++ b/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc @@ -27,8 +27,8 @@ class TestBasicJitterBuffer : public ::testing::Test { protected: virtual void SetUp() { clock_.reset(new SimulatedClock(0)); - jitter_buffer_.reset(new VCMJitterBuffer(clock_.get(), - &event_factory_, -1, -1, true)); + jitter_buffer_.reset( + new VCMJitterBuffer(clock_.get(), &event_factory_)); jitter_buffer_->Start(); seq_num_ = 1234; timestamp_ = 0; @@ -126,8 +126,7 @@ class TestRunningJitterBuffer : public ::testing::Test { clock_.reset(new SimulatedClock(0)); max_nack_list_size_ = 150; oldest_packet_to_nack_ = 250; - jitter_buffer_ = new VCMJitterBuffer(clock_.get(), &event_factory_, -1, -1, - true); + jitter_buffer_ = new VCMJitterBuffer(clock_.get(), &event_factory_); stream_generator_ = new StreamGenerator(0, 0, clock_->TimeInMilliseconds()); jitter_buffer_->Start(); jitter_buffer_->SetNackSettings(max_nack_list_size_, @@ -2034,4 +2033,31 @@ TEST_F(TestJitterBufferNack, NormalOperationWrap2) { EXPECT_EQ(65535, list[0]); } +TEST_F(TestJitterBufferNack, ResetByFutureKeyFrameDoesntError) { + stream_generator_->Init(0, 0, clock_->TimeInMilliseconds()); + InsertFrame(kVideoFrameKey); + EXPECT_TRUE(DecodeCompleteFrame()); + uint16_t nack_list_size = 0; + bool extended = false; + jitter_buffer_->GetNackList(&nack_list_size, &extended); + EXPECT_EQ(0, nack_list_size); + + // Far-into-the-future video frame, could be caused by resetting the encoder + // or otherwise restarting. This should not fail when error when the packet is + // a keyframe, even if all of the nack list needs to be flushed. + stream_generator_->Init(10000, 0, clock_->TimeInMilliseconds()); + clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs); + InsertFrame(kVideoFrameKey); + EXPECT_TRUE(DecodeCompleteFrame()); + jitter_buffer_->GetNackList(&nack_list_size, &extended); + EXPECT_EQ(0, nack_list_size); + + // Stream should be decodable from this point. + clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs); + InsertFrame(kVideoFrameDelta); + EXPECT_TRUE(DecodeCompleteFrame()); + jitter_buffer_->GetNackList(&nack_list_size, &extended); + EXPECT_EQ(0, nack_list_size); +} + } // namespace webrtc diff --git a/webrtc/modules/video_coding/main/source/jitter_estimator.cc b/webrtc/modules/video_coding/main/source/jitter_estimator.cc index 083a9e69c..71c54a00c 100644 --- a/webrtc/modules/video_coding/main/source/jitter_estimator.cc +++ b/webrtc/modules/video_coding/main/source/jitter_estimator.cc @@ -11,7 +11,6 @@ #include "webrtc/modules/video_coding/main/source/internal_defines.h" #include "webrtc/modules/video_coding/main/source/jitter_estimator.h" #include "webrtc/modules/video_coding/main/source/rtt_filter.h" -#include "webrtc/system_wrappers/interface/trace.h" #include #include @@ -20,20 +19,20 @@ namespace webrtc { -VCMJitterEstimator::VCMJitterEstimator(int32_t vcmId, int32_t receiverId) : -_vcmId(vcmId), -_receiverId(receiverId), -_phi(0.97), -_psi(0.9999), -_alphaCountMax(400), -_thetaLow(0.000001), -_nackLimit(3), -_numStdDevDelayOutlier(15), -_numStdDevFrameSizeOutlier(3), -_noiseStdDevs(2.33), // ~Less than 1% chance - // (look up in normal distribution table)... -_noiseStdDevOffset(30.0), // ...of getting 30 ms freezes -_rttFilter(vcmId, receiverId) { +VCMJitterEstimator::VCMJitterEstimator(int32_t vcmId, int32_t receiverId) + : _vcmId(vcmId), + _receiverId(receiverId), + _phi(0.97), + _psi(0.9999), + _alphaCountMax(400), + _thetaLow(0.000001), + _nackLimit(3), + _numStdDevDelayOutlier(15), + _numStdDevFrameSizeOutlier(3), + _noiseStdDevs(2.33), // ~Less than 1% chance + // (look up in normal distribution table)... + _noiseStdDevOffset(30.0), // ...of getting 30 ms freezes + _rttFilter() { Reset(); } @@ -108,10 +107,6 @@ void VCMJitterEstimator::UpdateEstimate(int64_t frameDelayMS, uint32_t frameSizeBytes, bool incompleteFrame /* = false */) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(_vcmId, _receiverId), - "Jitter estimate updated with: frameSize=%d frameDelayMS=%d", - frameSizeBytes, frameDelayMS); if (frameSizeBytes == 0) { return; @@ -195,16 +190,6 @@ VCMJitterEstimator::UpdateEstimate(int64_t frameDelayMS, uint32_t frameSizeBytes { _startupCount++; } - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "Framesize statistics: max=%f average=%f", _maxFrameSize, _avgFrameSize); - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "The estimated slope is: theta=(%f, %f)", _theta[0], _theta[1]); - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "Random jitter: mean=%f variance=%f", _avgNoise, _varNoise); - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "Current jitter estimate: %f", _filterJitterEstimate); - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "Current max RTT: %u", _rttFilter.RttMs()); } // Updates the nack/packet ratio diff --git a/webrtc/modules/video_coding/main/source/media_optimization.cc b/webrtc/modules/video_coding/main/source/media_optimization.cc index aeffac255..4dc72253b 100644 --- a/webrtc/modules/video_coding/main/source/media_optimization.cc +++ b/webrtc/modules/video_coding/main/source/media_optimization.cc @@ -14,6 +14,7 @@ #include "webrtc/modules/video_coding/main/source/qm_select.h" #include "webrtc/modules/video_coding/utility/include/frame_dropper.h" #include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { namespace media_optimization { @@ -73,9 +74,8 @@ struct MediaOptimization::EncodedFrameSample { int64_t time_complete_ms; }; -MediaOptimization::MediaOptimization(int32_t id, Clock* clock) - : id_(id), - clock_(clock), +MediaOptimization::MediaOptimization(Clock* clock) + : clock_(clock), max_bit_rate_(0), send_codec_type_(kVideoCodecUnknown), codec_width_(0), @@ -537,13 +537,9 @@ bool MediaOptimization::QMUpdate( codec_height_ = qm->codec_height; } - WEBRTC_TRACE(webrtc::kTraceDebug, - webrtc::kTraceVideoCoding, - id_, - "Resolution change from QM select: W = %d, H = %d, FR = %f", - qm->codec_width, - qm->codec_height, - qm->frame_rate); + LOG(LS_INFO) << "Media optimizer requests the video resolution to be changed " + "to " << qm->codec_width << "x" << qm->codec_height << "@" + << qm->frame_rate; // Update VPM with new target frame rate and frame size. // Note: use |qm->frame_rate| instead of |_incoming_frame_rate| for updating diff --git a/webrtc/modules/video_coding/main/source/media_optimization.h b/webrtc/modules/video_coding/main/source/media_optimization.h index 3c3c8e445..35a497125 100644 --- a/webrtc/modules/video_coding/main/source/media_optimization.h +++ b/webrtc/modules/video_coding/main/source/media_optimization.h @@ -18,7 +18,6 @@ #include "webrtc/modules/video_coding/main/source/media_opt_util.h" #include "webrtc/modules/video_coding/main/source/qm_select.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" -#include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { @@ -32,7 +31,7 @@ namespace media_optimization { // TODO(andresp): Make thread safe. class MediaOptimization { public: - MediaOptimization(int32_t id, Clock* clock); + explicit MediaOptimization(Clock* clock); ~MediaOptimization(); // TODO(andresp): Can Reset and SetEncodingData be done at construction time @@ -124,7 +123,6 @@ class MediaOptimization { // the state of |video_suspended_| accordingly. void CheckSuspendConditions(); - int32_t id_; Clock* clock_; int32_t max_bit_rate_; VideoCodecType send_codec_type_; diff --git a/webrtc/modules/video_coding/main/source/media_optimization_unittest.cc b/webrtc/modules/video_coding/main/source/media_optimization_unittest.cc index d58ada651..bacfdc604 100644 --- a/webrtc/modules/video_coding/main/source/media_optimization_unittest.cc +++ b/webrtc/modules/video_coding/main/source/media_optimization_unittest.cc @@ -17,9 +17,6 @@ namespace media_optimization { class TestMediaOptimization : public ::testing::Test { protected: - enum { - kId = 4711 // Id number for the MediaOptimization class. - }; enum { kSampleRate = 90000 // RTP timestamps per second. }; @@ -28,7 +25,7 @@ class TestMediaOptimization : public ::testing::Test { // a special case (e.g. frame rate in media optimization). TestMediaOptimization() : clock_(1000), - media_opt_(kId, &clock_), + media_opt_(&clock_), frame_time_ms_(33), next_timestamp_(0) {} diff --git a/webrtc/modules/video_coding/main/source/packet.cc b/webrtc/modules/video_coding/main/source/packet.cc index 61ef2ee85..c1f1a048e 100644 --- a/webrtc/modules/video_coding/main/source/packet.cc +++ b/webrtc/modules/video_coding/main/source/packet.cc @@ -19,6 +19,7 @@ VCMPacket::VCMPacket() : payloadType(0), timestamp(0), + ntp_time_ms_(0), seqNum(0), dataPtr(NULL), sizeBytes(0), @@ -38,6 +39,7 @@ VCMPacket::VCMPacket(const uint8_t* ptr, const WebRtcRTPHeader& rtpHeader) : payloadType(rtpHeader.header.payloadType), timestamp(rtpHeader.header.timestamp), + ntp_time_ms_(rtpHeader.ntp_time_ms), seqNum(rtpHeader.header.sequenceNumber), dataPtr(ptr), sizeBytes(size), @@ -58,6 +60,7 @@ VCMPacket::VCMPacket(const uint8_t* ptr, VCMPacket::VCMPacket(const uint8_t* ptr, uint32_t size, uint16_t seq, uint32_t ts, bool mBit) : payloadType(0), timestamp(ts), + ntp_time_ms_(0), seqNum(seq), dataPtr(ptr), sizeBytes(size), @@ -76,6 +79,7 @@ VCMPacket::VCMPacket(const uint8_t* ptr, uint32_t size, uint16_t seq, uint32_t t void VCMPacket::Reset() { payloadType = 0; timestamp = 0; + ntp_time_ms_ = 0; seqNum = 0; dataPtr = NULL; sizeBytes = 0; diff --git a/webrtc/modules/video_coding/main/source/packet.h b/webrtc/modules/video_coding/main/source/packet.h index e9a81bb1f..242d3a431 100644 --- a/webrtc/modules/video_coding/main/source/packet.h +++ b/webrtc/modules/video_coding/main/source/packet.h @@ -33,6 +33,8 @@ class VCMPacket { uint8_t payloadType; uint32_t timestamp; + // NTP time of the capture time in local timebase in milliseconds. + int64_t ntp_time_ms_; uint16_t seqNum; const uint8_t* dataPtr; uint32_t sizeBytes; diff --git a/webrtc/modules/video_coding/main/source/receiver.cc b/webrtc/modules/video_coding/main/source/receiver.cc index e95974659..e179423a7 100644 --- a/webrtc/modules/video_coding/main/source/receiver.cc +++ b/webrtc/modules/video_coding/main/source/receiver.cc @@ -18,7 +18,7 @@ #include "webrtc/modules/video_coding/main/source/internal_defines.h" #include "webrtc/modules/video_coding/main/source/media_opt_util.h" #include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -28,15 +28,11 @@ enum { kMaxReceiverDelayMs = 10000 }; VCMReceiver::VCMReceiver(VCMTiming* timing, Clock* clock, EventFactory* event_factory, - int32_t vcm_id, - int32_t receiver_id, bool master) : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - vcm_id_(vcm_id), clock_(clock), - receiver_id_(receiver_id), master_(master), - jitter_buffer_(clock_, event_factory, vcm_id, receiver_id, master), + jitter_buffer_(clock_, event_factory), timing_(timing), render_wait_event_(event_factory->CreateEvent()), state_(kPassive), @@ -78,13 +74,6 @@ void VCMReceiver::UpdateRtt(uint32_t rtt) { int32_t VCMReceiver::InsertPacket(const VCMPacket& packet, uint16_t frame_width, uint16_t frame_height) { - if (packet.frameType == kVideoFrameKey) { - WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "Inserting key frame packet seqnum=%u, timestamp=%u", - packet.seqNum, packet.timestamp); - } - // Insert the packet into the jitter buffer. The packet can either be empty or // contain media at this point. bool retransmitted = false; @@ -95,10 +84,6 @@ int32_t VCMReceiver::InsertPacket(const VCMPacket& packet, } else if (ret == kFlushIndicator) { return VCM_FLUSH_INDICATOR; } else if (ret < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "Error inserting packet seqnum=%u, timestamp=%u", - packet.seqNum, packet.timestamp); return VCM_JITTER_BUFFER_ERROR; } if (ret == kCompleteSession && !retransmitted) { @@ -107,15 +92,6 @@ int32_t VCMReceiver::InsertPacket(const VCMPacket& packet, // delay within the jitter estimate. timing_->IncomingTimestamp(packet.timestamp, clock_->TimeInMilliseconds()); } - if (master_) { - // Only trace the primary receiver to make it possible to parse and plot - // the trace file. - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "Packet seqnum=%u timestamp=%u inserted at %u", - packet.seqNum, packet.timestamp, - MaskWord64ToUWord32(clock_->TimeInMilliseconds())); - } return VCM_OK; } @@ -159,19 +135,16 @@ VCMEncodedFrame* VCMReceiver::FrameForDecoding( if (next_render_time_ms < 0) { timing_error = true; } else if (std::abs(next_render_time_ms - now_ms) > max_video_delay_ms_) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "This frame is out of our delay bounds, resetting jitter " - "buffer: %d > %d", - static_cast(std::abs(next_render_time_ms - now_ms)), - max_video_delay_ms_); + int frame_delay = static_cast(std::abs(next_render_time_ms - now_ms)); + LOG(LS_WARNING) << "A frame about to be decoded is out of the configured " + << "delay bounds (" << frame_delay << " > " + << max_video_delay_ms_ + << "). Resetting the video jitter buffer."; timing_error = true; } else if (static_cast(timing_->TargetVideoDelay()) > max_video_delay_ms_) { - WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, receiver_id_), - "More than %u ms target delay. Flushing jitter buffer and" - "resetting timing.", max_video_delay_ms_); + LOG(LS_WARNING) << "The video target delay has grown larger than " + << max_video_delay_ms_ << " ms. Resetting jitter buffer."; timing_error = true; } @@ -280,10 +253,7 @@ VCMNackStatus VCMReceiver::NackList(uint16_t* nack_list, bool request_key_frame = false; uint16_t* internal_nack_list = jitter_buffer_.GetNackList( nack_list_length, &request_key_frame); - if (*nack_list_length > size) { - *nack_list_length = 0; - return kNackNeedMoreMemory; - } + assert(*nack_list_length <= size); if (internal_nack_list != NULL && *nack_list_length > 0) { memcpy(nack_list, internal_nack_list, *nack_list_length * sizeof(uint16_t)); } diff --git a/webrtc/modules/video_coding/main/source/receiver.h b/webrtc/modules/video_coding/main/source/receiver.h index ac510ea22..c03730397 100644 --- a/webrtc/modules/video_coding/main/source/receiver.h +++ b/webrtc/modules/video_coding/main/source/receiver.h @@ -25,7 +25,6 @@ class VCMEncodedFrame; enum VCMNackStatus { kNackOk, - kNackNeedMoreMemory, kNackKeyFrameRequest }; @@ -40,8 +39,6 @@ class VCMReceiver { VCMReceiver(VCMTiming* timing, Clock* clock, EventFactory* event_factory, - int32_t vcm_id, - int32_t receiver_id, bool master); ~VCMReceiver(); @@ -95,9 +92,7 @@ class VCMReceiver { static int32_t GenerateReceiverId(); CriticalSectionWrapper* crit_sect_; - int32_t vcm_id_; Clock* clock_; - int32_t receiver_id_; bool master_; VCMJitterBuffer jitter_buffer_; VCMTiming* timing_; diff --git a/webrtc/modules/video_coding/main/source/receiver_unittest.cc b/webrtc/modules/video_coding/main/source/receiver_unittest.cc index 33a3d95f9..e80b9cc9e 100644 --- a/webrtc/modules/video_coding/main/source/receiver_unittest.cc +++ b/webrtc/modules/video_coding/main/source/receiver_unittest.cc @@ -31,7 +31,7 @@ class TestVCMReceiver : public ::testing::Test { TestVCMReceiver() : clock_(new SimulatedClock(0)), timing_(clock_.get()), - receiver_(&timing_, clock_.get(), &event_factory_, 1, 1, true) { + receiver_(&timing_, clock_.get(), &event_factory_, true) { stream_generator_.reset(new StreamGenerator(0, 0, clock_->TimeInMilliseconds())); memset(data_buffer_, 0, kDataBufferSize); diff --git a/webrtc/modules/video_coding/main/source/rtt_filter.cc b/webrtc/modules/video_coding/main/source/rtt_filter.cc index 473c8869a..739cc824c 100644 --- a/webrtc/modules/video_coding/main/source/rtt_filter.cc +++ b/webrtc/modules/video_coding/main/source/rtt_filter.cc @@ -10,7 +10,6 @@ #include "webrtc/modules/video_coding/main/source/internal_defines.h" #include "webrtc/modules/video_coding/main/source/rtt_filter.h" -#include "webrtc/system_wrappers/interface/trace.h" #include #include @@ -18,15 +17,11 @@ namespace webrtc { -VCMRttFilter::VCMRttFilter(int32_t vcmId, int32_t receiverId) -: -_vcmId(vcmId), -_receiverId(receiverId), -_filtFactMax(35), -_jumpStdDevs(2.5), -_driftStdDevs(3.5), -_detectThreshold(kMaxDriftJumpCount) -{ +VCMRttFilter::VCMRttFilter() + : _filtFactMax(35), + _jumpStdDevs(2.5), + _driftStdDevs(3.5), + _detectThreshold(kMaxDriftJumpCount) { Reset(); } @@ -105,9 +100,6 @@ VCMRttFilter::Update(uint32_t rttMs) _avgRtt = oldAvg; _varRtt = oldVar; } - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "RttFilter Update: sample=%u avgRtt=%f varRtt=%f maxRtt=%u", - rttMs, _avgRtt, _varRtt, _maxRtt); } bool @@ -141,8 +133,6 @@ VCMRttFilter::JumpDetection(uint32_t rttMs) ShortRttFilter(_jumpBuf, abs(_jumpCount)); _filtFactCount = _detectThreshold + 1; _jumpCount = 0; - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "Detected an RTT jump"); } else { @@ -174,8 +164,6 @@ VCMRttFilter::DriftDetection(uint32_t rttMs) ShortRttFilter(_driftBuf, _driftCount); _filtFactCount = _detectThreshold + 1; _driftCount = 0; - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _receiverId), - "Detected an RTT drift"); } } else diff --git a/webrtc/modules/video_coding/main/source/rtt_filter.h b/webrtc/modules/video_coding/main/source/rtt_filter.h index 9ce3798b0..8b816a0b4 100644 --- a/webrtc/modules/video_coding/main/source/rtt_filter.h +++ b/webrtc/modules/video_coding/main/source/rtt_filter.h @@ -19,7 +19,7 @@ namespace webrtc class VCMRttFilter { public: - VCMRttFilter(int32_t vcmId = 0, int32_t receiverId = 0); + VCMRttFilter(); VCMRttFilter& operator=(const VCMRttFilter& rhs); @@ -48,8 +48,6 @@ class VCMRttFilter // Computes the short time average and maximum of the vector buf. void ShortRttFilter(uint32_t* buf, uint32_t length); - int32_t _vcmId; - int32_t _receiverId; bool _gotNonZeroUpdate; double _avgRtt; double _varRtt; diff --git a/webrtc/modules/video_coding/main/source/session_info.cc b/webrtc/modules/video_coding/main/source/session_info.cc index 1cb60d39b..dab3da138 100644 --- a/webrtc/modules/video_coding/main/source/session_info.cc +++ b/webrtc/modules/video_coding/main/source/session_info.cc @@ -11,6 +11,7 @@ #include "webrtc/modules/video_coding/main/source/session_info.h" #include "webrtc/modules/video_coding/main/source/packet.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { @@ -403,6 +404,7 @@ int VCMSessionInfo::InsertPacket(const VCMPacket& packet, } if (packets_.size() == kMaxPacketsInSession) { + LOG(LS_ERROR) << "Max number of packets per frame has been reached."; return -1; } @@ -429,6 +431,8 @@ int VCMSessionInfo::InsertPacket(const VCMPacket& packet, first_packet_seq_num_ = static_cast(packet.seqNum); } else if (first_packet_seq_num_ != -1 && !IsNewerSequenceNumber(packet.seqNum, first_packet_seq_num_)) { + LOG(LS_WARNING) << "Received packet with a sequence number which is out of" + "frame boundaries"; return -3; } else if (frame_type_ == kFrameEmpty && packet.frameType != kFrameEmpty) { // Update the frame type with the type of the first media packet. @@ -441,6 +445,8 @@ int VCMSessionInfo::InsertPacket(const VCMPacket& packet, last_packet_seq_num_ = static_cast(packet.seqNum); } else if (last_packet_seq_num_ != -1 && IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_)) { + LOG(LS_WARNING) << "Received packet with a sequence number which is out of" + "frame boundaries"; return -3; } diff --git a/webrtc/modules/video_coding/main/source/session_info.h b/webrtc/modules/video_coding/main/source/session_info.h index 039f09763..cae3ee138 100644 --- a/webrtc/modules/video_coding/main/source/session_info.h +++ b/webrtc/modules/video_coding/main/source/session_info.h @@ -147,7 +147,6 @@ class VCMSessionInfo { bool complete_; bool decodable_; webrtc::FrameType frame_type_; - bool previous_frame_loss_; // Packets in this frame. PacketList packets_; int empty_seq_num_low_; diff --git a/webrtc/modules/video_coding/main/source/timing.cc b/webrtc/modules/video_coding/main/source/timing.cc index 98a69e962..af0e35c4e 100644 --- a/webrtc/modules/video_coding/main/source/timing.cc +++ b/webrtc/modules/video_coding/main/source/timing.cc @@ -10,25 +10,18 @@ #include "webrtc/modules/video_coding/main/source/timing.h" - #include "webrtc/modules/video_coding/main/source/internal_defines.h" #include "webrtc/modules/video_coding/main/source/jitter_buffer_common.h" -#include "webrtc/modules/video_coding/main/source/timestamp_extrapolator.h" #include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/trace.h" - +#include "webrtc/system_wrappers/interface/timestamp_extrapolator.h" namespace webrtc { VCMTiming::VCMTiming(Clock* clock, - int32_t vcm_id, - int32_t timing_id, VCMTiming* master_timing) : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - vcm_id_(vcm_id), clock_(clock), - timing_id_(timing_id), master_(false), ts_extrapolator_(), codec_timer_(), @@ -40,7 +33,7 @@ VCMTiming::VCMTiming(Clock* clock, prev_frame_timestamp_(0) { if (master_timing == NULL) { master_ = true; - ts_extrapolator_ = new VCMTimestampExtrapolator(clock_, vcm_id, timing_id); + ts_extrapolator_ = new TimestampExtrapolator(clock_->TimeInMilliseconds()); } else { ts_extrapolator_ = master_timing->ts_extrapolator_; } @@ -55,7 +48,7 @@ VCMTiming::~VCMTiming() { void VCMTiming::Reset() { CriticalSectionScoped cs(crit_sect_); - ts_extrapolator_->Reset(); + ts_extrapolator_->Reset(clock_->TimeInMilliseconds()); codec_timer_.Reset(); render_delay_ms_ = kDefaultRenderDelayMs; min_playout_delay_ms_ = 0; @@ -81,11 +74,6 @@ void VCMTiming::set_min_playout_delay(uint32_t min_playout_delay_ms) { void VCMTiming::SetJitterDelay(uint32_t jitter_delay_ms) { CriticalSectionScoped cs(crit_sect_); if (jitter_delay_ms != jitter_delay_ms_) { - if (master_) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, timing_id_), - "Desired jitter buffer level: %u ms", jitter_delay_ms); - } jitter_delay_ms_ = jitter_delay_ms; // When in initial state, set current delay to minimum delay. if (current_delay_ms_ == 0) { @@ -152,39 +140,21 @@ int32_t VCMTiming::StopDecodeTimer(uint32_t time_stamp, int64_t start_time_ms, int64_t now_ms) { CriticalSectionScoped cs(crit_sect_); - const int32_t max_dec_time = MaxDecodeTimeMs(); int32_t time_diff_ms = codec_timer_.StopTimer(start_time_ms, now_ms); - if (time_diff_ms < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, VCMId(vcm_id_, - timing_id_), "Codec timer error: %d", time_diff_ms); - assert(false); - } + assert(time_diff_ms >= 0); last_decode_ms_ = time_diff_ms; - if (master_) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(vcm_id_, - timing_id_), - "Frame decoded: time_stamp=%u dec_time=%d max_dec_time=%u, at %u", - time_stamp, time_diff_ms, max_dec_time, MaskWord64ToUWord32(now_ms)); - } return 0; } void VCMTiming::IncomingTimestamp(uint32_t time_stamp, int64_t now_ms) { CriticalSectionScoped cs(crit_sect_); - ts_extrapolator_->Update(now_ms, time_stamp, master_); + ts_extrapolator_->Update(now_ms, time_stamp); } int64_t VCMTiming::RenderTimeMs(uint32_t frame_timestamp, int64_t now_ms) const { CriticalSectionScoped cs(crit_sect_); const int64_t render_time_ms = RenderTimeMsInternal(frame_timestamp, now_ms); - if (master_) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(vcm_id_, - timing_id_), "Render frame %u at %u. Render delay %u", - "jitter delay %u, max decode time %u, playout delay %u", - frame_timestamp, MaskWord64ToUWord32(render_time_ms), render_delay_ms_, - jitter_delay_ms_, MaxDecodeTimeMs(), min_playout_delay_ms_); - } return render_time_ms; } @@ -192,11 +162,6 @@ int64_t VCMTiming::RenderTimeMsInternal(uint32_t frame_timestamp, int64_t now_ms) const { int64_t estimated_complete_time_ms = ts_extrapolator_->ExtrapolateLocalTime(frame_timestamp); - if (master_) { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, timing_id_), "ExtrapolateLocalTime(%u)=%u ms", - frame_timestamp, MaskWord64ToUWord32(estimated_complete_time_ms)); - } if (estimated_complete_time_ms == -1) { estimated_complete_time_ms = now_ms; } @@ -210,11 +175,7 @@ int64_t VCMTiming::RenderTimeMsInternal(uint32_t frame_timestamp, int32_t VCMTiming::MaxDecodeTimeMs(FrameType frame_type /*= kVideoFrameDelta*/) const { const int32_t decode_time_ms = codec_timer_.RequiredDecodeTimeMs(frame_type); - if (decode_time_ms < 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, VCMId(vcm_id_, - timing_id_), "Negative maximum decode time: %d", decode_time_ms); - return -1; - } + assert(decode_time_ms >= 0); return decode_time_ms; } @@ -254,11 +215,6 @@ uint32_t VCMTiming::TargetVideoDelay() const { } uint32_t VCMTiming::TargetDelayInternal() const { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, - VCMId(vcm_id_, timing_id_), - "Delay: min_playout=%u jitter=%u max_decode=%u render=%u", - min_playout_delay_ms_, jitter_delay_ms_, MaxDecodeTimeMs(), - render_delay_ms_); return std::max(min_playout_delay_ms_, jitter_delay_ms_ + MaxDecodeTimeMs() + render_delay_ms_); } diff --git a/webrtc/modules/video_coding/main/source/timing.h b/webrtc/modules/video_coding/main/source/timing.h index eb251b711..1dca5e605 100644 --- a/webrtc/modules/video_coding/main/source/timing.h +++ b/webrtc/modules/video_coding/main/source/timing.h @@ -18,15 +18,13 @@ namespace webrtc { class Clock; -class VCMTimestampExtrapolator; +class TimestampExtrapolator; class VCMTiming { public: // The primary timing component should be passed // if this is the dual timing component. VCMTiming(Clock* clock, - int32_t vcm_id = 0, - int32_t timing_id = 0, VCMTiming* master_timing = NULL); ~VCMTiming(); @@ -101,11 +99,9 @@ class VCMTiming { private: CriticalSectionWrapper* crit_sect_; - int32_t vcm_id_; Clock* clock_; - int32_t timing_id_; bool master_; - VCMTimestampExtrapolator* ts_extrapolator_; + TimestampExtrapolator* ts_extrapolator_; VCMCodecTimer codec_timer_; uint32_t render_delay_ms_; uint32_t min_playout_delay_ms_; diff --git a/webrtc/modules/video_coding/main/source/video_coding.gypi b/webrtc/modules/video_coding/main/source/video_coding.gypi index b4f6cb7b1..f19a58552 100644 --- a/webrtc/modules/video_coding/main/source/video_coding.gypi +++ b/webrtc/modules/video_coding/main/source/video_coding.gypi @@ -48,7 +48,6 @@ 'receiver.h', 'rtt_filter.h', 'session_info.h', - 'timestamp_extrapolator.h', 'timestamp_map.h', 'timing.h', 'video_coding_impl.h', @@ -72,7 +71,6 @@ 'receiver.cc', 'rtt_filter.cc', 'session_info.cc', - 'timestamp_extrapolator.cc', 'timestamp_map.cc', 'timing.cc', 'video_coding_impl.cc', diff --git a/webrtc/modules/video_coding/main/source/video_coding_impl.cc b/webrtc/modules/video_coding/main/source/video_coding_impl.cc index aea562d98..5b93a6567 100644 --- a/webrtc/modules/video_coding/main/source/video_coding_impl.cc +++ b/webrtc/modules/video_coding/main/source/video_coding_impl.cc @@ -16,7 +16,6 @@ #include "webrtc/modules/video_coding/main/source/packet.h" #include "webrtc/modules/video_coding/main/source/video_coding_impl.h" #include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/trace.h" #include "webrtc/system_wrappers/interface/trace_event.h" namespace webrtc { @@ -77,13 +76,12 @@ class EncodedImageCallbackWrapper : public EncodedImageCallback { class VideoCodingModuleImpl : public VideoCodingModule { public: - VideoCodingModuleImpl(const int32_t id, - Clock* clock, + VideoCodingModuleImpl(Clock* clock, EventFactory* event_factory, bool owns_event_factory) : VideoCodingModule(), - sender_(new vcm::VideoSender(id, clock, &post_encode_callback_)), - receiver_(new vcm::VideoReceiver(id, clock, event_factory)), + sender_(new vcm::VideoSender(clock, &post_encode_callback_)), + receiver_(new vcm::VideoReceiver(clock, event_factory)), own_event_factory_(owns_event_factory ? event_factory : NULL) {} virtual ~VideoCodingModuleImpl() { @@ -386,17 +384,16 @@ int32_t VideoCodingModule::Codec(VideoCodecType codecType, VideoCodec* codec) { return VCMCodecDataBase::Codec(codecType, codec) ? 0 : -1; } -VideoCodingModule* VideoCodingModule::Create(const int32_t id) { +VideoCodingModule* VideoCodingModule::Create() { return new VideoCodingModuleImpl( - id, Clock::GetRealTimeClock(), new EventFactoryImpl, true); + Clock::GetRealTimeClock(), new EventFactoryImpl, true); } -VideoCodingModule* VideoCodingModule::Create(const int32_t id, - Clock* clock, +VideoCodingModule* VideoCodingModule::Create(Clock* clock, EventFactory* event_factory) { assert(clock); assert(event_factory); - return new VideoCodingModuleImpl(id, clock, event_factory, false); + return new VideoCodingModuleImpl(clock, event_factory, false); } void VideoCodingModule::Destroy(VideoCodingModule* module) { diff --git a/webrtc/modules/video_coding/main/source/video_coding_impl.h b/webrtc/modules/video_coding/main/source/video_coding_impl.h index 13fae2b92..bf0bc7905 100644 --- a/webrtc/modules/video_coding/main/source/video_coding_impl.h +++ b/webrtc/modules/video_coding/main/source/video_coding_impl.h @@ -54,9 +54,7 @@ class VideoSender { public: typedef VideoCodingModule::SenderNackMode SenderNackMode; - VideoSender(const int32_t id, - Clock* clock, - EncodedImageCallback* post_encode_callback); + VideoSender(Clock* clock, EncodedImageCallback* post_encode_callback); ~VideoSender(); @@ -110,7 +108,6 @@ class VideoSender { int32_t Process(); private: - int32_t _id; Clock* clock_; scoped_ptr recorder_; @@ -134,7 +131,7 @@ class VideoReceiver { public: typedef VideoCodingModule::ReceiverRobustness ReceiverRobustness; - VideoReceiver(const int32_t id, Clock* clock, EventFactory* event_factory); + VideoReceiver(Clock* clock, EventFactory* event_factory); ~VideoReceiver(); int32_t InitializeReceiver(); @@ -202,7 +199,6 @@ class VideoReceiver { // in any frame }; - int32_t _id; Clock* clock_; scoped_ptr process_crit_sect_; CriticalSectionWrapper* _receiveCritSect; diff --git a/webrtc/modules/video_coding/main/source/video_coding_robustness_unittest.cc b/webrtc/modules/video_coding/main/source/video_coding_robustness_unittest.cc index fbb511448..435e18202 100644 --- a/webrtc/modules/video_coding/main/source/video_coding_robustness_unittest.cc +++ b/webrtc/modules/video_coding/main/source/video_coding_robustness_unittest.cc @@ -35,7 +35,7 @@ class VCMRobustnessTest : public ::testing::Test { virtual void SetUp() { clock_.reset(new SimulatedClock(0)); ASSERT_TRUE(clock_.get() != NULL); - vcm_ = VideoCodingModule::Create(0, clock_.get(), &event_factory_); + vcm_ = VideoCodingModule::Create(clock_.get(), &event_factory_); ASSERT_TRUE(vcm_ != NULL); ASSERT_EQ(0, vcm_->InitializeReceiver()); const size_t kMaxNackListSize = 250; diff --git a/webrtc/modules/video_coding/main/source/video_coding_test.gypi b/webrtc/modules/video_coding/main/source/video_coding_test.gypi index a64e02d02..b0fe510cf 100644 --- a/webrtc/modules/video_coding/main/source/video_coding_test.gypi +++ b/webrtc/modules/video_coding/main/source/video_coding_test.gypi @@ -20,6 +20,7 @@ '<(webrtc_root)/test/test.gyp:test_support', '<(webrtc_root)/test/metrics.gyp:metrics', '<(webrtc_root)/common_video/common_video.gyp:common_video', + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', ], 'sources': [ # headers diff --git a/webrtc/modules/video_coding/main/source/video_receiver.cc b/webrtc/modules/video_coding/main/source/video_receiver.cc index a60a6ff66..5bc1c90f5 100644 --- a/webrtc/modules/video_coding/main/source/video_receiver.cc +++ b/webrtc/modules/video_coding/main/source/video_receiver.cc @@ -16,7 +16,7 @@ #include "webrtc/modules/video_coding/main/source/packet.h" #include "webrtc/modules/video_coding/main/source/video_coding_impl.h" #include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/trace_event.h" // #define DEBUG_DECODER_BIT_STREAM @@ -24,18 +24,15 @@ namespace webrtc { namespace vcm { -VideoReceiver::VideoReceiver(const int32_t id, - Clock* clock, - EventFactory* event_factory) - : _id(id), - clock_(clock), +VideoReceiver::VideoReceiver(Clock* clock, EventFactory* event_factory) + : clock_(clock), process_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), _receiveCritSect(CriticalSectionWrapper::CreateCriticalSection()), _receiverInited(false), - _timing(clock_, id, 1), - _dualTiming(clock_, id, 2, &_timing), - _receiver(&_timing, clock_, event_factory, id, 1, true), - _dualReceiver(&_dualTiming, clock_, event_factory, id, 2, false), + _timing(clock_), + _dualTiming(clock_, &_timing), + _receiver(&_timing, clock_, event_factory, true), + _dualReceiver(&_dualTiming, clock_, event_factory, false), _decodedFrameCallback(_timing, clock_), _dualDecodedFrameCallback(_dualTiming, clock_), _frameTypeCallback(NULL), @@ -53,7 +50,7 @@ VideoReceiver::VideoReceiver(const int32_t id, _scheduleKeyRequest(false), max_nack_list_size_(0), pre_decode_image_callback_(NULL), - _codecDataBase(id), + _codecDataBase(), _receiveStatsTimer(1000, clock_), _retransmissionTimer(10, clock_), _keyRequestTimer(500, clock_) { @@ -446,17 +443,9 @@ int32_t VideoReceiver::RequestSliceLossIndication( const int32_t ret = _frameTypeCallback->SliceLossIndicationRequest(pictureID); if (ret < 0) { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Failed to request key frame"); return ret; } } else { - WEBRTC_TRACE(webrtc::kTraceWarning, - webrtc::kTraceVideoCoding, - VCMId(_id), - "No frame type request callback registered"); return VCM_MISSING_CALLBACK; } return VCM_OK; @@ -468,18 +457,10 @@ int32_t VideoReceiver::RequestKeyFrame() { if (_frameTypeCallback != NULL) { const int32_t ret = _frameTypeCallback->RequestKeyFrame(); if (ret < 0) { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Failed to request key frame"); return ret; } _scheduleKeyRequest = false; } else { - WEBRTC_TRACE(webrtc::kTraceWarning, - webrtc::kTraceVideoCoding, - VCMId(_id), - "No frame type request callback registered"); return VCM_MISSING_CALLBACK; } return VCM_OK; @@ -502,29 +483,18 @@ int32_t VideoReceiver::DecodeDualFrame(uint16_t maxWaitTimeMs) { VCMEncodedFrame* dualFrame = _dualReceiver.FrameForDecoding(maxWaitTimeMs, dummyRenderTime); if (dualFrame != NULL && _dualDecoder != NULL) { - WEBRTC_TRACE(webrtc::kTraceStream, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Decoding frame %u with dual decoder", - dualFrame->TimeStamp()); // Decode dualFrame and try to catch up int32_t ret = _dualDecoder->Decode(*dualFrame, clock_->TimeInMilliseconds()); if (ret != WEBRTC_VIDEO_CODEC_OK) { - WEBRTC_TRACE(webrtc::kTraceWarning, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Failed to decode frame with dual decoder"); + LOG(LS_ERROR) << "Failed to decode frame with dual decoder. Error code: " + << ret; _dualReceiver.ReleaseFrame(dualFrame); return VCM_CODEC_ERROR; } if (_receiver.DualDecoderCaughtUp(dualFrame, _dualReceiver)) { // Copy the complete decoder state of the dual decoder // to the primary decoder. - WEBRTC_TRACE(webrtc::kTraceStream, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Dual decoder caught up"); _codecDataBase.CopyDecoder(*_dualDecoder); _codecDataBase.ReleaseDecoder(_dualDecoder); _dualDecoder = NULL; @@ -565,11 +535,6 @@ int32_t VideoReceiver::Decode(const VCMEncodedFrame& frame) { return RequestSliceLossIndication( _decodedFrameCallback.LastReceivedPictureID() + 1); } else { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Failed to decode frame %u, requesting key frame", - frame.TimeStamp()); request_key_frame = true; } } else if (ret == VCM_REQUEST_SLI) { @@ -730,24 +695,8 @@ int32_t VideoReceiver::NackList(uint16_t* nackList, uint16_t* size) { nackStatus = _dualReceiver.NackList(nackList, *size, &nack_list_length); } *size = nack_list_length; - - switch (nackStatus) { - case kNackNeedMoreMemory: { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Out of memory"); - return VCM_MEMORY; - } - case kNackKeyFrameRequest: { - WEBRTC_TRACE(webrtc::kTraceWarning, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Failed to get NACK list, requesting key frame"); + if (nackStatus == kNackKeyFrameRequest) { return RequestKeyFrame(); - } - default: - break; } return VCM_OK; } diff --git a/webrtc/modules/video_coding/main/source/video_receiver_unittest.cc b/webrtc/modules/video_coding/main/source/video_receiver_unittest.cc index 4fd524d4b..502dfa9dd 100644 --- a/webrtc/modules/video_coding/main/source/video_receiver_unittest.cc +++ b/webrtc/modules/video_coding/main/source/video_receiver_unittest.cc @@ -33,7 +33,7 @@ class TestVideoReceiver : public ::testing::Test { TestVideoReceiver() : clock_(0) {} virtual void SetUp() { - receiver_.reset(new VideoReceiver(0, &clock_, &event_factory_)); + receiver_.reset(new VideoReceiver(&clock_, &event_factory_)); EXPECT_EQ(0, receiver_->InitializeReceiver()); EXPECT_EQ(0, receiver_->RegisterExternalDecoder( diff --git a/webrtc/modules/video_coding/main/source/video_sender.cc b/webrtc/modules/video_coding/main/source/video_sender.cc index 52a0f8652..38ecc5479 100644 --- a/webrtc/modules/video_coding/main/source/video_sender.cc +++ b/webrtc/modules/video_coding/main/source/video_sender.cc @@ -17,6 +17,7 @@ #include "webrtc/modules/video_coding/main/source/encoded_frame.h" #include "webrtc/modules/video_coding/main/source/video_coding_impl.h" #include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/system_wrappers/interface/logging.h" namespace webrtc { namespace vcm { @@ -57,20 +58,18 @@ class DebugRecorder { FILE* file_ GUARDED_BY(cs_); }; -VideoSender::VideoSender(const int32_t id, - Clock* clock, +VideoSender::VideoSender(Clock* clock, EncodedImageCallback* post_encode_callback) - : _id(id), - clock_(clock), + : clock_(clock), recorder_(new DebugRecorder()), process_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), _sendCritSect(CriticalSectionWrapper::CreateCriticalSection()), _encoder(), _encodedFrameCallback(post_encode_callback), _nextFrameTypes(1, kVideoFrameDelta), - _mediaOpt(id, clock_), + _mediaOpt(clock_), _sendStatsCallback(NULL), - _codecDataBase(id), + _codecDataBase(), frame_dropper_enabled_(true), _sendStatsTimer(1000, clock_), qm_settings_callback_(NULL), @@ -132,10 +131,8 @@ int32_t VideoSender::RegisterSendCodec(const VideoCodec* sendCodec, _encoder = _codecDataBase.GetEncoder(); if (!ret) { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Failed to initialize encoder"); + LOG(LS_ERROR) << "Failed to initialize the encoder with payload name " + << sendCodec->plName << ". Error code: " << ret; return VCM_CODEC_ERROR; } @@ -363,26 +360,18 @@ int32_t VideoSender::AddVideoFrame(const I420VideoFrame& videoFrame, return VCM_OK; } if (_mediaOpt.DropFrame()) { - WEBRTC_TRACE(webrtc::kTraceStream, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Drop frame due to bitrate"); - } else { - _mediaOpt.UpdateContentData(contentMetrics); - int32_t ret = - _encoder->Encode(videoFrame, codecSpecificInfo, _nextFrameTypes); - recorder_->Add(videoFrame); - if (ret < 0) { - WEBRTC_TRACE(webrtc::kTraceError, - webrtc::kTraceVideoCoding, - VCMId(_id), - "Encode error: %d", - ret); - return ret; - } - for (size_t i = 0; i < _nextFrameTypes.size(); ++i) { - _nextFrameTypes[i] = kVideoFrameDelta; // Default frame type. - } + return VCM_OK; + } + _mediaOpt.UpdateContentData(contentMetrics); + int32_t ret = + _encoder->Encode(videoFrame, codecSpecificInfo, _nextFrameTypes); + recorder_->Add(videoFrame); + if (ret < 0) { + LOG(LS_ERROR) << "Failed to encode frame. Error code: " << ret; + return ret; + } + for (size_t i = 0; i < _nextFrameTypes.size(); ++i) { + _nextFrameTypes[i] = kVideoFrameDelta; // Default frame type. } return VCM_OK; } diff --git a/webrtc/modules/video_coding/main/source/video_sender_unittest.cc b/webrtc/modules/video_coding/main/source/video_sender_unittest.cc index ca11ec473..67b3e7aec 100644 --- a/webrtc/modules/video_coding/main/source/video_sender_unittest.cc +++ b/webrtc/modules/video_coding/main/source/video_sender_unittest.cc @@ -173,7 +173,7 @@ class TestVideoSender : public ::testing::Test { TestVideoSender() : clock_(1000), packetization_callback_(&clock_) {} virtual void SetUp() { - sender_.reset(new VideoSender(0, &clock_, &post_encode_callback_)); + sender_.reset(new VideoSender(&clock_, &post_encode_callback_)); EXPECT_EQ(0, sender_->InitializeSender()); EXPECT_EQ(0, sender_->RegisterTransportCallback(&packetization_callback_)); } @@ -345,6 +345,8 @@ class TestVideoSenderWithVp8 : public TestVideoSender { void InsertFrames(float framerate, float seconds) { for (int i = 0; i < seconds * framerate; ++i) { clock_.AdvanceTimeMilliseconds(1000.0f / framerate); + EXPECT_CALL(post_encode_callback_, Encoded(_, NULL, NULL)) + .WillOnce(Return(0)); AddFrame(); // SetChannelParameters needs to be called frequently to propagate diff --git a/webrtc/modules/video_coding/main/test/codec_database_test.cc b/webrtc/modules/video_coding/main/test/codec_database_test.cc index 84ddcef41..3695cc1c9 100644 --- a/webrtc/modules/video_coding/main/test/codec_database_test.cc +++ b/webrtc/modules/video_coding/main/test/codec_database_test.cc @@ -29,7 +29,7 @@ using namespace webrtc; int CodecDataBaseTest::RunTest(CmdArgs& args) { - VideoCodingModule* vcm = VideoCodingModule::Create(1); + VideoCodingModule* vcm = VideoCodingModule::Create(); CodecDataBaseTest* cdbt = new CodecDataBaseTest(vcm); cdbt->Perform(args); VideoCodingModule::Destroy(vcm); diff --git a/webrtc/modules/video_coding/main/test/generic_codec_test.cc b/webrtc/modules/video_coding/main/test/generic_codec_test.cc index ac687c7ea..27dbb7e74 100644 --- a/webrtc/modules/video_coding/main/test/generic_codec_test.cc +++ b/webrtc/modules/video_coding/main/test/generic_codec_test.cc @@ -28,8 +28,7 @@ int GenericCodecTest::RunTest(CmdArgs& args) { SimulatedClock clock(0); NullEventFactory event_factory; - VideoCodingModule* vcm = VideoCodingModule::Create(1, &clock, - &event_factory); + VideoCodingModule* vcm = VideoCodingModule::Create(&clock, &event_factory); GenericCodecTest* get = new GenericCodecTest(vcm, &clock); Trace::CreateTrace(); Trace::SetTraceFile( diff --git a/webrtc/modules/video_coding/main/test/media_opt_test.cc b/webrtc/modules/video_coding/main/test/media_opt_test.cc index 55dfb8ac6..a8b8f19cd 100644 --- a/webrtc/modules/video_coding/main/test/media_opt_test.cc +++ b/webrtc/modules/video_coding/main/test/media_opt_test.cc @@ -32,7 +32,7 @@ int MediaOptTest::RunTest(int testNum, CmdArgs& args) Trace::CreateTrace(); Trace::SetTraceFile((test::OutputPath() + "mediaOptTestTrace.txt").c_str()); Trace::set_level_filter(webrtc::kTraceAll); - VideoCodingModule* vcm = VideoCodingModule::Create(1); + VideoCodingModule* vcm = VideoCodingModule::Create(); Clock* clock = Clock::GetRealTimeClock(); MediaOptTest* mot = new MediaOptTest(vcm, clock); if (testNum == 0) diff --git a/webrtc/modules/video_coding/main/test/mt_rx_tx_test.cc b/webrtc/modules/video_coding/main/test/mt_rx_tx_test.cc index 5cad5d40c..35cd1f385 100644 --- a/webrtc/modules/video_coding/main/test/mt_rx_tx_test.cc +++ b/webrtc/modules/video_coding/main/test/mt_rx_tx_test.cc @@ -145,7 +145,7 @@ int MTRxTxTest(CmdArgs& args) printf("Cannot read file %s.\n", outname.c_str()); return -1; } - VideoCodingModule* vcm = VideoCodingModule::Create(1); + VideoCodingModule* vcm = VideoCodingModule::Create(); RtpDataCallback dataCallback(vcm); RTPSendCompleteCallback* outgoingTransport = @@ -157,7 +157,7 @@ int MTRxTxTest(CmdArgs& args) configuration.outgoing_transport = outgoingTransport; RtpRtcp* rtp = RtpRtcp::CreateRtpRtcp(configuration); scoped_ptr registry(new RTPPayloadRegistry( - -1, RTPPayloadStrategy::CreateStrategy(false))); + RTPPayloadStrategy::CreateStrategy(false))); scoped_ptr rtp_receiver( RtpReceiver::CreateVideoReceiver(-1, Clock::GetRealTimeClock(), &dataCallback, NULL, registry.get())); diff --git a/webrtc/modules/video_coding/main/test/normal_test.cc b/webrtc/modules/video_coding/main/test/normal_test.cc index 3000a1a26..f23682bda 100644 --- a/webrtc/modules/video_coding/main/test/normal_test.cc +++ b/webrtc/modules/video_coding/main/test/normal_test.cc @@ -37,8 +37,7 @@ int NormalTest::RunTest(const CmdArgs& args) Trace::SetTraceFile( (test::OutputPath() + "VCMNormalTestTrace.txt").c_str()); Trace::set_level_filter(webrtc::kTraceAll); - VideoCodingModule* vcm = VideoCodingModule::Create(1, clock, - &event_factory); + VideoCodingModule* vcm = VideoCodingModule::Create(clock, &event_factory); NormalTest VCMNTest(vcm, clock); VCMNTest.Perform(args); VideoCodingModule::Destroy(vcm); diff --git a/webrtc/modules/video_coding/main/test/quality_modes_test.cc b/webrtc/modules/video_coding/main/test/quality_modes_test.cc index 0dbf6fdfe..d488fa92b 100644 --- a/webrtc/modules/video_coding/main/test/quality_modes_test.cc +++ b/webrtc/modules/video_coding/main/test/quality_modes_test.cc @@ -32,7 +32,7 @@ int qualityModeTest(const CmdArgs& args) { SimulatedClock clock(0); NullEventFactory event_factory; - VideoCodingModule* vcm = VideoCodingModule::Create(1, &clock, &event_factory); + VideoCodingModule* vcm = VideoCodingModule::Create(&clock, &event_factory); QualityModesTest QMTest(vcm, &clock); QMTest.Perform(args); VideoCodingModule::Destroy(vcm); @@ -249,7 +249,6 @@ QualityModesTest::Perform(const CmdArgs& args) VideoContentMetrics* contentMetrics = NULL; // setting user frame rate - _vpm->SetMaxFramerate((uint32_t)(_nativeFrameRate+ 0.5f)); // for starters: keeping native values: _vpm->SetTargetResolution(_width, _height, (uint32_t)(_frameRate+ 0.5f)); diff --git a/webrtc/modules/video_coding/main/test/rtp_player.cc b/webrtc/modules/video_coding/main/test/rtp_player.cc index 4c157a716..f02aebba5 100644 --- a/webrtc/modules/video_coding/main/test/rtp_player.cc +++ b/webrtc/modules/video_coding/main/test/rtp_player.cc @@ -61,7 +61,7 @@ class RawRtpPacket { uint16_t seq_num() const { return seq_num_; } private: - scoped_array data_; + scoped_ptr data_; uint32_t length_; int64_t resend_time_ms_; uint32_t ssrc_; @@ -273,7 +273,7 @@ class SsrcHandlers { LostPackets* lost_packets) : rtp_header_parser_(RtpHeaderParser::Create()), rtp_payload_registry_(new RTPPayloadRegistry( - 0, RTPPayloadStrategy::CreateStrategy(false))), + RTPPayloadStrategy::CreateStrategy(false))), rtp_module_(), payload_sink_(), ssrc_(ssrc), diff --git a/webrtc/modules/video_coding/main/test/test_util.h b/webrtc/modules/video_coding/main/test/test_util.h index 36ca2198f..9f8b5a955 100644 --- a/webrtc/modules/video_coding/main/test/test_util.h +++ b/webrtc/modules/video_coding/main/test/test_util.h @@ -17,9 +17,9 @@ #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/interface/module_common_types.h" #include "webrtc/modules/video_coding/main/interface/video_coding.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/event_wrapper.h" enum { kMaxNackListSize = 250 }; diff --git a/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.cc b/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.cc index d5877a4fe..ebd6f0546 100644 --- a/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.cc +++ b/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.cc @@ -108,9 +108,13 @@ class VcmPayloadSinkFactory::VcmPayloadSink }; VcmPayloadSinkFactory::VcmPayloadSinkFactory( - const std::string& base_out_filename, Clock* clock, bool protection_enabled, - VCMVideoProtection protection_method, uint32_t rtt_ms, - uint32_t render_delay_ms, uint32_t min_playout_delay_ms) + const std::string& base_out_filename, + Clock* clock, + bool protection_enabled, + VCMVideoProtection protection_method, + uint32_t rtt_ms, + uint32_t render_delay_ms, + uint32_t min_playout_delay_ms) : base_out_filename_(base_out_filename), clock_(clock), protection_enabled_(protection_enabled), @@ -120,8 +124,7 @@ VcmPayloadSinkFactory::VcmPayloadSinkFactory( min_playout_delay_ms_(min_playout_delay_ms), null_event_factory_(new NullEventFactory()), crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), - sinks_(), - next_id_(1) { + sinks_() { assert(clock); assert(crit_sect_.get()); } @@ -136,7 +139,7 @@ PayloadSinkInterface* VcmPayloadSinkFactory::Create( CriticalSectionScoped cs(crit_sect_.get()); scoped_ptr vcm( - VideoCodingModule::Create(next_id_++, clock_, null_event_factory_.get())); + VideoCodingModule::Create(clock_, null_event_factory_.get())); if (vcm.get() == NULL) { return NULL; } diff --git a/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.h b/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.h index a891b5c64..130bd428d 100644 --- a/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.h +++ b/webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.h @@ -11,9 +11,9 @@ #include #include +#include "webrtc/base/constructormagic.h" #include "webrtc/modules/video_coding/main/interface/video_coding_defines.h" #include "webrtc/modules/video_coding/main/test/rtp_player.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" class NullEventFactory; @@ -56,7 +56,6 @@ class VcmPayloadSinkFactory : public PayloadSinkFactoryInterface { scoped_ptr null_event_factory_; scoped_ptr crit_sect_; Sinks sinks_; - int next_id_; DISALLOW_IMPLICIT_CONSTRUCTORS(VcmPayloadSinkFactory); }; diff --git a/webrtc/modules/video_coding/utility/OWNERS b/webrtc/modules/video_coding/utility/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_coding/utility/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_processing/BUILD.gn b/webrtc/modules/video_processing/BUILD.gn new file mode 100644 index 000000000..40171caff --- /dev/null +++ b/webrtc/modules/video_processing/BUILD.gn @@ -0,0 +1,13 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("video_processing") { + # TODO(stefan): Implement. +} diff --git a/webrtc/modules/video_processing/OWNERS b/webrtc/modules/video_processing/OWNERS new file mode 100644 index 000000000..d5ae8473c --- /dev/null +++ b/webrtc/modules/video_processing/OWNERS @@ -0,0 +1 @@ +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/video_processing/main/interface/video_processing.h b/webrtc/modules/video_processing/main/interface/video_processing.h index b3e0483d0..817d43d9b 100644 --- a/webrtc/modules/video_processing/main/interface/video_processing.h +++ b/webrtc/modules/video_processing/main/interface/video_processing.h @@ -235,14 +235,6 @@ class VideoProcessingModule : public Module { uint32_t height, uint32_t frame_rate) = 0; - /** - Set max frame rate - \param[in] max_frame_rate: maximum frame rate (limited to native frame rate) - - \return VPM_OK on success, a negative value on error (see error codes) - */ - virtual int32_t SetMaxFramerate(uint32_t max_frame_rate) = 0; - /** Get decimated(target) frame rate */ diff --git a/webrtc/modules/video_processing/main/source/OWNERS b/webrtc/modules/video_processing/main/source/OWNERS new file mode 100644 index 000000000..3ee6b4bf5 --- /dev/null +++ b/webrtc/modules/video_processing/main/source/OWNERS @@ -0,0 +1,5 @@ + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/modules/video_processing/main/source/brighten.cc b/webrtc/modules/video_processing/main/source/brighten.cc index ffabbf7ff..907a54906 100644 --- a/webrtc/modules/video_processing/main/source/brighten.cc +++ b/webrtc/modules/video_processing/main/source/brighten.cc @@ -12,22 +12,15 @@ #include -#include "webrtc/system_wrappers/interface/trace.h" - namespace webrtc { namespace VideoProcessing { int32_t Brighten(I420VideoFrame* frame, int delta) { assert(frame); if (frame->IsZeroSize()) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, -1, - "zero size frame"); return VPM_PARAMETER_ERROR; } - if (frame->width() <= 0 || frame->height() <= 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, -1, - "Invalid frame size"); return VPM_PARAMETER_ERROR; } diff --git a/webrtc/modules/video_processing/main/source/brightness_detection.cc b/webrtc/modules/video_processing/main/source/brightness_detection.cc index 8817bac43..f33117d13 100644 --- a/webrtc/modules/video_processing/main/source/brightness_detection.cc +++ b/webrtc/modules/video_processing/main/source/brightness_detection.cc @@ -10,7 +10,6 @@ #include "webrtc/modules/video_processing/main/interface/video_processing.h" #include "webrtc/modules/video_processing/main/source/brightness_detection.h" -#include "webrtc/system_wrappers/interface/trace.h" #include @@ -37,16 +36,12 @@ int32_t VPMBrightnessDetection::ProcessFrame( const I420VideoFrame& frame, const VideoProcessingModule::FrameStats& stats) { if (frame.IsZeroSize()) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, id_, - "Null frame pointer"); return VPM_PARAMETER_ERROR; } int width = frame.width(); int height = frame.height(); if (!VideoProcessingModule::ValidFrameStats(stats)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, id_, - "Invalid frame stats"); return VPM_PARAMETER_ERROR; } @@ -58,7 +53,7 @@ int32_t VPMBrightnessDetection::ProcessFrame( for (uint32_t i = 0; i < low_th; i++) { prop_low += stats.hist[i]; } -prop_low /= stats.num_pixels; + prop_low /= stats.num_pixels; // Get proportion in highest bins. unsigned char high_th = 230; diff --git a/webrtc/modules/video_processing/main/source/color_enhancement.cc b/webrtc/modules/video_processing/main/source/color_enhancement.cc index eeec01659..aaa3a4622 100644 --- a/webrtc/modules/video_processing/main/source/color_enhancement.cc +++ b/webrtc/modules/video_processing/main/source/color_enhancement.cc @@ -12,44 +12,38 @@ #include "webrtc/modules/video_processing/main/source/color_enhancement.h" #include "webrtc/modules/video_processing/main/source/color_enhancement_private.h" -#include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { namespace VideoProcessing { int32_t ColorEnhancement(I420VideoFrame* frame) { -assert(frame); -// Pointers to U and V color pixels. -uint8_t* ptr_u; -uint8_t* ptr_v; -uint8_t temp_chroma; -if (frame->IsZeroSize()) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, - -1, "Null frame pointer"); - return VPM_GENERAL_ERROR; -} - -if (frame->width() == 0 || frame->height() == 0) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, - -1, "Invalid frame size"); - return VPM_GENERAL_ERROR; -} - -// Set pointers to first U and V pixels (skip luminance). -ptr_u = frame->buffer(kUPlane); -ptr_v = frame->buffer(kVPlane); -int size_uv = ((frame->width() + 1) / 2) * ((frame->height() + 1) / 2); - -// Loop through all chrominance pixels and modify color. -for (int ix = 0; ix < size_uv; ix++) { - temp_chroma = colorTable[*ptr_u][*ptr_v]; - *ptr_v = colorTable[*ptr_v][*ptr_u]; - *ptr_u = temp_chroma; - - ptr_u++; - ptr_v++; -} -return VPM_OK; + assert(frame); + // Pointers to U and V color pixels. + uint8_t* ptr_u; + uint8_t* ptr_v; + uint8_t temp_chroma; + if (frame->IsZeroSize()) { + return VPM_GENERAL_ERROR; + } + if (frame->width() == 0 || frame->height() == 0) { + return VPM_GENERAL_ERROR; + } + + // Set pointers to first U and V pixels (skip luminance). + ptr_u = frame->buffer(kUPlane); + ptr_v = frame->buffer(kVPlane); + int size_uv = ((frame->width() + 1) / 2) * ((frame->height() + 1) / 2); + + // Loop through all chrominance pixels and modify color. + for (int ix = 0; ix < size_uv; ix++) { + temp_chroma = colorTable[*ptr_u][*ptr_v]; + *ptr_v = colorTable[*ptr_v][*ptr_u]; + *ptr_u = temp_chroma; + + ptr_u++; + ptr_v++; + } + return VPM_OK; } } // namespace VideoProcessing diff --git a/webrtc/modules/video_processing/main/source/deflickering.cc b/webrtc/modules/video_processing/main/source/deflickering.cc index 898fd80f4..cdc617488 100644 --- a/webrtc/modules/video_processing/main/source/deflickering.cc +++ b/webrtc/modules/video_processing/main/source/deflickering.cc @@ -14,8 +14,8 @@ #include #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h" +#include "webrtc/system_wrappers/interface/logging.h" #include "webrtc/system_wrappers/interface/sort.h" -#include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { @@ -102,21 +102,16 @@ int32_t VPMDeflickering::ProcessFrame(I420VideoFrame* frame, int height = frame->height(); if (frame->IsZeroSize()) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, id_, - "Null frame pointer"); return VPM_GENERAL_ERROR; } // Stricter height check due to subsampling size calculation below. if (height < 2) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, id_, - "Invalid frame size"); + LOG(LS_ERROR) << "Invalid frame size."; return VPM_GENERAL_ERROR; } if (!VideoProcessingModule::ValidFrameStats(*stats)) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, id_, - "Invalid frame stats"); return VPM_GENERAL_ERROR; } @@ -152,8 +147,7 @@ int32_t VPMDeflickering::ProcessFrame(I420VideoFrame* frame, // Ensure we won't get an overflow below. // In practice, the number of subsampled pixels will not become this large. if (y_sub_size > (1 << 21) - 1) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, id_, - "Subsampled number of pixels too large"); + LOG(LS_ERROR) << "Subsampled number of pixels too large."; return -1; } diff --git a/webrtc/modules/video_processing/main/source/denoising.cc b/webrtc/modules/video_processing/main/source/denoising.cc index 79c4bcc3d..4c8dcb439 100644 --- a/webrtc/modules/video_processing/main/source/denoising.cc +++ b/webrtc/modules/video_processing/main/source/denoising.cc @@ -9,7 +9,6 @@ */ #include "webrtc/modules/video_processing/main/source/denoising.h" -#include "webrtc/system_wrappers/interface/trace.h" #include @@ -78,8 +77,6 @@ int32_t VPMDenoising::ProcessFrame(I420VideoFrame* frame) { int32_t num_pixels_changed = 0; if (frame->IsZeroSize()) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, id_, - "zero size frame"); return VPM_GENERAL_ERROR; } diff --git a/webrtc/modules/video_processing/main/source/frame_preprocessor.cc b/webrtc/modules/video_processing/main/source/frame_preprocessor.cc index de4907029..e1cd04ff7 100644 --- a/webrtc/modules/video_processing/main/source/frame_preprocessor.cc +++ b/webrtc/modules/video_processing/main/source/frame_preprocessor.cc @@ -9,14 +9,12 @@ */ #include "webrtc/modules/video_processing/main/source/frame_preprocessor.h" -#include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { VPMFramePreprocessor::VPMFramePreprocessor() : id_(0), content_metrics_(NULL), - max_frame_rate_(0), resampled_frame_(), enable_ca_(false), frame_cnt_(0) { @@ -60,14 +58,6 @@ void VPMFramePreprocessor::SetInputFrameResampleMode( spatial_resampler_->SetInputFrameResampleMode(resampling_mode); } -int32_t VPMFramePreprocessor::SetMaxFramerate(uint32_t max_frame_rate) { - if (max_frame_rate == 0) return VPM_PARAMETER_ERROR; - - // Max allowed frame_rate. - max_frame_rate_ = max_frame_rate; - return vd_->SetMaxFramerate(max_frame_rate); -} - int32_t VPMFramePreprocessor::SetTargetResolution( uint32_t width, uint32_t height, uint32_t frame_rate) { if ( (width == 0) || (height == 0) || (frame_rate == 0)) { @@ -78,7 +68,7 @@ int32_t VPMFramePreprocessor::SetTargetResolution( if (ret_val < 0) return ret_val; - ret_val = vd_->SetTargetframe_rate(frame_rate); + ret_val = vd_->SetTargetFramerate(frame_rate); if (ret_val < 0) return ret_val; return VPM_OK; @@ -112,8 +102,6 @@ int32_t VPMFramePreprocessor::PreprocessFrame(const I420VideoFrame& frame, vd_->UpdateIncomingframe_rate(); if (vd_->DropFrame()) { - WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceVideo, id_, - "Drop frame due to frame rate"); return 1; // drop 1 frame } diff --git a/webrtc/modules/video_processing/main/source/frame_preprocessor.h b/webrtc/modules/video_processing/main/source/frame_preprocessor.h index ca62d38fc..64a5797b9 100644 --- a/webrtc/modules/video_processing/main/source/frame_preprocessor.h +++ b/webrtc/modules/video_processing/main/source/frame_preprocessor.h @@ -39,9 +39,6 @@ class VPMFramePreprocessor { // Enable content analysis. void EnableContentAnalysis(bool enable); - // Set max frame rate. - int32_t SetMaxFramerate(uint32_t max_frame_rate); - // Set target resolution: frame rate and dimension. int32_t SetTargetResolution(uint32_t width, uint32_t height, uint32_t frame_rate); @@ -68,7 +65,6 @@ class VPMFramePreprocessor { int32_t id_; VideoContentMetrics* content_metrics_; - uint32_t max_frame_rate_; I420VideoFrame resampled_frame_; VPMSpatialResampler* spatial_resampler_; VPMContentAnalysis* ca_; diff --git a/webrtc/modules/video_processing/main/source/video_decimator.cc b/webrtc/modules/video_processing/main/source/video_decimator.cc index 8fd3d0369..bf05bd715 100644 --- a/webrtc/modules/video_processing/main/source/video_decimator.cc +++ b/webrtc/modules/video_processing/main/source/video_decimator.cc @@ -16,15 +16,7 @@ namespace webrtc { -VPMVideoDecimator::VPMVideoDecimator() - : overshoot_modifier_(0), - drop_count_(0), - keep_count_(0), - target_frame_rate_(30), - incoming_frame_rate_(0.0f), - max_frame_rate_(30), - incoming_frame_times_(), - enable_temporal_decimation_(true) { +VPMVideoDecimator::VPMVideoDecimator() { Reset(); } @@ -36,7 +28,6 @@ void VPMVideoDecimator::Reset() { keep_count_ = 0; target_frame_rate_ = 30; incoming_frame_rate_ = 0.0f; - max_frame_rate_ = 30; memset(incoming_frame_times_, 0, sizeof(incoming_frame_times_)); enable_temporal_decimation_ = true; } @@ -45,26 +36,10 @@ void VPMVideoDecimator::EnableTemporalDecimation(bool enable) { enable_temporal_decimation_ = enable; } -int32_t VPMVideoDecimator::SetMaxFramerate(uint32_t max_frame_rate) { - if (max_frame_rate == 0) return VPM_PARAMETER_ERROR; - - max_frame_rate_ = max_frame_rate; - - if (target_frame_rate_ > max_frame_rate_) - target_frame_rate_ = max_frame_rate_; - - return VPM_OK; -} - -int32_t VPMVideoDecimator::SetTargetframe_rate(uint32_t frame_rate) { +int32_t VPMVideoDecimator::SetTargetFramerate(uint32_t frame_rate) { if (frame_rate == 0) return VPM_PARAMETER_ERROR; - if (frame_rate > max_frame_rate_) { - // Override. - target_frame_rate_ = max_frame_rate_; - } else { - target_frame_rate_ = frame_rate; - } + target_frame_rate_ = frame_rate; return VPM_OK; } diff --git a/webrtc/modules/video_processing/main/source/video_decimator.h b/webrtc/modules/video_processing/main/source/video_decimator.h index d17da6188..fca74aeae 100644 --- a/webrtc/modules/video_processing/main/source/video_decimator.h +++ b/webrtc/modules/video_processing/main/source/video_decimator.h @@ -25,8 +25,7 @@ class VPMVideoDecimator { void EnableTemporalDecimation(bool enable); - int32_t SetMaxFramerate(uint32_t max_frame_rate); - int32_t SetTargetframe_rate(uint32_t frame_rate); + int32_t SetTargetFramerate(uint32_t frame_rate); bool DropFrame(); @@ -50,7 +49,6 @@ class VPMVideoDecimator { uint32_t keep_count_; uint32_t target_frame_rate_; float incoming_frame_rate_; - uint32_t max_frame_rate_; int64_t incoming_frame_times_[kFrameCountHistory_size]; bool enable_temporal_decimation_; }; diff --git a/webrtc/modules/video_processing/main/source/video_processing_impl.cc b/webrtc/modules/video_processing/main/source/video_processing_impl.cc index af1bfe1a4..3560030c8 100644 --- a/webrtc/modules/video_processing/main/source/video_processing_impl.cc +++ b/webrtc/modules/video_processing/main/source/video_processing_impl.cc @@ -11,7 +11,7 @@ #include "webrtc/modules/video_processing/main/source/video_processing_impl.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/logging.h" #include @@ -68,13 +68,9 @@ VideoProcessingModuleImpl::VideoProcessingModuleImpl(const int32_t id) deflickering_.ChangeUniqueId(id); denoising_.ChangeUniqueId(id); frame_pre_processor_.ChangeUniqueId(id); - WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideoPreocessing, id_, - "Created"); } VideoProcessingModuleImpl::~VideoProcessingModuleImpl() { - WEBRTC_TRACE(webrtc::kTraceMemory, webrtc::kTraceVideoPreocessing, id_, - "Destroyed"); delete &mutex_; } @@ -89,8 +85,7 @@ void VideoProcessingModuleImpl::Reset() { int32_t VideoProcessingModule::GetFrameStats(FrameStats* stats, const I420VideoFrame& frame) { if (frame.IsZeroSize()) { - WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoPreocessing, -1, - "zero size frame"); + LOG(LS_ERROR) << "Zero size frame."; return VPM_PARAMETER_ERROR; } @@ -121,7 +116,10 @@ int32_t VideoProcessingModule::GetFrameStats(FrameStats* stats, } bool VideoProcessingModule::ValidFrameStats(const FrameStats& stats) { - if (stats.num_pixels == 0) return false; + if (stats.num_pixels == 0) { + LOG(LS_WARNING) << "Invalid frame stats."; + return false; + } return true; } @@ -173,11 +171,6 @@ void VideoProcessingModuleImpl::SetInputFrameResampleMode(VideoFrameResampling frame_pre_processor_.SetInputFrameResampleMode(resampling_mode); } -int32_t VideoProcessingModuleImpl::SetMaxFramerate(uint32_t max_frame_rate) { - CriticalSectionScoped cs(&mutex_); - return frame_pre_processor_.SetMaxFramerate(max_frame_rate); -} - int32_t VideoProcessingModuleImpl::SetTargetResolution(uint32_t width, uint32_t height, uint32_t frame_rate) { diff --git a/webrtc/modules/video_processing/main/source/video_processing_impl.h b/webrtc/modules/video_processing/main/source/video_processing_impl.h index 913bb6483..deae6ff65 100644 --- a/webrtc/modules/video_processing/main/source/video_processing_impl.h +++ b/webrtc/modules/video_processing/main/source/video_processing_impl.h @@ -51,9 +51,6 @@ class VideoProcessingModuleImpl : public VideoProcessingModule { // Enable content analysis virtual void EnableContentAnalysis(bool enable); - // Set max frame rate - virtual int32_t SetMaxFramerate(uint32_t max_frame_rate); - // Set Target Resolution: frame rate and dimension virtual int32_t SetTargetResolution(uint32_t width, uint32_t height, diff --git a/webrtc/modules/video_processing/main/test/unit_test/brightness_detection_test.cc b/webrtc/modules/video_processing/main/test/unit_test/brightness_detection_test.cc index d7ac72908..c53c1fb83 100644 --- a/webrtc/modules/video_processing/main/test/unit_test/brightness_detection_test.cc +++ b/webrtc/modules/video_processing/main/test/unit_test/brightness_detection_test.cc @@ -19,7 +19,7 @@ TEST_F(VideoProcessingModuleTest, BrightnessDetection) uint32_t frameNum = 0; int32_t brightnessWarning = 0; uint32_t warningCount = 0; - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); while (fread(video_buffer.get(), 1, frame_length_, source_file_) == frame_length_) { diff --git a/webrtc/modules/video_processing/main/test/unit_test/color_enhancement_test.cc b/webrtc/modules/video_processing/main/test/unit_test/color_enhancement_test.cc index fc560bef1..c1cd46231 100644 --- a/webrtc/modules/video_processing/main/test/unit_test/color_enhancement_test.cc +++ b/webrtc/modules/video_processing/main/test/unit_test/color_enhancement_test.cc @@ -39,7 +39,7 @@ TEST_F(VideoProcessingModuleTest, ColorEnhancement) ASSERT_TRUE(modFile != NULL) << "Could not open output file.\n"; uint32_t frameNum = 0; - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); while (fread(video_buffer.get(), 1, frame_length_, source_file_) == frame_length_) { @@ -86,7 +86,7 @@ TEST_F(VideoProcessingModuleTest, ColorEnhancement) width_, half_width_, half_width_); // Compare frame-by-frame. - scoped_array ref_buffer(new uint8_t[frame_length_]); + scoped_ptr ref_buffer(new uint8_t[frame_length_]); while (fread(video_buffer.get(), 1, frame_length_, modFile) == frame_length_) { @@ -114,7 +114,7 @@ TEST_F(VideoProcessingModuleTest, ColorEnhancement) // Verify that all color pixels are enhanced, and no luminance values are // altered. - scoped_array testFrame(new uint8_t[frame_length_]); + scoped_ptr testFrame(new uint8_t[frame_length_]); // Use value 128 as probe value, since we know that this will be changed // in the enhancement. diff --git a/webrtc/modules/video_processing/main/test/unit_test/content_metrics_test.cc b/webrtc/modules/video_processing/main/test/unit_test/content_metrics_test.cc index 36a1ad762..c0d1ab434 100644 --- a/webrtc/modules/video_processing/main/test/unit_test/content_metrics_test.cc +++ b/webrtc/modules/video_processing/main/test/unit_test/content_metrics_test.cc @@ -23,7 +23,7 @@ TEST_F(VideoProcessingModuleTest, ContentAnalysis) { ca__c.Initialize(width_,height_); ca__sse.Initialize(width_,height_); - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); while (fread(video_buffer.get(), 1, frame_length_, source_file_) == frame_length_) { // Using ConvertToI420 to add stride to the image. diff --git a/webrtc/modules/video_processing/main/test/unit_test/deflickering_test.cc b/webrtc/modules/video_processing/main/test/unit_test/deflickering_test.cc index 0fa3f48b4..1bf53fc89 100644 --- a/webrtc/modules/video_processing/main/test/unit_test/deflickering_test.cc +++ b/webrtc/modules/video_processing/main/test/unit_test/deflickering_test.cc @@ -43,7 +43,7 @@ TEST_F(VideoProcessingModuleTest, Deflickering) "Could not open output file: " << output_file << "\n"; printf("\nRun time [us / frame]:\n"); - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); for (uint32_t run_idx = 0; run_idx < NumRuns; run_idx++) { TickTime t0; diff --git a/webrtc/modules/video_processing/main/test/unit_test/denoising_test.cc b/webrtc/modules/video_processing/main/test/unit_test/denoising_test.cc index 3023a2d7a..c00db6ab5 100644 --- a/webrtc/modules/video_processing/main/test/unit_test/denoising_test.cc +++ b/webrtc/modules/video_processing/main/test/unit_test/denoising_test.cc @@ -49,7 +49,7 @@ TEST_F(VideoProcessingModuleTest, DISABLED_ON_ANDROID(Denoising)) int32_t modifiedPixels = 0; frameNum = 0; - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); while (fread(video_buffer.get(), 1, frame_length_, source_file_) == frame_length_) { diff --git a/webrtc/modules/video_processing/main/test/unit_test/video_processing_unittest.cc b/webrtc/modules/video_processing/main/test/unit_test/video_processing_unittest.cc index 6e5492306..973552c80 100644 --- a/webrtc/modules/video_processing/main/test/unit_test/video_processing_unittest.cc +++ b/webrtc/modules/video_processing/main/test/unit_test/video_processing_unittest.cc @@ -89,7 +89,7 @@ TEST_F(VideoProcessingModuleTest, HandleNullBuffer) { TEST_F(VideoProcessingModuleTest, HandleBadStats) { VideoProcessingModule::FrameStats stats; - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); ASSERT_EQ(frame_length_, fread(video_buffer.get(), 1, frame_length_, source_file_)); EXPECT_EQ(0, ConvertToI420(kI420, video_buffer.get(), 0, 0, @@ -118,7 +118,6 @@ TEST_F(VideoProcessingModuleTest, HandleBadSize) { EXPECT_EQ(-3, vpm_->BrightnessDetection(video_frame_, stats)); EXPECT_EQ(VPM_PARAMETER_ERROR, vpm_->SetTargetResolution(0,0,0)); - EXPECT_EQ(VPM_PARAMETER_ERROR, vpm_->SetMaxFramerate(0)); I420VideoFrame *out_frame = NULL; EXPECT_EQ(VPM_PARAMETER_ERROR, vpm_->PreprocessFrame(video_frame_, @@ -129,7 +128,7 @@ TEST_F(VideoProcessingModuleTest, IdenticalResultsAfterReset) { I420VideoFrame video_frame2; VideoProcessingModule::FrameStats stats; // Only testing non-static functions here. - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); ASSERT_EQ(frame_length_, fread(video_buffer.get(), 1, frame_length_, source_file_)); EXPECT_EQ(0, ConvertToI420(kI420, video_buffer.get(), 0, 0, @@ -172,7 +171,7 @@ TEST_F(VideoProcessingModuleTest, IdenticalResultsAfterReset) { TEST_F(VideoProcessingModuleTest, FrameStats) { VideoProcessingModule::FrameStats stats; - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); ASSERT_EQ(frame_length_, fread(video_buffer.get(), 1, frame_length_, source_file_)); EXPECT_EQ(0, ConvertToI420(kI420, video_buffer.get(), 0, 0, @@ -200,7 +199,6 @@ TEST_F(VideoProcessingModuleTest, PreprocessorLogic) { // Disable temporal sampling (frame dropping). vpm_->EnableTemporalDecimation(false); int resolution = 100; - EXPECT_EQ(VPM_OK, vpm_->SetMaxFramerate(30)); EXPECT_EQ(VPM_OK, vpm_->SetTargetResolution(resolution, resolution, 15)); EXPECT_EQ(VPM_OK, vpm_->SetTargetResolution(resolution, resolution, 30)); // Disable spatial sampling. @@ -242,7 +240,7 @@ TEST_F(VideoProcessingModuleTest, Resampler) { vpm_->EnableTemporalDecimation(false); // Reading test frame - scoped_array video_buffer(new uint8_t[frame_length_]); + scoped_ptr video_buffer(new uint8_t[frame_length_]); ASSERT_EQ(frame_length_, fread(video_buffer.get(), 1, frame_length_, source_file_)); // Using ConvertToI420 to add stride to the image. diff --git a/webrtc/modules/video_render/BUILD.gn b/webrtc/modules/video_render/BUILD.gn new file mode 100644 index 000000000..c569b7fac --- /dev/null +++ b/webrtc/modules/video_render/BUILD.gn @@ -0,0 +1,13 @@ +# Copyright (c) 2014 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. + +import("../../build/webrtc.gni") + +source_set("video_render") { + # TODO(wuchengli): Implement. +} diff --git a/webrtc/modules/video_render/OWNERS b/webrtc/modules/video_render/OWNERS index 5e8ed0909..da941f40e 100644 --- a/webrtc/modules/video_render/OWNERS +++ b/webrtc/modules/video_render/OWNERS @@ -3,3 +3,12 @@ mflodman@webrtc.org perkj@webrtc.org wu@webrtc.org mallinath@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/video_render/android/java/src/org/webrtc/videoengine/ViERenderer.java b/webrtc/modules/video_render/android/java/src/org/webrtc/videoengine/ViERenderer.java index 578bfa2d4..50b1a595c 100644 --- a/webrtc/modules/video_render/android/java/src/org/webrtc/videoengine/ViERenderer.java +++ b/webrtc/modules/video_render/android/java/src/org/webrtc/videoengine/ViERenderer.java @@ -15,10 +15,6 @@ import android.view.SurfaceView; public class ViERenderer { - - // View used for local rendering that Cameras can use for Video Overlay. - private static SurfaceHolder g_localRenderer; - public static SurfaceView CreateRenderer(Context context) { return CreateRenderer(context, false); } @@ -30,28 +26,4 @@ public static SurfaceView CreateRenderer(Context context, else return new SurfaceView(context); } - - // Creates a SurfaceView to be used by Android Camera - // service to display a local preview. - // This needs to be used on Android prior to version 2.1 - // in order to run the camera. - // Call this function before ViECapture::StartCapture. - // The created view needs to be added to a visible layout - // after a camera has been allocated - // (with the call ViECapture::AllocateCaptureDevice). - // IE. - // CreateLocalRenderer - // ViECapture::AllocateCaptureDevice - // LinearLayout.addview - // ViECapture::StartCapture - public static SurfaceView CreateLocalRenderer(Context context) { - SurfaceView localRender = new SurfaceView(context); - g_localRenderer = localRender.getHolder(); - return localRender; - } - - public static SurfaceHolder GetLocalRenderer() { - return g_localRenderer; - } - } diff --git a/webrtc/modules/video_render/android/video_render_android_native_opengl2.cc b/webrtc/modules/video_render/android/video_render_android_native_opengl2.cc index 2057e282f..c2afbbd63 100644 --- a/webrtc/modules/video_render/android/video_render_android_native_opengl2.cc +++ b/webrtc/modules/video_render/android/video_render_android_native_opengl2.cc @@ -245,7 +245,6 @@ AndroidNativeOpenGl2Channel::AndroidNativeOpenGl2Channel( AndroidNativeOpenGl2Channel::~AndroidNativeOpenGl2Channel() { WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _id, "AndroidNativeOpenGl2Channel dtor"); - delete &_renderCritSect; if (_jvm) { // get the JNI env for this thread bool isAttached = false; @@ -277,6 +276,8 @@ AndroidNativeOpenGl2Channel::~AndroidNativeOpenGl2Channel() { } } } + + delete &_renderCritSect; } int32_t AndroidNativeOpenGl2Channel::Init(int32_t zOrder, diff --git a/webrtc/modules/video_render/video_render.gypi b/webrtc/modules/video_render/video_render.gypi index a3c0f6d50..71d969bab 100644 --- a/webrtc/modules/video_render/video_render.gypi +++ b/webrtc/modules/video_render/video_render.gypi @@ -87,6 +87,12 @@ 'android/video_render_android_surface_view.cc', 'android/video_render_opengles20.cc', ], + }, { + 'link_settings': { + 'libraries': [ + '-lGLESv2', + ], + }, }], ['OS!="ios" or include_internal_video_render==0', { 'sources!': [ @@ -112,6 +118,12 @@ 'linux/video_x11_channel.cc', 'linux/video_x11_render.cc', ], + }, { + 'link_settings': { + 'libraries': [ + '-lXext', + ], + }, }], ['OS!="mac" or include_internal_video_render==0', { 'sources!': [ @@ -208,11 +220,11 @@ ], }], ['OS=="linux"', { - 'libraries': [ - '-lrt', - '-lXext', - '-lX11', - ], + 'link_settings': { + 'libraries': [ + '-lX11', + ], + }, }], ['OS=="mac"', { 'xcode_settings': { diff --git a/webrtc/modules/video_render/video_render_frames.cc b/webrtc/modules/video_render/video_render_frames.cc index 7025d62a7..d790877e3 100644 --- a/webrtc/modules/video_render/video_render_frames.cc +++ b/webrtc/modules/video_render/video_render_frames.cc @@ -55,12 +55,7 @@ int32_t VideoRenderFrames::AddFrame(I420VideoFrame* new_frame) { } if (new_frame->native_handle() != NULL) { - incoming_frames_.push_back(new TextureVideoFrame( - static_cast(new_frame->native_handle()), - new_frame->width(), - new_frame->height(), - new_frame->timestamp(), - new_frame->render_time_ms())); + incoming_frames_.push_back(new_frame->CloneFrame()); return static_cast(incoming_frames_.size()); } diff --git a/webrtc/modules/video_render/video_render_tests.isolate b/webrtc/modules/video_render/video_render_tests.isolate index 397ec04e8..15c80141d 100644 --- a/webrtc/modules/video_render/video_render_tests.isolate +++ b/webrtc/modules/video_render/video_render_tests.isolate @@ -8,27 +8,25 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../../data/', - '../../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/video_render_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/video_render_tests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/overrides/webrtc/base/basictypes.h b/webrtc/overrides/webrtc/base/basictypes.h new file mode 100644 index 000000000..c7cec5e7a --- /dev/null +++ b/webrtc/overrides/webrtc/base/basictypes.h @@ -0,0 +1,108 @@ +/* + * Copyright 2012 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 file overrides the inclusion of webrtc/base/basictypes.h to remove +// collisions with Chromium's base/basictypes.h. We then add back a few +// items that Chromium's version doesn't provide, but libjingle expects. + +#ifndef OVERRIDES_WEBRTC_BASE_BASICTYPES_H__ +#define OVERRIDES_WEBRTC_BASE_BASICTYPES_H__ + +#include "base/basictypes.h" +#include "build/build_config.h" + +#ifndef INT_TYPES_DEFINED +#define INT_TYPES_DEFINED + +#ifdef COMPILER_MSVC +#if _MSC_VER >= 1600 +#include +#else +typedef unsigned __int64 uint64; +typedef __int64 int64; +#endif +#ifndef INT64_C +#define INT64_C(x) x ## I64 +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## UI64 +#endif +#define INT64_F "I64" +#else // COMPILER_MSVC +#ifndef INT64_C +#define INT64_C(x) x ## LL +#endif +#ifndef UINT64_C +#define UINT64_C(x) x ## ULL +#endif +#ifndef INT64_F +#define INT64_F "ll" +#endif +#endif // COMPILER_MSVC +#endif // INT_TYPES_DEFINED + +// Detect compiler is for x86 or x64. +#if defined(__x86_64__) || defined(_M_X64) || \ + defined(__i386__) || defined(_M_IX86) +#define CPU_X86 1 +#endif +// Detect compiler is for arm. +#if defined(__arm__) || defined(_M_ARM) +#define CPU_ARM 1 +#endif +#if defined(CPU_X86) && defined(CPU_ARM) +#error CPU_X86 and CPU_ARM both defined. +#endif +#if !defined(ARCH_CPU_BIG_ENDIAN) && !defined(ARCH_CPU_LITTLE_ENDIAN) +// x86, arm or GCC provided __BYTE_ORDER__ macros +#if CPU_X86 || CPU_ARM || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define ARCH_CPU_LITTLE_ENDIAN +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define ARCH_CPU_BIG_ENDIAN +#else +#error ARCH_CPU_BIG_ENDIAN or ARCH_CPU_LITTLE_ENDIAN should be defined. +#endif +#endif +#if defined(ARCH_CPU_BIG_ENDIAN) && defined(ARCH_CPU_LITTLE_ENDIAN) +#error ARCH_CPU_BIG_ENDIAN and ARCH_CPU_LITTLE_ENDIAN both defined. +#endif + +#if defined(WEBRTC_WIN) +typedef int socklen_t; +#endif + +namespace rtc { +template inline T _min(T a, T b) { return (a > b) ? b : a; } +template inline T _max(T a, T b) { return (a < b) ? b : a; } + +// For wait functions that take a number of milliseconds, kForever indicates +// unlimited time. +const int kForever = -1; +} + +#if defined(WEBRTC_WIN) +#if _MSC_VER < 1700 + #define alignof(t) __alignof(t) +#endif +#else // !WEBRTC_WIN +#define alignof(t) __alignof__(t) +#endif // !WEBRTC_WIN +#define RTC_IS_ALIGNED(p, a) (0==(reinterpret_cast(p) & ((a)-1))) +#define ALIGNP(p, t) \ + (reinterpret_cast(((reinterpret_cast(p) + \ + ((t)-1)) & ~((t)-1)))) + +// LIBJINGLE_DEFINE_STATIC_LOCAL() is a libjingle's copy +// of CR_DEFINE_STATIC_LOCAL(). +#define LIBJINGLE_DEFINE_STATIC_LOCAL(type, name, arguments) \ + CR_DEFINE_STATIC_LOCAL(type, name, arguments) + +#endif // OVERRIDES_WEBRTC_BASE_BASICTYPES_H__ diff --git a/webrtc/overrides/webrtc/base/constructormagic.h b/webrtc/overrides/webrtc/base/constructormagic.h new file mode 100644 index 000000000..bb89f91f1 --- /dev/null +++ b/webrtc/overrides/webrtc/base/constructormagic.h @@ -0,0 +1,20 @@ +/* + * Copyright 2009 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 file overrides the inclusion of webrtc/base/constructormagic.h +// We do this because constructor magic defines DISALLOW_EVIL_CONSTRUCTORS, +// but we want to use the version from Chromium. + +#ifndef OVERRIDES_WEBRTC_BASE_CONSTRUCTORMAGIC_H__ +#define OVERRIDES_WEBRTC_BASE_CONSTRUCTORMAGIC_H__ + +#include "base/basictypes.h" + +#endif // OVERRIDES_WEBRTC_BASE_CONSTRUCTORMAGIC_H__ diff --git a/webrtc/overrides/webrtc/base/logging.cc b/webrtc/overrides/webrtc/base/logging.cc new file mode 100644 index 000000000..f0c79106d --- /dev/null +++ b/webrtc/overrides/webrtc/base/logging.cc @@ -0,0 +1,318 @@ +/* + * Copyright 2012 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 "third_party/webrtc/overrides/webrtc/base/logging.h" + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include +#endif // OS_MACOSX + +#include + +#include "base/atomicops.h" +#include "base/strings/string_util.h" +#include "base/threading/platform_thread.h" +#include "third_party/webrtc/base/ipaddress.h" +#include "third_party/webrtc/base/stream.h" +#include "third_party/webrtc/base/stringencode.h" +#include "third_party/webrtc/base/stringutils.h" +#include "third_party/webrtc/base/timeutils.h" + +// From this file we can't use VLOG since it expands into usage of the __FILE__ +// macro (for correct filtering). The actual logging call from DIAGNOSTIC_LOG in +// ~DiagnosticLogMessage. Note that the second parameter to the LAZY_STREAM +// macro is true since the filter check has already been done for +// DIAGNOSTIC_LOG. +#define LOG_LAZY_STREAM_DIRECT(file_name, line_number, sev) \ + LAZY_STREAM(logging::LogMessage(file_name, line_number, \ + -sev).stream(), true) + +namespace rtc { + +void (*g_logging_delegate_function)(const std::string&) = NULL; +void (*g_extra_logging_init_function)( + void (*logging_delegate_function)(const std::string&)) = NULL; +#ifndef NDEBUG +COMPILE_ASSERT(sizeof(base::subtle::Atomic32) == sizeof(base::PlatformThreadId), + atomic32_not_same_size_as_platformthreadid); +base::subtle::Atomic32 g_init_logging_delegate_thread_id = 0; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Constant Labels +///////////////////////////////////////////////////////////////////////////// + +const char* FindLabel(int value, const ConstantLabel entries[]) { + for (int i = 0; entries[i].label; ++i) { + if (value == entries[i].value) return entries[i].label; + } + return 0; +} + +std::string ErrorName(int err, const ConstantLabel* err_table) { + if (err == 0) + return "No error"; + + if (err_table != 0) { + if (const char * value = FindLabel(err, err_table)) + return value; + } + + char buffer[16]; + base::snprintf(buffer, sizeof(buffer), "0x%08x", err); + return buffer; +} + +///////////////////////////////////////////////////////////////////////////// +// Log helper functions +///////////////////////////////////////////////////////////////////////////// + +// Generates extra information for LOG_E. +static std::string GenerateExtra(LogErrorContext err_ctx, + int err, + const char* module) { + if (err_ctx != ERRCTX_NONE) { + std::ostringstream tmp; + tmp << ": "; + tmp << "[0x" << std::setfill('0') << std::hex << std::setw(8) << err << "]"; + switch (err_ctx) { + case ERRCTX_ERRNO: + tmp << " " << strerror(err); + break; +#if defined(WEBRTC_WIN) + case ERRCTX_HRESULT: { + char msgbuf[256]; + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM; + HMODULE hmod = GetModuleHandleA(module); + if (hmod) + flags |= FORMAT_MESSAGE_FROM_HMODULE; + if (DWORD len = FormatMessageA( + flags, hmod, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), NULL)) { + while ((len > 0) && + isspace(static_cast(msgbuf[len-1]))) { + msgbuf[--len] = 0; + } + tmp << " " << msgbuf; + } + break; + } +#endif // OS_WIN +#if defined(WEBRTC_IOS) + case ERRCTX_OSSTATUS: + tmp << " " << "Unknown LibJingle error: " << err; + break; +#elif defined(WEBRTC_MAC) + case ERRCTX_OSSTATUS: { + tmp << " " << nonnull(GetMacOSStatusErrorString(err), "Unknown error"); + if (const char* desc = GetMacOSStatusCommentString(err)) { + tmp << ": " << desc; + } + break; + } +#endif // OS_MACOSX + default: + break; + } + return tmp.str(); + } + return ""; +} + +DiagnosticLogMessage::DiagnosticLogMessage(const char* file, + int line, + LoggingSeverity severity, + bool log_to_chrome, + LogErrorContext err_ctx, + int err) + : file_name_(file), + line_(line), + severity_(severity), + log_to_chrome_(log_to_chrome) { + extra_ = GenerateExtra(err_ctx, err, NULL); +} + +DiagnosticLogMessage::DiagnosticLogMessage(const char* file, + int line, + LoggingSeverity severity, + bool log_to_chrome, + LogErrorContext err_ctx, + int err, + const char* module) + : file_name_(file), + line_(line), + severity_(severity), + log_to_chrome_(log_to_chrome) { + extra_ = GenerateExtra(err_ctx, err, module); +} + +DiagnosticLogMessage::~DiagnosticLogMessage() { + print_stream_ << extra_; + const std::string& str = print_stream_.str(); + if (log_to_chrome_) + LOG_LAZY_STREAM_DIRECT(file_name_, line_, severity_) << str; + if (g_logging_delegate_function && severity_ <= LS_INFO) { + g_logging_delegate_function(str); + } +} + +// Note: this function is a copy from the overriden libjingle implementation. +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state) { + if (!LOG_CHECK_LEVEL_V(level)) + return; + + const char * direction = (input ? " << " : " >> "); + + // NULL data means to flush our count of unprintable characters. + if (!data) { + if (state && state->unprintable_count_[input]) { + LOG_V(level) << label << direction << "## " + << state->unprintable_count_[input] + << " consecutive unprintable ##"; + state->unprintable_count_[input] = 0; + } + return; + } + + // The ctype classification functions want unsigned chars. + const unsigned char* udata = static_cast(data); + + if (hex_mode) { + const size_t LINE_SIZE = 24; + char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1]; + while (len > 0) { + memset(asc_line, ' ', sizeof(asc_line)); + memset(hex_line, ' ', sizeof(hex_line)); + size_t line_len = _min(len, LINE_SIZE); + for (size_t i = 0; i < line_len; ++i) { + unsigned char ch = udata[i]; + asc_line[i] = isprint(ch) ? ch : '.'; + hex_line[i*2 + i/4] = hex_encode(ch >> 4); + hex_line[i*2 + i/4 + 1] = hex_encode(ch & 0xf); + } + asc_line[sizeof(asc_line)-1] = 0; + hex_line[sizeof(hex_line)-1] = 0; + LOG_V(level) << label << direction + << asc_line << " " << hex_line << " "; + udata += line_len; + len -= line_len; + } + return; + } + + size_t consecutive_unprintable = state ? state->unprintable_count_[input] : 0; + + const unsigned char* end = udata + len; + while (udata < end) { + const unsigned char* line = udata; + const unsigned char* end_of_line = strchrn(udata, + end - udata, + '\n'); + if (!end_of_line) { + udata = end_of_line = end; + } else { + udata = end_of_line + 1; + } + + bool is_printable = true; + + // If we are in unprintable mode, we need to see a line of at least + // kMinPrintableLine characters before we'll switch back. + const ptrdiff_t kMinPrintableLine = 4; + if (consecutive_unprintable && ((end_of_line - line) < kMinPrintableLine)) { + is_printable = false; + } else { + // Determine if the line contains only whitespace and printable + // characters. + bool is_entirely_whitespace = true; + for (const unsigned char* pos = line; pos < end_of_line; ++pos) { + if (isspace(*pos)) + continue; + is_entirely_whitespace = false; + if (!isprint(*pos)) { + is_printable = false; + break; + } + } + // Treat an empty line following unprintable data as unprintable. + if (consecutive_unprintable && is_entirely_whitespace) { + is_printable = false; + } + } + if (!is_printable) { + consecutive_unprintable += (udata - line); + continue; + } + // Print out the current line, but prefix with a count of prior unprintable + // characters. + if (consecutive_unprintable) { + LOG_V(level) << label << direction << "## " << consecutive_unprintable + << " consecutive unprintable ##"; + consecutive_unprintable = 0; + } + // Strip off trailing whitespace. + while ((end_of_line > line) && isspace(*(end_of_line-1))) { + --end_of_line; + } + // Filter out any private data + std::string substr(reinterpret_cast(line), end_of_line - line); + std::string::size_type pos_private = substr.find("Email"); + if (pos_private == std::string::npos) { + pos_private = substr.find("Passwd"); + } + if (pos_private == std::string::npos) { + LOG_V(level) << label << direction << substr; + } else { + LOG_V(level) << label << direction << "## omitted for privacy ##"; + } + } + + if (state) { + state->unprintable_count_[input] = consecutive_unprintable; + } +} + +void InitDiagnosticLoggingDelegateFunction( + void (*delegate)(const std::string&)) { +#ifndef NDEBUG + // Ensure that this function is always called from the same thread. + base::subtle::NoBarrier_CompareAndSwap(&g_init_logging_delegate_thread_id, 0, + static_cast(base::PlatformThread::CurrentId())); + DCHECK_EQ( + g_init_logging_delegate_thread_id, + static_cast(base::PlatformThread::CurrentId())); +#endif + CHECK(delegate); + // This function may be called with the same argument several times if the + // page is reloaded or there are several PeerConnections on one page with + // logging enabled. This is OK, we simply don't have to do anything. + if (delegate == g_logging_delegate_function) + return; + CHECK(!g_logging_delegate_function); +#ifdef NDEBUG + IPAddress::set_strip_sensitive(true); +#endif + g_logging_delegate_function = delegate; + + if (g_extra_logging_init_function) + g_extra_logging_init_function(delegate); +} + +void SetExtraLoggingInit( + void (*function)(void (*delegate)(const std::string&))) { + CHECK(function); + CHECK(!g_extra_logging_init_function); + g_extra_logging_init_function = function; +} + +} // namespace rtc diff --git a/webrtc/overrides/webrtc/base/logging.h b/webrtc/overrides/webrtc/base/logging.h new file mode 100644 index 000000000..d8dfca2ce --- /dev/null +++ b/webrtc/overrides/webrtc/base/logging.h @@ -0,0 +1,221 @@ +/* + * Copyright 2012 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 file overrides the logging macros in libjingle (webrtc/base/logging.h). +// Instead of using libjingle's logging implementation, the libjingle macros are +// mapped to the corresponding base/logging.h macro (chromium's VLOG). +// If this file is included outside of libjingle (e.g. in wrapper code) it +// should be included after base/logging.h (if any) or compiler error or +// unexpected behavior may occur (macros that have the same name in libjingle as +// in chromium will use the libjingle definition if this file is included +// first). + +// Setting the LoggingSeverity (and lower) that should be written to file should +// be done via command line by specifying the flags: +// --vmodule or --v please see base/logging.h for details on how to use them. +// Specifying what file to write to is done using InitLogging also in +// base/logging.h. + +// The macros and classes declared in here are not described as they are +// NOT TO BE USED outside of libjingle. + +#ifndef THIRD_PARTY_LIBJINGLE_OVERRIDES_WEBRTC_BASE_LOGGING_H_ +#define THIRD_PARTY_LIBJINGLE_OVERRIDES_WEBRTC_BASE_LOGGING_H_ + +#include +#include + +#include "base/logging.h" +#include "third_party/webrtc/base/scoped_ref_ptr.h" + +namespace rtc { + +/////////////////////////////////////////////////////////////////////////////// +// ConstantLabel can be used to easily generate string names from constant +// values. This can be useful for logging descriptive names of error messages. +// Usage: +// const ConstantLabel LIBRARY_ERRORS[] = { +// KLABEL(SOME_ERROR), +// KLABEL(SOME_OTHER_ERROR), +// ... +// LASTLABEL +// } +// +// int err = LibraryFunc(); +// LOG(LS_ERROR) << "LibraryFunc returned: " +// << ErrorName(err, LIBRARY_ERRORS); + +struct ConstantLabel { + int value; + const char* label; +}; +#define KLABEL(x) { x, #x } +#define LASTLABEL { 0, 0 } + +const char* FindLabel(int value, const ConstantLabel entries[]); +std::string ErrorName(int err, const ConstantLabel* err_table); + +////////////////////////////////////////////////////////////////////// +// Note that the non-standard LoggingSeverity aliases exist because they are +// still in broad use. The meanings of the levels are: +// LS_SENSITIVE: Information which should only be logged with the consent +// of the user, due to privacy concerns. +// LS_VERBOSE: This level is for data which we do not want to appear in the +// normal debug log, but should appear in diagnostic logs. +// LS_INFO: Chatty level used in debugging for all sorts of things, the default +// in debug builds. +// LS_WARNING: Something that may warrant investigation. +// LS_ERROR: Something that should not have occurred. +// Note that LoggingSeverity is mapped over to chromiums verbosity levels where +// anything lower than or equal to the current verbosity level is written to +// file which is the opposite of logging severity in libjingle where higher +// severity numbers than or equal to the current severity level are written to +// file. Also, note that the values are explicitly defined here for convenience +// since the command line flag must be set using numerical values. +enum LoggingSeverity { LS_ERROR = 1, + LS_WARNING = 2, + LS_INFO = 3, + LS_VERBOSE = 4, + LS_SENSITIVE = 5, + INFO = LS_INFO, + WARNING = LS_WARNING, + LERROR = LS_ERROR }; + +// LogErrorContext assists in interpreting the meaning of an error value. +enum LogErrorContext { + ERRCTX_NONE, + ERRCTX_ERRNO, // System-local errno + ERRCTX_HRESULT, // Windows HRESULT + ERRCTX_OSSTATUS, // MacOS OSStatus + + // Abbreviations for LOG_E macro + ERRCTX_EN = ERRCTX_ERRNO, // LOG_E(sev, EN, x) + ERRCTX_HR = ERRCTX_HRESULT, // LOG_E(sev, HR, x) + ERRCTX_OS = ERRCTX_OSSTATUS, // LOG_E(sev, OS, x) +}; + +// Class that writes a log message to the logging delegate ("WebRTC logging +// stream" in Chrome) and to Chrome's logging stream. +class DiagnosticLogMessage { + public: + DiagnosticLogMessage(const char* file, int line, LoggingSeverity severity, + bool log_to_chrome, LogErrorContext err_ctx, int err); + DiagnosticLogMessage(const char* file, int line, LoggingSeverity severity, + bool log_to_chrome, LogErrorContext err_ctx, int err, + const char* module); + ~DiagnosticLogMessage(); + + void CreateTimestamp(); + + std::ostream& stream() { return print_stream_; } + + private: + const char* file_name_; + const int line_; + const LoggingSeverity severity_; + const bool log_to_chrome_; + + std::string extra_; + + std::ostringstream print_stream_; +}; + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". +class LogMessageVoidify { + public: + LogMessageVoidify() { } + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream&) { } +}; + +////////////////////////////////////////////////////////////////////// +// Logging Helpers +////////////////////////////////////////////////////////////////////// + +class LogMultilineState { + public: + size_t unprintable_count_[2]; + LogMultilineState() { + unprintable_count_[0] = unprintable_count_[1] = 0; + } +}; + +// When possible, pass optional state variable to track various data across +// multiple calls to LogMultiline. Otherwise, pass NULL. +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const void* data, size_t len, bool hex_mode, + LogMultilineState* state); + +// TODO(grunell): Change name to InitDiagnosticLoggingDelegate or +// InitDiagnosticLogging. Change also in init_webrtc.h/cc. +// TODO(grunell): typedef the delegate function. +void InitDiagnosticLoggingDelegateFunction( + void (*delegate)(const std::string&)); + +void SetExtraLoggingInit( + void (*function)(void (*delegate)(const std::string&))); +} // namespace rtc + +////////////////////////////////////////////////////////////////////// +// Libjingle macros which are mapped over to their VLOG equivalent in +// base/logging.h +////////////////////////////////////////////////////////////////////// + +#if defined(LOGGING_INSIDE_WEBRTC) + +#define DIAGNOSTIC_LOG(sev, ctx, err, ...) \ + rtc::DiagnosticLogMessage( \ + __FILE__, __LINE__, sev, VLOG_IS_ON(sev), \ + rtc::ERRCTX_ ## ctx, err, ##__VA_ARGS__).stream() + +#define LOG_CHECK_LEVEL(sev) VLOG_IS_ON(rtc::sev) +#define LOG_CHECK_LEVEL_V(sev) VLOG_IS_ON(sev) + +#define LOG_V(sev) DIAGNOSTIC_LOG(sev, NONE, 0) +#undef LOG +#define LOG(sev) DIAGNOSTIC_LOG(rtc::sev, NONE, 0) + +// The _F version prefixes the message with the current function name. +#if defined(__GNUC__) && defined(_DEBUG) +#define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": " +#else +#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": " +#endif + +#define LOG_E(sev, ctx, err, ...) \ + DIAGNOSTIC_LOG(rtc::sev, ctx, err, ##__VA_ARGS__) + +#undef LOG_ERRNO_EX +#define LOG_ERRNO_EX(sev, err) LOG_E(sev, ERRNO, err) +#undef LOG_ERRNO +#define LOG_ERRNO(sev) LOG_ERRNO_EX(sev, errno) + +#if defined(WEBRTC_WIN) +#define LOG_GLE_EX(sev, err) LOG_E(sev, HRESULT, err) +#define LOG_GLE(sev) LOG_GLE_EX(sev, GetLastError()) +#define LOG_GLEM(sev, mod) LOG_E(sev, HRESULT, GetLastError(), mod) +#define LOG_ERR_EX(sev, err) LOG_GLE_EX(sev, err) +#define LOG_ERR(sev) LOG_GLE(sev) +#define LAST_SYSTEM_ERROR (::GetLastError()) +#else +#define LOG_ERR_EX(sev, err) LOG_ERRNO_EX(sev, err) +#define LOG_ERR(sev) LOG_ERRNO(sev) +#define LAST_SYSTEM_ERROR (errno) +#endif // OS_WIN + +#undef PLOG +#define PLOG(sev, err) LOG_ERR_EX(sev, err) + +#endif // LOGGING_INSIDE_WEBRTC + +#endif // THIRD_PARTY_LIBJINGLE_OVERRIDES_WEBRTC_BASE_LOGGING_H_ diff --git a/webrtc/overrides/webrtc/base/win32socketinit.cc b/webrtc/overrides/webrtc/base/win32socketinit.cc new file mode 100644 index 000000000..929ce8d36 --- /dev/null +++ b/webrtc/overrides/webrtc/base/win32socketinit.cc @@ -0,0 +1,28 @@ +/* + * Copyright 2006 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. + */ + +// Redirect Libjingle's winsock initialization activity into Chromium's +// singleton object that managest precisely that for the browser. + +#include "webrtc/base/win32socketinit.h" + +#include "net/base/winsock_init.h" + +#if !defined(WEBRTC_WIN) +#error "Only compile this on Windows" +#endif + +namespace rtc { + +void EnsureWinsockInit() { + net::EnsureWinsockInit(); +} + +} // namespace rtc diff --git a/webrtc/sanitizer_options.gyp b/webrtc/sanitizer_options.gyp new file mode 100644 index 000000000..ece9dc920 --- /dev/null +++ b/webrtc/sanitizer_options.gyp @@ -0,0 +1,59 @@ +# Copyright (c) 2014 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 is a similar target to the one in Chromium's base.gyp. It's needed to get +# the same sanitizer settings as Chromium uses (i.e. ASan, LSan, TSan...). +{ + 'targets': [ + { + 'target_name': 'sanitizer_options', + 'type': 'static_library', + 'toolsets': ['host', 'target'], + 'variables': { + # Every target is going to depend on sanitizer_options, so allow + # this one to depend on itself. + 'prune_self_dependency': 1, + # Do not let 'none' targets depend on this one, they don't need to. + 'link_dependency': 1, + }, + 'sources': [ + '<(DEPTH)/tools/sanitizer_options/sanitizer_options.cc', + ], + 'include_dirs': [ + '<(DEPTH)', + ], + # Some targets may want to opt-out from ASan, TSan and MSan and link + # without the corresponding runtime libraries. We drop the libc++ + # dependency and omit the compiler flags to avoid bringing instrumented + # code to those targets. + 'conditions': [ + ['use_custom_libcxx==1', { + 'dependencies!': [ + '<(DEPTH)/third_party/libc++/libc++.gyp:libcxx_proxy', + ], + }], + ['tsan==1', { + 'sources': [ + '<(DEPTH)/tools/tsan_suppressions/tsan_suppressions.cc', + ], + }], + ], + 'cflags!': [ + '-fsanitize=address', + '-fsanitize=thread', + '-fsanitize=memory', + '-fsanitize-memory-track-origins', + ], + 'direct_dependent_settings': { + 'ldflags': [ + '-Wl,-u_sanitizer_options_link_helper', + ], + }, + }, + ], # targets +} diff --git a/webrtc/supplement.gypi b/webrtc/supplement.gypi index 7898d4cbd..351e10f2f 100644 --- a/webrtc/supplement.gypi +++ b/webrtc/supplement.gypi @@ -1,5 +1,23 @@ { 'variables': { + 'variables': { + 'webrtc_root%': '<(DEPTH)/webrtc', + }, + 'webrtc_root%': '<(webrtc_root)', 'build_with_chromium': 0, - } + 'use_sanitizer_options': 0, + }, + 'target_defaults': { + 'conditions': [ + # Add default sanitizer options similar to Chromium. This cannot be + # put in webrtc/build/common.gypi since that file is not included by + # third party code (yasm will throw leak errors during compile when + # GYP_DEFINES="asan=1". + ['OS=="linux" and (chromeos==0 or target_arch!="ia32")', { + 'dependencies': [ + '<(webrtc_root)/sanitizer_options.gyp:sanitizer_options', + ], + }], + ], + }, } diff --git a/webrtc/system_wrappers/BUILD.gn b/webrtc/system_wrappers/BUILD.gn new file mode 100644 index 000000000..4c6c6051c --- /dev/null +++ b/webrtc/system_wrappers/BUILD.gn @@ -0,0 +1,213 @@ +# Copyright (c) 2014 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. + +import("../build/webrtc.gni") + +config("system_wrappers_inherited_config") { + include_dirs = [ + "interface", + ] +} + +static_library("system_wrappers") { + sources = [ + "interface/aligned_malloc.h", + "interface/atomic32.h", + "interface/clock.h", + "interface/compile_assert.h", + "interface/condition_variable_wrapper.h", + "interface/cpu_info.h", + "interface/cpu_features_wrapper.h", + "interface/critical_section_wrapper.h", + "interface/data_log.h", + "interface/data_log_c.h", + "interface/data_log_impl.h", + "interface/event_tracer.h", + "interface/event_wrapper.h", + "interface/field_trial.h", + "interface/file_wrapper.h", + "interface/fix_interlocked_exchange_pointer_win.h", + "interface/logging.h", + "interface/ref_count.h", + "interface/rtp_to_ntp.h", + "interface/rw_lock_wrapper.h", + "interface/scoped_ptr.h", + "interface/scoped_refptr.h", + "interface/scoped_vector.h", + "interface/sleep.h", + "interface/sort.h", + "interface/static_instance.h", + "interface/stl_util.h", + "interface/stringize_macros.h", + "interface/thread_annotations.h", + "interface/thread_wrapper.h", + "interface/tick_util.h", + "interface/timestamp_extrapolator.h", + "interface/trace.h", + "interface/trace_event.h", + "interface/utf_util_win.h", + "source/aligned_malloc.cc", + "source/atomic32_mac.cc", + "source/atomic32_win.cc", + "source/clock.cc", + "source/condition_variable.cc", + "source/condition_variable_posix.cc", + "source/condition_variable_posix.h", + "source/condition_variable_event_win.cc", + "source/condition_variable_event_win.h", + "source/condition_variable_native_win.cc", + "source/condition_variable_native_win.h", + "source/cpu_info.cc", + "source/cpu_features.cc", + "source/critical_section.cc", + "source/critical_section_posix.cc", + "source/critical_section_posix.h", + "source/critical_section_win.cc", + "source/critical_section_win.h", + "source/data_log_c.cc", + "source/event.cc", + "source/event_posix.cc", + "source/event_posix.h", + "source/event_tracer.cc", + "source/event_win.cc", + "source/event_win.h", + "source/file_impl.cc", + "source/file_impl.h", + "source/logging.cc", + "source/rtp_to_ntp.cc", + "source/rw_lock.cc", + "source/rw_lock_generic.cc", + "source/rw_lock_generic.h", + "source/rw_lock_posix.cc", + "source/rw_lock_posix.h", + "source/rw_lock_win.cc", + "source/rw_lock_win.h", + "source/set_thread_name_win.h", + "source/sleep.cc", + "source/sort.cc", + "source/tick_util.cc", + "source/thread.cc", + "source/thread_posix.cc", + "source/thread_posix.h", + "source/thread_win.cc", + "source/thread_win.h", + "source/timestamp_extrapolator.cc", + "source/trace_impl.cc", + "source/trace_impl.h", + "source/trace_posix.cc", + "source/trace_posix.h", + "source/trace_win.cc", + "source/trace_win.h", + ] + + configs += [ "..:common_config" ] + + direct_dependent_configs = [ + "..:common_inherited_config", + ":system_wrappers_inherited_config", + ] + + if (enable_data_logging) { + sources += [ "source/data_log.cc" ] + } else { + sources += [ "source/data_log_no_op.cc" ] + } + + defines = [] + libs = [] + + if (is_android) { + sources += [ + "interface/logcat_trace_context.h", + "source/logcat_trace_context.cc", + ] + + defines += [ + "WEBRTC_THREAD_RR", + # TODO(leozwang): Investigate CLOCK_REALTIME and CLOCK_MONOTONIC + # support on Android. Keep WEBRTC_CLOCK_TYPE_REALTIME for now, + # remove it after I verify that CLOCK_MONOTONIC is fully functional + # with condition and event functions in system_wrappers. + "WEBRTC_CLOCK_TYPE_REALTIME", + ] + + deps = [ "cpu_features_android" ] + + libs += [ "log" ] + } + + if (is_linux) { + defines += [ + "WEBRTC_THREAD_RR", + # TODO(andrew): can we select this automatically? + # Define this if the Linux system does not support CLOCK_MONOTONIC. + #"WEBRTC_CLOCK_TYPE_REALTIME", + ] + + libs += [ "rt" ] + } + + if (!is_mac && !is_ios) { + sources += [ + "source/atomic32_posix.cc", + ] + } + + if (is_ios || is_mac) { + defines += [ + "WEBRTC_THREAD_RR", + "WEBRTC_CLOCK_TYPE_REALTIME", + ] + } + + if (is_ios) { + sources += [ + "source/atomic32_mac.cc", + ] + } + + if (is_win) { + libs += [ "winmm.lib" ] + + cflags = [ + "/wd4267", # size_t to int truncation. + "/wd4334", # Ignore warning on shift operator promotion. + ] + } + + include_dirs = [ + "source/spreadsortlib", + ] + + deps = [ + "../base:webrtc_base", + ] +} + +source_set("field_trial_default") { + sources = [ + "source/field_trial_default.cc", + ] + deps = [ + ":system_wrappers", + ] +} + +if (is_android) { + source_set("cpu_features_android") { + sources = [ + "source/cpu_features_android.c", + ] + + if (is_android_webview_build) { + libs += [ "cpufeatures.a" ] + } else { + deps = [ "//third_party/android_tools:cpu_features" ] + } + } +} diff --git a/webrtc/system_wrappers/OWNERS b/webrtc/system_wrappers/OWNERS index 4091a93d7..d292dfdb0 100644 --- a/webrtc/system_wrappers/OWNERS +++ b/webrtc/system_wrappers/OWNERS @@ -4,4 +4,6 @@ perkj@webrtc.org henrika@webrtc.org henrikg@webrtc.org mflodman@webrtc.org -niklas.enbom@webrtc.org \ No newline at end of file +niklas.enbom@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/system_wrappers/interface/atomic32.h b/webrtc/system_wrappers/interface/atomic32.h index 08ab4f255..8633e2636 100644 --- a/webrtc/system_wrappers/interface/atomic32.h +++ b/webrtc/system_wrappers/interface/atomic32.h @@ -17,8 +17,8 @@ #include +#include "webrtc/base/constructormagic.h" #include "webrtc/common_types.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" namespace webrtc { diff --git a/webrtc/system_wrappers/interface/clock.h b/webrtc/system_wrappers/interface/clock.h index ce3269137..c03f976d4 100644 --- a/webrtc/system_wrappers/interface/clock.h +++ b/webrtc/system_wrappers/interface/clock.h @@ -11,10 +11,14 @@ #ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CLOCK_H_ #define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CLOCK_H_ +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" #include "webrtc/typedefs.h" namespace webrtc { +class RWLockWrapper; + // January 1970, in NTP seconds. const uint32_t kNtpJan1970 = 2208988800UL; @@ -28,17 +32,17 @@ class Clock { // Return a timestamp in milliseconds relative to some arbitrary source; the // source is fixed for this clock. - virtual int64_t TimeInMilliseconds() = 0; + virtual int64_t TimeInMilliseconds() const = 0; // Return a timestamp in microseconds relative to some arbitrary source; the // source is fixed for this clock. - virtual int64_t TimeInMicroseconds() = 0; + virtual int64_t TimeInMicroseconds() const = 0; // Retrieve an NTP absolute timestamp in seconds and fractions of a second. - virtual void CurrentNtp(uint32_t& seconds, uint32_t& fractions) = 0; + virtual void CurrentNtp(uint32_t& seconds, uint32_t& fractions) const = 0; // Retrieve an NTP absolute timestamp in milliseconds. - virtual int64_t CurrentNtpInMilliseconds() = 0; + virtual int64_t CurrentNtpInMilliseconds() const = 0; // Converts an NTP timestamp to a millisecond timestamp. static int64_t NtpToMs(uint32_t seconds, uint32_t fractions); @@ -51,21 +55,22 @@ class SimulatedClock : public Clock { public: explicit SimulatedClock(int64_t initial_time_us); - virtual ~SimulatedClock() {} + virtual ~SimulatedClock(); // Return a timestamp in milliseconds relative to some arbitrary source; the // source is fixed for this clock. - virtual int64_t TimeInMilliseconds() OVERRIDE; + virtual int64_t TimeInMilliseconds() const OVERRIDE; // Return a timestamp in microseconds relative to some arbitrary source; the // source is fixed for this clock. - virtual int64_t TimeInMicroseconds() OVERRIDE; + virtual int64_t TimeInMicroseconds() const OVERRIDE; // Retrieve an NTP absolute timestamp in milliseconds. - virtual void CurrentNtp(uint32_t& seconds, uint32_t& fractions) OVERRIDE; + virtual void CurrentNtp(uint32_t& seconds, + uint32_t& fractions) const OVERRIDE; // Converts an NTP timestamp to a millisecond timestamp. - virtual int64_t CurrentNtpInMilliseconds() OVERRIDE; + virtual int64_t CurrentNtpInMilliseconds() const OVERRIDE; // Advance the simulated clock with a given number of milliseconds or // microseconds. @@ -73,7 +78,8 @@ class SimulatedClock : public Clock { void AdvanceTimeMicroseconds(int64_t microseconds); private: - int64_t time_us_; + int64_t time_us_ GUARDED_BY(lock_); + scoped_ptr lock_; }; }; // namespace webrtc diff --git a/webrtc/system_wrappers/interface/compile_assert.h b/webrtc/system_wrappers/interface/compile_assert.h index cdeaa5676..a075184b5 100644 --- a/webrtc/system_wrappers/interface/compile_assert.h +++ b/webrtc/system_wrappers/interface/compile_assert.h @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -// Borrowed from Chromium's src/base/basictypes.h. +// Borrowed from Chromium's src/base/macros.h. #ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_COMPILE_ASSERT_H_ #define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_COMPILE_ASSERT_H_ @@ -31,13 +31,20 @@ // TODO(ajm): Hack to avoid multiple definitions until the base/ of webrtc and // libjingle are merged. #if !defined(COMPILE_ASSERT) +#if __cplusplus >= 201103L +// Under C++11, just use static_assert. +#define COMPILE_ASSERT(expr, msg) static_assert(expr, #msg) + +#else template struct CompileAssert { }; #define COMPILE_ASSERT(expr, msg) \ typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] -#endif // COMPILE_ASSERT + +#endif // __cplusplus >= 201103L +#endif // !defined(COMPILE_ASSERT) // Implementation details of COMPILE_ASSERT: // diff --git a/webrtc/system_wrappers/interface/field_trial.h b/webrtc/system_wrappers/interface/field_trial.h new file mode 100644 index 000000000..f2cf88027 --- /dev/null +++ b/webrtc/system_wrappers/interface/field_trial.h @@ -0,0 +1,70 @@ +// +// Copyright (c) 2014 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_SYSTEM_WRAPPERS_INTERFACE_FIELD_TRIAL_H_ +#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_FIELD_TRIAL_H_ + +#include + +#include "webrtc/common_types.h" + +// Field trials allow webrtc clients (such as Chrome) to turn on feature code +// in binaries out in the field and gather information with that. +// +// WebRTC clients MUST provide an implementation of: +// +// std::string webrtc::field_trial::FindFullName(const std::string& trial). +// +// Or link with a default one provided in: +// +// system_wrappers/source/system_wrappers.gyp:field_trial_default +// +// +// They are designed to wire up directly to chrome field trials and to speed up +// developers by reducing the need to wire APIs to control whether a feature is +// on/off. E.g. to experiment with a new method that could lead to a different +// trade-off between CPU/bandwidth: +// +// 1 - Develop the feature with default behaviour off: +// +// if (FieldTrial::FindFullName("WebRTCExperimenMethod2") == "Enabled") +// method2(); +// else +// method1(); +// +// 2 - Once the changes are rolled to chrome, the new code path can be +// controlled as normal chrome field trials. +// +// 3 - Evaluate the new feature and clean the code paths. +// +// Notes: +// - NOT every feature is a candidate to be controlled by this mechanism as +// it may require negotation between involved parties (e.g. SDP). +// +// TODO(andresp): since chrome --force-fieldtrials does not marks the trial +// as active it does not gets propaged to renderer process. For now one +// needs to push a config with start_active:true or run a local finch +// server. +// +// TODO(andresp): find out how to get bots to run tests with trials enabled. + +namespace webrtc { +namespace field_trial { + +// Returns the group name chosen for the named trial, or the empty string +// if the trial does not exists. +// +// Note: To keep things tidy append all the trial names with WebRTC. +std::string FindFullName(const std::string& name); + +} // namespace field_trial +} // namespace webrtc + +#endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_FIELD_TRIAL_H_ diff --git a/webrtc/modules/remote_bitrate_estimator/include/rtp_to_ntp.h b/webrtc/system_wrappers/interface/rtp_to_ntp.h similarity index 69% rename from webrtc/modules/remote_bitrate_estimator/include/rtp_to_ntp.h rename to webrtc/system_wrappers/interface/rtp_to_ntp.h index 7928abfac..dfc25cd9e 100644 --- a/webrtc/modules/remote_bitrate_estimator/include/rtp_to_ntp.h +++ b/webrtc/system_wrappers/interface/rtp_to_ntp.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_RTP_TO_NTP_H_ -#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_RTP_TO_NTP_H_ +#ifndef SYSTEM_WRAPPERS_INTERFACE_RTP_TO_NTP_H_ +#define SYSTEM_WRAPPERS_INTERFACE_RTP_TO_NTP_H_ #include @@ -17,8 +17,6 @@ namespace webrtc { -namespace synchronization { - struct RtcpMeasurement { RtcpMeasurement(); RtcpMeasurement(uint32_t ntp_secs, uint32_t ntp_frac, uint32_t timestamp); @@ -29,6 +27,15 @@ struct RtcpMeasurement { typedef std::list RtcpList; +// Updates |rtcp_list| with timestamps from the latest RTCP SR. +// |new_rtcp_sr| will be set to true if these are the timestamps which have +// never be added to |rtcp_list|. +bool UpdateRtcpList(uint32_t ntp_secs, + uint32_t ntp_frac, + uint32_t rtp_timestamp, + RtcpList* rtcp_list, + bool* new_rtcp_sr); + // Converts an RTP timestamp to the NTP domain in milliseconds using two // (RTP timestamp, NTP timestamp) pairs. bool RtpToNtpMs(int64_t rtp_timestamp, const RtcpList& rtcp, @@ -37,7 +44,7 @@ bool RtpToNtpMs(int64_t rtp_timestamp, const RtcpList& rtcp, // Returns 1 there has been a forward wrap around, 0 if there has been no wrap // around and -1 if there has been a backwards wrap around (i.e. reordering). int CheckForWrapArounds(uint32_t rtp_timestamp, uint32_t rtcp_rtp_timestamp); -} // namespace synchronization + } // namespace webrtc -#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_RTP_TO_NTP_H_ +#endif // SYSTEM_WRAPPERS_INTERFACE_RTP_TO_NTP_H_ diff --git a/webrtc/system_wrappers/interface/scoped_ptr.h b/webrtc/system_wrappers/interface/scoped_ptr.h index fb203638f..42bb8a6dd 100644 --- a/webrtc/system_wrappers/interface/scoped_ptr.h +++ b/webrtc/system_wrappers/interface/scoped_ptr.h @@ -104,8 +104,8 @@ #include // For std::swap(). +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/compile_assert.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" #include "webrtc/system_wrappers/interface/template_util.h" #include "webrtc/system_wrappers/source/move.h" #include "webrtc/typedefs.h" @@ -563,82 +563,4 @@ bool operator!=(T* p1, const webrtc::scoped_ptr& p2) { return p1 != p2.get(); } -namespace webrtc { - -// DEPRECATED: Use scoped_ptr instead. -// TODO(ajm): Remove scoped_array. -// -// scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to -// is guaranteed, either on destruction of the scoped_array or via an explicit -// reset(). Use shared_array or std::vector if your needs are more complex. - -template -class scoped_array { - private: - - T* ptr; - - scoped_array(scoped_array const &); - scoped_array & operator=(scoped_array const &); - - public: - - typedef T element_type; - - explicit scoped_array(T* p = NULL) : ptr(p) {} - - ~scoped_array() { - typedef char type_must_be_complete[sizeof(T)]; - delete[] ptr; - } - - void reset(T* p = NULL) { - typedef char type_must_be_complete[sizeof(T)]; - - if (ptr != p) { - T* arr = ptr; - ptr = p; - // Delete last, in case arr destructor indirectly results in ~scoped_array - delete [] arr; - } - } - - T& operator[](ptrdiff_t i) const { - assert(ptr != NULL); - assert(i >= 0); - return ptr[i]; - } - - T* get() const { - return ptr; - } - - void swap(scoped_array & b) { - T* tmp = b.ptr; - b.ptr = ptr; - ptr = tmp; - } - - T* release() { - T* tmp = ptr; - ptr = NULL; - return tmp; - } - - T** accept() { - if (ptr) { - delete [] ptr; - ptr = NULL; - } - return &ptr; - } -}; - -template inline -void swap(scoped_array& a, scoped_array& b) { - a.swap(b); -} - -} // namespace webrtc - #endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_SCOPED_PTR_H_ diff --git a/webrtc/system_wrappers/interface/scoped_vector.h b/webrtc/system_wrappers/interface/scoped_vector.h new file mode 100644 index 000000000..68db3a121 --- /dev/null +++ b/webrtc/system_wrappers/interface/scoped_vector.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/memory/scoped_vector.h. + +#ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_SCOPED_VECTOR_H_ +#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_SCOPED_VECTOR_H_ + +#include +#include +#include + +#include "webrtc/system_wrappers/interface/stl_util.h" +#include "webrtc/system_wrappers/source/move.h" + +namespace webrtc { + +// ScopedVector wraps a vector deleting the elements from its +// destructor. +template +class ScopedVector { + WEBRTC_MOVE_ONLY_TYPE_FOR_CPP_03(ScopedVector, RValue) + + public: + typedef typename std::vector::allocator_type allocator_type; + typedef typename std::vector::size_type size_type; + typedef typename std::vector::difference_type difference_type; + typedef typename std::vector::pointer pointer; + typedef typename std::vector::const_pointer const_pointer; + typedef typename std::vector::reference reference; + typedef typename std::vector::const_reference const_reference; + typedef typename std::vector::value_type value_type; + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; + typedef typename std::vector::reverse_iterator reverse_iterator; + typedef typename std::vector::const_reverse_iterator + const_reverse_iterator; + + ScopedVector() {} + ~ScopedVector() { clear(); } + ScopedVector(RValue other) { swap(*other.object); } + + ScopedVector& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + reference operator[](size_t index) { return v_[index]; } + const_reference operator[](size_t index) const { return v_[index]; } + + bool empty() const { return v_.empty(); } + size_t size() const { return v_.size(); } + + reverse_iterator rbegin() { return v_.rbegin(); } + const_reverse_iterator rbegin() const { return v_.rbegin(); } + reverse_iterator rend() { return v_.rend(); } + const_reverse_iterator rend() const { return v_.rend(); } + + iterator begin() { return v_.begin(); } + const_iterator begin() const { return v_.begin(); } + iterator end() { return v_.end(); } + const_iterator end() const { return v_.end(); } + + const_reference front() const { return v_.front(); } + reference front() { return v_.front(); } + const_reference back() const { return v_.back(); } + reference back() { return v_.back(); } + + void push_back(T* elem) { v_.push_back(elem); } + + void pop_back() { + assert(!empty()); + delete v_.back(); + v_.pop_back(); + } + + std::vector& get() { return v_; } + const std::vector& get() const { return v_; } + void swap(std::vector& other) { v_.swap(other); } + void swap(ScopedVector& other) { v_.swap(other.v_); } + void release(std::vector* out) { + out->swap(v_); + v_.clear(); + } + + void reserve(size_t capacity) { v_.reserve(capacity); } + + // Resize, deleting elements in the disappearing range if we are shrinking. + void resize(size_t new_size) { + if (v_.size() > new_size) + STLDeleteContainerPointers(v_.begin() + new_size, v_.end()); + v_.resize(new_size); + } + + template + void assign(InputIterator begin, InputIterator end) { + v_.assign(begin, end); + } + + void clear() { STLDeleteElements(&v_); } + + // Like |clear()|, but doesn't delete any elements. + void weak_clear() { v_.clear(); } + + // Lets the ScopedVector take ownership of |x|. + iterator insert(iterator position, T* x) { + return v_.insert(position, x); + } + + // Lets the ScopedVector take ownership of elements in [first,last). + template + void insert(iterator position, InputIterator first, InputIterator last) { + v_.insert(position, first, last); + } + + iterator erase(iterator position) { + delete *position; + return v_.erase(position); + } + + iterator erase(iterator first, iterator last) { + STLDeleteContainerPointers(first, last); + return v_.erase(first, last); + } + + // Like |erase()|, but doesn't delete the element at |position|. + iterator weak_erase(iterator position) { + return v_.erase(position); + } + + // Like |erase()|, but doesn't delete the elements in [first, last). + iterator weak_erase(iterator first, iterator last) { + return v_.erase(first, last); + } + + private: + std::vector v_; +}; + +} // namespace webrtc + +#endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_SCOPED_VECTOR_H_ diff --git a/webrtc/system_wrappers/interface/stl_util.h b/webrtc/system_wrappers/interface/stl_util.h new file mode 100644 index 000000000..ebe855fb1 --- /dev/null +++ b/webrtc/system_wrappers/interface/stl_util.h @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/stl_util.h. + +#ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_STL_UTIL_H_ +#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_STL_UTIL_H_ + +#include +#include +#include +#include +#include +#include + +namespace webrtc { + +// Clears internal memory of an STL object. +// STL clear()/reserve(0) does not always free internal memory allocated +// This function uses swap/destructor to ensure the internal memory is freed. +template +void STLClearObject(T* obj) { + T tmp; + tmp.swap(*obj); + // Sometimes "T tmp" allocates objects with memory (arena implementation?). + // Hence using additional reserve(0) even if it doesn't always work. + obj->reserve(0); +} + +// For a range within a container of pointers, calls delete (non-array version) +// on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template +void STLDeleteContainerPointers(ForwardIterator begin, ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// BOTH items in the pairs. +// NOTE: Like STLDeleteContainerPointers, it is important that this deletes +// behind the iterator because if both the key and value are deleted, the +// container may call the hash function on the iterator when it is advanced, +// which could result in the hash function trying to dereference a stale +// pointer. +template +void STLDeleteContainerPairPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + delete temp->second; + } +} + +// For a range within a container of pairs, calls delete (non-array version) on +// the FIRST item in the pairs. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +template +void STLDeleteContainerPairFirstPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->first; + } +} + +// For a range within a container of pairs, calls delete. +// NOTE: Like STLDeleteContainerPointers, deleting behind the iterator. +// Deleting the value does not always invalidate the iterator, but it may +// do so if the key is a pointer into the value object. +template +void STLDeleteContainerPairSecondPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete temp->second; + } +} + +// To treat a possibly-empty vector as an array, use these functions. +// If you know the array will never be empty, you can use &*v.begin() +// directly, but that is undefined behaviour if |v| is empty. +template +inline T* vector_as_array(std::vector* v) { + return v->empty() ? NULL : &*v->begin(); +} + +template +inline const T* vector_as_array(const std::vector* v) { + return v->empty() ? NULL : &*v->begin(); +} + +// Return a mutable char* pointing to a string's internal buffer, +// which may not be null-terminated. Writing through this pointer will +// modify the string. +// +// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the +// next call to a string method that invalidates iterators. +// +// As of 2006-04, there is no standard-blessed way of getting a +// mutable reference to a string's internal buffer. However, issue 530 +// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#530) +// proposes this as the method. According to Matt Austern, this should +// already work on all current implementations. +inline char* string_as_array(std::string* str) { + // DO NOT USE const_cast(str->data()) + return str->empty() ? NULL : &*str->begin(); +} + +// The following functions are useful for cleaning up STL containers whose +// elements point to allocated memory. + +// STLDeleteElements() deletes all the elements in an STL container and clears +// the container. This function is suitable for use with a vector, set, +// hash_set, or any other STL container which defines sensible begin(), end(), +// and clear() methods. +// +// If container is NULL, this function is a no-op. +// +// As an alternative to calling STLDeleteElements() directly, consider +// STLElementDeleter (defined below), which ensures that your container's +// elements are deleted when the STLElementDeleter goes out of scope. +template +void STLDeleteElements(T* container) { + if (!container) + return; + STLDeleteContainerPointers(container->begin(), container->end()); + container->clear(); +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template +void STLDeleteValues(T* container) { + if (!container) + return; + for (typename T::iterator i(container->begin()); i != container->end(); ++i) + delete i->second; + container->clear(); +} + + +// The following classes provide a convenient way to delete all elements or +// values from STL containers when they goes out of scope. This greatly +// simplifies code that creates temporary objects and has multiple return +// statements. Example: +// +// vector tmp_proto; +// STLElementDeleter > d(&tmp_proto); +// if (...) return false; +// ... +// return success; + +// Given a pointer to an STL container this class will delete all the element +// pointers when it goes out of scope. +template +class STLElementDeleter { + public: + STLElementDeleter(T* container) : container_(container) {} + ~STLElementDeleter() { STLDeleteElements(container_); } + + private: + T* container_; +}; + +// Given a pointer to an STL container this class will delete all the value +// pointers when it goes out of scope. +template +class STLValueDeleter { + public: + STLValueDeleter(T* container) : container_(container) {} + ~STLValueDeleter() { STLDeleteValues(container_); } + + private: + T* container_; +}; + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +// Returns true if the container is sorted. +template +bool STLIsSorted(const Container& cont) { + // Note: Use reverse iterator on container to ensure we only require + // value_type to implement operator<. + return std::adjacent_find(cont.rbegin(), cont.rend(), + std::less()) + == cont.rend(); +} + +// Returns a new ResultType containing the difference of two sorted containers. +template +ResultType STLSetDifference(const Arg1& a1, const Arg2& a2) { + assert(STLIsSorted(a1)); + assert(STLIsSorted(a2)); + ResultType difference; + std::set_difference(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(difference, difference.end())); + return difference; +} + +// Returns a new ResultType containing the union of two sorted containers. +template +ResultType STLSetUnion(const Arg1& a1, const Arg2& a2) { + assert(STLIsSorted(a1)); + assert(STLIsSorted(a2)); + ResultType result; + std::set_union(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(result, result.end())); + return result; +} + +// Returns a new ResultType containing the intersection of two sorted +// containers. +template +ResultType STLSetIntersection(const Arg1& a1, const Arg2& a2) { + assert(STLIsSorted(a1)); + assert(STLIsSorted(a2)); + ResultType result; + std::set_intersection(a1.begin(), a1.end(), + a2.begin(), a2.end(), + std::inserter(result, result.end())); + return result; +} + +// Returns true if the sorted container |a1| contains all elements of the sorted +// container |a2|. +template +bool STLIncludes(const Arg1& a1, const Arg2& a2) { + assert(STLIsSorted(a1)); + assert(STLIsSorted(a2)); + return std::includes(a1.begin(), a1.end(), + a2.begin(), a2.end()); +} + +} // namespace webrtc + +#endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_STL_UTIL_H_ diff --git a/webrtc/modules/video_coding/main/source/timestamp_extrapolator.h b/webrtc/system_wrappers/interface/timestamp_extrapolator.h similarity index 68% rename from webrtc/modules/video_coding/main/source/timestamp_extrapolator.h rename to webrtc/system_wrappers/interface/timestamp_extrapolator.h index 4565186a3..d067198d8 100644 --- a/webrtc/modules/video_coding/main/source/timestamp_extrapolator.h +++ b/webrtc/system_wrappers/interface/timestamp_extrapolator.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef WEBRTC_MODULES_VIDEO_CODING_TIMESTAMP_EXTRAPOLATOR_H_ -#define WEBRTC_MODULES_VIDEO_CODING_TIMESTAMP_EXTRAPOLATOR_H_ +#ifndef SYSTEM_WRAPPERS_INTERFACE_TIMESTAMP_EXTRAPOLATOR_H_ +#define SYSTEM_WRAPPERS_INTERFACE_TIMESTAMP_EXTRAPOLATOR_H_ #include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" #include "webrtc/typedefs.h" @@ -17,26 +17,19 @@ namespace webrtc { -class Clock; - -class VCMTimestampExtrapolator +class TimestampExtrapolator { public: - VCMTimestampExtrapolator(Clock* clock, - int32_t vcmId = 0, - int32_t receiverId = 0); - ~VCMTimestampExtrapolator(); - void Update(int64_t tMs, uint32_t ts90khz, bool trace = true); + explicit TimestampExtrapolator(int64_t start_ms); + ~TimestampExtrapolator(); + void Update(int64_t tMs, uint32_t ts90khz); int64_t ExtrapolateLocalTime(uint32_t timestamp90khz); - void Reset(); + void Reset(int64_t start_ms); private: void CheckForWrapArounds(uint32_t ts90khz); - bool DelayChangeDetection(double error, bool trace = true); + bool DelayChangeDetection(double error); RWLockWrapper* _rwLock; - int32_t _vcmId; - int32_t _id; - Clock* _clock; double _w[2]; double _P[2][2]; int64_t _startMs; @@ -60,4 +53,4 @@ class VCMTimestampExtrapolator } // namespace webrtc -#endif // WEBRTC_MODULES_VIDEO_CODING_TIMESTAMP_EXTRAPOLATOR_H_ +#endif // SYSTEM_WRAPPERS_INTERFACE_TIMESTAMP_EXTRAPOLATOR_H_ diff --git a/webrtc/system_wrappers/interface/utf_util_win.h b/webrtc/system_wrappers/interface/utf_util_win.h new file mode 100644 index 000000000..f88f0799f --- /dev/null +++ b/webrtc/system_wrappers/interface/utf_util_win.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014 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. + */ + +// Conversion functions for UTF-8 and UTF-16 strings on Windows. +// Duplicated from talk/base/win32.h. +#ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_UTF_UTIL_H_ +#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_UTF_UTIL_H_ + +#ifdef WIN32 +#include +#include + +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +inline std::wstring ToUtf16(const char* utf8, size_t len) { + int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast(len), + NULL, 0); + scoped_ptr ws(new wchar_t[len16]); + ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast(len), ws.get(), + len16); + return std::wstring(ws.get(), len16); +} + +inline std::wstring ToUtf16(const std::string& str) { + return ToUtf16(str.data(), str.length()); +} + +inline std::string ToUtf8(const wchar_t* wide, size_t len) { + int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast(len), + NULL, 0, NULL, NULL); + scoped_ptr ns(new char[len8]); + ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast(len), ns.get(), len8, + NULL, NULL); + return std::string(ns.get(), len8); +} + +inline std::string ToUtf8(const wchar_t* wide) { + return ToUtf8(wide, wcslen(wide)); +} + +inline std::string ToUtf8(const std::wstring& wstr) { + return ToUtf8(wstr.data(), wstr.length()); +} + +} // namespace webrtc + +#endif // WIN32 +#endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_UTF_UTIL_H_ diff --git a/webrtc/system_wrappers/source/Android.mk b/webrtc/system_wrappers/source/Android.mk index f48019d6a..4767e72cb 100644 --- a/webrtc/system_wrappers/source/Android.mk +++ b/webrtc/system_wrappers/source/Android.mk @@ -35,11 +35,13 @@ LOCAL_SRC_FILES := \ condition_variable_posix.cc \ critical_section_posix.cc \ event_posix.cc \ + rtp_to_ntp.cc \ sleep.cc \ thread_posix.cc \ tick_util.cc \ + timestamp_extrapolator.cc \ trace_posix.cc \ - rw_lock_posix.cc + rw_lock_posix.cc LOCAL_CFLAGS := \ $(MY_WEBRTC_COMMON_DEFS) diff --git a/webrtc/system_wrappers/source/OWNERS b/webrtc/system_wrappers/source/OWNERS new file mode 100644 index 000000000..bbffda7e4 --- /dev/null +++ b/webrtc/system_wrappers/source/OWNERS @@ -0,0 +1,6 @@ +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/system_wrappers/source/clock.cc b/webrtc/system_wrappers/source/clock.cc index 7ef6c66fb..33eb8561f 100644 --- a/webrtc/system_wrappers/source/clock.cc +++ b/webrtc/system_wrappers/source/clock.cc @@ -20,6 +20,7 @@ #include #endif +#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" #include "webrtc/system_wrappers/interface/tick_util.h" namespace webrtc { @@ -128,18 +129,19 @@ void get_time(WindowsHelpTimer* help_timer, FILETIME& current_time) { class RealTimeClock : public Clock { // Return a timestamp in milliseconds relative to some arbitrary source; the // source is fixed for this clock. - virtual int64_t TimeInMilliseconds() OVERRIDE { + virtual int64_t TimeInMilliseconds() const OVERRIDE { return TickTime::MillisecondTimestamp(); } // Return a timestamp in microseconds relative to some arbitrary source; the // source is fixed for this clock. - virtual int64_t TimeInMicroseconds() OVERRIDE { + virtual int64_t TimeInMicroseconds() const OVERRIDE { return TickTime::MicrosecondTimestamp(); } // Retrieve an NTP absolute timestamp in seconds and fractions of a second. - virtual void CurrentNtp(uint32_t& seconds, uint32_t& fractions) OVERRIDE { + virtual void CurrentNtp(uint32_t& seconds, + uint32_t& fractions) const OVERRIDE { timeval tv = CurrentTimeVal(); double microseconds_in_seconds; Adjust(tv, &seconds, µseconds_in_seconds); @@ -148,7 +150,7 @@ class RealTimeClock : public Clock { } // Retrieve an NTP absolute timestamp in milliseconds. - virtual int64_t CurrentNtpInMilliseconds() OVERRIDE { + virtual int64_t CurrentNtpInMilliseconds() const OVERRIDE { timeval tv = CurrentTimeVal(); uint32_t seconds; double microseconds_in_seconds; @@ -233,12 +235,26 @@ class UnixRealTimeClock : public RealTimeClock { // Keeps the global state for the Windows implementation of RtpRtcpClock. // Note that this is a POD. Only PODs are allowed to have static storage // duration according to the Google Style guide. -static WindowsHelpTimer global_help_timer = {0, 0, {{ 0, 0}, 0}, 0}; +// +// Note that on Windows, GetSystemTimeAsFileTime has poorer (up to 15 ms) +// resolution than the media timers, hence the WindowsHelpTimer context +// object and Synchronize API to sync the two. +// +// We only sync up once, which means that on Windows, our realtime clock +// wont respond to system time/date changes without a program restart. +// TODO(henrike): We should probably call sync more often to catch +// drift and time changes for parity with other platforms. + +static WindowsHelpTimer *SyncGlobalHelpTimer() { + static WindowsHelpTimer global_help_timer = {0, 0, {{ 0, 0}, 0}, 0}; + Synchronize(&global_help_timer); + return &global_help_timer; +} #endif Clock* Clock::GetRealTimeClock() { #if defined(_WIN32) - static WindowsRealTimeClock clock(&global_help_timer); + static WindowsRealTimeClock clock(SyncGlobalHelpTimer()); return &clock; #elif defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) static UnixRealTimeClock clock; @@ -249,23 +265,30 @@ Clock* Clock::GetRealTimeClock() { } SimulatedClock::SimulatedClock(int64_t initial_time_us) - : time_us_(initial_time_us) {} + : time_us_(initial_time_us), lock_(RWLockWrapper::CreateRWLock()) { +} + +SimulatedClock::~SimulatedClock() { +} -int64_t SimulatedClock::TimeInMilliseconds() { +int64_t SimulatedClock::TimeInMilliseconds() const { + ReadLockScoped synchronize(*lock_); return (time_us_ + 500) / 1000; } -int64_t SimulatedClock::TimeInMicroseconds() { +int64_t SimulatedClock::TimeInMicroseconds() const { + ReadLockScoped synchronize(*lock_); return time_us_; } -void SimulatedClock::CurrentNtp(uint32_t& seconds, uint32_t& fractions) { - seconds = (TimeInMilliseconds() / 1000) + kNtpJan1970; - fractions = (uint32_t)((TimeInMilliseconds() % 1000) * - kMagicNtpFractionalUnit / 1000); +void SimulatedClock::CurrentNtp(uint32_t& seconds, uint32_t& fractions) const { + int64_t now_ms = TimeInMilliseconds(); + seconds = (now_ms / 1000) + kNtpJan1970; + fractions = + static_cast((now_ms % 1000) * kMagicNtpFractionalUnit / 1000); } -int64_t SimulatedClock::CurrentNtpInMilliseconds() { +int64_t SimulatedClock::CurrentNtpInMilliseconds() const { return TimeInMilliseconds() + 1000 * static_cast(kNtpJan1970); } @@ -274,6 +297,7 @@ void SimulatedClock::AdvanceTimeMilliseconds(int64_t milliseconds) { } void SimulatedClock::AdvanceTimeMicroseconds(int64_t microseconds) { + WriteLockScoped synchronize(*lock_); time_us_ += microseconds; } diff --git a/webrtc/system_wrappers/source/clock_unittest.cc b/webrtc/system_wrappers/source/clock_unittest.cc index 67d699e56..71969edcd 100644 --- a/webrtc/system_wrappers/source/clock_unittest.cc +++ b/webrtc/system_wrappers/source/clock_unittest.cc @@ -20,6 +20,7 @@ TEST(ClockTest, NtpTime) { uint32_t fractions; clock->CurrentNtp(seconds, fractions); int64_t milliseconds = clock->CurrentNtpInMilliseconds(); + EXPECT_GT(milliseconds / 1000, kNtpJan1970); EXPECT_GE(milliseconds, Clock::NtpToMs(seconds, fractions)); EXPECT_NEAR(milliseconds, Clock::NtpToMs(seconds, fractions), 5); } diff --git a/webrtc/system_wrappers/source/field_trial_default.cc b/webrtc/system_wrappers/source/field_trial_default.cc new file mode 100644 index 000000000..892623cef --- /dev/null +++ b/webrtc/system_wrappers/source/field_trial_default.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2014 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 "webrtc/system_wrappers/interface/field_trial.h" + +// Clients of webrtc that do not want to configure field trials can link with +// this instead of providing their own implementation. +namespace webrtc { +namespace field_trial { + +std::string FindFullName(const std::string& name) { + return std::string(); +} + +} // namespace field_trial +} // namespace webrtc diff --git a/webrtc/system_wrappers/source/move.h b/webrtc/system_wrappers/source/move.h index 53109c73a..2e93641f4 100644 --- a/webrtc/system_wrappers/source/move.h +++ b/webrtc/system_wrappers/source/move.h @@ -144,6 +144,16 @@ // choose the one that adheres to the standard. // // +// WHY HAVE typedef void MoveOnlyTypeForCPP03 +// +// Callback<>/Bind() needs to understand movable-but-not-copyable semantics +// to call .Pass() appropriately when it is expected to transfer the value. +// The cryptic typedef MoveOnlyTypeForCPP03 is added to make this check +// easy and automatic in helper templates for Callback<>/Bind(). +// See IsMoveOnlyType template and its usage in base/callback_internal.h +// for more details. +// +// // COMPARED TO C++11 // // In C++11, you would implement this functionality using an r-value reference @@ -210,6 +220,7 @@ public: \ operator rvalue_type() { return rvalue_type(this); } \ type Pass() { return type(rvalue_type(this)); } \ + typedef void MoveOnlyTypeForCPP03; \ private: #endif // WEBRTC_SYSTEM_WRAPPERS_INTEFACE_MOVE_H_ diff --git a/webrtc/modules/remote_bitrate_estimator/rtp_to_ntp.cc b/webrtc/system_wrappers/source/rtp_to_ntp.cc similarity index 78% rename from webrtc/modules/remote_bitrate_estimator/rtp_to_ntp.cc rename to webrtc/system_wrappers/source/rtp_to_ntp.cc index 109edae7c..d6b7b1408 100644 --- a/webrtc/modules/remote_bitrate_estimator/rtp_to_ntp.cc +++ b/webrtc/system_wrappers/source/rtp_to_ntp.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/remote_bitrate_estimator/include/rtp_to_ntp.h" +#include "webrtc/system_wrappers/interface/rtp_to_ntp.h" #include "webrtc/system_wrappers/interface/clock.h" @@ -16,8 +16,6 @@ namespace webrtc { -namespace synchronization { - RtcpMeasurement::RtcpMeasurement() : ntp_secs(0), ntp_frac(0), rtp_timestamp(0) {} @@ -47,8 +45,7 @@ bool CompensateForWrapAround(uint32_t new_timestamp, uint32_t old_timestamp, int64_t* compensated_timestamp) { assert(compensated_timestamp); - int64_t wraps = synchronization::CheckForWrapArounds(new_timestamp, - old_timestamp); + int64_t wraps = CheckForWrapArounds(new_timestamp, old_timestamp); if (wraps < 0) { // Reordering, don't use this packet. return false; @@ -57,12 +54,46 @@ bool CompensateForWrapAround(uint32_t new_timestamp, return true; } +bool UpdateRtcpList(uint32_t ntp_secs, + uint32_t ntp_frac, + uint32_t rtp_timestamp, + RtcpList* rtcp_list, + bool* new_rtcp_sr) { + *new_rtcp_sr = false; + if (ntp_secs == 0 && ntp_frac == 0) { + return false; + } + + RtcpMeasurement measurement; + measurement.ntp_secs = ntp_secs; + measurement.ntp_frac = ntp_frac; + measurement.rtp_timestamp = rtp_timestamp; + + for (RtcpList::iterator it = rtcp_list->begin(); + it != rtcp_list->end(); ++it) { + if (measurement.ntp_secs == (*it).ntp_secs && + measurement.ntp_frac == (*it).ntp_frac) { + // This RTCP has already been added to the list. + return true; + } + } + + // We need two RTCP SR reports to map between RTP and NTP. More than two will + // not improve the mapping. + if (rtcp_list->size() == 2) { + rtcp_list->pop_back(); + } + rtcp_list->push_front(measurement); + *new_rtcp_sr = true; + return true; +} + // Converts |rtp_timestamp| to the NTP time base using the NTP and RTP timestamp // pairs in |rtcp|. The converted timestamp is returned in // |rtp_timestamp_in_ms|. This function compensates for wrap arounds in RTP // timestamps and returns false if it can't do the conversion due to reordering. bool RtpToNtpMs(int64_t rtp_timestamp, - const synchronization::RtcpList& rtcp, + const RtcpList& rtcp, int64_t* rtp_timestamp_in_ms) { assert(rtcp.size() == 2); int64_t rtcp_ntp_ms_new = Clock::NtpToMs(rtcp.front().ntp_secs, @@ -115,5 +146,5 @@ int CheckForWrapArounds(uint32_t new_timestamp, uint32_t old_timestamp) { } return 0; } -} // namespace synchronization + } // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/rtp_to_ntp_unittest.cc b/webrtc/system_wrappers/source/rtp_to_ntp_unittest.cc similarity index 50% rename from webrtc/modules/remote_bitrate_estimator/rtp_to_ntp_unittest.cc rename to webrtc/system_wrappers/source/rtp_to_ntp_unittest.cc index aff314aaa..a4d75aed0 100644 --- a/webrtc/modules/remote_bitrate_estimator/rtp_to_ntp_unittest.cc +++ b/webrtc/system_wrappers/source/rtp_to_ntp_unittest.cc @@ -9,67 +9,62 @@ */ #include "testing/gtest/include/gtest/gtest.h" -#include "webrtc/modules/remote_bitrate_estimator/include/rtp_to_ntp.h" +#include "webrtc/system_wrappers/interface/rtp_to_ntp.h" namespace webrtc { TEST(WrapAroundTests, NoWrap) { - EXPECT_EQ(0, synchronization::CheckForWrapArounds(0xFFFFFFFF, 0xFFFFFFFE)); - EXPECT_EQ(0, synchronization::CheckForWrapArounds(1, 0)); - EXPECT_EQ(0, synchronization::CheckForWrapArounds(0x00010000, 0x0000FFFF)); + EXPECT_EQ(0, CheckForWrapArounds(0xFFFFFFFF, 0xFFFFFFFE)); + EXPECT_EQ(0, CheckForWrapArounds(1, 0)); + EXPECT_EQ(0, CheckForWrapArounds(0x00010000, 0x0000FFFF)); } TEST(WrapAroundTests, ForwardWrap) { - EXPECT_EQ(1, synchronization::CheckForWrapArounds(0, 0xFFFFFFFF)); - EXPECT_EQ(1, synchronization::CheckForWrapArounds(0, 0xFFFF0000)); - EXPECT_EQ(1, synchronization::CheckForWrapArounds(0x0000FFFF, 0xFFFFFFFF)); - EXPECT_EQ(1, synchronization::CheckForWrapArounds(0x0000FFFF, 0xFFFF0000)); + EXPECT_EQ(1, CheckForWrapArounds(0, 0xFFFFFFFF)); + EXPECT_EQ(1, CheckForWrapArounds(0, 0xFFFF0000)); + EXPECT_EQ(1, CheckForWrapArounds(0x0000FFFF, 0xFFFFFFFF)); + EXPECT_EQ(1, CheckForWrapArounds(0x0000FFFF, 0xFFFF0000)); } TEST(WrapAroundTests, BackwardWrap) { - EXPECT_EQ(-1, synchronization::CheckForWrapArounds(0xFFFFFFFF, 0)); - EXPECT_EQ(-1, synchronization::CheckForWrapArounds(0xFFFF0000, 0)); - EXPECT_EQ(-1, synchronization::CheckForWrapArounds(0xFFFFFFFF, 0x0000FFFF)); - EXPECT_EQ(-1, synchronization::CheckForWrapArounds(0xFFFF0000, 0x0000FFFF)); + EXPECT_EQ(-1, CheckForWrapArounds(0xFFFFFFFF, 0)); + EXPECT_EQ(-1, CheckForWrapArounds(0xFFFF0000, 0)); + EXPECT_EQ(-1, CheckForWrapArounds(0xFFFFFFFF, 0x0000FFFF)); + EXPECT_EQ(-1, CheckForWrapArounds(0xFFFF0000, 0x0000FFFF)); } TEST(WrapAroundTests, OldRtcpWrapped) { - synchronization::RtcpList rtcp; + RtcpList rtcp; uint32_t ntp_sec = 0; uint32_t ntp_frac = 0; uint32_t timestamp = 0; const uint32_t kOneMsInNtpFrac = 4294967; const uint32_t kTimestampTicksPerMs = 90; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp -= kTimestampTicksPerMs; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp -= kTimestampTicksPerMs; int64_t timestamp_in_ms = -1; // This expected to fail since it's highly unlikely that the older RTCP // has a much smaller RTP timestamp than the newer. - EXPECT_FALSE(synchronization::RtpToNtpMs(timestamp, rtcp, ×tamp_in_ms)); + EXPECT_FALSE(RtpToNtpMs(timestamp, rtcp, ×tamp_in_ms)); } TEST(WrapAroundTests, NewRtcpWrapped) { - synchronization::RtcpList rtcp; + RtcpList rtcp; uint32_t ntp_sec = 0; uint32_t ntp_frac = 0; uint32_t timestamp = 0xFFFFFFFF; const uint32_t kOneMsInNtpFrac = 4294967; const uint32_t kTimestampTicksPerMs = 90; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp += kTimestampTicksPerMs; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); int64_t timestamp_in_ms = -1; - EXPECT_TRUE(synchronization::RtpToNtpMs(rtcp.back().rtp_timestamp, rtcp, - ×tamp_in_ms)); + EXPECT_TRUE(RtpToNtpMs(rtcp.back().rtp_timestamp, rtcp, ×tamp_in_ms)); // Since this RTP packet has the same timestamp as the RTCP packet constructed // at time 0 it should be mapped to 0 as well. EXPECT_EQ(0, timestamp_in_ms); @@ -78,21 +73,18 @@ TEST(WrapAroundTests, NewRtcpWrapped) { TEST(WrapAroundTests, RtpWrapped) { const uint32_t kOneMsInNtpFrac = 4294967; const uint32_t kTimestampTicksPerMs = 90; - synchronization::RtcpList rtcp; + RtcpList rtcp; uint32_t ntp_sec = 0; uint32_t ntp_frac = 0; uint32_t timestamp = 0xFFFFFFFF - 2 * kTimestampTicksPerMs; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp += kTimestampTicksPerMs; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp += kTimestampTicksPerMs; int64_t timestamp_in_ms = -1; - EXPECT_TRUE(synchronization::RtpToNtpMs(timestamp, rtcp, - ×tamp_in_ms)); + EXPECT_TRUE(RtpToNtpMs(timestamp, rtcp, ×tamp_in_ms)); // Since this RTP packet has the same timestamp as the RTCP packet constructed // at time 0 it should be mapped to 0 as well. EXPECT_EQ(2, timestamp_in_ms); @@ -101,41 +93,35 @@ TEST(WrapAroundTests, RtpWrapped) { TEST(WrapAroundTests, OldRtp_RtcpsWrapped) { const uint32_t kOneMsInNtpFrac = 4294967; const uint32_t kTimestampTicksPerMs = 90; - synchronization::RtcpList rtcp; + RtcpList rtcp; uint32_t ntp_sec = 0; uint32_t ntp_frac = 0; uint32_t timestamp = 0; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp += kTimestampTicksPerMs; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp -= 2*kTimestampTicksPerMs; int64_t timestamp_in_ms = -1; - EXPECT_FALSE(synchronization::RtpToNtpMs(timestamp, rtcp, - ×tamp_in_ms)); + EXPECT_FALSE(RtpToNtpMs(timestamp, rtcp, ×tamp_in_ms)); } TEST(WrapAroundTests, OldRtp_NewRtcpWrapped) { const uint32_t kOneMsInNtpFrac = 4294967; const uint32_t kTimestampTicksPerMs = 90; - synchronization::RtcpList rtcp; + RtcpList rtcp; uint32_t ntp_sec = 0; uint32_t ntp_frac = 0; uint32_t timestamp = 0xFFFFFFFF; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp += kTimestampTicksPerMs; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp -= kTimestampTicksPerMs; int64_t timestamp_in_ms = -1; - EXPECT_TRUE(synchronization::RtpToNtpMs(timestamp, rtcp, - ×tamp_in_ms)); + EXPECT_TRUE(RtpToNtpMs(timestamp, rtcp, ×tamp_in_ms)); // Constructed at the same time as the first RTCP and should therefore be // mapped to zero. EXPECT_EQ(0, timestamp_in_ms); @@ -144,20 +130,17 @@ TEST(WrapAroundTests, OldRtp_NewRtcpWrapped) { TEST(WrapAroundTests, OldRtp_OldRtcpWrapped) { const uint32_t kOneMsInNtpFrac = 4294967; const uint32_t kTimestampTicksPerMs = 90; - synchronization::RtcpList rtcp; + RtcpList rtcp; uint32_t ntp_sec = 0; uint32_t ntp_frac = 0; uint32_t timestamp = 0; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp -= kTimestampTicksPerMs; - rtcp.push_front(synchronization::RtcpMeasurement(ntp_sec, ntp_frac, - timestamp)); + rtcp.push_front(RtcpMeasurement(ntp_sec, ntp_frac, timestamp)); ntp_frac += kOneMsInNtpFrac; timestamp += 2*kTimestampTicksPerMs; int64_t timestamp_in_ms = -1; - EXPECT_FALSE(synchronization::RtpToNtpMs(timestamp, rtcp, - ×tamp_in_ms)); + EXPECT_FALSE(RtpToNtpMs(timestamp, rtcp, ×tamp_in_ms)); } }; // namespace webrtc diff --git a/webrtc/system_wrappers/source/scoped_vector_unittest.cc b/webrtc/system_wrappers/source/scoped_vector_unittest.cc new file mode 100644 index 000000000..c1b9d01cc --- /dev/null +++ b/webrtc/system_wrappers/source/scoped_vector_unittest.cc @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/memory/scoped_vector_unittest.cc + +#include "webrtc/system_wrappers/interface/scoped_vector.h" + +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { +namespace { + +// The LifeCycleObject notifies its Observer upon construction & destruction. +class LifeCycleObject { + public: + class Observer { + public: + virtual void OnLifeCycleConstruct(LifeCycleObject* o) = 0; + virtual void OnLifeCycleDestroy(LifeCycleObject* o) = 0; + + protected: + virtual ~Observer() {} + }; + + ~LifeCycleObject() { + observer_->OnLifeCycleDestroy(this); + } + + private: + friend class LifeCycleWatcher; + + explicit LifeCycleObject(Observer* observer) + : observer_(observer) { + observer_->OnLifeCycleConstruct(this); + } + + Observer* observer_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleObject); +}; + +// The life cycle states we care about for the purposes of testing ScopedVector +// against objects. +enum LifeCycleState { + LC_INITIAL, + LC_CONSTRUCTED, + LC_DESTROYED, +}; + +// Because we wish to watch the life cycle of an object being constructed and +// destroyed, and further wish to test expectations against the state of that +// object, we cannot save state in that object itself. Instead, we use this +// pairing of the watcher, which observes the object and notifies of +// construction & destruction. Since we also may be testing assumptions about +// things not getting freed, this class also acts like a scoping object and +// deletes the |constructed_life_cycle_object_|, if any when the +// LifeCycleWatcher is destroyed. To keep this simple, the only expected state +// changes are: +// INITIAL -> CONSTRUCTED -> DESTROYED. +// Anything more complicated than that should start another test. +class LifeCycleWatcher : public LifeCycleObject::Observer { + public: + LifeCycleWatcher() : life_cycle_state_(LC_INITIAL) {} + virtual ~LifeCycleWatcher() {} + + // Assert INITIAL -> CONSTRUCTED and no LifeCycleObject associated with this + // LifeCycleWatcher. + virtual void OnLifeCycleConstruct(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_INITIAL, life_cycle_state_); + ASSERT_EQ(NULL, constructed_life_cycle_object_.get()); + life_cycle_state_ = LC_CONSTRUCTED; + constructed_life_cycle_object_.reset(object); + } + + // Assert CONSTRUCTED -> DESTROYED and the |object| being destroyed is the + // same one we saw constructed. + virtual void OnLifeCycleDestroy(LifeCycleObject* object) OVERRIDE { + ASSERT_EQ(LC_CONSTRUCTED, life_cycle_state_); + LifeCycleObject* constructed_life_cycle_object = + constructed_life_cycle_object_.release(); + ASSERT_EQ(constructed_life_cycle_object, object); + life_cycle_state_ = LC_DESTROYED; + } + + LifeCycleState life_cycle_state() const { return life_cycle_state_; } + + // Factory method for creating a new LifeCycleObject tied to this + // LifeCycleWatcher. + LifeCycleObject* NewLifeCycleObject() { + return new LifeCycleObject(this); + } + + // Returns true iff |object| is the same object that this watcher is tracking. + bool IsWatching(LifeCycleObject* object) const { + return object == constructed_life_cycle_object_.get(); + } + + private: + LifeCycleState life_cycle_state_; + scoped_ptr constructed_life_cycle_object_; + + DISALLOW_COPY_AND_ASSIGN(LifeCycleWatcher); +}; + +TEST(ScopedVectorTest, LifeCycleWatcher) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + LifeCycleObject* object = watcher.NewLifeCycleObject(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + delete object; + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, PopBack) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.pop_back(); + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, Clear) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.clear(); + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, WeakClear) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.weak_clear(); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + +TEST(ScopedVectorTest, ResizeShrink) { + LifeCycleWatcher first_watcher; + EXPECT_EQ(LC_INITIAL, first_watcher.life_cycle_state()); + LifeCycleWatcher second_watcher; + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + ScopedVector scoped_vector; + + scoped_vector.push_back(first_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(second_watcher.IsWatching(scoped_vector[0])); + + scoped_vector.push_back(second_watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_CONSTRUCTED, second_watcher.life_cycle_state()); + EXPECT_FALSE(first_watcher.IsWatching(scoped_vector[1])); + EXPECT_TRUE(second_watcher.IsWatching(scoped_vector[1])); + + // Test that shrinking a vector deletes elements in the disappearing range. + scoped_vector.resize(1); + EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state()); + EXPECT_EQ(LC_DESTROYED, second_watcher.life_cycle_state()); + EXPECT_EQ(1u, scoped_vector.size()); + EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0])); +} + +TEST(ScopedVectorTest, ResizeGrow) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + scoped_vector.resize(5); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + ASSERT_EQ(5u, scoped_vector.size()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector[0])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[1])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[2])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[3])); + EXPECT_FALSE(watcher.IsWatching(scoped_vector[4])); +} + +TEST(ScopedVectorTest, Scope) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveConstruct) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + ScopedVector scoped_vector_copy(scoped_vector.Pass()); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_copy.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_copy.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +TEST(ScopedVectorTest, MoveAssign) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + { + ScopedVector scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + ScopedVector scoped_vector_assign; + EXPECT_FALSE(scoped_vector.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + + scoped_vector_assign = scoped_vector.Pass(); + EXPECT_TRUE(scoped_vector.empty()); + EXPECT_FALSE(scoped_vector_assign.empty()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector_assign.back())); + + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + } + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); +} + +class DeleteCounter { + public: + explicit DeleteCounter(int* deletes) + : deletes_(deletes) { + } + + ~DeleteCounter() { + (*deletes_)++; + } + + void VoidMethod0() {} + + private: + int* const deletes_; + + DISALLOW_COPY_AND_ASSIGN(DeleteCounter); +}; + +// This class is used in place of Chromium's base::Callback. +template +class PassThru { + public: + explicit PassThru(ScopedVector scoper) : scoper_(scoper.Pass()) {} + + ScopedVector Run() { + return scoper_.Pass(); + } + + private: + ScopedVector scoper_; +}; + +TEST(ScopedVectorTest, Passed) { + int deletes = 0; + ScopedVector deleter_vector; + deleter_vector.push_back(new DeleteCounter(&deletes)); + EXPECT_EQ(0, deletes); + PassThru pass_thru(deleter_vector.Pass()); + EXPECT_EQ(0, deletes); + ScopedVector result = pass_thru.Run(); + EXPECT_EQ(0, deletes); + result.clear(); + EXPECT_EQ(1, deletes); +}; + +TEST(ScopedVectorTest, InsertRange) { + LifeCycleWatcher watchers[5]; + size_t watchers_size = sizeof(watchers) / sizeof(*watchers); + + std::vector vec; + for (LifeCycleWatcher* it = watchers; it != watchers + watchers_size; + ++it) { + EXPECT_EQ(LC_INITIAL, it->life_cycle_state()); + vec.push_back(it->NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + // Start scope for ScopedVector. + { + ScopedVector scoped_vector; + scoped_vector.insert(scoped_vector.end(), vec.begin() + 1, vec.begin() + 3); + for (LifeCycleWatcher* it = watchers; it != watchers + watchers_size; + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + } + for (LifeCycleWatcher* it = watchers; it != watchers + 1; ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); + for (LifeCycleWatcher* it = watchers + 1; it != watchers + 3; ++it) + EXPECT_EQ(LC_DESTROYED, it->life_cycle_state()); + for (LifeCycleWatcher* it = watchers + 3; it != watchers + watchers_size; + ++it) + EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state()); +} + +} // namespace +} // namespace webrtc diff --git a/webrtc/system_wrappers/source/stl_util_unittest.cc b/webrtc/system_wrappers/source/stl_util_unittest.cc new file mode 100644 index 000000000..e60a913cf --- /dev/null +++ b/webrtc/system_wrappers/source/stl_util_unittest.cc @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2014 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. + */ + +// Borrowed from Chromium's src/base/stl_util_unittest.cc +#include "webrtc/system_wrappers/interface/stl_util.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Used as test case to ensure the various base::STLXxx functions don't require +// more than operators "<" and "==" on values stored in containers. +class ComparableValue { + public: + explicit ComparableValue(int value) : value_(value) {} + + bool operator==(const ComparableValue& rhs) const { + return value_ == rhs.value_; + } + + bool operator<(const ComparableValue& rhs) const { + return value_ < rhs.value_; + } + + private: + int value_; +}; + +} // namespace + +namespace webrtc { +namespace { + +TEST(STLUtilTest, STLIsSorted) { + { + std::set set; + set.insert(24); + set.insert(1); + set.insert(12); + EXPECT_TRUE(STLIsSorted(set)); + } + + { + std::set set; + set.insert(ComparableValue(24)); + set.insert(ComparableValue(1)); + set.insert(ComparableValue(12)); + EXPECT_TRUE(STLIsSorted(set)); + } + + { + std::vector vector; + vector.push_back(1); + vector.push_back(1); + vector.push_back(4); + vector.push_back(64); + vector.push_back(12432); + EXPECT_TRUE(STLIsSorted(vector)); + vector.back() = 1; + EXPECT_FALSE(STLIsSorted(vector)); + } +} + +TEST(STLUtilTest, STLSetDifference) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set difference; + difference.insert(1); + difference.insert(2); + EXPECT_EQ(difference, STLSetDifference >(a1, a2)); + } + + { + std::set difference; + difference.insert(5); + difference.insert(6); + difference.insert(7); + EXPECT_EQ(difference, STLSetDifference >(a2, a1)); + } + + { + std::vector difference; + difference.push_back(1); + difference.push_back(2); + EXPECT_EQ(difference, STLSetDifference >(a1, a2)); + } + + { + std::vector difference; + difference.push_back(5); + difference.push_back(6); + difference.push_back(7); + EXPECT_EQ(difference, STLSetDifference >(a2, a1)); + } +} + +TEST(STLUtilTest, STLSetUnion) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set result; + result.insert(1); + result.insert(2); + result.insert(3); + result.insert(4); + result.insert(5); + result.insert(6); + result.insert(7); + EXPECT_EQ(result, STLSetUnion >(a1, a2)); + } + + { + std::set result; + result.insert(1); + result.insert(2); + result.insert(3); + result.insert(4); + result.insert(5); + result.insert(6); + result.insert(7); + EXPECT_EQ(result, STLSetUnion >(a2, a1)); + } + + { + std::vector result; + result.push_back(1); + result.push_back(2); + result.push_back(3); + result.push_back(4); + result.push_back(5); + result.push_back(6); + result.push_back(7); + EXPECT_EQ(result, STLSetUnion >(a1, a2)); + } + + { + std::vector result; + result.push_back(1); + result.push_back(2); + result.push_back(3); + result.push_back(4); + result.push_back(5); + result.push_back(6); + result.push_back(7); + EXPECT_EQ(result, STLSetUnion >(a2, a1)); + } +} + +TEST(STLUtilTest, STLSetIntersection) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + a2.insert(5); + a2.insert(6); + a2.insert(7); + + { + std::set result; + result.insert(3); + result.insert(4); + EXPECT_EQ(result, STLSetIntersection >(a1, a2)); + } + + { + std::set result; + result.insert(3); + result.insert(4); + EXPECT_EQ(result, STLSetIntersection >(a2, a1)); + } + + { + std::vector result; + result.push_back(3); + result.push_back(4); + EXPECT_EQ(result, STLSetIntersection >(a1, a2)); + } + + { + std::vector result; + result.push_back(3); + result.push_back(4); + EXPECT_EQ(result, STLSetIntersection >(a2, a1)); + } +} + +TEST(STLUtilTest, STLIncludes) { + std::set a1; + a1.insert(1); + a1.insert(2); + a1.insert(3); + a1.insert(4); + + std::set a2; + a2.insert(3); + a2.insert(4); + + std::set a3; + a3.insert(3); + a3.insert(4); + a3.insert(5); + + EXPECT_TRUE(STLIncludes >(a1, a2)); + EXPECT_FALSE(STLIncludes >(a1, a3)); + EXPECT_FALSE(STLIncludes >(a2, a1)); + EXPECT_FALSE(STLIncludes >(a2, a3)); + EXPECT_FALSE(STLIncludes >(a3, a1)); + EXPECT_TRUE(STLIncludes >(a3, a2)); +} + +} // namespace +} // namespace webrtc + diff --git a/webrtc/system_wrappers/source/system_wrappers.gyp b/webrtc/system_wrappers/source/system_wrappers.gyp index 109a72aac..66b34356c 100644 --- a/webrtc/system_wrappers/source/system_wrappers.gyp +++ b/webrtc/system_wrappers/source/system_wrappers.gyp @@ -16,6 +16,9 @@ 'spreadsortlib', '../interface', ], + 'dependencies': [ + '../../base/base.gyp:webrtc_base', + ], 'direct_dependent_settings': { 'include_dirs': [ '../interface', @@ -35,23 +38,29 @@ '../interface/data_log_impl.h', '../interface/event_tracer.h', '../interface/event_wrapper.h', + '../interface/field_trial.h', '../interface/file_wrapper.h', '../interface/fix_interlocked_exchange_pointer_win.h', '../interface/logcat_trace_context.h', '../interface/logging.h', '../interface/ref_count.h', + '../interface/rtp_to_ntp.h', '../interface/rw_lock_wrapper.h', '../interface/scoped_ptr.h', '../interface/scoped_refptr.h', + '../interface/scoped_vector.h', '../interface/sleep.h', '../interface/sort.h', '../interface/static_instance.h', + '../interface/stl_util.h', '../interface/stringize_macros.h', '../interface/thread_annotations.h', '../interface/thread_wrapper.h', '../interface/tick_util.h', + '../interface/timestamp_extrapolator.h', '../interface/trace.h', '../interface/trace_event.h', + '../interface/utf_util_win.h', 'aligned_malloc.cc', 'atomic32_mac.cc', 'atomic32_posix.cc', @@ -84,6 +93,7 @@ 'file_impl.h', 'logcat_trace_context.cc', 'logging.cc', + 'rtp_to_ntp.cc', 'rw_lock.cc', 'rw_lock_generic.cc', 'rw_lock_generic.h', @@ -100,6 +110,7 @@ 'thread_posix.h', 'thread_win.cc', 'thread_win.h', + 'timestamp_extrapolator.cc', 'trace_impl.cc', 'trace_impl.h', 'trace_posix.cc', @@ -184,6 +195,15 @@ 4267, # size_t to int truncation. 4334, # Ignore warning on shift operator promotion. ], + }, { + 'target_name': 'field_trial_default', + 'type': 'static_library', + 'sources': [ + 'field_trial_default.cc', + ], + 'dependencies': [ + 'system_wrappers', + ] }, ], # targets 'conditions': [ diff --git a/webrtc/system_wrappers/source/system_wrappers_tests.gyp b/webrtc/system_wrappers/source/system_wrappers_tests.gyp index 06737d23c..f2f615669 100644 --- a/webrtc/system_wrappers/source/system_wrappers_tests.gyp +++ b/webrtc/system_wrappers/source/system_wrappers_tests.gyp @@ -29,7 +29,10 @@ 'data_log_helpers_unittest.cc', 'data_log_c_helpers_unittest.c', 'data_log_c_helpers_unittest.h', + 'rtp_to_ntp_unittest.cc', + 'scoped_vector_unittest.cc', 'stringize_macros_unittest.cc', + 'stl_util_unittest.cc', 'thread_unittest.cc', 'thread_posix_unittest.cc', 'unittest_utilities_unittest.cc', @@ -45,7 +48,7 @@ }], # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], @@ -60,7 +63,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['include_tests==1 and build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['include_tests==1 and build_with_chromium==1 and OS=="android"', { 'targets': [ { 'target_name': 'system_wrappers_unittests_apk_target', diff --git a/webrtc/system_wrappers/source/system_wrappers_unittests.isolate b/webrtc/system_wrappers/source/system_wrappers_unittests.isolate index 6ec5e7c64..f50577102 100644 --- a/webrtc/system_wrappers/source/system_wrappers_unittests.isolate +++ b/webrtc/system_wrappers/source/system_wrappers_unittests.isolate @@ -9,27 +9,25 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../../data/', - '../../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/system_wrappers_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/system_wrappers_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/modules/video_coding/main/source/timestamp_extrapolator.cc b/webrtc/system_wrappers/source/timestamp_extrapolator.cc similarity index 71% rename from webrtc/modules/video_coding/main/source/timestamp_extrapolator.cc rename to webrtc/system_wrappers/source/timestamp_extrapolator.cc index 1d911a54e..afd212b0c 100644 --- a/webrtc/modules/video_coding/main/source/timestamp_extrapolator.cc +++ b/webrtc/system_wrappers/source/timestamp_extrapolator.cc @@ -8,50 +8,41 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "webrtc/modules/video_coding/main/source/internal_defines.h" -#include "webrtc/modules/video_coding/main/source/timestamp_extrapolator.h" -#include "webrtc/system_wrappers/interface/clock.h" -#include "webrtc/system_wrappers/interface/trace.h" +#include "webrtc/system_wrappers/interface/timestamp_extrapolator.h" + +#include namespace webrtc { -VCMTimestampExtrapolator::VCMTimestampExtrapolator(Clock* clock, - int32_t vcmId, - int32_t id) -: -_rwLock(RWLockWrapper::CreateRWLock()), -_vcmId(vcmId), -_id(id), -_clock(clock), -_startMs(0), -_firstTimestamp(0), -_wrapArounds(0), -_prevUnwrappedTimestamp(-1), -_prevWrapTimestamp(-1), -_lambda(1), -_firstAfterReset(true), -_packetCount(0), -_startUpFilterDelayInPackets(2), -_detectorAccumulatorPos(0), -_detectorAccumulatorNeg(0), -_alarmThreshold(60e3), -_accDrift(6600), // in timestamp ticks, i.e. 15 ms -_accMaxError(7000), -_P11(1e10) -{ - Reset(); +TimestampExtrapolator::TimestampExtrapolator(int64_t start_ms) + : _rwLock(RWLockWrapper::CreateRWLock()), + _startMs(0), + _firstTimestamp(0), + _wrapArounds(0), + _prevUnwrappedTimestamp(-1), + _prevWrapTimestamp(-1), + _lambda(1), + _firstAfterReset(true), + _packetCount(0), + _startUpFilterDelayInPackets(2), + _detectorAccumulatorPos(0), + _detectorAccumulatorNeg(0), + _alarmThreshold(60e3), + _accDrift(6600), // in timestamp ticks, i.e. 15 ms + _accMaxError(7000), + _P11(1e10) { + Reset(start_ms); } -VCMTimestampExtrapolator::~VCMTimestampExtrapolator() +TimestampExtrapolator::~TimestampExtrapolator() { delete _rwLock; } -void -VCMTimestampExtrapolator::Reset() +void TimestampExtrapolator::Reset(int64_t start_ms) { WriteLockScoped wl(*_rwLock); - _startMs = _clock->TimeInMilliseconds(); + _startMs = start_ms; _prevMs = _startMs; _firstTimestamp = 0; _w[0] = 90.0; @@ -69,7 +60,7 @@ VCMTimestampExtrapolator::Reset() } void -VCMTimestampExtrapolator::Update(int64_t tMs, uint32_t ts90khz, bool trace) +TimestampExtrapolator::Update(int64_t tMs, uint32_t ts90khz) { _rwLock->AcquireLockExclusive(); @@ -78,7 +69,7 @@ VCMTimestampExtrapolator::Update(int64_t tMs, uint32_t ts90khz, bool trace) // Ten seconds without a complete frame. // Reset the extrapolator _rwLock->ReleaseLockExclusive(); - Reset(); + Reset(tMs); _rwLock->AcquireLockExclusive(); } else @@ -115,7 +106,7 @@ VCMTimestampExtrapolator::Update(int64_t tMs, uint32_t ts90khz, bool trace) double residual = (static_cast(unwrapped_ts90khz) - _firstTimestamp) - static_cast(tMs) * _w[0] - _w[1]; - if (DelayChangeDetection(residual, trace) && + if (DelayChangeDetection(residual) && _packetCount >= _startUpFilterDelayInPackets) { // A sudden change of average network delay has been detected. @@ -147,15 +138,11 @@ VCMTimestampExtrapolator::Update(int64_t tMs, uint32_t ts90khz, bool trace) { _packetCount++; } - if (trace) - { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _id), "w[0]=%f w[1]=%f ts=%u tMs=%u", _w[0], _w[1], ts90khz, tMs); - } _rwLock->ReleaseLockExclusive(); } int64_t -VCMTimestampExtrapolator::ExtrapolateLocalTime(uint32_t timestamp90khz) +TimestampExtrapolator::ExtrapolateLocalTime(uint32_t timestamp90khz) { ReadLockScoped rl(*_rwLock); int64_t localTimeMs = 0; @@ -193,7 +180,7 @@ VCMTimestampExtrapolator::ExtrapolateLocalTime(uint32_t timestamp90khz) // Investigates if the timestamp clock has overflowed since the last timestamp and // keeps track of the number of wrap arounds since reset. void -VCMTimestampExtrapolator::CheckForWrapArounds(uint32_t ts90khz) +TimestampExtrapolator::CheckForWrapArounds(uint32_t ts90khz) { if (_prevWrapTimestamp == -1) { @@ -222,26 +209,21 @@ VCMTimestampExtrapolator::CheckForWrapArounds(uint32_t ts90khz) } bool -VCMTimestampExtrapolator::DelayChangeDetection(double error, bool trace) +TimestampExtrapolator::DelayChangeDetection(double error) { // CUSUM detection of sudden delay changes - error = (error > 0) ? VCM_MIN(error, _accMaxError) : VCM_MAX(error, -_accMaxError); - _detectorAccumulatorPos = VCM_MAX(_detectorAccumulatorPos + error - _accDrift, (double)0); - _detectorAccumulatorNeg = VCM_MIN(_detectorAccumulatorNeg + error + _accDrift, (double)0); + error = (error > 0) ? std::min(error, _accMaxError) : + std::max(error, -_accMaxError); + _detectorAccumulatorPos = + std::max(_detectorAccumulatorPos + error - _accDrift, (double)0); + _detectorAccumulatorNeg = + std::min(_detectorAccumulatorNeg + error + _accDrift, (double)0); if (_detectorAccumulatorPos > _alarmThreshold || _detectorAccumulatorNeg < -_alarmThreshold) { // Alarm - if (trace) - { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _id), "g1=%f g2=%f alarm=1", _detectorAccumulatorPos, _detectorAccumulatorNeg); - } _detectorAccumulatorPos = _detectorAccumulatorNeg = 0; return true; } - if (trace) - { - WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, _id), "g1=%f g2=%f alarm=0", _detectorAccumulatorPos, _detectorAccumulatorNeg); - } return false; } diff --git a/webrtc/system_wrappers/source/trace_impl.cc b/webrtc/system_wrappers/source/trace_impl.cc index 370d1d5f0..5b56e76aa 100644 --- a/webrtc/system_wrappers/source/trace_impl.cc +++ b/webrtc/system_wrappers/source/trace_impl.cc @@ -269,10 +269,6 @@ int32_t TraceImpl::AddModuleAndId(char* trace_message, sprintf(trace_message, "VIDEO CAPTUR:%5ld %5ld;", id_engine, id_channel); break; - case kTraceVideoPreocessing: - sprintf(trace_message, " VIDEO PROC:%5ld %5ld;", id_engine, - id_channel); - break; case kTraceRemoteBitrateEstimator: sprintf(trace_message, " BWE RBE:%5ld %5ld;", id_engine, id_channel); @@ -333,9 +329,6 @@ int32_t TraceImpl::AddModuleAndId(char* trace_message, case kTraceVideoCapture: sprintf(trace_message, "VIDEO CAPTUR:%11ld;", idl); break; - case kTraceVideoPreocessing: - sprintf(trace_message, " VIDEO PROC:%11ld;", idl); - break; case kTraceRemoteBitrateEstimator: sprintf(trace_message, " BWE RBE:%11ld;", idl); break; diff --git a/webrtc/test/OWNERS b/webrtc/test/OWNERS index fec9caaf6..1532e3fda 100644 --- a/webrtc/test/OWNERS +++ b/webrtc/test/OWNERS @@ -1,4 +1,9 @@ -phoglund@webrtc.org -kjellander@webrtc.org -ivinnichenko@webrtc.org - +phoglund@webrtc.org +kjellander@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/test/direct_transport.h b/webrtc/test/direct_transport.h index 9dc9e1fc0..c40a8c388 100644 --- a/webrtc/test/direct_transport.h +++ b/webrtc/test/direct_transport.h @@ -49,7 +49,7 @@ class DirectTransport : public newapi::Transport { scoped_ptr lock_; scoped_ptr packet_event_; scoped_ptr thread_; - Clock* clock_; + Clock* const clock_; bool shutting_down_; diff --git a/webrtc/test/encoder_settings.cc b/webrtc/test/encoder_settings.cc index d02c29f99..5193be65f 100644 --- a/webrtc/test/encoder_settings.cc +++ b/webrtc/test/encoder_settings.cc @@ -16,18 +16,14 @@ namespace webrtc { namespace test { -VideoSendStream::Config::EncoderSettings CreateEncoderSettings( - VideoEncoder* encoder, - const char* payload_name, - int payload_type, - size_t num_streams) { +std::vector CreateVideoStreams(size_t num_streams) { assert(num_streams > 0); // Add more streams to the settings above with reasonable values if required. static const size_t kNumSettings = 3; assert(num_streams <= kNumSettings); - VideoStream stream_settings[kNumSettings]; + std::vector stream_settings(kNumSettings); stream_settings[0].width = 320; stream_settings[0].height = 180; @@ -52,28 +48,20 @@ VideoSendStream::Config::EncoderSettings CreateEncoderSettings( stream_settings[2].target_bitrate_bps = stream_settings[2].max_bitrate_bps = 1500000; stream_settings[2].max_qp = 56; - - VideoSendStream::Config::EncoderSettings settings; - - for (size_t i = 0; i < num_streams; ++i) - settings.streams.push_back(stream_settings[i]); - - settings.encoder = encoder; - settings.payload_name = payload_name; - settings.payload_type = payload_type; - return settings; + stream_settings.resize(num_streams); + return stream_settings; } VideoCodec CreateDecoderVideoCodec( - const VideoSendStream::Config::EncoderSettings& settings) { - assert(settings.streams.size() > 0); + const VideoSendStream::Config::EncoderSettings& encoder_settings) { VideoCodec codec; memset(&codec, 0, sizeof(codec)); - codec.plType = settings.payload_type; - strcpy(codec.plName, settings.payload_name.c_str()); + codec.plType = encoder_settings.payload_type; + strcpy(codec.plName, encoder_settings.payload_name.c_str()); codec.codecType = - (settings.payload_name == "VP8" ? kVideoCodecVP8 : kVideoCodecGeneric); + (encoder_settings.payload_name == "VP8" ? kVideoCodecVP8 + : kVideoCodecGeneric); if (codec.codecType == kVideoCodecVP8) { codec.codecSpecific.VP8.resilience = kResilientStream; @@ -85,33 +73,9 @@ VideoCodec CreateDecoderVideoCodec( codec.codecSpecific.VP8.keyFrameInterval = 3000; } - codec.minBitrate = settings.streams[0].min_bitrate_bps / 1000; - for (size_t i = 0; i < settings.streams.size(); ++i) { - const VideoStream& stream = settings.streams[i]; - if (stream.width > codec.width) - codec.width = static_cast(stream.width); - if (stream.height > codec.height) - codec.height = static_cast(stream.height); - if (static_cast(stream.min_bitrate_bps / 1000) < - codec.minBitrate) - codec.minBitrate = - static_cast(stream.min_bitrate_bps / 1000); - codec.maxBitrate += stream.max_bitrate_bps / 1000; - if (static_cast(stream.max_qp) > codec.qpMax) - codec.qpMax = static_cast(stream.max_qp); - } - - if (codec.minBitrate < kViEMinCodecBitrate) - codec.minBitrate = kViEMinCodecBitrate; - if (codec.maxBitrate < kViEMinCodecBitrate) - codec.maxBitrate = kViEMinCodecBitrate; - - codec.startBitrate = 300; - - if (codec.startBitrate < codec.minBitrate) - codec.startBitrate = codec.minBitrate; - if (codec.startBitrate > codec.maxBitrate) - codec.startBitrate = codec.maxBitrate; + codec.width = 320; + codec.height = 180; + codec.startBitrate = codec.minBitrate = codec.maxBitrate = 300; return codec; } diff --git a/webrtc/test/encoder_settings.h b/webrtc/test/encoder_settings.h index 1d8e355ab..ea2be9774 100644 --- a/webrtc/test/encoder_settings.h +++ b/webrtc/test/encoder_settings.h @@ -14,14 +14,10 @@ namespace webrtc { namespace test { -VideoSendStream::Config::EncoderSettings CreateEncoderSettings( - VideoEncoder* encoder, - const char* payload_name, - int payload_type, - size_t num_streams); +std::vector CreateVideoStreams(size_t num_streams); VideoCodec CreateDecoderVideoCodec( - const VideoSendStream::Config::EncoderSettings& settings); + const VideoSendStream::Config::EncoderSettings& encoder_settings); } // namespace test } // namespace webrtc diff --git a/webrtc/test/fake_audio_device.cc b/webrtc/test/fake_audio_device.cc index a6fe165b2..989c12b7f 100644 --- a/webrtc/test/fake_audio_device.cc +++ b/webrtc/test/fake_audio_device.cc @@ -121,13 +121,17 @@ void FakeAudioDevice::CaptureAudio() { samples_needed = std::min(kFrequencyHz / time_since_last_playout_ms, kBufferSizeBytes / 2); uint32_t samples_out = 0; + int64_t elapsed_time_ms = -1; + int64_t ntp_time_ms = -1; EXPECT_EQ(0, audio_callback_->NeedMorePlayData(samples_needed, 2, 1, kFrequencyHz, playout_buffer_, - samples_out)); + samples_out, + &elapsed_time_ms, + &ntp_time_ms)); } } tick_->Wait(WEBRTC_EVENT_INFINITE); diff --git a/webrtc/test/fake_decoder.cc b/webrtc/test/fake_decoder.cc index a9e6f50e1..1624ea014 100644 --- a/webrtc/test/fake_decoder.cc +++ b/webrtc/test/fake_decoder.cc @@ -36,6 +36,7 @@ int32_t FakeDecoder::Decode(const EncodedImage& input, const CodecSpecificInfo* codec_specific_info, int64_t render_time_ms) { frame_.set_timestamp(input._timeStamp); + frame_.set_ntp_time_ms(input.ntp_time_ms_); frame_.set_render_time_ms(render_time_ms); callback_->Decoded(frame_); diff --git a/webrtc/test/fake_encoder.h b/webrtc/test/fake_encoder.h index c0709c129..2a444a10e 100644 --- a/webrtc/test/fake_encoder.h +++ b/webrtc/test/fake_encoder.h @@ -42,7 +42,7 @@ class FakeEncoder : public VideoEncoder { uint32_t framerate) OVERRIDE; private: - Clock* clock_; + Clock* const clock_; VideoCodec config_; EncodedImageCallback* callback_; int target_bitrate_kbps_; diff --git a/webrtc/test/fake_network_pipe.h b/webrtc/test/fake_network_pipe.h index 3dcda2982..b9690e79d 100644 --- a/webrtc/test/fake_network_pipe.h +++ b/webrtc/test/fake_network_pipe.h @@ -13,7 +13,7 @@ #include -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/event_wrapper.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/typedefs.h" diff --git a/webrtc/test/fake_network_pipe_unittest.cc b/webrtc/test/fake_network_pipe_unittest.cc index 5076bc0ac..0399688f6 100644 --- a/webrtc/test/fake_network_pipe_unittest.cc +++ b/webrtc/test/fake_network_pipe_unittest.cc @@ -33,7 +33,7 @@ class MockReceiver : public PacketReceiver { delete [] data; } - MOCK_METHOD2(DeliverPacket, bool(const uint8_t*, size_t)); + MOCK_METHOD2(DeliverPacket, DeliveryStatus(const uint8_t*, size_t)); }; class FakeNetworkPipeTest : public ::testing::Test { @@ -47,7 +47,7 @@ class FakeNetworkPipeTest : public ::testing::Test { } void SendPackets(FakeNetworkPipe* pipe, int number_packets, int kPacketSize) { - scoped_array packet(new uint8_t[kPacketSize]); + scoped_ptr packet(new uint8_t[kPacketSize]); for (int i = 0; i < number_packets; ++i) { pipe->SendPacket(packet.get(), kPacketSize); } diff --git a/webrtc/test/field_trial.cc b/webrtc/test/field_trial.cc new file mode 100644 index 000000000..6b3d83cf5 --- /dev/null +++ b/webrtc/test/field_trial.cc @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2014 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 "webrtc/test/field_trial.h" + +#include +#include +#include +#include +#include +#include + +#include "webrtc/system_wrappers/interface/field_trial.h" + +namespace webrtc { +namespace { +// Clients of this library have show a clear intent to setup field trials by +// linking with it. As so try to crash if they forget to call +// InitFieldTrialsFromString before webrtc tries to access a field trial. +bool field_trials_initiated_ = false; +std::map field_trials_; +} // namespace + +namespace field_trial { +std::string FindFullName(const std::string& trial_name) { + assert(field_trials_initiated_); + std::map::const_iterator it = + field_trials_.find(trial_name); + if (it == field_trials_.end()) + return std::string(); + return it->second; +} +} // namespace field_trial + +namespace test { +// Note: this code is copied from src/base/metrics/field_trial.cc since the aim +// is to mimic chromium --force-fieldtrials. +void InitFieldTrialsFromString(const std::string& trials_string) { + static const char kPersistentStringSeparator = '/'; + + // Catch an error if this is called more than once. + assert(field_trials_initiated_ == false); + field_trials_initiated_ = true; + + if (trials_string.empty()) return; + + size_t next_item = 0; + while (next_item < trials_string.length()) { + size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); + if (name_end == trials_string.npos || next_item == name_end) + break; + size_t group_name_end = trials_string.find(kPersistentStringSeparator, + name_end + 1); + if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) + break; + std::string name(trials_string, next_item, name_end - next_item); + std::string group_name(trials_string, name_end + 1, + group_name_end - name_end - 1); + next_item = group_name_end + 1; + + // Fail if duplicate with different group name. + if (field_trials_.find(name) != field_trials_.end() && + field_trials_.find(name)->second != group_name) + break; + + field_trials_[name] = group_name; + + // Successfully parsed all field trials from the string. + if (next_item == trials_string.length()) + return; + } + // LOG does not prints when this is called early on main. + fprintf(stderr, "Invalid field trials string.\n"); + + // Using abort so it crashs both in debug and release mode. + abort(); +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/field_trial.h b/webrtc/test/field_trial.h new file mode 100644 index 000000000..650325472 --- /dev/null +++ b/webrtc/test/field_trial.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2014 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_TEST_FIELD_TRIAL_H_ +#define WEBRTC_TEST_FIELD_TRIAL_H_ + +#include + +namespace webrtc { +namespace test { + +// Parses enabled field trials from a string config, such as the one passed +// to chrome's argument --force-fieldtrials and initializes webrtc::field_trial +// with such a config. +// E.g.: +// "WebRTC-experimentFoo/Enabled/WebRTC-experimentBar/Enabled100kbps/" +// Assigns the process to group "Enabled" on WebRTCExperimentFoo trial +// and to group "Enabled100kbps" on WebRTCExperimentBar. +// +// E.g. invalid config: +// "WebRTC-experiment1/Enabled" (note missing / separator at the end). +// +// Note: This method crashes with an error message if an invalid config is +// passed to it. That can be used to find out if a binary is parsing the flags. +void InitFieldTrialsFromString(const std::string& config); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_FIELD_TRIAL_H_ diff --git a/webrtc/test/flags.cc b/webrtc/test/flags.cc deleted file mode 100644 index 088efff0b..000000000 --- a/webrtc/test/flags.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2013 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 "webrtc/test/flags.h" - -#include "gflags/gflags.h" - -namespace webrtc { -namespace test { -namespace flags { - -void Init(int* argc, char*** argv) { - // AllowCommandLineParsing allows us to ignore flags passed on to us by - // Chromium build bots without having to explicitly disable them. - google::AllowCommandLineReparsing(); - google::ParseCommandLineFlags(argc, argv, true); -} - -DEFINE_int32(width, 640, "Video width."); -size_t Width() { return static_cast(FLAGS_width); } - -DEFINE_int32(height, 480, "Video height."); -size_t Height() { return static_cast(FLAGS_height); } - -DEFINE_int32(fps, 30, "Frames per second."); -int Fps() { return static_cast(FLAGS_fps); } - -DEFINE_int32(min_bitrate, 50, "Minimum video bitrate."); -size_t MinBitrate() { return static_cast(FLAGS_min_bitrate); } - -DEFINE_int32(start_bitrate, 300, "Video starting bitrate."); -size_t StartBitrate() { return static_cast(FLAGS_start_bitrate); } - -DEFINE_int32(max_bitrate, 800, "Maximum video bitrate."); -size_t MaxBitrate() { return static_cast(FLAGS_max_bitrate); } -} // flags -} // test -} // webrtc diff --git a/webrtc/test/flags.h b/webrtc/test/flags.h deleted file mode 100644 index fb5f5fc9c..000000000 --- a/webrtc/test/flags.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2013 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_TEST_COMMON_FLAGS_H_ -#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_FLAGS_H_ - -#include - -namespace webrtc { -namespace test { -namespace flags { - -void Init(int* argc, char ***argv); - -size_t Width(); -size_t Height(); -int Fps(); -size_t MinBitrate(); -size_t StartBitrate(); -size_t MaxBitrate(); -} // flags -} // test -} // webrtc - -#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_FLAGS_H_ diff --git a/webrtc/test/frame_generator_capturer.cc b/webrtc/test/frame_generator_capturer.cc index 6570c6f15..d1c17c790 100644 --- a/webrtc/test/frame_generator_capturer.cc +++ b/webrtc/test/frame_generator_capturer.cc @@ -67,7 +67,8 @@ FrameGeneratorCapturer::FrameGeneratorCapturer(Clock* clock, tick_(EventWrapper::Create()), lock_(CriticalSectionWrapper::CreateCriticalSection()), frame_generator_(frame_generator), - target_fps_(target_fps) { + target_fps_(target_fps), + first_frame_capture_time_(-1) { assert(input != NULL); assert(frame_generator != NULL); assert(target_fps > 0); @@ -113,6 +114,9 @@ void FrameGeneratorCapturer::InsertFrame() { if (sending_) { I420VideoFrame* frame = frame_generator_->NextFrame(); frame->set_render_time_ms(clock_->CurrentNtpInMilliseconds()); + if (first_frame_capture_time_ == -1) { + first_frame_capture_time_ = frame->render_time_ms(); + } input_->SwapFrame(frame); } } diff --git a/webrtc/test/frame_generator_capturer.h b/webrtc/test/frame_generator_capturer.h index 5be6bb2cb..ee3f0e012 100644 --- a/webrtc/test/frame_generator_capturer.h +++ b/webrtc/test/frame_generator_capturer.h @@ -43,6 +43,8 @@ class FrameGeneratorCapturer : public VideoCapturer { virtual void Start() OVERRIDE; virtual void Stop() OVERRIDE; + int64_t first_frame_capture_time() const { return first_frame_capture_time_; } + private: FrameGeneratorCapturer(Clock* clock, VideoSendStreamInput* input, @@ -52,7 +54,7 @@ class FrameGeneratorCapturer : public VideoCapturer { void InsertFrame(); static bool Run(void* obj); - Clock* clock_; + Clock* const clock_; bool sending_; scoped_ptr tick_; @@ -61,6 +63,8 @@ class FrameGeneratorCapturer : public VideoCapturer { scoped_ptr frame_generator_; int target_fps_; + + int64_t first_frame_capture_time_; }; } // test } // webrtc diff --git a/webrtc/test/mac/run_tests.mm b/webrtc/test/mac/run_test.mm similarity index 68% rename from webrtc/test/mac/run_tests.mm rename to webrtc/test/mac/run_test.mm index c338773f1..e8f38401c 100644 --- a/webrtc/test/mac/run_tests.mm +++ b/webrtc/test/mac/run_test.mm @@ -10,15 +10,20 @@ #import -#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/run_test.h" +// Converting a C++ function pointer to an Objective-C block. +typedef void(^TestBlock)(); +TestBlock functionToBlock(void(*function)()) { + return [^(void) { function(); } copy]; +} + +// Class calling the test function on the platform specific thread. @interface TestRunner : NSObject { BOOL running_; - int testResult_; } -- (void)runAllTests:(NSObject *)ignored; +- (void)runAllTests:(TestBlock)ignored; - (BOOL)running; -- (int)result; @end @implementation TestRunner @@ -30,9 +35,9 @@ - (id)init { return self; } -- (void)runAllTests:(NSObject *)ignored { +- (void)runAllTests:(TestBlock)testBlock { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - testResult_ = RUN_ALL_TESTS(); + testBlock(); running_ = NO; [pool release]; } @@ -40,33 +45,30 @@ - (void)runAllTests:(NSObject *)ignored { - (BOOL)running { return running_; } - -- (int)result { - return testResult_; -} @end namespace webrtc { namespace test { -int RunAllTests() { +void RunTest(void(*test)()) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [NSApplication sharedApplication]; + // Convert the function pointer to an Objective-C block and call on a + // separate thread, to avoid blocking the main thread. TestRunner *testRunner = [[TestRunner alloc] init]; + TestBlock testBlock = functionToBlock(test); [NSThread detachNewThreadSelector:@selector(runAllTests:) toTarget:testRunner - withObject:nil]; + withObject:testBlock]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; while ([testRunner running] && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); - int result = [testRunner result]; [testRunner release]; [pool release]; - return result; } } // namespace test diff --git a/webrtc/test/mac/video_renderer_mac.h b/webrtc/test/mac/video_renderer_mac.h index 7701840c0..7950b0f95 100644 --- a/webrtc/test/mac/video_renderer_mac.h +++ b/webrtc/test/mac/video_renderer_mac.h @@ -11,8 +11,8 @@ #ifndef WEBRTC_VIDEO_ENGINE_TEST_COMMON_MAC_VIDEO_RENDERER_MAC_H_ #define WEBRTC_VIDEO_ENGINE_TEST_COMMON_MAC_VIDEO_RENDERER_MAC_H_ +#include "webrtc/base/constructormagic.h" #include "webrtc/test/gl/gl_renderer.h" -#include "webrtc/system_wrappers/interface/constructor_magic.h" @class CocoaWindow; diff --git a/webrtc/test/rtcp_packet_parser.cc b/webrtc/test/rtcp_packet_parser.cc index 2e5880e2b..558bee379 100644 --- a/webrtc/test/rtcp_packet_parser.cc +++ b/webrtc/test/rtcp_packet_parser.cc @@ -23,25 +23,116 @@ void RtcpPacketParser::Parse(const void *data, int len) { for (RTCPUtility::RTCPPacketTypes type = parser.Begin(); type != RTCPUtility::kRtcpNotValidCode; type = parser.Iterate()) { - if (type == RTCPUtility::kRtcpSrCode) { - sender_report_.Set(parser.Packet().SR); - } else if (type == RTCPUtility::kRtcpRrCode) { - receiver_report_.Set(parser.Packet().RR); - } else if (type == RTCPUtility::kRtcpByeCode) { - bye_.Set(parser.Packet().BYE); - } else if (type == RTCPUtility::kRtcpReportBlockItemCode) { - report_block_.Set(parser.Packet().ReportBlockItem); - ++report_blocks_per_ssrc_[parser.Packet().ReportBlockItem.SSRC]; - } else if (type == RTCPUtility::kRtcpPsfbFirCode) { - fir_.Set(parser.Packet().FIR); - } else if (type == webrtc::RTCPUtility::kRtcpPsfbFirItemCode) { - fir_item_.Set(parser.Packet().FIRItem); - } else if (type == RTCPUtility::kRtcpRtpfbNackCode) { - nack_.Set(parser.Packet().NACK); - } else if (type == RTCPUtility::kRtcpRtpfbNackItemCode) { - nack_item_.Set(parser.Packet().NACKItem); + switch (type) { + case RTCPUtility::kRtcpSrCode: + sender_report_.Set(parser.Packet().SR); + break; + case RTCPUtility::kRtcpRrCode: + receiver_report_.Set(parser.Packet().RR); + break; + case RTCPUtility::kRtcpReportBlockItemCode: + report_block_.Set(parser.Packet().ReportBlockItem); + ++report_blocks_per_ssrc_[parser.Packet().ReportBlockItem.SSRC]; + break; + case RTCPUtility::kRtcpSdesCode: + sdes_.Set(); + break; + case RTCPUtility::kRtcpSdesChunkCode: + sdes_chunk_.Set(parser.Packet().CName); + break; + case RTCPUtility::kRtcpByeCode: + bye_.Set(parser.Packet().BYE); + break; + case RTCPUtility::kRtcpAppCode: + app_.Set(parser.Packet().APP); + break; + case RTCPUtility::kRtcpAppItemCode: + app_item_.Set(parser.Packet().APP); + break; + case RTCPUtility::kRtcpExtendedIjCode: + ij_.Set(); + break; + case RTCPUtility::kRtcpExtendedIjItemCode: + ij_item_.Set(parser.Packet().ExtendedJitterReportItem); + break; + case RTCPUtility::kRtcpPsfbPliCode: + pli_.Set(parser.Packet().PLI); + break; + case RTCPUtility::kRtcpPsfbSliCode: + sli_.Set(parser.Packet().SLI); + break; + case RTCPUtility::kRtcpPsfbSliItemCode: + sli_item_.Set(parser.Packet().SLIItem); + break; + case RTCPUtility::kRtcpPsfbRpsiCode: + rpsi_.Set(parser.Packet().RPSI); + break; + case RTCPUtility::kRtcpPsfbFirCode: + fir_.Set(parser.Packet().FIR); + break; + case RTCPUtility::kRtcpPsfbFirItemCode: + fir_item_.Set(parser.Packet().FIRItem); + break; + case RTCPUtility::kRtcpRtpfbNackCode: + nack_.Set(parser.Packet().NACK); + nack_item_.Clear(); + break; + case RTCPUtility::kRtcpRtpfbNackItemCode: + nack_item_.Set(parser.Packet().NACKItem); + break; + case RTCPUtility::kRtcpPsfbAppCode: + psfb_app_.Set(parser.Packet().PSFBAPP); + break; + case RTCPUtility::kRtcpPsfbRembItemCode: + remb_item_.Set(parser.Packet().REMBItem); + break; + case RTCPUtility::kRtcpRtpfbTmmbrCode: + tmmbr_.Set(parser.Packet().TMMBR); + break; + case RTCPUtility::kRtcpRtpfbTmmbrItemCode: + tmmbr_item_.Set(parser.Packet().TMMBRItem); + break; + case RTCPUtility::kRtcpRtpfbTmmbnCode: + tmmbn_.Set(parser.Packet().TMMBN); + tmmbn_items_.Clear(); + break; + case RTCPUtility::kRtcpRtpfbTmmbnItemCode: + tmmbn_items_.Set(parser.Packet().TMMBNItem); + break; + case RTCPUtility::kRtcpXrHeaderCode: + xr_header_.Set(parser.Packet().XR); + dlrr_items_.Clear(); + break; + case RTCPUtility::kRtcpXrReceiverReferenceTimeCode: + rrtr_.Set(parser.Packet().XRReceiverReferenceTimeItem); + break; + case RTCPUtility::kRtcpXrDlrrReportBlockCode: + dlrr_.Set(); + break; + case RTCPUtility::kRtcpXrDlrrReportBlockItemCode: + dlrr_items_.Set(parser.Packet().XRDLRRReportBlockItem); + break; + case RTCPUtility::kRtcpXrVoipMetricCode: + voip_metric_.Set(parser.Packet().XRVOIPMetricItem); + break; + default: + break; } } } + +uint64_t Rpsi::PictureId() const { + assert(num_packets_ > 0); + uint16_t num_bytes = rpsi_.NumberOfValidBits / 8; + assert(num_bytes > 0); + uint64_t picture_id = 0; + for (uint16_t i = 0; i < num_bytes - 1; ++i) { + picture_id += (rpsi_.NativeBitString[i] & 0x7f); + picture_id <<= 7; + } + picture_id += (rpsi_.NativeBitString[num_bytes - 1] & 0x7f); + return picture_id; +} + } // namespace test } // namespace webrtc diff --git a/webrtc/test/rtcp_packet_parser.h b/webrtc/test/rtcp_packet_parser.h index a9e650cf2..f7d36ba80 100644 --- a/webrtc/test/rtcp_packet_parser.h +++ b/webrtc/test/rtcp_packet_parser.h @@ -13,6 +13,7 @@ #define WEBRTC_TEST_RTCP_PACKET_PARSER_H_ #include +#include #include #include "webrtc/modules/rtp_rtcp/source/rtcp_utility.h" @@ -23,167 +24,347 @@ namespace test { class RtcpPacketParser; -class SenderReport { +class PacketType { public: - SenderReport() : num_packets_(0) {} - ~SenderReport() {} + virtual ~PacketType() {} - int num_packets() { return num_packets_; } - uint32_t Ssrc() { return sr_.SenderSSRC; } - uint32_t NtpSec() { return sr_.NTPMostSignificant; } - uint32_t NtpFrac() { return sr_.NTPLeastSignificant; } - uint32_t RtpTimestamp() { return sr_.RTPTimestamp; } - uint32_t PacketCount() { return sr_.SenderPacketCount; } - uint32_t OctetCount() { return sr_.SenderOctetCount; } + int num_packets() const { return num_packets_; } + + protected: + PacketType() : num_packets_(0) {} + + int num_packets_; +}; + +class SenderReport : public PacketType { + public: + SenderReport() {} + virtual ~SenderReport() {} + + uint32_t Ssrc() const { return sr_.SenderSSRC; } + uint32_t NtpSec() const { return sr_.NTPMostSignificant; } + uint32_t NtpFrac() const { return sr_.NTPLeastSignificant; } + uint32_t RtpTimestamp() const { return sr_.RTPTimestamp; } + uint32_t PacketCount() const { return sr_.SenderPacketCount; } + uint32_t OctetCount() const { return sr_.SenderOctetCount; } private: friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketSR& sr) { sr_ = sr; ++num_packets_; } - int num_packets_; RTCPUtility::RTCPPacketSR sr_; }; -class ReceiverReport { +class ReceiverReport : public PacketType { public: - ReceiverReport() : num_packets_(0) {} - ~ReceiverReport() {} + ReceiverReport() {} + virtual ~ReceiverReport() {} - int num_packets() { return num_packets_; } - uint32_t Ssrc() { return rr_.SenderSSRC; } + uint32_t Ssrc() const { return rr_.SenderSSRC; } private: friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketRR& rr) { rr_ = rr; ++num_packets_; } - int num_packets_; RTCPUtility::RTCPPacketRR rr_; }; -class ReportBlock { +class ReportBlock : public PacketType { public: - ReportBlock() : num_packets_(0) {} - ~ReportBlock() {} + ReportBlock() {} + virtual ~ReportBlock() {} - int num_packets() { return num_packets_; } - uint32_t Ssrc() { return rb_.SSRC; } - uint8_t FractionLost() { return rb_.FractionLost; } - uint32_t CumPacketLost() { return rb_.CumulativeNumOfPacketsLost; } - uint32_t ExtHighestSeqNum() { return rb_.ExtendedHighestSequenceNumber; } - uint32_t Jitter() { return rb_.Jitter; } - uint32_t LastSr() { return rb_.LastSR; } - uint32_t DelayLastSr() { return rb_.DelayLastSR; } + uint32_t Ssrc() const { return rb_.SSRC; } + uint8_t FractionLost() const { return rb_.FractionLost; } + uint32_t CumPacketLost() const { return rb_.CumulativeNumOfPacketsLost; } + uint32_t ExtHighestSeqNum() const { return rb_.ExtendedHighestSequenceNumber;} + uint32_t Jitter() const { return rb_.Jitter; } + uint32_t LastSr() const { return rb_.LastSR; } + uint32_t DelayLastSr()const { return rb_.DelayLastSR; } private: friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketReportBlockItem& rb) { rb_ = rb; ++num_packets_; } - int num_packets_; RTCPUtility::RTCPPacketReportBlockItem rb_; }; -class Bye { +class Ij : public PacketType { + public: + Ij() {} + virtual ~Ij() {} + + private: + friend class RtcpPacketParser; + + void Set() { ++num_packets_; } +}; + +class IjItem : public PacketType { + public: + IjItem() {} + virtual ~IjItem() {} + + uint32_t Jitter() const { return ij_item_.Jitter; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketExtendedJitterReportItem& ij_item) { + ij_item_ = ij_item; + ++num_packets_; + } + + RTCPUtility::RTCPPacketExtendedJitterReportItem ij_item_; +}; + +class Sdes : public PacketType { public: - Bye() : num_packets_(0) {} - ~Bye() {} + Sdes() {} + virtual ~Sdes() {} - int num_packets() { return num_packets_; } - uint32_t Ssrc() { return bye_.SenderSSRC; } + private: + friend class RtcpPacketParser; + + void Set() { ++num_packets_; } +}; + +class SdesChunk : public PacketType { + public: + SdesChunk() {} + virtual ~SdesChunk() {} + + uint32_t Ssrc() const { return cname_.SenderSSRC; } + std::string Cname() const { return cname_.CName; } private: friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketSDESCName& cname) { + cname_ = cname; + ++num_packets_; + } + + RTCPUtility::RTCPPacketSDESCName cname_; +}; + +class Bye : public PacketType { + public: + Bye() {} + virtual ~Bye() {} + + uint32_t Ssrc() const { return bye_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketBYE& bye) { bye_ = bye; ++num_packets_; } - int num_packets_; RTCPUtility::RTCPPacketBYE bye_; }; -class Fir { +class Rpsi : public PacketType { + public: + Rpsi() {} + virtual ~Rpsi() {} + + uint32_t Ssrc() const { return rpsi_.SenderSSRC; } + uint32_t MediaSsrc() const { return rpsi_.MediaSSRC; } + uint8_t PayloadType() const { return rpsi_.PayloadType; } + uint16_t NumberOfValidBits() const { return rpsi_.NumberOfValidBits; } + uint64_t PictureId() const; + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBRPSI& rpsi) { + rpsi_ = rpsi; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBRPSI rpsi_; +}; + +class App : public PacketType { public: - Fir() : num_packets_(0) {} - ~Fir() {} + App() {} + virtual ~App() {} - int num_packets() { return num_packets_; } - uint32_t Ssrc() { return fir_.SenderSSRC; } + uint8_t SubType() const { return app_.SubType; } + uint32_t Name() const { return app_.Name; } private: friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketAPP& app) { + app_ = app; + ++num_packets_; + } + + RTCPUtility::RTCPPacketAPP app_; +}; + +class AppItem : public PacketType { + public: + AppItem() {} + virtual ~AppItem() {} + + uint8_t* Data() { return app_item_.Data; } + uint16_t DataLength() const { return app_item_.Size; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketAPP& app) { + app_item_ = app; + ++num_packets_; + } + + RTCPUtility::RTCPPacketAPP app_item_; +}; + +class Pli : public PacketType { + public: + Pli() {} + virtual ~Pli() {} + + uint32_t Ssrc() const { return pli_.SenderSSRC; } + uint32_t MediaSsrc() const { return pli_.MediaSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBPLI& pli) { + pli_ = pli; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBPLI pli_; +}; + +class Sli : public PacketType { + public: + Sli() {} + virtual ~Sli() {} + + uint32_t Ssrc() const { return sli_.SenderSSRC; } + uint32_t MediaSsrc() const { return sli_.MediaSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBSLI& sli) { + sli_ = sli; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBSLI sli_; +}; + +class SliItem : public PacketType { + public: + SliItem() {} + virtual ~SliItem() {} + + uint16_t FirstMb() const { return sli_item_.FirstMB; } + uint16_t NumberOfMb() const { return sli_item_.NumberOfMB; } + uint8_t PictureId() const { return sli_item_.PictureId; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBSLIItem& sli_item) { + sli_item_ = sli_item; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBSLIItem sli_item_; +}; + +class Fir : public PacketType { + public: + Fir() {} + virtual ~Fir() {} + + uint32_t Ssrc() const { return fir_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketPSFBFIR& fir) { fir_ = fir; ++num_packets_; } - int num_packets_; RTCPUtility::RTCPPacketPSFBFIR fir_; }; -class FirItem { +class FirItem : public PacketType { public: - FirItem() : num_packets_(0) {} - ~FirItem() {} + FirItem() {} + virtual ~FirItem() {} - int num_packets() { return num_packets_; } - uint32_t Ssrc() { return fir_item_.SSRC; } - uint8_t SeqNum() { return fir_item_.CommandSequenceNumber; } + uint32_t Ssrc() const { return fir_item_.SSRC; } + uint8_t SeqNum() const { return fir_item_.CommandSequenceNumber; } private: friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketPSFBFIRItem& fir_item) { fir_item_ = fir_item; ++num_packets_; } - int num_packets_; RTCPUtility::RTCPPacketPSFBFIRItem fir_item_; }; -class Nack { +class Nack : public PacketType { public: - Nack() : num_packets_(0) {} - ~Nack() {} + Nack() {} + virtual ~Nack() {} - int num_packets() { return num_packets_; } - uint32_t Ssrc() { return nack_.SenderSSRC; } - uint32_t MediaSsrc() { return nack_.MediaSSRC; } + uint32_t Ssrc() const { return nack_.SenderSSRC; } + uint32_t MediaSsrc() const { return nack_.MediaSSRC; } private: friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketRTPFBNACK& nack) { nack_ = nack; ++num_packets_; } - int num_packets_; RTCPUtility::RTCPPacketRTPFBNACK nack_; }; -class NackItem { +class NackItem : public PacketType { public: - NackItem() : num_packets_(0) {} - ~NackItem() {} + NackItem() {} + virtual ~NackItem() {} - int num_packets() { return num_packets_; } - std::vector last_nack_list() { - assert(num_packets_ > 0); + std::vector last_nack_list() const { return last_nack_list_; } private: friend class RtcpPacketParser; + void Set(const RTCPUtility::RTCPPacketRTPFBNACKItem& nack_item) { - last_nack_list_.clear(); last_nack_list_.push_back(nack_item.PacketID); for (int i = 0; i < 16; ++i) { if (nack_item.BitMask & (1 << i)) { @@ -192,11 +373,258 @@ class NackItem { } ++num_packets_; } + void Clear() { last_nack_list_.clear(); } - int num_packets_; std::vector last_nack_list_; }; +class PsfbApp : public PacketType { + public: + PsfbApp() {} + virtual ~PsfbApp() {} + + uint32_t Ssrc() const { return psfb_app_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBAPP& psfb_app) { + psfb_app_ = psfb_app; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBAPP psfb_app_; +}; + +class RembItem : public PacketType { + public: + RembItem() : last_bitrate_bps_(0) {} + virtual ~RembItem() {} + + int last_bitrate_bps() const { return last_bitrate_bps_; } + std::vector last_ssrc_list() { + return last_ssrc_list_; + } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBREMBItem& remb_item) { + last_bitrate_bps_ = remb_item.BitRate; + last_ssrc_list_.clear(); + last_ssrc_list_.insert( + last_ssrc_list_.end(), + remb_item.SSRCs, + remb_item.SSRCs + remb_item.NumberOfSSRCs); + ++num_packets_; + } + + uint32_t last_bitrate_bps_; + std::vector last_ssrc_list_; +}; + +class Tmmbr : public PacketType { + public: + Tmmbr() {} + virtual ~Tmmbr() {} + + uint32_t Ssrc() const { return tmmbr_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBR& tmmbr) { + tmmbr_ = tmmbr; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRTPFBTMMBR tmmbr_; +}; + +class TmmbrItem : public PacketType { + public: + TmmbrItem() {} + virtual ~TmmbrItem() {} + + uint32_t Ssrc() const { return tmmbr_item_.SSRC; } + uint32_t BitrateKbps() const { return tmmbr_item_.MaxTotalMediaBitRate; } + uint32_t Overhead() const { return tmmbr_item_.MeasuredOverhead; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBRItem& tmmbr_item) { + tmmbr_item_ = tmmbr_item; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRTPFBTMMBRItem tmmbr_item_; +}; + + +class Tmmbn : public PacketType { + public: + Tmmbn() {} + virtual ~Tmmbn() {} + + uint32_t Ssrc() const { return tmmbn_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBN& tmmbn) { + tmmbn_ = tmmbn; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRTPFBTMMBN tmmbn_; +}; + +class TmmbnItems : public PacketType { + public: + TmmbnItems() {} + virtual ~TmmbnItems() {} + + uint32_t Ssrc(uint8_t num) const { + assert(num < tmmbns_.size()); + return tmmbns_[num].SSRC; + } + uint32_t BitrateKbps(uint8_t num) const { + assert(num < tmmbns_.size()); + return tmmbns_[num].MaxTotalMediaBitRate; + } + uint32_t Overhead(uint8_t num) const { + assert(num < tmmbns_.size()); + return tmmbns_[num].MeasuredOverhead; + } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBNItem& tmmbn_item) { + tmmbns_.push_back(tmmbn_item); + ++num_packets_; + } + void Clear() { tmmbns_.clear(); } + + std::vector tmmbns_; +}; + +class XrHeader : public PacketType { + public: + XrHeader() {} + virtual ~XrHeader() {} + + uint32_t Ssrc() const { return xr_header_.OriginatorSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXR& xr_header) { + xr_header_ = xr_header; + ++num_packets_; + } + + RTCPUtility::RTCPPacketXR xr_header_; +}; + +class Rrtr : public PacketType { + public: + Rrtr() {} + virtual ~Rrtr() {} + + uint32_t NtpSec() const { return rrtr_.NTPMostSignificant; } + uint32_t NtpFrac() const { return rrtr_.NTPLeastSignificant; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXRReceiverReferenceTimeItem& rrtr) { + rrtr_ = rrtr; + ++num_packets_; + } + + RTCPUtility::RTCPPacketXRReceiverReferenceTimeItem rrtr_; +}; + +class Dlrr : public PacketType { + public: + Dlrr() {} + virtual ~Dlrr() {} + + private: + friend class RtcpPacketParser; + + void Set() { ++num_packets_; } +}; + +class DlrrItems : public PacketType { + public: + DlrrItems() {} + virtual ~DlrrItems() {} + + uint32_t Ssrc(uint8_t num) const { + assert(num < dlrrs_.size()); + return dlrrs_[num].SSRC; + } + uint32_t LastRr(uint8_t num) const { + assert(num < dlrrs_.size()); + return dlrrs_[num].LastRR; + } + uint32_t DelayLastRr(uint8_t num) const { + assert(num < dlrrs_.size()); + return dlrrs_[num].DelayLastRR; + } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXRDLRRReportBlockItem& dlrr) { + dlrrs_.push_back(dlrr); + ++num_packets_; + } + void Clear() { dlrrs_.clear(); } + + std::vector dlrrs_; +}; + +class VoipMetric : public PacketType { + public: + VoipMetric() {} + virtual ~VoipMetric() {} + + uint32_t Ssrc() const { return voip_metric_.SSRC; } + uint8_t LossRate() { return voip_metric_.lossRate; } + uint8_t DiscardRate() { return voip_metric_.discardRate; } + uint8_t BurstDensity() { return voip_metric_.burstDensity; } + uint8_t GapDensity() { return voip_metric_.gapDensity; } + uint16_t BurstDuration() { return voip_metric_.burstDuration; } + uint16_t GapDuration() { return voip_metric_.gapDuration; } + uint16_t RoundTripDelay() { return voip_metric_.roundTripDelay; } + uint16_t EndSystemDelay() { return voip_metric_.endSystemDelay; } + uint8_t SignalLevel() { return voip_metric_.signalLevel; } + uint8_t NoiseLevel() { return voip_metric_.noiseLevel; } + uint8_t Rerl() { return voip_metric_.RERL; } + uint8_t Gmin() { return voip_metric_.Gmin; } + uint8_t Rfactor() { return voip_metric_.Rfactor; } + uint8_t ExtRfactor() { return voip_metric_.extRfactor; } + uint8_t MosLq() { return voip_metric_.MOSLQ; } + uint8_t MosCq() { return voip_metric_.MOSCQ; } + uint8_t RxConfig() { return voip_metric_.RXconfig; } + uint16_t JbNominal() { return voip_metric_.JBnominal; } + uint16_t JbMax() { return voip_metric_.JBmax; } + uint16_t JbAbsMax() { return voip_metric_.JBabsMax; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXRVOIPMetricItem& voip_metric) { + voip_metric_ = voip_metric; + ++num_packets_; + } + + RTCPUtility::RTCPPacketXRVOIPMetricItem voip_metric_; +}; class RtcpPacketParser { public: @@ -208,11 +636,32 @@ class RtcpPacketParser { SenderReport* sender_report() { return &sender_report_; } ReceiverReport* receiver_report() { return &receiver_report_; } ReportBlock* report_block() { return &report_block_; } + Sdes* sdes() { return &sdes_; } + SdesChunk* sdes_chunk() { return &sdes_chunk_; } Bye* bye() { return &bye_; } + App* app() { return &app_; } + AppItem* app_item() { return &app_item_; } + Ij* ij() { return &ij_; } + IjItem* ij_item() { return &ij_item_; } + Pli* pli() { return &pli_; } + Sli* sli() { return &sli_; } + SliItem* sli_item() { return &sli_item_; } + Rpsi* rpsi() { return &rpsi_; } Fir* fir() { return &fir_; } FirItem* fir_item() { return &fir_item_; } Nack* nack() { return &nack_; } NackItem* nack_item() { return &nack_item_; } + PsfbApp* psfb_app() { return &psfb_app_; } + RembItem* remb_item() { return &remb_item_; } + Tmmbr* tmmbr() { return &tmmbr_; } + TmmbrItem* tmmbr_item() { return &tmmbr_item_; } + Tmmbn* tmmbn() { return &tmmbn_; } + TmmbnItems* tmmbn_items() { return &tmmbn_items_; } + XrHeader* xr_header() { return &xr_header_; } + Rrtr* rrtr() { return &rrtr_; } + Dlrr* dlrr() { return &dlrr_; } + DlrrItems* dlrr_items() { return &dlrr_items_; } + VoipMetric* voip_metric() { return &voip_metric_; } int report_blocks_per_ssrc(uint32_t ssrc) { return report_blocks_per_ssrc_[ssrc]; @@ -222,11 +671,32 @@ class RtcpPacketParser { SenderReport sender_report_; ReceiverReport receiver_report_; ReportBlock report_block_; + Sdes sdes_; + SdesChunk sdes_chunk_; Bye bye_; + App app_; + AppItem app_item_; + Ij ij_; + IjItem ij_item_; + Pli pli_; + Sli sli_; + SliItem sli_item_; + Rpsi rpsi_; Fir fir_; FirItem fir_item_; Nack nack_; NackItem nack_item_; + PsfbApp psfb_app_; + RembItem remb_item_; + Tmmbr tmmbr_; + TmmbrItem tmmbr_item_; + Tmmbn tmmbn_; + TmmbnItems tmmbn_items_; + XrHeader xr_header_; + Rrtr rrtr_; + Dlrr dlrr_; + DlrrItems dlrr_items_; + VoipMetric voip_metric_; std::map report_blocks_per_ssrc_; }; diff --git a/webrtc/test/rtp_rtcp_observer.h b/webrtc/test/rtp_rtcp_observer.h index 00422cceb..e4486534c 100644 --- a/webrtc/test/rtp_rtcp_observer.h +++ b/webrtc/test/rtp_rtcp_observer.h @@ -53,15 +53,15 @@ class RtpRtcpObserver { protected: RtpRtcpObserver(unsigned int event_timeout_ms, const FakeNetworkPipe::Config& configuration) - : lock_(CriticalSectionWrapper::CreateCriticalSection()), + : crit_(CriticalSectionWrapper::CreateCriticalSection()), observation_complete_(EventWrapper::Create()), parser_(RtpHeaderParser::Create()), - send_transport_(lock_.get(), + send_transport_(crit_.get(), this, &RtpRtcpObserver::OnSendRtp, &RtpRtcpObserver::OnSendRtcp, configuration), - receive_transport_(lock_.get(), + receive_transport_(crit_.get(), this, &RtpRtcpObserver::OnReceiveRtp, &RtpRtcpObserver::OnReceiveRtcp, @@ -69,15 +69,15 @@ class RtpRtcpObserver { timeout_ms_(event_timeout_ms) {} explicit RtpRtcpObserver(unsigned int event_timeout_ms) - : lock_(CriticalSectionWrapper::CreateCriticalSection()), + : crit_(CriticalSectionWrapper::CreateCriticalSection()), observation_complete_(EventWrapper::Create()), parser_(RtpHeaderParser::Create()), - send_transport_(lock_.get(), + send_transport_(crit_.get(), this, &RtpRtcpObserver::OnSendRtp, &RtpRtcpObserver::OnSendRtcp, FakeNetworkPipe::Config()), - receive_transport_(lock_.get(), + receive_transport_(crit_.get(), this, &RtpRtcpObserver::OnReceiveRtp, &RtpRtcpObserver::OnReceiveRtcp, @@ -89,23 +89,26 @@ class RtpRtcpObserver { DROP_PACKET, }; - virtual Action OnSendRtp(const uint8_t* packet, size_t length) { + virtual Action OnSendRtp(const uint8_t* packet, size_t length) + EXCLUSIVE_LOCKS_REQUIRED(crit_) { return SEND_PACKET; } - virtual Action OnSendRtcp(const uint8_t* packet, size_t length) { + virtual Action OnSendRtcp(const uint8_t* packet, size_t length) + EXCLUSIVE_LOCKS_REQUIRED(crit_) { return SEND_PACKET; } - virtual Action OnReceiveRtp(const uint8_t* packet, size_t length) { + virtual Action OnReceiveRtp(const uint8_t* packet, size_t length) + EXCLUSIVE_LOCKS_REQUIRED(crit_) { return SEND_PACKET; } - virtual Action OnReceiveRtcp(const uint8_t* packet, size_t length) { + virtual Action OnReceiveRtcp(const uint8_t* packet, size_t length) + EXCLUSIVE_LOCKS_REQUIRED(crit_) { return SEND_PACKET; } - private: class PacketTransport : public test::DirectTransport { public: @@ -118,7 +121,7 @@ class RtpRtcpObserver { PacketTransportAction on_rtcp, const FakeNetworkPipe::Config& configuration) : test::DirectTransport(configuration), - lock_(lock), + crit_(lock), observer_(observer), on_rtp_(on_rtp), on_rtcp_(on_rtcp) {} @@ -128,7 +131,7 @@ class RtpRtcpObserver { EXPECT_FALSE(RtpHeaderParser::IsRtcp(packet, static_cast(length))); Action action; { - CriticalSectionScoped crit_(lock_); + CriticalSectionScoped lock(crit_); action = (observer_->*on_rtp_)(packet, length); } switch (action) { @@ -145,7 +148,7 @@ class RtpRtcpObserver { EXPECT_TRUE(RtpHeaderParser::IsRtcp(packet, static_cast(length))); Action action; { - CriticalSectionScoped crit_(lock_); + CriticalSectionScoped lock(crit_); action = (observer_->*on_rtcp_)(packet, length); } switch (action) { @@ -159,16 +162,16 @@ class RtpRtcpObserver { } // Pointer to shared lock instance protecting on_rtp_/on_rtcp_ calls. - CriticalSectionWrapper* lock_; + CriticalSectionWrapper* const crit_; - RtpRtcpObserver* observer_; - PacketTransportAction on_rtp_, on_rtcp_; + RtpRtcpObserver* const observer_; + const PacketTransportAction on_rtp_, on_rtcp_; }; protected: - scoped_ptr lock_; - scoped_ptr observation_complete_; - scoped_ptr parser_; + const scoped_ptr crit_; + const scoped_ptr observation_complete_; + const scoped_ptr parser_; private: PacketTransport send_transport_, receive_transport_; diff --git a/webrtc/test/run_tests.cc b/webrtc/test/run_test.cc similarity index 83% rename from webrtc/test/run_tests.cc rename to webrtc/test/run_test.cc index 4692ba615..4daea42a1 100644 --- a/webrtc/test/run_tests.cc +++ b/webrtc/test/run_test.cc @@ -7,13 +7,16 @@ * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ -#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/test/run_test.h" + +#include namespace webrtc { namespace test { -int RunAllTests() { - return RUN_ALL_TESTS(); +void RunTest(void(*test)()) { + (*test)(); } } // namespace test diff --git a/webrtc/test/run_tests.h b/webrtc/test/run_test.h similarity index 59% rename from webrtc/test/run_tests.h rename to webrtc/test/run_test.h index 3f42866fa..b51525468 100644 --- a/webrtc/test/run_tests.h +++ b/webrtc/test/run_test.h @@ -7,19 +7,16 @@ * 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_TEST_COMMON_TEST_RUNNER_H_ -#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_TEST_RUNNER_H_ +#ifndef WEBRTC_TEST_RUN_TEST_H +#define WEBRTC_TEST_RUN_TEST_H namespace webrtc { namespace test { -// Blocks until the user presses enter. -void PressEnterToContinue(); +// Running a test function on a separate thread, if required by the OS. +void RunTest(void(*test)()); -// Performs platform-dependent initializations and calls gtest's -// RUN_ALL_TESTS(). -int RunAllTests(); } // namespace test } // namespace webrtc -#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_TEST_RUNNER_H_ +#endif // WEBRTC_TEST_RUN_TEST_H diff --git a/webrtc/test/test.gyp b/webrtc/test/test.gyp index e920d6ea6..69776e7b9 100644 --- a/webrtc/test/test.gyp +++ b/webrtc/test/test.gyp @@ -63,18 +63,38 @@ '<(webrtc_root)/modules/modules.gyp:rtp_rtcp', ], }, + { + 'target_name': 'field_trial', + 'type': 'static_library', + 'sources': [ + 'field_trial.cc', + 'field_trial.h', + ], + 'dependencies': [ + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + ], + }, + { + 'target_name': 'test_main', + 'type': 'static_library', + 'sources': [ + 'test_main.cc', + ], + 'dependencies': [ + 'field_trial', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + ], + }, { 'target_name': 'test_support', 'type': 'static_library', 'dependencies': [ '<(DEPTH)/testing/gtest.gyp:gtest', '<(DEPTH)/testing/gmock.gyp:gmock', - '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', ], 'sources': [ - 'test_suite.cc', - 'test_suite.h', 'testsupport/android/root_path_android.cc', 'testsupport/android/root_path_android_chromium.cc', 'testsupport/fileutils.cc', @@ -97,7 +117,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/base/base.gyp:base', ], @@ -117,10 +137,16 @@ 'target_name': 'test_support_main', 'type': 'static_library', 'dependencies': [ + 'field_trial', 'test_support', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', ], 'sources': [ 'run_all_unittests.cc', + 'test_suite.cc', + 'test_suite.h', ], }, { @@ -167,7 +193,7 @@ 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are # using Chromium's buildbots. - ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['build_with_chromium==1 and OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', ], @@ -202,7 +228,7 @@ }], # TODO(henrike): remove build_with_chromium==1 when the bots are using # Chromium's buildbots. - ['include_tests==1 and build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + ['include_tests==1 and build_with_chromium==1 and OS=="android"', { 'targets': [ { 'target_name': 'test_support_unittests_apk_target', diff --git a/webrtc/test/test_main.cc b/webrtc/test/test_main.cc index b57b032da..733831f5b 100644 --- a/webrtc/test/test_main.cc +++ b/webrtc/test/test_main.cc @@ -8,16 +8,25 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "gflags/gflags.h" #include "testing/gtest/include/gtest/gtest.h" - -#include "webrtc/test/flags.h" -#include "webrtc/test/run_tests.h" +#include "webrtc/test/field_trial.h" #include "webrtc/test/testsupport/fileutils.h" +DEFINE_string(force_fieldtrials, "", + "Field trials control experimental feature code which can be forced. " + "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/" + " will assign the group Enable to field trial WebRTC-FooFeature."); + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); - webrtc::test::flags::Init(&argc, &argv); - webrtc::test::SetExecutablePath(argv[0]); - return webrtc::test::RunAllTests(); + // AllowCommandLineParsing allows us to ignore flags passed on to us by + // Chromium build bots without having to explicitly disable them. + google::AllowCommandLineReparsing(); + google::ParseCommandLineFlags(&argc, &argv, false); + + webrtc::test::SetExecutablePath(argv[0]); + webrtc::test::InitFieldTrialsFromString(FLAGS_force_fieldtrials); + return RUN_ALL_TESTS(); } diff --git a/webrtc/test/test_suite.cc b/webrtc/test/test_suite.cc index 7cfb856f3..e88b0301a 100644 --- a/webrtc/test/test_suite.cc +++ b/webrtc/test/test_suite.cc @@ -15,9 +15,15 @@ #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/trace_to_stderr.h" +#include "webrtc/test/field_trial.h" DEFINE_bool(logs, false, "print logs to stderr"); +DEFINE_string(force_fieldtrials, "", + "Field trials control experimental feature code which can be forced. " + "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/" + " will assign the group Enable to field trial WebRTC-FooFeature."); + namespace webrtc { namespace test { @@ -28,6 +34,8 @@ TestSuite::TestSuite(int argc, char** argv) { // Chromium build bots without having to explicitly disable them. google::AllowCommandLineReparsing(); google::ParseCommandLineFlags(&argc, &argv, true); + + webrtc::test::InitFieldTrialsFromString(FLAGS_force_fieldtrials); } TestSuite::~TestSuite() { diff --git a/webrtc/test/test_suite.h b/webrtc/test/test_suite.h index 16c035795..e28fb2335 100644 --- a/webrtc/test/test_suite.h +++ b/webrtc/test/test_suite.h @@ -17,7 +17,7 @@ // instantiate this class in your main function and call its Run method to run // any gtest based tests that are linked into your executable. -#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/base/constructormagic.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { diff --git a/webrtc/test/test_support_unittests.isolate b/webrtc/test/test_support_unittests.isolate index 0f5de6507..08bd4a4a1 100644 --- a/webrtc/test/test_support_unittests.isolate +++ b/webrtc/test/test_support_unittests.isolate @@ -8,28 +8,26 @@ { 'conditions': [ ['OS=="android"', { - # When doing Android builds, the WebRTC code is put in third_party/webrtc - # of a Chromium checkout, this is one level above the standalone build. 'variables': { 'isolate_dependency_untracked': [ - '../../../data/', - '../../../resources/', + '<(DEPTH)/data/', + '<(DEPTH)/resources/', ], }, }], ['OS=="linux" or OS=="mac" or OS=="win"', { 'variables': { 'command': [ - '../../testing/test_env.py', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/test_support_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_tracked': [ - '../../DEPS', - '../../testing/test_env.py', + '<(DEPTH)/DEPS', + '<(DEPTH)/testing/test_env.py', '<(PRODUCT_DIR)/test_support_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../../tools/swarming_client/', + '<(DEPTH)/tools/swarming_client/', ], }, }], diff --git a/webrtc/test/testsupport/fileutils.cc b/webrtc/test/testsupport/fileutils.cc index c89f9bd1a..a3e66201f 100644 --- a/webrtc/test/testsupport/fileutils.cc +++ b/webrtc/test/testsupport/fileutils.cc @@ -10,12 +10,20 @@ #include "webrtc/test/testsupport/fileutils.h" +#include + #ifdef WIN32 #include +#include +#include #include + +#include "webrtc/system_wrappers/interface/utf_util_win.h" #define GET_CURRENT_DIR _getcwd #else #include + +#include "webrtc/system_wrappers/interface/scoped_ptr.h" #define GET_CURRENT_DIR getcwd #endif @@ -25,6 +33,7 @@ #endif #include +#include #include #include "webrtc/typedefs.h" // For architecture defines @@ -92,7 +101,7 @@ std::string OutputPathImpl() { return kFallbackPath; } path += kOutputDirName; - if (!CreateDirectory(path)) { + if (!CreateDir(path)) { return kFallbackPath; } return path + kPathDelimiter; @@ -154,7 +163,35 @@ std::string WorkingDir() { #endif // !WEBRTC_ANDROID -bool CreateDirectory(std::string directory_name) { +// Generate a temporary filename in a safe way. +// Largely copied from talk/base/{unixfilesystem,win32filesystem}.cc. +std::string TempFilename(const std::string &dir, const std::string &prefix) { +#ifdef WIN32 + wchar_t filename[MAX_PATH]; + if (::GetTempFileName(ToUtf16(dir).c_str(), + ToUtf16(prefix).c_str(), 0, filename) != 0) + return ToUtf8(filename); + assert(false); + return ""; +#else + int len = dir.size() + prefix.size() + 2 + 6; + scoped_ptr tempname(new char[len]); + + snprintf(tempname.get(), len, "%s/%sXXXXXX", dir.c_str(), + prefix.c_str()); + int fd = ::mkstemp(tempname.get()); + if (fd == -1) { + assert(false); + return ""; + } else { + ::close(fd); + } + std::string ret(tempname.get()); + return ret; +#endif +} + +bool CreateDir(std::string directory_name) { struct stat path_info = {0}; // Check if the path exists already: if (stat(directory_name.c_str(), &path_info) == 0) { diff --git a/webrtc/test/testsupport/fileutils.h b/webrtc/test/testsupport/fileutils.h index d51bbde21..78789fa87 100644 --- a/webrtc/test/testsupport/fileutils.h +++ b/webrtc/test/testsupport/fileutils.h @@ -103,6 +103,10 @@ std::string ProjectRootPath(); // found, the current working directory ("./") is returned as a fallback. std::string OutputPath(); +// Generates an empty file with a unique name in the specified directory and +// returns the file name and path. +std::string TempFilename(const std::string &dir, const std::string &prefix); + // Returns a path to a resource file for the currently executing platform. // Adapts to what filenames are currently present in the // [project-root]/resources/ dir. @@ -132,7 +136,10 @@ std::string WorkingDir(); // Creates a directory if it not already exists. // Returns true if successful. Will print an error message to stderr and return // false if a file with the same name already exists. -bool CreateDirectory(std::string directory_name); +bool CreateDir(std::string directory_name); + +// Checks if a file exists. +bool FileExists(std::string& file_name); // File size of the supplied file in bytes. Will return 0 if the file is // empty or if the file does not exist/is readable. diff --git a/webrtc/test/testsupport/fileutils_unittest.cc b/webrtc/test/testsupport/fileutils_unittest.cc index c38e4533b..4cd451372 100644 --- a/webrtc/test/testsupport/fileutils_unittest.cc +++ b/webrtc/test/testsupport/fileutils_unittest.cc @@ -46,7 +46,7 @@ class FileUtilsTest : public testing::Test { original_working_dir_ = webrtc::test::WorkingDir(); std::string resources_path = original_working_dir_ + kPathDelimiter + kResourcesDir + kPathDelimiter; - webrtc::test::CreateDirectory(resources_path); + webrtc::test::CreateDir(resources_path); files_.push_back(resources_path + kTestName + "." + kExtension); files_.push_back(resources_path + kTestName + "_32." + kExtension); @@ -117,12 +117,19 @@ TEST_F(FileUtilsTest, DISABLED_ON_ANDROID(OutputPathFromRootWorkingDir)) { ASSERT_EQ("./", webrtc::test::OutputPath()); } +TEST_F(FileUtilsTest, DISABLED_ON_ANDROID(TempFilename)) { + std::string temp_filename = webrtc::test::TempFilename( + webrtc::test::OutputPath(), "TempFilenameTest"); + ASSERT_TRUE(webrtc::test::FileExists(temp_filename)); + remove(temp_filename.c_str()); +} + // Only tests that the code executes -TEST_F(FileUtilsTest, CreateDirectory) { +TEST_F(FileUtilsTest, CreateDir) { std::string directory = "fileutils-unittest-empty-dir"; // Make sure it's removed if a previous test has failed: remove(directory.c_str()); - ASSERT_TRUE(webrtc::test::CreateDirectory(directory)); + ASSERT_TRUE(webrtc::test::CreateDir(directory)); remove(directory.c_str()); } diff --git a/webrtc/test/testsupport/metrics/video_metrics.cc b/webrtc/test/testsupport/metrics/video_metrics.cc index f537e0392..1e19806b4 100644 --- a/webrtc/test/testsupport/metrics/video_metrics.cc +++ b/webrtc/test/testsupport/metrics/video_metrics.cc @@ -111,8 +111,8 @@ int CalculateMetrics(VideoMetricsType video_metrics_type, const size_t frame_length = 3 * width * height >> 1; I420VideoFrame ref_frame; I420VideoFrame test_frame; - scoped_array ref_buffer(new uint8_t[frame_length]); - scoped_array test_buffer(new uint8_t[frame_length]); + scoped_ptr ref_buffer(new uint8_t[frame_length]); + scoped_ptr test_buffer(new uint8_t[frame_length]); // Set decoded image parameters. int half_width = (width + 1) / 2; diff --git a/webrtc/test/w3c/getusermedia_conformance_test.html b/webrtc/test/w3c/getusermedia_conformance_test.html index 8a95032e2..7b724a936 100644 --- a/webrtc/test/w3c/getusermedia_conformance_test.html +++ b/webrtc/test/w3c/getusermedia_conformance_test.html @@ -27,9 +27,9 @@

Conformance test for the Media Capture and Streams API

This page contains a foundation of conformance tests that can be expanded to cover most things in the W3C specification of the Media Capture and Streams API.

-

VERSION: These tests are based on the W3C Editor's Draft of August 24th, - 2013 - (http://dev.w3.org/2011/webrtc/editor/archives/20130824/getusermedia.html) +

VERSION: These tests are based on the W3C Editor's Draft of 07 May, + 2014 + (http://dev.w3.org/2011/webrtc/editor/archives/20140507/getusermedia.html)

STATUS: In its current state, it only performs simple checks on the various attributes and methods of the objects exposed by the API. There's not much functionality tested so far. The spec doesn't define if an attribute shall be @@ -37,9 +37,7 @@

Conformance test for the Media Capture and Streams API

inherited (assert_inherits). Since testharness.js doesn't offer any generic function that covers both, the method for verification is currently chosen according to the current Chrome implementation.

-

PREFIXES: These tests currently utilizes the adapter.js - script, which handle the prefixes used by different browsers.

-

HOW TO RUN: The easiest way is to tell your browser to: +

HOW TO RUN: For Chrome, do this:

  • Provide a fake webcam (--use-fake-ui-for-media-stream in Chrome)
  • @@ -49,14 +47,21 @@

    Conformance test for the Media Capture and Streams API

    (--allow-file-access-from-files in Chrome)
Then just load this HTML file to execute the tests.

+

For Firefox, set the media.navigator.permission.disabled property to true + about:config. You must have a webcam and microphone available on the system. +

- - - - - + + + + diff --git a/webrtc/test/w3c/getusermedia_conformance_test.js b/webrtc/test/w3c/getusermedia_conformance_test.js index 6be293207..d7c782807 100644 --- a/webrtc/test/w3c/getusermedia_conformance_test.js +++ b/webrtc/test/w3c/getusermedia_conformance_test.js @@ -6,7 +6,7 @@ // in the file PATENTS. All contributing project authors may // be found in the AUTHORS file in the root of the source tree. -setup({timeout:10000}); +setup({timeout:5000}); // Helper functions to minimize code duplication. function failedCallback(test) { @@ -15,10 +15,18 @@ function failedCallback(test) { }); } function invokeGetUserMedia(test, okCallback) { - getUserMedia({ video: true, audio: true }, okCallback, + navigator.getUserMedia({ video: true, audio: true }, okCallback, failedCallback(test)); } +function createInvisibleVideoTag() { + var video = document.createElement('video'); + video.autoplay = true; + video.style.display = 'none'; + document.body.appendChild(video); + return video; +} + // 4.2 MediaStream. var mediaStreamTest = async_test('4.2 MediaStream'); @@ -56,21 +64,25 @@ function verifyMediaStream(stream) { }, '[MediaStream] removeTrack function'); test(function () { - // Missing in Chrome. assert_inherits(stream, 'clone'); assert_true(typeof stream.clone === 'function'); }, '[MediaStream] clone function'); test(function () { - assert_own_property(stream, 'ended'); - assert_true(typeof stream.ended === 'boolean'); - assert_readonly(stream, 'ended'); - }, '[MediaStream] ended attribute'); + assert_own_property(stream, 'active'); + assert_true(typeof stream.active === 'boolean'); + assert_readonly(stream, 'active'); + }, '[MediaStream] active attribute'); + + test(function () { + assert_own_property(stream, 'onactive'); + assert_true(stream.onactive === null); + }, '[MediaStream] onactive EventHandler'); test(function () { - assert_own_property(stream, 'onended'); - assert_true(stream.onended === null); - }, '[MediaStream] onended EventHandler'); + assert_own_property(stream, 'oninactive'); + assert_true(stream.oninactive === null); + }, '[MediaStream] oninactive EventHandler'); test(function () { assert_own_property(stream, 'onaddtrack'); @@ -86,28 +98,43 @@ function verifyMediaStream(stream) { mediaStreamTest.step(function() { var okCallback = mediaStreamTest.step_func(function (stream) { verifyMediaStream(stream); - var videoTracks = stream.getVideoTracks(); assert_true(videoTracks.length > 0); + mediaStreamTest.done(); + }); + + invokeGetUserMedia(mediaStreamTest, okCallback); +}); + +var mediaStreamCallbacksTest = async_test('4.2.2 MediaStream callbacks'); + +mediaStreamCallbacksTest.step(function() { + var addCallbackCalled = false; + var onAddTrackCallback = mediaStreamCallbacksTest.step_func(function (event) { + assert_true(event.track instanceof MediaStreamTrack); + addCallbackCalled = true; + }); + var onRemoveTrackCallback = + mediaStreamCallbacksTest.step_func(function (event) { + assert_true(event.track instanceof MediaStreamTrack); + assert_true(addCallbackCalled, 'Add should have been called after remove.'); + mediaStreamCallbacksTest.done(); + }); + var okCallback = mediaStreamCallbacksTest.step_func(function (stream) { + var videoTracks = stream.getVideoTracks(); // Verify event handlers are working. - stream.onaddtrack = onAddTrackCallback - stream.onremovetrack = onRemoveTrackCallback + stream.onaddtrack = onAddTrackCallback; + stream.onremovetrack = onRemoveTrackCallback; stream.removeTrack(videoTracks[0]); stream.addTrack(videoTracks[0]); - mediaStreamTest.done(); - }); - var onAddTrackCallback = mediaStreamTest.step_func(function () { - // TODO(kjellander): verify number of tracks. - mediaStreamTest.done(); - }); - var onRemoveTrackCallback = mediaStreamTest.step_func(function () { - // TODO(kjellander): verify number of tracks. - mediaStreamTest.done(); }); - invokeGetUserMedia(mediaStreamTest, okCallback);; + + invokeGetUserMedia(mediaStreamCallbacksTest, okCallback); }); +// TODO(phoglund): add test for onactive/oninactive. + // 4.3 MediaStreamTrack. var mediaStreamTrackTest = async_test('4.3 MediaStreamTrack'); @@ -140,7 +167,6 @@ function verifyTrack(type, track) { }, '[MediaStreamTrack (' + type + ')] enabled attribute'); test(function () { - // Missing in Chrome. assert_own_property(track, 'muted'); assert_readonly(track, 'muted'); assert_true(typeof track.muted === 'boolean'); @@ -158,14 +184,12 @@ function verifyTrack(type, track) { }, '[MediaStreamTrack (' + type + ')] onunmute EventHandler'); test(function () { - // Missing in Chrome. assert_own_property(track, '_readonly'); assert_readonly(track, '_readonly'); assert_true(typeof track._readonly === 'boolean'); }, '[MediaStreamTrack (' + type + ')] _readonly attribute'); test(function () { - // Missing in Chrome. assert_own_property(track, 'remote'); assert_readonly(track, 'remote'); assert_true(typeof track.remote === 'boolean'); @@ -179,7 +203,6 @@ function verifyTrack(type, track) { }, '[MediaStreamTrack (' + type + ')] readyState attribute'); test(function () { - // Missing in Chrome. assert_own_property(track, 'onstarted'); assert_true(track.onstarted === null); }, '[MediaStreamTrack (' + type + ')] onstarted EventHandler'); @@ -190,53 +213,41 @@ function verifyTrack(type, track) { }, '[MediaStreamTrack (' + type + ')] onended EventHandler'); test(function () { - // Missing in Chrome. - assert_inherits(track, 'getSourceInfos'); - assert_true(typeof track.getSourceInfos === 'function'); - }, '[MediaStreamTrack (' + type + ')]: getSourceInfos function'); + assert_inherits(track, 'getNativeSettings'); + assert_true(typeof track.capabilities === 'function'); + }, '[MediaStreamTrack (' + type + ')]: getNativeSettings function'); test(function () { - // Missing in Chrome. - assert_inherits(track, 'constraints'); - assert_true(typeof track.constraints === 'function'); - }, '[MediaStreamTrack (' + type + ')]: constraints function'); + assert_inherits(track, 'clone'); + assert_true(typeof track.clone === 'function'); + }, '[MediaStreamTrack (' + type + ')] clone function'); test(function () { - // Missing in Chrome. - assert_inherits(track, 'states'); - assert_true(typeof track.states === 'function'); - }, '[MediaStreamTrack (' + type + ')]: states function'); + assert_inherits(track, 'stop'); + assert_true(typeof track.stop === 'function'); + }, '[MediaStreamTrack (' + type + ')] stop function'); test(function () { - // Missing in Chrome. - assert_inherits(track, 'capabilities'); + assert_inherits(track, 'getCapabilities'); assert_true(typeof track.capabilities === 'function'); - }, '[MediaStreamTrack (' + type + ')]: capabilities function'); - - test(function () { - // Missing in Chrome. - assert_inherits(track, 'applyConstraints'); - assert_true(typeof track.applyConstraints === 'function'); - }, '[MediaStreamTrack (' + type + ')]: applyConstraints function'); + }, '[MediaStreamTrack (' + type + ')]: getCapabilities function'); test(function () { - // Missing in Chrome. - assert_own_property(track, 'onoverconstrained'); - assert_true(track.onoverconstrained === null); - }, '[MediaStreamTrack (' + type + ')] onoverconstrained EventHandler'); + assert_inherits(track, 'getConstraints'); + assert_true(typeof track.constraints === 'function'); + }, '[MediaStreamTrack (' + type + ')]: getConstraints function'); test(function () { - // Missing in Chrome. - assert_inherits(track, 'clone'); - assert_true(typeof track.clone === 'function'); - }, '[MediaStreamTrack (' + type + ')] clone function'); + assert_inherits(track, 'getSettings'); + assert_true(typeof track.constraints === 'function'); + }, '[MediaStreamTrack (' + type + ')]: getSettings function'); test(function () { - // Missing in Chrome. - assert_inherits(track, 'stop'); - assert_true(typeof track.stop === 'function'); - }, '[MediaStreamTrack (' + type + ')] stop function'); + assert_inherits(track, 'applyConstraints'); + assert_true(typeof track.applyConstraints === 'function'); + }, '[MediaStreamTrack (' + type + ')]: applyConstraints function'); }; + mediaStreamTrackTest.step(function() { var okCallback = mediaStreamTrackTest.step_func(function (stream) { verifyTrack('audio', stream.getAudioTracks()[0]); @@ -250,11 +261,11 @@ mediaStreamTrackTest.step(function() { var okCallback = mediaStreamTrackTest.step_func(function (stream) { // Verify event handlers are working. var track = stream.getVideoTracks()[0]; - track.onended = onendedCallback + track.onended = onEndedCallback track.stop(); mediaStreamTrackTest.done(); }); - var onendedCallback = mediaStreamTrackTest.step_func(function () { + var onEndedCallback = mediaStreamTrackTest.step_func(function () { assert_true(track.ended); mediaStreamTrackTest.done(); }); @@ -265,59 +276,113 @@ mediaStreamTrackTest.step(function() { var mediaStreamTrackEventTest = async_test('4.4 MediaStreamTrackEvent'); mediaStreamTrackEventTest.step(function() { var okCallback = mediaStreamTrackEventTest.step_func(function (stream) { - // TODO(kjellander): verify attributes + // TODO(kjellander): verify attributes. mediaStreamTrackEventTest.done(); }); invokeGetUserMedia(mediaStreamTrackEventTest, okCallback); }); -// 4.5 Video and Audio Tracks tests. -var avTracksTest = async_test('4.5 Video and Audio Tracks'); -avTracksTest.step(function() { - var okCallback = avTracksTest.step_func(function (stream) { - // TODO(kjellander): verify attributes - avTracksTest.done(); - }); - invokeGetUserMedia(avTracksTest, okCallback); -}); +// 6. Media streams as media elements. + +var playingInMediaElementTest = async_test( + '6.2 Loading and Playing a MediaStream in a Media Element'); +playingInMediaElementTest.step(function() { + var video = createInvisibleVideoTag(); -// 5. The model: sources, sinks, constraints, and states + var okCallback = playingInMediaElementTest.step_func(function (stream) { + video.onplay = playingInMediaElementTest.step_func(function() { + // This depends on what webcam we're actually running with, but the + // resolution should at least be greater than or equal to QVGA. + assert_greater_than_equal(video.videoWidth, 320); + assert_greater_than_equal(video.videoHeight, 240); -// 6. Source states -// 6.1 Dictionary MediaSourceStates Members + playingInMediaElementTest.done(); + }); + video.srcObject = stream; + }); + invokeGetUserMedia(playingInMediaElementTest, okCallback); +}); -// 7. Source capabilities -// 7.1 Dictionary CapabilityRange Members -// 7.2 CapabilityList array -// 7.3 Dictionary AllVideoCapabilities Members -// 7.4 Dictionary AllAudioCapabilities Members +// Verifies a media element track (for instance belonging to a video tag) +// after it has been assigned a media stream. +function verifyOneMediaElementTrack(track, correspondingMediaStreamTrack) { + assert_equals(track.id, correspondingMediaStreamTrack.id); + assert_equals(track.kind, 'main'); + assert_equals(track.label, correspondingMediaStreamTrack.label); + assert_equals(track.language, ''); +} -// 8. URL tests. -var createObjectURLTest = async_test('8.1 URL createObjectURL method'); -createObjectURLTest.step(function() { - var okCallback = createObjectURLTest.step_func(function (stream) { - var url = webkitURL.createObjectURL(stream); - assert_true(typeof url === 'string'); - createObjectURLTest.done(); +var setsUpMediaTracksRightTest = async_test( + '6.2 Sets up